5.8 KiB
OpenSpec Change: enterprise-bitpacker
- Change ID: enterprise-bitpacker
- Package:
@suaveplan/bitpacker - Category: universal
- Target path:
universal/compression/bitpacker - Status: PROPOSED
- Date: 2026-04-24
- Reference spec:
/mnt/s/web/suaveplan/enterprise/bitpacker.md
Summary
Deliver @suaveplan/bitpacker on the genesis branch from scratch at enterprise quality. The package provides bit-level binary packing and unpacking: arbitrary-width unsigned/signed integers (1–53 bits), booleans, fixed-point numbers, bit-field schemas with named fields, and streaming read/write cursors. Designed for network protocols, game state serialisation, and compact binary formats. Pure TypeScript, zero deps beyond @suaveplan/error.
Current-state audit (genesis)
The package does not exist on genesis yet — this is a fresh build. No prior source, no test results, no coverage data. The main-branch package had 91% coverage.
Available workspace deps on genesis
| Package | Role |
|---|---|
@suaveplan/error (workspace:^) |
Base class for BitpackerError; BITPACKER.* codes |
@suaveplan/types (workspace:^) |
LoggerLike |
zod (catalog:forms) |
Zod schemas for schema definitions |
@opentelemetry/api (catalog:observability) |
Spans + counters |
Dev deps: vitest, @vitest/coverage-istanbul, fast-check, mitata (all catalog:testing).
Public API
BitWriter
class BitpackerError extends SuaveplanError
class BitWriter {
constructor(initialCapacityBytes?: number)
writeUint(value: number, bits: number): this // 1–53 bits
writeInt(value: number, bits: number): this // 1–53 bits, two's complement
writeBool(value: boolean): this
writeFixed(value: number, intBits: number, fracBits: number): this
writeBytes(bytes: Uint8Array): this
align(toBytes?: number): this // pad to byte/word boundary
toUint8Array(): Uint8Array
get bitLength(): number
get byteLength(): number
}
BitReader
class BitReader {
constructor(data: Uint8Array)
readUint(bits: number): number
readInt(bits: number): number
readBool(): boolean
readFixed(intBits: number, fracBits: number): number
readBytes(byteCount: number): Uint8Array
align(toBytes?: number): void
get bitPosition(): number
get bitsRemaining(): number
peek(bits: number): number // read without advancing
seek(bitPosition: number): void
}
Schema-based packing
type FieldType = 'uint' | 'int' | 'bool' | 'fixed' | 'bytes'
interface FieldDef {
name: string
type: FieldType
bits?: number // for uint/int
intBits?: number // for fixed
fracBits?: number // for fixed
byteCount?: number // for bytes
}
interface BitSchema<T extends Record<string, unknown> = Record<string, unknown>> {
fields: FieldDef[]
pack(values: T): Uint8Array
unpack(data: Uint8Array): T
byteSize: number // fixed size if computable; 0 = variable
}
function defineBitSchema<T extends Record<string, unknown>>(fields: FieldDef[]): BitSchema<T>
Utilities
function packBits(values: number[], bitsPerValue: number): Uint8Array
function unpackBits(data: Uint8Array, count: number, bitsPerValue: number): number[]
function countBits(n: number): number // position of highest set bit
function zigzagEncode(n: number): number // signed → unsigned
function zigzagDecode(n: number): number // unsigned → signed
Error codes
type BitpackerErrorCode =
| 'BITPACKER_OVERFLOW'
| 'BITPACKER_UNDERFLOW'
| 'BITPACKER_INVALID_BIT_WIDTH'
| 'BITPACKER_VALUE_OUT_OF_RANGE'
| 'BITPACKER_SCHEMA_INVALID'
const BITPACKER_ERROR_METADATA: Readonly<Record<BitpackerErrorCode, CodeMetadata>>
function registerBitpackerErrorCodes(): void
Zod schemas
FieldDefSchema
New additions for genesis
defineBitSchema— type-safe named-field schemas with compile-time inferred value types.- Zigzag encoding —
zigzagEncode/zigzagDecodefor efficient signed-integer variable-length encoding. peekandseekonBitReader— non-destructive lookahead and random-access positioning.- Alignment helpers —
align()pads writer or advances reader to next byte/word boundary for protocol framing. - OTel instrumentation — counter
bitpacker.pack_total, counterbitpacker.unpack_total, histogrambitpacker.pack_bytes. - Property-based tests — pack/unpack round-trip for arbitrary bit widths and values within range,
zigzagEncode(zigzagDecode(n)) === nfor all signed 32-bit integers. - Benchmark suite — pack 1000 × 7-bit uints, schema pack/unpack 100-field struct,
packBitson 10000 values.
File layout
universal/compression/bitpacker/
package.json
tsconfig.json
vitest.config.ts
src/
index.ts
error-codes.ts
constants/
index.ts
types/
common.ts
common.md
core/
bit-writer/
bit-writer.ts
bit-writer.md
bit-writer.types.ts
bit-writer.test.ts
index.ts
bit-reader/
bit-reader.ts
bit-reader.md
bit-reader.types.ts
bit-reader.test.ts
index.ts
bit-schema/
bit-schema.ts
bit-schema.md
bit-schema.types.ts
bit-schema.test.ts
index.ts
utils/
pack-bits.ts
pack-bits.md
pack-bits.test.ts
zigzag.ts
zigzag.md
zigzag.test.ts
__benchmarks__/
bitpacker.bench.ts
tests/
integration.test.ts
property.test.ts
Impact
Downstream: @suaveplan/packer, network protocol packages, game state serialisation.
Breaking changes: none — fresh build.
Prerequisites: @suaveplan/error, @suaveplan/types published to Verdaccio.