# CompressedBitPacker _(no submodules)_ - compressed-bit-packer.schema — Zod schema for `Comp… - [compressed-bit-packer.schema](./compressed-bit-packer.schema.md) — Zod schema for `CompressedBitPackerOptions`, the constructor options bundle accepted by `CompressedBitPacker`. Validate… - compressed-bit-packer.schema — Zod schema for `Comp… - [compressed-bit-packer.schema](./compressed-bit-packer.schema.md) — Zod schema for `CompressedBitPackerOptions`, the constructor options bundle accepted by `CompressedBitPacker`. Validate… - compressed-bit-packer.schema — Zod schema for `Comp… - [compressed-bit-packer.schema](./compressed-bit-packer.schema.md) — Zod schema for `CompressedBitPackerOptions`, the constructor options bundle accepted by `CompressedBitPacker`. Validate… - compressed-bit-packer.schema — Zod schema for `Comp… - [compressed-bit-packer.schema](./compressed-bit-packer.schema.md) — Zod schema for `CompressedBitPackerOptions`, the constructor options bundle accepted by `CompressedBitPacker`. Validate… - [compressed-bit-packer.schema](./compressed-bit-packer.schema.md) — Zod schema for `CompressedBitPackerOptions`, the constructor options bundle accepted by `CompressedBitPacker`. Validate… ## Purpose `CompressedBitPacker` extends `@suaveplan/bitpacker::BitPacker` to add a configurable compression stage between bit-packing and the wire bytes. The compressed bytes are framed with a self-describing algorithm header so a receiver can decompress without knowing the sender's algorithm choice in advance — the byte stream itself names the codec. This is the port of upstream `bitpacker/src/compressed/bitpacker.ts`. The port keeps the upstream wire shape (`[headerLen: u8][header: ASCII algorithm][compressed payload]`) so any consumer carrying upstream-encoded bytes round-trips unchanged. Hardening over upstream: - Plain `Error` throws → `BitpackerError` with stable codes (`UNKNOWN_COMPRESSION_ALGORITHM`, `COMPRESSION_FAILED`, `DECOMPRESSION_FAILED`, `COMPRESSED_BAD_HEADER`). - The synchronous `pack`/`unpack` overrides from upstream — which threw when compression was enabled — are dropped. Consumers call the explicit async `packWithCompression` / `unpackWithDecompression` methods. This separates the sync no-compression hot path from the async compressed path cleanly. - Adapter resolution goes through the injected `CompressionRegistry`, not a private static singleton. - No `EventEmitter`. suaveplan's `BitPacker` does not extend an emitter; observability is OTel-driven via `bitpacker-compressed.*` spans. ## Features - `packWithCompression(data, schema?)` — bit-pack, compress, frame. - `unpackWithDecompression(data, schema?)` — read frame, decompress, bit-unpack. - Configurable algorithm + options + enabled flag via constructor `compression` config or runtime accessors. - Self-describing wire format: receiver dispatches to the named algorithm without prior knowledge. - Synchronous `pack`/`unpack` from `BitPacker` remain for the no-compression case (no header added — bytes are exactly what `BitPacker.pack` produces). - Pluggable `CompressionRegistry` via constructor option (defaults to the process-wide singleton). ## Usage: Basic Example ```typescript import { CompressedBitPacker } from "@suaveplan/bitpacker-compressed"; const schema = { x: 255, y: 255 } as const; const packer = new CompressedBitPacker(schema); // Compression disabled (default) — frames with "none" header const bytes = await packer.packWithCompression({ x: 42, y: 99 }); const decoded = await packer.unpackWithDecompression(bytes); // → { x: 42, y: 99 } ``` ## Usage: Advanced Example ```typescript import { CompressedBitPacker, createCompressionRegistry, } from "@suaveplan/bitpacker-compressed"; // Custom registry isolated from process-wide singleton, useful for tests // or multi-tenant servers where adapter sets must not leak. const registry = createCompressionRegistry(); // Register a custom adapter (e.g. brotli) into this isolated registry. registry.register(new MyBrotliAdapter()); const packer = new CompressedBitPacker( { position: { x: "f32", y: "f32", z: "f32" }, velocity: { x: "f32", y: "f32", z: "f32" }, }, { compression: { algorithm: "brotli", enabled: true, options: { quality: 11 }, }, compressionRegistry: registry, } ); const wireBytes = await packer.packWithCompression({ position: { x: 1, y: 2, z: 3 }, velocity: { x: 0.1, y: 0, z: 0 }, }); // wireBytes is [headerLen=6][b][r][o][t][l][i][brotli-compressed bit-packed payload] ``` ## API Reference ### `new CompressedBitPacker(schema, options?)` `schema` — a `BitPackerSchema` describing the data shape. `options.compression?` — `{ algorithm?, options?, enabled? }`. Default is `{ algorithm: "none", options: {}, enabled: false }`. `options.compressionRegistry?` — optional `CompressionRegistry` override. Defaults to `defaultCompressionRegistry()`. ### `packWithCompression(data, schema?) → Promise` Bit-packs `data`, runs the bytes through the configured compression adapter (or `NoCompressionAdapter` if `enabled` is false or `algorithm` is `"none"`), and frames the result with `[headerLen: u8][header: ASCII algorithm name][payload]`. Returns the framed wire bytes. ### `unpackWithDecompression(data?, schema?) → Promise` Reverse of `packWithCompression`. Reads the framing header, dispatches to the named adapter for decompression (or routes the body through unchanged if the header is `"none"`), then bit-unpacks the result. If `data` is `undefined` or empty, falls back to `super.unpack(empty)` for parity with upstream. ### Accessors `compressionAlgorithm` / `compressionOptions` / `compressionEnabled` — getters and setters for the runtime configuration. Allow callers to switch algorithms or toggle compression mid-flight. ### Inherited from `BitPacker` Synchronous `pack(data, schema?) → Uint8Array` and `unpack(bytes, schema?) → T` remain available. They produce/consume bytes that are NOT framed (no `[headerLen][header]` prefix) — use them when compression is genuinely off and you don't want the framing overhead. ## Implementation Notes The wire format's `headerLen` byte limits algorithm names to 1–255 ASCII bytes. The four shipping defaults (`none`, `gzip`, `deflate`, plus future `brotli`/`zstd`) are all well under that. If a future custom algorithm name exceeds the limit, register the adapter under a shorter alias. The synchronous `pack`/`unpack` from `BitPacker` are NOT overridden here. The upstream version overrode them but threw an error when compression was enabled — that mixed sync and async expectations confusingly. suaveplan splits cleanly: callers wanting compression call the async methods; callers OK with no compression use the inherited sync methods. `CompressionRegistry` is injectable so tests can use isolated registries (`createCompressionRegistry()`) without polluting the process-wide singleton. The lazy registration of `gzip`/`deflate` on the singleton (when `CompressionStream` is available) means the singleton is environment-aware without forcing every consumer through the streams API path. The `compressed-bit-packer.test.ts` test file uses `vitest.skipIf(!hasStreams)` to skip stream-backed tests in environments without `CompressionStream`. The frame/unframe helpers are tested directly via `_frameForTests` / `_unframeForTests` so the wire format is locked even in non-streaming environments.