refineSwitch<>(refiners: TRefiners,): Coder<RefinedUnion<TRefiners>>
Creates a coder that conditionally applies refiners based on selector functions, using switch-like semantics for bidirectional encoding and decoding.
This primitive enables multi-stage conditional coding where different refiners are applied based on runtime values. The selector functions determine which refiner to use for both decode (refine) and encode (unrefine) operations.
Switch Statement Analogy:
// Traditional switch switch (getKey(value)) { case 'A': return refinerA(value); case 'B': return refinerB(value); default: throw new Error('No match'); } // refineSwitch equivalent refineSwitch(baseCoder, refiners, { refine: (value) => getKey(value), // switch expression for decode unrefine: (value) => getKey(value), // switch expression for encode })
Discriminator Stability Contract: The selector must return the same key for a value before and after refinement. Violating this contract will cause encoding to fail or produce incorrect results.
Example violation:
refiner.refine({ type: "A", ... }) → { type: "B", ... } // INVALID - type changed
Error Handling:
If the selector returns null, an error is thrown. This fail-fast behavior
ensures type safety and prevents unexpected values from being processed.
PNG chunk refinement with type-based selection
PNG chunk refinement with type-based selection
import { assertEquals } from "@std/assert"; import { refineSwitch, type Refiner, type Context, decode, encode } from "@hertzg/binstruct"; import { struct, u32be, bytes, string, ref } from "@hertzg/binstruct"; interface PngChunkUnknown { length: number; type: Uint8Array; data: Uint8Array; crc: number; } interface IhdrChunk { length: number; type: "IHDR"; data: { width: number; height: number }; crc: number; } const lengthCoder = u32be(); const pngChunkUnknown = struct({ length: lengthCoder, type: bytes(4), data: bytes(ref(lengthCoder)), crc: u32be(), }); const ihdrRefiner = (): Refiner<PngChunkUnknown, IhdrChunk, []> => { const typeCoder = string(4); const dataCoder = struct({ width: u32be(), height: u32be() }); return { refine: (chunk, ctx) => ({ ...chunk, type: decode(typeCoder, chunk.type, ctx) as "IHDR", data: decode(dataCoder, chunk.data, ctx), }), unrefine: (chunk, ctx) => ({ ...chunk, type: encode(typeCoder, chunk.type, ctx, new Uint8Array(4)), data: encode(dataCoder, chunk.data, ctx, new Uint8Array(8)), }), }; }; const pngChunkCoder = refineSwitch( pngChunkUnknown, { IHDR: ihdrRefiner() }, { refine: (chunk: PngChunkUnknown, ctx: Context) => { const type = decode(string(4), chunk.type, ctx); return type === "IHDR" ? "IHDR" : null; }, unrefine: (chunk: IhdrChunk, _ctx: Context) => chunk.type === "IHDR" ? "IHDR" : null, } ); const buffer = new Uint8Array(100); const chunk: IhdrChunk = { length: 8, type: "IHDR", data: { width: 100, height: 200 }, crc: 0, }; const written = pngChunkCoder.encode(chunk, buffer); const [decoded] = pngChunkCoder.decode(buffer); assertEquals((decoded as IhdrChunk).type, "IHDR"); assertEquals((decoded as IhdrChunk).data.width, 100);
Supporting unknown values with explicit fallback refiner
Supporting unknown values with explicit fallback refiner
import { assertEquals } from "@std/assert"; import { refineSwitch, type Refiner, type Context } from "@hertzg/binstruct"; import { struct, u8, bytes } from "@hertzg/binstruct"; interface BaseMsg { type: number; data: Uint8Array; } interface KnownMsg { type: 1; value: number; } interface UnknownMsg { type: number; data: Uint8Array; } const knownRefiner = (): Refiner<BaseMsg, KnownMsg, []> => ({ refine: (msg, _ctx) => ({ type: 1, value: msg.data[0] }), unrefine: (msg, _ctx) => ({ type: 1, data: new Uint8Array([msg.value]) }), }); const unknownRefiner = (): Refiner<BaseMsg, UnknownMsg, []> => ({ refine: (msg, _ctx) => msg, unrefine: (msg, _ctx) => msg, }); const baseCoder = struct({ type: u8(), data: bytes(1) }); const msgCoder = refineSwitch( baseCoder, { known: knownRefiner(), unknown: unknownRefiner(), }, { refine: (msg: BaseMsg, _ctx: Context) => msg.type === 1 ? "known" : "unknown", unrefine: (msg: KnownMsg | UnknownMsg, _ctx: Context) => ("value" in msg) ? "known" : "unknown", } ); const buffer = new Uint8Array(10); const unknown: UnknownMsg = { type: 99, data: new Uint8Array([42]) }; // @ts-ignore - Complex union type inference const written = msgCoder.encode(unknown, buffer); const [decoded] = msgCoder.decode(buffer); assertEquals((decoded as UnknownMsg).type, 99); assertEquals((decoded as UnknownMsg).data[0], 42);
refiners: TRefiners
Record mapping keys to refiners that transform base values
A coder that produces a union of all refined types