# EnvelopeSchema A typed, version-aware envelope schema for bit-packed payloads. Wraps a caller-supplied `BodyCodec` with a `[magic][formatVersion][body]` frame so the wire format is self-describing on the magic + version dimensions, while the body remains pluggable. Replaces the manual `defineBitSchema([{magic}, {formatVersion}, ...])` pattern that suaveplan crypto, packer, and other consumers had been re-authoring. _(no submodules)_ ## Purpose Many suaveplan wire formats begin with a magic number + format version: chain bundles (`SVCB`), public bundles, the chunked packer, and several network encoders all do the same thing manually. The duplication isn't just code churn — every callsite is a place where the magic/version contract can drift out of sync with its parser. `EnvelopeSchema` consolidates the pattern and makes "this byte string belongs to schema X version Y" a first-class runtime check. The body region is opaque to the envelope itself; encoding is delegated to a `BodyCodec` (`pack` + `unpack`). The bundled `rawBytesCodec()` is provided for callers whose bodies are already-encoded bytes (typical when composing with `SignedBitSchema` or `EncryptedBitSchema`); for structured bodies, callers pass a codec built from the legacy `defineBitSchema(...)` or from any encoder satisfying the two-method contract. ## Features - `pack(body)` produces `[magic][formatVersion][bodyCodec.pack(body)]` - `parse(bytes)` extracts magic + version + raw body bytes WITHOUT decoding the body — enabling forward-compat dispatch when a future version arrives - `parseStrict(bytes)` validates magic + version, then decodes the body to `TBody` via `bodyCodec.unpack` - Mismatched magic throws `BitpackerError` with code `CRYPTO_ENVELOPE_MAGIC_MISMATCH` and `context.{expected,actual}` - Mismatched version throws `BitpackerError` with code `CRYPTO_ENVELOPE_VERSION_MISMATCH` and `context.{expected,actual}` - Zod-validated options at construction; unknown keys rejected - OTel spans `bitpacker-crypto.envelope-pack` and `bitpacker-crypto.envelope-parse` carry `bitpacker.crypto.payload_bytes` - Stays codec-agnostic so a future Protobuf-bodied envelope or a schema-fingerprinted variant requires zero changes to envelope itself ## Usage: Basic Example ```ts import { type BodyCodec, defineEnvelopeSchema, } from "@suaveplan/bitpacker-crypto"; import { defineBitSchema } from "@suaveplan/bitpacker"; interface Header { foo: number; bar: number; } const headerSchema = defineBitSchema
([ { name: "foo", type: "uint", bits: 8 }, { name: "bar", type: "uint", bits: 16 }, ]); const codec: BodyCodec
= { pack: (body) => headerSchema.pack(body), unpack: (bytes) => headerSchema.unpack(bytes), }; const envelope = defineEnvelopeSchema
({ magic: new Uint8Array([0x53, 0x56, 0x43, 0x42]), // "SVCB" formatVersion: 1, bodyCodec: codec, }); const bytes = envelope.pack({ foo: 7, bar: 1234 }); const { foo, bar } = envelope.parseStrict(bytes); // typed: Header ``` ## Usage: Advanced Example — forward-compat dispatch Use `parse` to peek at magic + version before deciding which decoder to run, so a v1 reader can detect v2 envelopes and route accordingly: ```ts import { defineEnvelopeSchema } from "@suaveplan/bitpacker-crypto"; const envV1 = defineEnvelopeSchema({ magic, formatVersion: 1, bodyCodec: codecV1 }); const envV2 = defineEnvelopeSchema({ magic, formatVersion: 2, bodyCodec: codecV2 }); function decode(bytes: Uint8Array) { const partial = envV1.parse(bytes); if (partial.formatVersion === 1) return envV1.parseStrict(bytes); if (partial.formatVersion === 2) return envV2.parseStrict(bytes); throw new Error(`Unsupported envelope version: ${partial.formatVersion}`); } ``` The body bytes are not consumed by `parse`, so the same input can be passed back to `parseStrict` of the matching version without re-allocation cost on the magic+version prefix. ## API Reference ### `interface BodyCodec` - `pack(body: TBody): Uint8Array` — encode the body - `unpack(bytes: Uint8Array): TBody` — decode the body ### `interface EnvelopeSchemaOptions` - `magic: Uint8Array` — non-empty - `formatVersion: number` — integer 0..255 - `bodyCodec: BodyCodec` — body encoding hook ### `defineEnvelopeSchema(options): EnvelopeSchema` Returns: - `magic: Uint8Array` - `formatVersion: number` - `pack(body: TBody): Uint8Array` - `parse(bytes: Uint8Array): { magic, formatVersion, bodyBytes }` - `parseStrict(bytes: Uint8Array): TBody` - throws `CRYPTO_ENVELOPE_MAGIC_MISMATCH` on magic mismatch - throws `CRYPTO_ENVELOPE_VERSION_MISMATCH` on version mismatch ### `rawBytesCodec(): BodyCodec` Pass-through codec for bodies that are already-encoded raw bytes — useful when composing with `SignedBitSchema` or `EncryptedBitSchema` whose `pack` output goes inside an envelope. ## Implementation Notes - The envelope wire format is `[magic_bytes][1-byte u8 formatVersion][body_bytes]`. The body is NOT framed with a length prefix — the codec is expected to either be fixed-size or to consume the entire trailing buffer. - `parse` throws `CRYPTO_ENVELOPE_MAGIC_MISMATCH` when input is shorter than `magic.length + 1` (the minimum header size). Magic and version validation in `parseStrict` happens against the parsed values, so a too-short input is caught at the parse stage. - Magic comparison is constant-time-irrelevant — magic bytes are not secret. Equality is byte-wise. The error context includes hex-encoded `expected` and `actual` for diagnosability. - The codec is held by reference; mutating the codec after `defineEnvelopeSchema(...)` returns will affect future `pack`/`parseStrict` calls. Callers should treat the codec as immutable in practice. - OTel spans are emitted unconditionally (even when no SDK is wired) because they cost essentially nothing under the no-op tracer; production traces materialise once a tracer provider is installed.