Files
skills/docs/foundation/bitpacker/bitpacker-streaming/src/streaming-bit-packer/streaming-bit-packer.schema.md
2026-05-29 08:20:56 +01:00

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 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

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.
  • 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.