Initial Commit
This commit is contained in:
199
openspec/archive/2026-04-24-enterprise-bitpacker/proposal.md
Normal file
199
openspec/archive/2026-04-24-enterprise-bitpacker/proposal.md
Normal file
@@ -0,0 +1,199 @@
|
||||
# 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
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```text
|
||||
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.
|
||||
84
openspec/archive/2026-04-24-enterprise-bitpacker/tasks.md
Normal file
84
openspec/archive/2026-04-24-enterprise-bitpacker/tasks.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Tasks: enterprise-bitpacker
|
||||
|
||||
**Change ID**: enterprise-bitpacker
|
||||
**Package**: `@suaveplan/bitpacker`
|
||||
**Target path**: `universal/compression/bitpacker`
|
||||
**Prerequisite packages on Verdaccio**: `@suaveplan/error`, `@suaveplan/types`
|
||||
|
||||
---
|
||||
|
||||
## 1. Scaffold
|
||||
|
||||
- [x] Create directory `universal/compression/bitpacker/` with `src/`, `tests/`, `src/__benchmarks__/`, and all feature subdirectories per the layout in `proposal.md`.
|
||||
- [x] Write `package.json` at version `0.1.0` with `"type": "module"`, `"sideEffects": false`, full `exports` map, deps: `@suaveplan/error workspace:^`, `@suaveplan/types workspace:^`, `zod catalog:forms`, `@opentelemetry/api catalog:observability`.
|
||||
- [x] Write `tsconfig.json` extending `@suaveplan/typescript-config/library.json`.
|
||||
- [x] Write `vitest.config.ts` using `createTestConfig()` from `@suaveplan/testing/config`.
|
||||
- [x] Add `lint`, `typecheck`, `test`, `test:coverage`, `build`, `docs:check` scripts.
|
||||
|
||||
---
|
||||
|
||||
## 2. Types + errors
|
||||
|
||||
- [x] Write `src/types/common.ts` — `FieldType`, `FieldDef`, `BitSchema` interface.
|
||||
- [x] Write `src/types/common.md`.
|
||||
- [x] Write `src/error-codes.ts` — `BitpackerErrorCode`, `BITPACKER_ERROR_METADATA`, `registerBitpackerErrorCodes()`.
|
||||
- [x] Write Zod schema: `FieldDefSchema`.
|
||||
|
||||
---
|
||||
|
||||
## 3. BitWriter
|
||||
|
||||
- [x] Implement `BitWriter` — dynamic `Uint8Array` buffer (double-capacity growth), bit-cursor tracking, `writeUint`, `writeInt` (two's complement), `writeBool`, `writeFixed`, `writeBytes`, `align`, `toUint8Array`, `bitLength`, `byteLength`.
|
||||
- [x] Throws `BitpackerError(BITPACKER_INVALID_BIT_WIDTH)` for bits ≤ 0 or bits > 53.
|
||||
- [x] Throws `BitpackerError(BITPACKER_VALUE_OUT_OF_RANGE)` for out-of-range values.
|
||||
- [x] Write `bit-writer.test.ts` (≤500 lines): each method, boundary values, growth, align padding.
|
||||
- [x] Write `bit-writer.md`.
|
||||
|
||||
---
|
||||
|
||||
## 4. BitReader
|
||||
|
||||
- [x] Implement `BitReader` — reads from frozen `Uint8Array`, `readUint`, `readInt`, `readBool`, `readFixed`, `readBytes`, `align`, `bitPosition`, `bitsRemaining`, `peek`, `seek`.
|
||||
- [x] Throws `BitpackerError(BITPACKER_UNDERFLOW)` when reading past end.
|
||||
- [x] Throws `BitpackerError(BITPACKER_INVALID_BIT_WIDTH)` for bits ≤ 0 or bits > 53.
|
||||
- [x] Write `bit-reader.test.ts` (≤500 lines): each method, underflow, seek/peek.
|
||||
- [x] Write `bit-reader.md`.
|
||||
|
||||
---
|
||||
|
||||
## 5. BitSchema
|
||||
|
||||
- [x] Implement `defineBitSchema<T>(fields): BitSchema<T>` — validates field definitions via Zod, computes `byteSize` for fixed-width schemas, exposes typed `pack(values): Uint8Array` and `unpack(data): T`.
|
||||
- [x] Write `bit-schema.test.ts` and `bit-schema.md`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Pack utilities
|
||||
|
||||
- [x] Implement `packBits`, `unpackBits`, `countBits`, `zigzagEncode`, `zigzagDecode` in `utils/pack-bits.ts` and `utils/zigzag.ts`.
|
||||
- [x] Wire OTel counters on `packBits` / `unpackBits`.
|
||||
- [x] Write `pack-bits.test.ts`, `pack-bits.md`, `zigzag.test.ts`, `zigzag.md`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Benchmarks
|
||||
|
||||
- [x] Write `src/__benchmarks__/bitpacker.bench.ts` — pack 1000 × 7-bit uints, schema pack/unpack 100-field struct, `packBits` 10000 values.
|
||||
|
||||
---
|
||||
|
||||
## 8. Tests
|
||||
|
||||
- [x] Write `tests/integration.test.ts` — BitWriter → BitReader full round-trip including schema.
|
||||
- [x] Write `tests/property.test.ts` (fast-check) — pack/unpack round-trip for arbitrary bit widths and values in range, `zigzagEncode(zigzagDecode(n)) === n`.
|
||||
|
||||
---
|
||||
|
||||
## 9. Docs + verify + publish
|
||||
|
||||
- [x] All co-located `.md` files (≥200 words each).
|
||||
- [x] Write `README.md` at package root.
|
||||
- [x] `bunx turbo lint typecheck test --filter=@suaveplan/bitpacker` — green.
|
||||
- [x] `bunx turbo test:coverage --filter=@suaveplan/bitpacker` — 100%.
|
||||
- [x] `bun scripts/publish.ts --package @suaveplan/bitpacker`.
|
||||
- [x] Archive to `openspec/archive/2026-04-24-enterprise-bitpacker/`.
|
||||
Reference in New Issue
Block a user