UNPKG

node-serum2-preset-packager

Version:
75 lines (71 loc) 3.48 kB
'use strict'; const promises = require('node:fs/promises'); const cbor2 = require('cbor2'); const zstd = require('@mongodb-js/zstd'); /** * @fileoverview Utilities for **packing** and **unpacking** Xfer Serum preset * (`*.SerumPreset`) files. * * The binary layout, reconstructed from the reference Python implementation, is: * * ``` * ┌───────────────┬────────────────────────────────────────────────────────┐ * │ Offset (hex) │ Description │ * ├───────────────┼────────────────────────────────────────────────────────┤ * │ 00 │ MAGIC = "XferJson\0" (9 bytes, ASCII) │ * │ 09 │ JSON length (u32 LE) │ * │ 0D │ Reserved (u32 LE, always 0) │ * │ 11 │ Metadata (UTF-8 JSON, `JSON length` bytes) │ * │ … │ CBOR length (u32 LE) │ * │ … │ Flags (u32 LE, 2 ⇒ Zstandard compressed) │ * │ … │ Data (zstd-compressed CBOR, `CBOR length` bytes) │ * └───────────────┴────────────────────────────────────────────────────────┘ * ``` * * All multi-byte integers are little-endian. The helpers below abstract the * repeated 32-bit-LE conversions; the public API is exposed through the * {@link unpack} and {@link pack} functions. * * @author (c) 2025 — Charles Brébant * @license MIT */ const MAGIC = Buffer.from("XferJson\0", "ascii"); function u32le(buf, offset) { return buf.readUInt32LE(offset); } function u32leBuf(val) { const b = Buffer.allocUnsafe(4); b.writeUInt32LE(val, 0); return b; } async function unpack(srcPath, dstPath) { const buf = await promises.readFile(srcPath); if (!buf.subarray(0, MAGIC.length).equals(MAGIC)) { throw new Error("Not a valid .SerumPreset file (magic mismatch)"); } let off = MAGIC.length; const jsonLen = u32le(buf, off); off += 8; const meta = JSON.parse(buf.subarray(off, off + jsonLen).toString("utf8")); off += jsonLen; const cborLen = u32le(buf, off); off += 8; const cborBuf = await zstd.decompress(buf.subarray(off)); if (cborBuf.length !== cborLen) { throw new Error("Decompressed length mismatch"); } const data = cbor2.decode(cborBuf); await promises.writeFile(dstPath, JSON.stringify({ metadata: meta, data }, null, 2)); } async function pack(srcPath, dstPath) { const obj = JSON.parse(await promises.readFile(srcPath, "utf8")); const mBuf = Buffer.from(JSON.stringify(obj.metadata), "utf8"); const cBuf = cbor2.encode(obj.data); const zBuf = await zstd.compress(Buffer.from(cBuf), 3); const header1 = Buffer.concat([u32leBuf(mBuf.length), u32leBuf(0)]); const header2 = Buffer.concat([u32leBuf(cBuf.length), u32leBuf(2)]); const out = Buffer.concat([MAGIC, header1, mBuf, header2, zBuf]); await promises.writeFile(dstPath, out); } exports.pack = pack; exports.unpack = unpack;