# 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((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` 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` Strict schema for the constructor bundle. `registerDefaults` is an optional boolean. ### `streamingPackOptionsSchema: z.ZodType` 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` Same shape as `streamingPackOptionsSchema`. `StreamingUnpackOptions` is an interface that extends `StreamingPackOptions` with no additional fields. ### `readableStreamFromBytesOptionsSchema: z.ZodType` Strict schema. Accepts `chunkSize`, `onProgress`, and `clock`. `chunkProcessor` is rejected because it is not part of the source-side `ReadableStream` API. ### `writablePackOptionsSchema: z.ZodType` 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` Same shape as `writablePackOptionsSchema`. ## Implementation Notes - **Identity for callables.** All callback fields are validated with `z.custom((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((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.