Initial Commit
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
<!-- markdownlint-disable MD024 -->
|
||||
|
||||
# Capability Spec Delta: bitpacker-compressed (bitpacker-stub-completion)
|
||||
|
||||
**Package**: `@suaveplan/bitpacker-compressed`
|
||||
**Status**: PROPOSED
|
||||
|
||||
> Every requirement below carries a `**Verified by:**` line at authoring time.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: CompressedBitPacker subclass
|
||||
|
||||
The package SHALL export `CompressedBitPacker<T extends BitPackerSchema> extends BitPacker<T>`. The subclass SHALL run packed bytes through a configured `CompressionAdapter` before returning them, and SHALL run incoming bytes through `decompressData` before delegating to `BitPacker.unpack`. Compression behavior SHALL be configurable via `BitPackerConfig.compression` (`algorithm`, `options`, `enabled`); accessors `compressionAlgorithm`, `compressionOptions`, `compressionEnabled` SHALL surface and mutate the current settings.
|
||||
|
||||
#### Scenario: pack → decompress round-trip is byte-identical
|
||||
|
||||
- **WHEN** a `CompressedBitPacker` packs `{ a: 1, b: "hello" }` with `algorithm: "gzip"` and the result is decompressed and unpacked
|
||||
- **THEN** the unpacked value deep-equals the input
|
||||
|
||||
#### Scenario: `enabled: false` skips compression entirely
|
||||
|
||||
- **WHEN** `compressionEnabled` is `false`
|
||||
- **THEN** `pack` returns bytes byte-identical to `BitPacker.pack` (no compression header, no codec invocation)
|
||||
|
||||
#### Scenario: `algorithm: "none"` invokes NoCompressionAdapter
|
||||
|
||||
- **WHEN** `algorithm` is `"none"`
|
||||
- **THEN** `pack` produces the same bytes as `BitPacker.pack` AND the OTel counter `bitpacker.compress.bytes_in` records the input length while `bitpacker.compress.bytes_out` records the same value
|
||||
|
||||
**Verified by:** `packages/bitpacker/bitpacker-compressed/src/compressed-bit-packer/compressed-bit-packer.test.ts::"pack-decompress round-trip is byte-identical for gzip"` AND `packages/bitpacker/bitpacker-compressed/src/compressed-bit-packer/compressed-bit-packer.test.ts::"compressionEnabled false skips codec invocation"` AND `packages/bitpacker/bitpacker-compressed/src/compressed-bit-packer/compressed-bit-packer.test.ts::"algorithm 'none' yields BitPacker.pack output"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: CompressionAdapter contract and built-in adapters
|
||||
|
||||
The package SHALL expose a `CompressionAdapter` interface with `compress(data, options): Promise<Uint8Array>` and `decompress(data, options): Promise<Uint8Array>`. The package SHALL ship `NoCompressionAdapter`, `GzipCompressionAdapter`, and `DeflateCompressionAdapter` matching upstream behavior (`compression.ts` L121-L336).
|
||||
|
||||
#### Scenario: gzip adapter round-trip
|
||||
|
||||
- **WHEN** `GzipCompressionAdapter` compresses 1 KiB of zeros, then decompresses
|
||||
- **THEN** the output is the original 1 KiB AND the compressed length is < 50 bytes
|
||||
|
||||
#### Scenario: deflate adapter round-trip
|
||||
|
||||
- **WHEN** `DeflateCompressionAdapter` compresses random 1 KiB bytes, then decompresses
|
||||
- **THEN** the decompressed output deep-equals the original
|
||||
|
||||
#### Scenario: NoCompressionAdapter passes bytes through
|
||||
|
||||
- **WHEN** `NoCompressionAdapter.compress(input)` is called
|
||||
- **THEN** the result is byte-identical to the input
|
||||
|
||||
**Verified by:** `packages/bitpacker/bitpacker-compressed/src/compression-registry/compression-registry.test.ts::"GzipCompressionAdapter round-trip"` AND `packages/bitpacker/bitpacker-compressed/src/compression-registry/compression-registry.test.ts::"DeflateCompressionAdapter round-trip"` AND `packages/bitpacker/bitpacker-compressed/src/compression-registry/compression-registry.test.ts::"NoCompressionAdapter passes bytes through"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: CompressionRegistry replacement and unknown-algorithm error
|
||||
|
||||
The package SHALL expose a `CompressionRegistry` allowing callers to `register(algorithm, adapter)` and `resolve(algorithm)`. Resolving an unknown algorithm SHALL throw `BitpackerError` with code `BITPACKER.UNKNOWN_COMPRESSION_ALGORITHM` and `context.algorithm`.
|
||||
|
||||
#### Scenario: registering an adapter overrides the default
|
||||
|
||||
- **WHEN** a custom `BrotliCompressionAdapter` is registered under key `"brotli"` then `resolve("brotli")` is called
|
||||
- **THEN** the registered adapter is returned; subsequent `pack` calls with `algorithm: "brotli"` route through it
|
||||
|
||||
#### Scenario: resolving an unknown algorithm throws coded error
|
||||
|
||||
- **WHEN** `resolve("zstd")` is called and no adapter is registered for that key
|
||||
- **THEN** the call throws `BitpackerError` with code `BITPACKER.UNKNOWN_COMPRESSION_ALGORITHM` and `context.algorithm === "zstd"`
|
||||
|
||||
**Verified by:** `packages/bitpacker/bitpacker-compressed/src/compression-registry/compression-registry.spec-conformance.test.ts::"register overrides default adapter"` AND `packages/bitpacker/bitpacker-compressed/src/compression-registry/compression-registry.spec-conformance.test.ts::"resolve throws UNKNOWN_COMPRESSION_ALGORITHM for missing key"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Compression failure surfaces coded error
|
||||
|
||||
When the underlying codec rejects (e.g. truncated gzip stream), the package SHALL wrap the cause and throw `BitpackerError` with code `BITPACKER.COMPRESSION_FAILED` (during compress) or `BITPACKER.DECOMPRESSION_FAILED` (during decompress); `context.algorithm` SHALL identify the codec.
|
||||
|
||||
#### Scenario: truncated gzip stream emits DECOMPRESSION_FAILED
|
||||
|
||||
- **WHEN** a half-truncated gzip-compressed buffer is passed to `decompressData(buf, "gzip")`
|
||||
- **THEN** a `BitpackerError` with code `BITPACKER.DECOMPRESSION_FAILED` is thrown with the underlying codec error chained as `cause`
|
||||
|
||||
**Verified by:** `packages/bitpacker/bitpacker-compressed/src/compression-registry/compression-registry.spec-conformance.test.ts::"truncated gzip stream emits DECOMPRESSION_FAILED with chained cause"`
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: RLE pass-through helpers
|
||||
|
||||
The package SHALL keep `compressRle` / `decompressRle` as exported free functions for the bytes-only fast path; they SHALL be available both at the package root (one-release deprecation) and at the `./rle` subpath. Their behavior SHALL match the existing 61-line implementation byte-for-byte (no semantic change).
|
||||
|
||||
#### Scenario: existing RLE call sites still resolve from package root
|
||||
|
||||
- **WHEN** a consumer imports `{ compressRle } from "@suaveplan/bitpacker-compressed"`
|
||||
- **THEN** the import resolves and `compressRle` produces the same bytes as before this change
|
||||
|
||||
#### Scenario: canonical import location is `./rle`
|
||||
|
||||
- **WHEN** a consumer imports `{ compressRle } from "@suaveplan/bitpacker-compressed/rle"`
|
||||
- **THEN** the import resolves and the function is the same reference as the root export
|
||||
|
||||
**Verified by:** `packages/bitpacker/bitpacker-compressed/src/rle/rle.test.ts::"compressRle output unchanged from pre-rewrite snapshot"` AND `packages/bitpacker/bitpacker-compressed/src/rle/rle.test.ts::"./rle subpath and root export resolve to the same function"`
|
||||
|
||||
<!-- markdownlint-enable MD024 -->
|
||||
Reference in New Issue
Block a user