API reference
Exports of @sigx/serialize v0.13.0. A single entry point — no subpaths.
defineTypeHandler
Author a handler with type-guard-driven inference. Runtime it is the identity function; it exists purely to drive the types.
function defineTypeHandler<T, Encoded = unknown>(handler: {
name: string;
tag?: string;
test(value: unknown): value is T; // type guard → drives serialize/revive inference
serialize(value: T): Encoded;
revive?(encoded: Encoded): T;
}): TypeHandler<T, Encoded>;
The test guard must be a bare instanceof / typeof arrow to infer T; annotate a compound predicate explicitly ((v): v is URL => …).
encodeWithHandlers
function encodeWithHandlers(value: unknown, handlers?: readonly TypeHandler[]): unknown;
Walk a value into a JSON-safe tree, applying registered handlers first, then the built-ins. Handlers see raw values (the walk runs before toJSON, which is why Date matches). Throws TypeError('Converting circular structure to JSON') on cycles, like JSON.stringify.
reviveWithHandlers
function reviveWithHandlers<T = unknown>(value: unknown, handlers?: readonly TypeHandler[]): T;
The inverse — turns { [tag]: payload } back into live values. T is an assertion, not validation (it types the result; nothing checks wire data). Apply only to trees encodeWithHandlers produced. Idempotent over already-live values; an unknown $-tag is left encoded (forward-compat) with a dev warning.
TypeHandler
interface TypeHandler<T = unknown, Encoded = unknown> {
name: string; // identifies the handler (dev warnings, dedupe)
tag?: string; // wire discriminator, e.g. '$date' → { [tag]: payload }
test(value: unknown): boolean; // owns the value? — receives the raw value
serialize(value: T): Encoded; // JSON-safe payload (itself re-walked)
revive?(encoded: Encoded): T; // payload → live value (omit for serialize-only)
}
test is intentionally typed boolean, not a type predicate (a predicate member would reject boolean-returning tests) — the guard lives on defineTypeHandler's parameter instead. Bare TypeHandler is TypeHandler<unknown, unknown>, so pre-generic handlers compile unchanged.
BUILTIN_TYPE_HANDLERS
const BUILTIN_TYPE_HANDLERS: readonly TypeHandler[];
The zero-config vocabulary, consulted after any registered handlers:
| name | tag | type → encoded |
|---|---|---|
date | $date | Date → epoch ms (NaN → null; revives to Invalid Date) |
map | $map | Map → array of [k, v] |
set | $set | Set → array |
bigint | $bigint | bigint → decimal string |
url | $url | URL → href |
regexp | $regexp | RegExp → [source, flags] |
undefined | $undef | === undefined → 0 (so explicit undefined survives) |
A user object whose sole key starts with $ is escaped as { "$esc": original } and unwrapped on revive, so a literal { "$date": "hi" } never wrongly revives to a Date.
Registration
@sigx/serialize exports no registration function. Register handlers through the runtime:
provideTypeHandlers(app._context, handlers)— fromsigx/internals; the per-app DI registry (consulted before built-ins).serverPlugin({ types })— from@sigx/server/plugin; one call registers both the RPC wire codec and the per-app registry.
