first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
# defaultClock
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`defaultClock` is the single point in `@suaveplan/bitpacker-streaming` where the wall clock is read. Every other module that needs a time reading either accepts a `Clock` from the caller or imports this default. Centralising the `Date.now()` call this way makes the package deterministic-by-default in tests (callers inject `FakeClock`) while the production hot path still reads the real wall clock.
This pattern matches the convention used by `@suaveplan/bitpacker-network` and `@suaveplan/bitpacker-compressed`. Suaveplan enforces a "no module-scope `Date.now()`/`Math.random()`" rule outside allow-listed defaults — this file is one of those allow-listed defaults.
## Features
- Single named export `defaultClock: Clock` typed via `@suaveplan/types`.
- Pure function — no side effects beyond reading the wall clock.
- Idiomatic Suaveplan convention for clock injection.
## Usage: Basic Example
```ts
import { defaultClock } from "@suaveplan/bitpacker-streaming";
const now = defaultClock();
```
## Usage: Advanced Example
```ts
import type { Clock } from "@suaveplan/types";
import { StreamingBitPacker } from "@suaveplan/bitpacker-streaming";
// In tests, inject a deterministic clock instead of the default.
const fakeClock: Clock = (() => {
let t = 0;
return () => {
t += 100;
return t;
};
})();
const packer = new StreamingBitPacker({ x: 255 });
await packer.packStream({ x: 1 }, { clock: fakeClock });
```
## API Reference
### `defaultClock: Clock`
Returns the current wall-clock time in milliseconds (`Date.now()`).
## Implementation Notes
The `Clock` type is `() => number`. suaveplan prefers function types over class instances for monotonic primitives because they are trivially serialisable, comparable, and substitutable in tests without dependency injection frameworks.
The reason this lives in `defaults/` rather than as a static class member is the suaveplan lint rule that bans module-scope `Date.now()` calls in production code. By scoping the call to a single named, lint-allow-listed module, we keep the rest of the package free of static analysis exceptions.

View File

@@ -0,0 +1,8 @@
## index
**Path:** `bitpacker-streaming/src/index.ts`
**Export:** See source for details.
### What it does
Part of the @suaveplan/ package.

View File

@@ -0,0 +1,84 @@
# Bitpacker Streaming OTel Instruments
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
OTel meter, tracer, counter, and histogram declarations for `@suaveplan/bitpacker-streaming`. Ships canonical instrumentation primitives for the streaming pack/unpack pipeline plus the framing layer (`length-prefix`, `delimiter`, `fixed-size`, `none`). Centralizing the metric/span vocabulary here lets every entry point — `StreamingBitPacker.packStream`, `createPackTransformStream`, `createFramedUnpackStream` — emit consistent observability data.
The metric and span names follow the suaveplan convention with snake-case for the multi-word package: tracer/meter named `@suaveplan/bitpacker-streaming`; spans named `bitpacker_streaming.<operation>`; counters/histograms named `bitpacker_streaming.<scope>.<metric>`.
## Features
- `getBitPackerStreamingTracer()` — returns the package's tracer
- `streamingOpsTotal` — counter labelled by `op` and `success`
- `streamingOpDurationMs` — histogram of pack/unpack duration
- `streamingChunkBytes` — histogram of bytes-per-chunk on the pipeline
- `streamingFramesTotal` — counter labelled by `framing` strategy
- `BITPACKER_STREAMING_SPAN_NAMES` — four canonical span names
## Usage: Basic Example
```ts
import {
BITPACKER_STREAMING_SPAN_NAMES,
streamingOpsTotal,
getBitPackerStreamingTracer,
} from "@suaveplan/bitpacker-streaming";
const tracer = getBitPackerStreamingTracer();
tracer.startActiveSpan(BITPACKER_STREAMING_SPAN_NAMES.pack, (span) => {
try {
// pack work
streamingOpsTotal.add(1, { op: "pack", success: "true" });
} finally {
span.end();
}
});
```
## Usage: Advanced Example
```ts
import {
streamingChunkBytes,
streamingFramesTotal,
} from "@suaveplan/bitpacker-streaming";
function recordChunk(chunk: Uint8Array, framing: "length-prefix" | "delimiter") {
streamingChunkBytes.record(chunk.byteLength, { op: "pack" });
streamingFramesTotal.add(1, { framing });
}
```
## API Reference
### `getBitPackerStreamingTracer(): Tracer`
Returns the OTel `Tracer` instance for the package.
### `streamingOpsTotal: Counter`
Counter labelled by `op: "pack" | "unpack"` and `success: "true" | "false"`.
### `streamingOpDurationMs: Histogram`
Histogram of elapsed milliseconds per pack/unpack call, labelled by `op`.
### `streamingChunkBytes: Histogram`
Histogram of bytes per chunk on the pipeline, labelled by `op`.
### `streamingFramesTotal: Counter`
Counter labelled by `framing: "length-prefix" | "delimiter" | "fixed-size" | "none"`.
### `BITPACKER_STREAMING_SPAN_NAMES`
Const map: `{ pack, unpack, framedPack, framedUnpack }` → canonical span names.
## Implementation Notes
- **Per-chunk events, not per-chunk spans.** A streaming pack producing 10k chunks would produce 10k spans if each were its own; instead, chunks are recorded as `streamingChunkBytes` histogram observations and as nested span events on the parent pack/unpack span.
- **Framing is a counter label.** `streamingFramesTotal{framing}` lets dashboards group frame throughput by framing strategy without spawning separate counters per type.

View File

@@ -0,0 +1,66 @@
# Register Schemas — `@suaveplan/bitpacker-streaming`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Idempotent integration with `@suaveplan/schema-registry` for the `@suaveplan/bitpacker-streaming` package. Exposes `registerBitPackerStreamingSchemas(registry)` which converts the package's nine public boundary schemas into JSON Schema form and registers them under `bitpacker:@suaveplan/bitpacker-streaming` at version 1. Re-registration on the same registry is a no-op.
The nine schemas covered are: the six streaming-bit-packer option bundles (`streamingBitPackerOptions`, `streamingPackOptions`, `streamingUnpackOptions`, `readableStreamFromBytesOptions`, `writablePackOptions`, `writableUnpackOptions`) plus the three transform-stream schemas (`framingConfig`, `packTransformOptions`, `unpackTransformOptions`).
## Features
- `BITPACKER_STREAMING_SUBJECT` constant — `"bitpacker:@suaveplan/bitpacker-streaming"`
- `BITPACKER_STREAMING_SCHEMA_VERSION` constant — `1`
- `registerBitPackerStreamingSchemas(registry)` — idempotent registration
- Nine-key payload covering every option bundle the package accepts
## Usage: Basic Example
```ts
import { createRegistry } from "@suaveplan/schema-registry";
import {
BITPACKER_STREAMING_SUBJECT,
registerBitPackerStreamingSchemas,
} from "@suaveplan/bitpacker-streaming/register-schemas";
const registry = createRegistry();
registerBitPackerStreamingSchemas(registry);
```
## Usage: Advanced Example
```ts
import { createRegistry, SchemaRegistry } from "@suaveplan/schema-registry";
import { registerBitPackerSchemas } from "@suaveplan/bitpacker/register-schemas";
import { registerBitPackerStreamingSchemas } from "@suaveplan/bitpacker-streaming/register-schemas";
const registry = createRegistry();
registerBitPackerSchemas(registry);
registerBitPackerStreamingSchemas(registry);
const payload = registry.getLatest("bitpacker:@suaveplan/bitpacker-streaming")
.schema as Record<string, unknown>;
// payload.framingConfig, payload.streamingPackOptions, …
```
## API Reference
### `BITPACKER_STREAMING_SUBJECT: "bitpacker:@suaveplan/bitpacker-streaming"`
Const string.
### `BITPACKER_STREAMING_SCHEMA_VERSION: 1`
Const number.
### `registerBitPackerStreamingSchemas(registry: SchemaRegistry): void`
Idempotent registration. Returns without writing if the subject is already present.
## Implementation Notes
- **Nine-key payload.** Each key corresponds to one of the package's exported schemas.
- **`unrepresentable: "any"`.** Required because the schemas use `z.custom` for `AbortSignal`, callables, and `Uint8Array` checks.

View File

@@ -0,0 +1,129 @@
# StreamingBitPacker
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs --> - streaming-bit-packer.schema — Zod schemas for the o…
- [streaming-bit-packer.schema](./streaming-bit-packer.schema.md) — Zod schemas for the option bundles consumed by `StreamingBitPacker` and its peer streaming helpers (`packStream`, `unpa…
- streaming-bit-packer.schema — Zod schemas for the o…
- [streaming-bit-packer.schema](./streaming-bit-packer.schema.md) — Zod schemas for the option bundles consumed by `StreamingBitPacker` and its peer streaming helpers (`packStream`, `unpa…
- streaming-bit-packer.schema — Zod schemas for the o…
- [streaming-bit-packer.schema](./streaming-bit-packer.schema.md) — Zod schemas for the option bundles consumed by `StreamingBitPacker` and its peer streaming helpers (`packStream`, `unpa…
- streaming-bit-packer.schema — Zod schemas for the o…
- [streaming-bit-packer.schema](./streaming-bit-packer.schema.md) — Zod schemas for the option bundles consumed by `StreamingBitPacker` and its peer streaming helpers (`packStream`, `unpa…
- [streaming-bit-packer.schema](./streaming-bit-packer.schema.md) — Zod schemas for the option bundles consumed by `StreamingBitPacker` and its peer streaming helpers (`packStream`, `unpa…
## Purpose
`StreamingBitPacker<T>` is the class-shaped façade that lets callers process pack/unpack operations as chunked pipelines instead of single-shot calls. It owns an underlying `BitPacker<T>` and surfaces five entry points that mirror the upstream `bitpacker/src/streaming/streaming.ts` API: `packStream`, `unpackStream`, `createReadableStream`, `createPackingStream`, and `createUnpackingStream`. Wire-format compatibility is preserved — bytes produced by `packStream` round-trip through `unpackStream` exactly as bytes produced by `BitPacker.pack` round-trip through `BitPacker.unpack`.
The streaming surface is for callers that need progress callbacks, per-chunk processing (compression, encryption), or backpressure semantics — anything where the single-shot `pack/unpack` is too coarse. For everything else, prefer the simpler chunk helpers (`packToChunks` / `unpackFromChunks`) that already live in this package's `index.ts`.
## Features
- Five entry points covering the chunked-pipeline matrix: synchronous-with-known-input (`packStream`/`unpackStream`), readable-side (`createReadableStream`), and writable-side with in-flight pack/unpack (`createPackingStream`/`createUnpackingStream`).
- Optional `chunkProcessor` runs between bit-packing and emission, so the same façade composes cleanly with downstream compression/encryption stages without callers needing to wire the WHATWG streams API by hand.
- Progress reported via the canonical `TransformProgress` shape from `@suaveplan/types` — observability tools speak one vocabulary across every Suaveplan pipeline.
- Injectable `Clock` for throughput timing; tests run with a `FakeClock` to avoid real-timer flakiness.
- `BitpackerError` codes (`STREAM_ABORTED`, `STREAM_CHUNK_PROCESSOR_FAILED`, `STREAM_INCOMPLETE_DATA`) for every failure mode, replacing upstream's plain `Error` throws.
- Default packing strategies registered automatically (opt-out via `registerDefaults: false`).
- AbortSignal supported on all `*Stream` methods — caller can cancel mid-pipeline.
## Usage: Basic Example
```ts
import { StreamingBitPacker } from "@suaveplan/bitpacker-streaming";
const packer = new StreamingBitPacker({ x: 255, y: 255 });
const packed = await packer.packStream({ x: 42, y: 99 });
const decoded = await packer.unpackStream<{ x: number; y: number }>(packed);
// → { x: 42, y: 99 }
```
## Usage: Advanced Example
```ts
import { StreamingBitPacker } from "@suaveplan/bitpacker-streaming";
import type { TransformProgress } from "@suaveplan/types";
const packer = new StreamingBitPacker({
position: { x: "f32", y: "f32", z: "f32" },
velocity: { x: "f32", y: "f32", z: "f32" },
});
const progressLog: TransformProgress[] = [];
const onProgress = (p: TransformProgress): void => {
progressLog.push(p);
};
// chunkProcessor runs between bit-pack and emission — compose with any
// downstream codec without touching the WHATWG streams API directly.
const xor = (chunk: Uint8Array): Uint8Array => {
const out = new Uint8Array(chunk.length);
for (let i = 0; i < chunk.length; i++) out[i] = (chunk[i] as number) ^ 0xaa;
return out;
};
const packed = await packer.packStream(
{
position: { x: 1, y: 2, z: 3 },
velocity: { x: 0.1, y: 0, z: 0 },
},
{
chunkSize: 4,
chunkProcessor: xor,
onProgress,
}
);
// Reverse path with the same processor undoes the XOR before unpack.
const decoded = await packer.unpackStream(packed, {
chunkSize: 4,
chunkProcessor: xor,
});
```
## API Reference
### `new StreamingBitPacker<T>(schema, options?)`
`schema` — a `BitPackerSchema` describing the data shape.
`options.registerDefaults?` — `boolean`. Default `true`. When `false`, the underlying `BitPacker` ships with no strategies; the caller must register their own before any pack/unpack call.
### `packStream(data, options?) → Promise<Uint8Array>`
Bit-packs `data`, walks the bytes in `chunkSize`-byte chunks (default `1024`), runs each chunk through `options.chunkProcessor` if supplied, and returns the concatenated bytes. `onProgress` fires once per chunk. Throws `BitpackerError(STREAM_ABORTED)` if `options.signal` aborts before or mid-loop.
### `unpackStream<R>(data, options?) → Promise<R>`
Reverse of `packStream`. Walks `data` in chunks (running the inverse processor if supplied), concatenates, and bit-unpacks.
### `createReadableStream(data, options?) → ReadableStream<Uint8Array>`
Returns a WHATWG `ReadableStream<Uint8Array>` that emits `data` in chunks. Useful for piping into another transform (`pipeThrough(compressionStream)`). Emits all chunks during `start()` and immediately closes — back-pressure is delegated to the consumer.
### `createPackingStream(options?) → { stream, result }`
`stream` is a `WritableStream<unknown>` that packs each `write(item)` and pushes the bytes into a buffer. `result` resolves to the concatenated packed bytes when the stream closes. If `chunkProcessor` is supplied, each packed chunk is run through it before being buffered. `result` rejects with `BitpackerError(STREAM_ABORTED)` on `abort()`.
### `createUnpackingStream<R>(options?) → { stream, result }`
`stream` is a `WritableStream<Uint8Array>` that pushes each `write(bytes)` (optionally through `chunkProcessor`) into a buffer. On `close()`, the buffer is concatenated and bit-unpacked. `result` rejects with `BitpackerError(STREAM_INCOMPLETE_DATA)` if the stream closes empty, or `STREAM_ABORTED` on `abort()`.
### Inherited
`bitPacker` — public readonly. The underlying `BitPacker<T>` instance. Useful for callers wanting to register a custom strategy or schema.
## Implementation Notes
The port drops upstream's `EventEmitter` inheritance because `BitPacker` does not extend an emitter and the `'progress'` event fires the same payload `onProgress` would have received — the callback is the simpler shape. This also avoids the duplicate-listener footgun upstream callers occasionally hit.
`validateChunks` from upstream is gone. `BitPacker` does not expose `validate()` or `getValidationErrors()` — those upstream helpers were thin wrappers over schema-introspection logic that suaveplan pushes onto `@suaveplan/validation` callers. If pre-pack validation is needed, run a Zod schema at the call site before invoking `packStream`.
The `runChunkPipeline` helper is shared between `packStream` and `unpackStream` because the per-chunk loop logic — chunk slice, optional processor, progress emit — is identical in both directions. Diverging the two paths would duplicate ~30 lines of logic for no semantic gain.
`createPackingStream` and `createUnpackingStream` deliberately do not auto-emit on `start` (unlike `createReadableStream`) — they are _writable_ sides and emission timing is fully driven by caller writes. The `result` promise pattern lets callers `await` the closure without needing a separate completion signal.

View File

@@ -0,0 +1,99 @@
# Streaming BitPacker Schemas
## Purpose
Zod schemas for the option bundles consumed by `StreamingBitPacker` and its peer streaming helpers (`packStream`, `unpackStream`, `readableStreamFromBytes`, the writable-side equivalents). Each schema is the runtime gatekeeper at the corresponding constructor or method boundary so a typo'd flag (`registerDefualts`) or a mis-typed `signal` (a plain object instead of an `AbortSignal`) fails at parse time instead of breaking the chunk pipeline halfway through.
The schemas use `z.custom<T>((v): v is T => …)` for `AbortSignal`, the chunk-processor callback, the progress callback, and the structural `Clock` so referential identity is preserved end-to-end. This matters for cancellation (`signal === user.signal` comparisons elsewhere in the chain), for callback registration (the same function reference must reach progress reporters), and for clock injection (test fakes need the same instance their assertions reference).
## Features
- `streamingBitPackerOptionsSchema` — strict, today only `registerDefaults`
- `streamingPackOptionsSchema` — `chunkSize`, `chunkProcessor`, `onProgress`, `clock`, `signal`
- `streamingUnpackOptionsSchema` — same shape as pack (re-export)
- `readableStreamFromBytesOptionsSchema` — source-side subset (no chunkProcessor, no signal)
- `writablePackOptionsSchema` — sink-side subset (no chunkSize, no signal)
- `writableUnpackOptionsSchema` — same shape as writable pack (re-export)
- Identity-preserving `z.custom` for callables, AbortSignal, and Clock duck-types
- `as unknown as z.ZodType<T>` casts for `exactOptionalPropertyTypes` mismatches
## Usage: Basic Example
```ts
import {
StreamingBitPacker,
streamingBitPackerOptionsSchema,
streamingPackOptionsSchema,
} from "@suaveplan/bitpacker-streaming";
const constructorOpts = streamingBitPackerOptionsSchema.parse({
registerDefaults: true,
});
const packer = new StreamingBitPacker({ x: 255 }, constructorOpts);
const ac = new AbortController();
const callOpts = streamingPackOptionsSchema.parse({
chunkSize: 1024,
signal: ac.signal,
});
const bytes = await packer.packStream({ x: 100 }, callOpts);
```
## Usage: Advanced Example
```ts
import {
StreamingBitPacker,
streamingPackOptionsSchema,
writablePackOptionsSchema,
} from "@suaveplan/bitpacker-streaming";
function safelyPack(packer: StreamingBitPacker, raw: unknown) {
const result = streamingPackOptionsSchema.safeParse(raw);
if (!result.success) {
throw new Error(
`Streaming pack options rejected: ${result.error.message}`
);
}
return packer.packStream({ x: 0 }, result.data);
}
// Writable-side callback wired through the progress channel:
const writableOpts = writablePackOptionsSchema.parse({
chunkProcessor: (chunk) => chunk,
onProgress: (p) => console.log(p),
});
```
## API Reference
### `streamingBitPackerOptionsSchema: z.ZodType<StreamingBitPackerOptions>`
Strict schema for the constructor bundle. `registerDefaults` is an optional boolean.
### `streamingPackOptionsSchema: z.ZodType<StreamingPackOptions>`
Strict schema for the per-call options. `chunkSize` is a positive integer; `chunkProcessor` and `onProgress` are functions (identity-preserved); `clock` is a structural `{ now(): number }`; `signal` is an `AbortSignal` instance.
### `streamingUnpackOptionsSchema: z.ZodType<StreamingUnpackOptions>`
Same shape as `streamingPackOptionsSchema`. `StreamingUnpackOptions` is an interface that extends `StreamingPackOptions` with no additional fields.
### `readableStreamFromBytesOptionsSchema: z.ZodType<ReadableStreamFromBytesOptions>`
Strict schema. Accepts `chunkSize`, `onProgress`, and `clock`. `chunkProcessor` is rejected because it is not part of the source-side `ReadableStream` API.
### `writablePackOptionsSchema: z.ZodType<WritablePackOptions>`
Strict schema. Accepts `chunkProcessor`, `onProgress`, `clock`. `chunkSize` and `signal` are rejected because chunk boundaries and cancellation are controlled upstream by the writable consumer.
### `writableUnpackOptionsSchema: z.ZodType<WritableUnpackOptions>`
Same shape as `writablePackOptionsSchema`.
## Implementation Notes
- **Identity for callables.** All callback fields are validated with `z.custom<Fn>((v): v is Fn => typeof v === "function", …)` so the same instance flows through. `z.function()` in Zod 4 wraps the callable and breaks identity for the chunk pipeline, which depends on `===` comparisons during sequential chunk dispatch.
- **`AbortSignal` instance check.** `z.custom<AbortSignal>((v): v is AbortSignal => v instanceof AbortSignal, …)` rejects duck-typed signals. The cancellation path checks `signal.aborted` via the prototype, so a non-`AbortSignal` would silently fail to cancel.
- **`Clock` is duck-typed.** `clock` is `z.custom<{ now(): number }>`. Tests inject a `FakeClock` from `@suaveplan/testing` that satisfies the shape without inheriting from a class. Reflecting that flexibility in the schema avoids over-coupling.
- **Subset schemas reject extra fields.** `readableStreamFromBytesOptionsSchema` rejects `chunkProcessor`; `writablePackOptionsSchema` rejects `chunkSize`/`signal`. This isn't decorative — it surfaces miscompositions where a caller copies a `StreamingPackOptions` bundle into a writable-side method.

View File

@@ -0,0 +1,53 @@
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs --> - transform.schema — Zod schemas for the option bundl…
- [transform.schema](./transform.schema.md) — Zod schemas for the option bundles consumed by `createPackTransformStream`, `createUnpackTransformStream`, and the fram…
- transform.schema — Zod schemas for the option bundl…
- [transform.schema](./transform.schema.md) — Zod schemas for the option bundles consumed by `createPackTransformStream`, `createUnpackTransformStream`, and the fram…
- transform.schema — Zod schemas for the option bundl…
- [transform.schema](./transform.schema.md) — Zod schemas for the option bundles consumed by `createPackTransformStream`, `createUnpackTransformStream`, and the fram…
- transform.schema — Zod schemas for the option bundl…
- [transform.schema](./transform.schema.md) — Zod schemas for the option bundles consumed by `createPackTransformStream`, `createUnpackTransformStream`, and the fram…
- [transform.schema](./transform.schema.md) — Zod schemas for the option bundles consumed by `createPackTransformStream`, `createUnpackTransformStream`, and the fram…
## Transform Streams
**Path:** `src/transform/transform.ts`
**Export:** `createPackTransformStream`, `createUnpackTransformStream`, `createFramedPackStream`, `createFramedUnpackStream`, `parseFrames`, `addFrame`
### What it does
True `TransformStream` support for BitPacker with backpressure, batching, progress callbacks, and multiple framing strategies.
### Key API
| Function | Description |
|----------|-------------|
| `createPackTransformStream(bp, schema, options?)` | Transform objects → `Uint8Array` chunks |
| `createUnpackTransformStream(bp, schema, options?)` | Transform `Uint8Array` → objects (length-prefix framing) |
| `createFramedPackStream(bp, schema, framing?, options?)` | Pack with explicit framing config |
| `createFramedUnpackStream(bp, schema, framing?, options?)` | Unpack with explicit framing config |
| `parseFrames(buffer, framing)` | Extract complete frames from a buffer |
| `addFrame(data, framing)` | Add framing to a message |
### Framing Types
- `length-prefix` — prefix with 1/2/4 byte length (default)
- `delimiter` — suffix with delimiter bytes
- `fixed-size` — pad/truncate to fixed size
- `none` — no framing (single message per chunk)
### Depends on
- `BitPacker` from `@suaveplan/bitpacker`
### Used by
- Network streaming pipelines
- File serialization with chunked I/O

View File

@@ -0,0 +1,79 @@
# Transform Stream Schemas
## Purpose
Zod schemas for the option bundles consumed by `createPackTransformStream`, `createUnpackTransformStream`, and the framing helpers (`addFrame`, `parseFrames`, `createFramedPackStream`, `createFramedUnpackStream`). Each schema is the runtime gatekeeper at the corresponding boundary — `framingConfigSchema` validates the frame-strategy descriptor, and `packTransformOptionsSchema` / `unpackTransformOptionsSchema` validate the per-call tuning bundles.
The framing schema deliberately does NOT enforce cross-field requirements (e.g. "if `type === 'length-prefix'` then `lengthPrefixSize` is required"). The runtime parser tolerates the extra fields, and forcing the cross-field check at parse time would force callers to always specify `lengthPrefixSize: 4` even when defaulting it. Strict-mode rejects unknown keys; the runtime parser is the authority on which combinations make sense.
## Features
- `framingConfigSchema` — discriminated by `type` (`length-prefix` / `delimiter` / `fixed-size` / `none`)
- `packTransformOptionsSchema` — `batchSize`, `highWaterMark`, `onProgress`
- `unpackTransformOptionsSchema` — `highWaterMark`, `onProgress`, `headerSize`
- Identity-preserving `z.custom` for `onProgress`
- `Uint8Array` instance check for `delimiter` (rejects array-of-numbers literals)
- `as unknown as z.ZodType<T>` casts for EOPT mismatches
## Usage: Basic Example
```ts
import {
framingConfigSchema,
packTransformOptionsSchema,
} from "@suaveplan/bitpacker-streaming";
const framing = framingConfigSchema.parse({
type: "length-prefix",
lengthPrefixSize: 4,
});
const packOpts = packTransformOptionsSchema.parse({
batchSize: 4,
highWaterMark: 16,
onProgress: (p) => console.log(p),
});
```
## Usage: Advanced Example
```ts
import {
addFrame,
framingConfigSchema,
parseFrames,
unpackTransformOptionsSchema,
} from "@suaveplan/bitpacker-streaming";
// Validate framing at the boundary, then trust the parsed shape downstream:
function withFraming(rawFraming: unknown, payload: Uint8Array) {
const cfg = framingConfigSchema.parse(rawFraming);
return addFrame(payload, cfg);
}
// Per-call unpack options, including a length-prefix header width:
const unpackOpts = unpackTransformOptionsSchema.parse({
highWaterMark: 32,
headerSize: 4,
});
```
## API Reference
### `framingConfigSchema: z.ZodType<FramingConfig>`
Strict schema for the `FramingConfig` discriminator. `type` is one of four literal tokens. `lengthPrefixSize` is restricted to `1 | 2 | 4`. `delimiter` must be a `Uint8Array` (not a `number[]` literal). `fixedSize` is a positive integer.
### `packTransformOptionsSchema: z.ZodType<PackTransformOptions>`
Strict schema. `batchSize` and `highWaterMark` are positive integers. `onProgress` is a function preserved by identity.
### `unpackTransformOptionsSchema: z.ZodType<UnpackTransformOptions>`
Strict schema. `highWaterMark` and `headerSize` are positive integers. `onProgress` is a function preserved by identity.
## Implementation Notes
- **No cross-field validation.** A `length-prefix` framing without `lengthPrefixSize` is technically incomplete, but the runtime parser defaults it to `4` and tolerates the omission. Forcing a cross-field rule at parse time would over-constrain callers.
- **`Uint8Array` instance check on `delimiter`.** A `[0x0a]` literal looks like a delimiter but does not implement the typed-array surface the parser uses (`.subarray`, `.byteLength`). Refusing it at parse time prevents a silent type coercion that would surface as an obscure runtime error.
- **`as unknown as` casts.** Same EOPT mismatch as elsewhere in this change. Runtime semantics are identical.

View File

@@ -0,0 +1,8 @@
## transform.types
**Path:** `bitpacker-streaming/src/transform/transform.types.ts`
**Export:** See source for details.
### What it does
Part of the @suaveplan/ package.