5.9 KiB
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<TBody> (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 arrivesparseStrict(bytes)validates magic + version, then decodes the body toTBodyviabodyCodec.unpack- Mismatched magic throws
BitpackerErrorwith codeCRYPTO_ENVELOPE_MAGIC_MISMATCHandcontext.{expected,actual} - Mismatched version throws
BitpackerErrorwith codeCRYPTO_ENVELOPE_VERSION_MISMATCHandcontext.{expected,actual} - Zod-validated options at construction; unknown keys rejected
- OTel spans
bitpacker-crypto.envelope-packandbitpacker-crypto.envelope-parsecarrybitpacker.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
import {
type BodyCodec,
defineEnvelopeSchema,
} from "@suaveplan/bitpacker-crypto";
import { defineBitSchema } from "@suaveplan/bitpacker";
interface Header {
foo: number;
bar: number;
}
const headerSchema = defineBitSchema<Header>([
{ name: "foo", type: "uint", bits: 8 },
{ name: "bar", type: "uint", bits: 16 },
]);
const codec: BodyCodec<Header> = {
pack: (body) => headerSchema.pack(body),
unpack: (bytes) => headerSchema.unpack(bytes),
};
const envelope = defineEnvelopeSchema<Header>({
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:
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<TBody>
pack(body: TBody): Uint8Array— encode the bodyunpack(bytes: Uint8Array): TBody— decode the body
interface EnvelopeSchemaOptions<TBody>
magic: Uint8Array— non-emptyformatVersion: number— integer 0..255bodyCodec: BodyCodec<TBody>— body encoding hook
defineEnvelopeSchema<TBody>(options): EnvelopeSchema<TBody>
Returns:
magic: Uint8ArrayformatVersion: numberpack(body: TBody): Uint8Arrayparse(bytes: Uint8Array): { magic, formatVersion, bodyBytes }parseStrict(bytes: Uint8Array): TBody- throws
CRYPTO_ENVELOPE_MAGIC_MISMATCHon magic mismatch - throws
CRYPTO_ENVELOPE_VERSION_MISMATCHon version mismatch
- throws
rawBytesCodec(): BodyCodec<Uint8Array>
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. parsethrowsCRYPTO_ENVELOPE_MAGIC_MISMATCHwhen input is shorter thanmagic.length + 1(the minimum header size). Magic and version validation inparseStricthappens 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
expectedandactualfor diagnosability. - The codec is held by reference; mutating the codec after
defineEnvelopeSchema(...)returns will affect futurepack/parseStrictcalls. 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.