Server/Packages/Serialize/API reference
@sigx/serialize · Stable

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.

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

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

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

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

TypeScript
const BUILTIN_TYPE_HANDLERS: readonly TypeHandler[];

The zero-config vocabulary, consulted after any registered handlers:

nametagtype → encoded
date$dateDate → epoch ms (NaNnull; revives to Invalid Date)
map$mapMap → array of [k, v]
set$setSet → array
bigint$bigintbigint → decimal string
url$urlURLhref
regexp$regexpRegExp[source, flags]
undefined$undef=== undefined0 (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) — from sigx/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.