5.2 KiB
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 onlyregisterDefaultsstreamingPackOptionsSchema—chunkSize,chunkProcessor,onProgress,clock,signalstreamingUnpackOptionsSchema— 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.customfor callables, AbortSignal, and Clock duck-types as unknown as z.ZodType<T>casts forexactOptionalPropertyTypesmismatches
Usage: Basic Example
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
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. AbortSignalinstance check.z.custom<AbortSignal>((v): v is AbortSignal => v instanceof AbortSignal, …)rejects duck-typed signals. The cancellation path checkssignal.abortedvia the prototype, so a non-AbortSignalwould silently fail to cancel.Clockis duck-typed.clockisz.custom<{ now(): number }>. Tests inject aFakeClockfrom@suaveplan/testingthat satisfies the shape without inheriting from a class. Reflecting that flexibility in the schema avoids over-coupling.- Subset schemas reject extra fields.
readableStreamFromBytesOptionsSchemarejectschunkProcessor;writablePackOptionsSchemarejectschunkSize/signal. This isn't decorative — it surfaces miscompositions where a caller copies aStreamingPackOptionsbundle into a writable-side method.