refactor(kiwi): move FIG container helpers
This commit is contained in:
parent
d068237ee4
commit
8365112c51
|
|
@ -1,75 +1,7 @@
|
|||
import { deflateSync, inflateSync } from 'fflate'
|
||||
|
||||
export const FIG_KIWI_DEFAULT_VERSION = 101
|
||||
|
||||
export function parseFigKiwiChunks(binary: Uint8Array): Uint8Array[] | null {
|
||||
const header = new TextDecoder().decode(binary.slice(0, 8))
|
||||
if (header !== 'fig-kiwi') return null
|
||||
|
||||
const view = new DataView(binary.buffer, binary.byteOffset, binary.byteLength)
|
||||
let offset = 12
|
||||
|
||||
const chunks: Uint8Array[] = []
|
||||
while (offset < binary.length) {
|
||||
const chunkLen = view.getUint32(offset, true)
|
||||
offset += 4
|
||||
chunks.push(binary.slice(offset, offset + chunkLen))
|
||||
offset += chunkLen
|
||||
}
|
||||
return chunks.length >= 2 ? chunks : null
|
||||
}
|
||||
|
||||
export async function decompressFigKiwiDataAsync(compressed: Uint8Array): Promise<Uint8Array> {
|
||||
if (
|
||||
compressed.length >= 4 &&
|
||||
compressed[0] === 0x28 &&
|
||||
compressed[1] === 0xb5 &&
|
||||
compressed[2] === 0x2f &&
|
||||
compressed[3] === 0xfd
|
||||
) {
|
||||
const { decompress } = await import('fzstd')
|
||||
return decompress(compressed)
|
||||
}
|
||||
try {
|
||||
return inflateSync(compressed)
|
||||
} catch {
|
||||
const { decompress } = await import('fzstd')
|
||||
return decompress(compressed)
|
||||
}
|
||||
}
|
||||
|
||||
export function buildFigKiwi(
|
||||
schemaDeflated: Uint8Array,
|
||||
dataRaw: Uint8Array,
|
||||
version = FIG_KIWI_DEFAULT_VERSION
|
||||
): Uint8Array {
|
||||
let dataCompressed: Uint8Array
|
||||
const zstdCompress: ((data: Uint8Array) => Uint8Array) | undefined = (() => {
|
||||
const g = globalThis as { Bun?: { zstdCompressSync?: (data: Uint8Array) => Uint8Array } }
|
||||
return g.Bun?.zstdCompressSync
|
||||
})()
|
||||
if (zstdCompress) {
|
||||
dataCompressed = zstdCompress(dataRaw)
|
||||
} else {
|
||||
dataCompressed = deflateSync(dataRaw)
|
||||
}
|
||||
|
||||
const total = 8 + 4 + 4 + schemaDeflated.length + 4 + dataCompressed.length
|
||||
const out = new Uint8Array(total)
|
||||
const view = new DataView(out.buffer)
|
||||
|
||||
out.set(new TextEncoder().encode('fig-kiwi'), 0)
|
||||
view.setUint32(8, version, true)
|
||||
|
||||
let offset = 12
|
||||
view.setUint32(offset, schemaDeflated.length, true)
|
||||
offset += 4
|
||||
out.set(schemaDeflated, offset)
|
||||
offset += schemaDeflated.length
|
||||
|
||||
view.setUint32(offset, dataCompressed.length, true)
|
||||
offset += 4
|
||||
out.set(dataCompressed, offset)
|
||||
|
||||
return out
|
||||
}
|
||||
export {
|
||||
buildFigKiwi,
|
||||
decompressFigKiwiData,
|
||||
decompressFigKiwiDataAsync,
|
||||
FIG_KIWI_DEFAULT_VERSION,
|
||||
parseFigKiwiChunks
|
||||
} from '@open-pencil/kiwi/fig/container'
|
||||
|
|
|
|||
|
|
@ -28,6 +28,12 @@
|
|||
"import": "./dist/fig/codec.js",
|
||||
"default": "./dist/fig/codec.js"
|
||||
},
|
||||
"./fig/container": {
|
||||
"types": "./dist/src/fig/container.d.ts",
|
||||
"bun": "./src/fig/container.ts",
|
||||
"import": "./dist/fig/container.js",
|
||||
"default": "./dist/fig/container.js"
|
||||
},
|
||||
"./fig/parse": {
|
||||
"types": "./dist/src/fig/parse.d.ts",
|
||||
"bun": "./src/fig/parse.ts",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { getSchemaBytes, initCodec, isCodecReady } from '../dist/fig/codec.js'
|
||||
import { buildFigKiwi, parseFigKiwiChunks } from '../dist/fig/container.js'
|
||||
import { parseFigKiwiContainer } from '../dist/fig/parse.js'
|
||||
import { figmaSchema, getKiwiMessageType, parseSchema, validateSchema } from '../dist/index.js'
|
||||
|
||||
|
|
@ -27,3 +28,8 @@ if (!isCodecReady() || getSchemaBytes().length === 0) {
|
|||
if (parseFigKiwiContainer(new TextEncoder().encode('not-fig-kiwi')) !== null) {
|
||||
throw new Error('Failed to reject invalid FIG Kiwi container')
|
||||
}
|
||||
|
||||
const figKiwi = buildFigKiwi(new Uint8Array([1]), new Uint8Array([2]))
|
||||
if (parseFigKiwiChunks(figKiwi)?.length !== 2) {
|
||||
throw new Error('Failed to read built FIG Kiwi container chunks')
|
||||
}
|
||||
|
|
|
|||
64
packages/kiwi/src/fig/container.ts
Normal file
64
packages/kiwi/src/fig/container.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import { deflateSync, inflateSync } from 'fflate'
|
||||
|
||||
export const FIG_KIWI_DEFAULT_VERSION = 101
|
||||
|
||||
export function parseFigKiwiChunks(binary: Uint8Array): Uint8Array[] | null {
|
||||
const header = new TextDecoder().decode(binary.slice(0, 8))
|
||||
if (header !== 'fig-kiwi') return null
|
||||
|
||||
const view = new DataView(binary.buffer, binary.byteOffset, binary.byteLength)
|
||||
let offset = 12
|
||||
|
||||
const chunks: Uint8Array[] = []
|
||||
while (offset < binary.length) {
|
||||
const chunkLen = view.getUint32(offset, true)
|
||||
offset += 4
|
||||
chunks.push(binary.slice(offset, offset + chunkLen))
|
||||
offset += chunkLen
|
||||
}
|
||||
return chunks.length >= 2 ? chunks : null
|
||||
}
|
||||
|
||||
export function decompressFigKiwiData(compressed: Uint8Array): Uint8Array {
|
||||
try {
|
||||
return inflateSync(compressed)
|
||||
} catch {
|
||||
throw new Error('Failed to decompress fig-kiwi data')
|
||||
}
|
||||
}
|
||||
|
||||
export async function decompressFigKiwiDataAsync(compressed: Uint8Array): Promise<Uint8Array> {
|
||||
try {
|
||||
return inflateSync(compressed)
|
||||
} catch {
|
||||
const fzstd = await import('fzstd')
|
||||
return fzstd.decompress(compressed)
|
||||
}
|
||||
}
|
||||
|
||||
export function buildFigKiwi(
|
||||
schemaDeflated: Uint8Array,
|
||||
dataRaw: Uint8Array,
|
||||
version = FIG_KIWI_DEFAULT_VERSION
|
||||
): Uint8Array {
|
||||
const dataDeflated = deflateSync(dataRaw)
|
||||
|
||||
const total = 8 + 4 + 4 + schemaDeflated.length + 4 + dataDeflated.length
|
||||
const out = new Uint8Array(total)
|
||||
const view = new DataView(out.buffer)
|
||||
|
||||
out.set(new TextEncoder().encode('fig-kiwi'), 0)
|
||||
view.setUint32(8, version, true)
|
||||
|
||||
let offset = 12
|
||||
view.setUint32(offset, schemaDeflated.length, true)
|
||||
offset += 4
|
||||
out.set(schemaDeflated, offset)
|
||||
offset += schemaDeflated.length
|
||||
|
||||
view.setUint32(offset, dataDeflated.length, true)
|
||||
offset += 4
|
||||
out.set(dataDeflated, offset)
|
||||
|
||||
return out
|
||||
}
|
||||
44
packages/kiwi/tests/container.test.ts
Normal file
44
packages/kiwi/tests/container.test.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import { describe, expect, test } from 'bun:test'
|
||||
|
||||
import { deflateSync } from 'fflate'
|
||||
|
||||
import {
|
||||
buildFigKiwi,
|
||||
decompressFigKiwiData,
|
||||
decompressFigKiwiDataAsync,
|
||||
FIG_KIWI_DEFAULT_VERSION,
|
||||
parseFigKiwiChunks
|
||||
} from '../src/fig/container'
|
||||
|
||||
describe('Figma FIG Kiwi container helpers', () => {
|
||||
test('builds and parses fig-kiwi chunks', () => {
|
||||
const schemaDeflated = new Uint8Array([1, 2, 3])
|
||||
const dataRaw = new TextEncoder().encode('payload')
|
||||
const binary = buildFigKiwi(schemaDeflated, dataRaw)
|
||||
|
||||
const chunks = parseFigKiwiChunks(binary)
|
||||
|
||||
expect(chunks).not.toBeNull()
|
||||
expect(chunks?.[0]).toEqual(schemaDeflated)
|
||||
expect(chunks?.[1]).toEqual(deflateSync(dataRaw))
|
||||
})
|
||||
|
||||
test('uses the default container version', () => {
|
||||
const binary = buildFigKiwi(new Uint8Array([1]), new Uint8Array([2]))
|
||||
const view = new DataView(binary.buffer, binary.byteOffset, binary.byteLength)
|
||||
|
||||
expect(view.getUint32(8, true)).toBe(FIG_KIWI_DEFAULT_VERSION)
|
||||
})
|
||||
|
||||
test('decompresses deflated data synchronously and asynchronously', async () => {
|
||||
const dataRaw = new TextEncoder().encode('payload')
|
||||
const deflated = deflateSync(dataRaw)
|
||||
|
||||
expect(decompressFigKiwiData(deflated)).toEqual(dataRaw)
|
||||
await expect(decompressFigKiwiDataAsync(deflated)).resolves.toEqual(dataRaw)
|
||||
})
|
||||
|
||||
test('rejects non fig-kiwi data', () => {
|
||||
expect(parseFigKiwiChunks(new TextEncoder().encode('not-fig-kiwi'))).toBeNull()
|
||||
})
|
||||
})
|
||||
|
|
@ -21,6 +21,7 @@ export default defineConfig({
|
|||
'schema-runtime': './src/schema-runtime/index.ts',
|
||||
fig: './src/fig/index.ts',
|
||||
'fig/codec': './src/fig/codec.ts',
|
||||
'fig/container': './src/fig/container.ts',
|
||||
'fig/parse': './src/fig/parse.ts'
|
||||
},
|
||||
plugins: [rawText()],
|
||||
|
|
|
|||
Loading…
Reference in a new issue