Files
suaveplan/openspec/archive/2026-04-24-enterprise-bitpacker/proposal.md
2026-05-27 23:29:53 +01:00

5.8 KiB
Raw Blame History

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

  1. defineBitSchema — type-safe named-field schemas with compile-time inferred value types.
  2. Zigzag encoding — zigzagEncode/zigzagDecode for efficient signed-integer variable-length encoding.
  3. peek and seek on BitReader — non-destructive lookahead and random-access positioning.
  4. Alignment helpers — align() pads writer or advances reader to next byte/word boundary for protocol framing.
  5. OTel instrumentation — counter bitpacker.pack_total, counter bitpacker.unpack_total, histogram bitpacker.pack_bytes.
  6. Property-based tests — pack/unpack round-trip for arbitrary bit widths and values within range, zigzagEncode(zigzagDecode(n)) === n for all signed 32-bit integers.
  7. Benchmark suite — pack 1000 × 7-bit uints, schema pack/unpack 100-field struct, packBits on 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.