Files
skills/docs/foundation/data/serialize/src/registry/registry.md
2026-05-29 08:20:56 +01:00

4.4 KiB

registry

(no submodules)

Purpose

Maintains a global singleton map of type definitions that the JSON and MessagePack codecs consult to encode and decode non-primitive JavaScript values in a round-trip-safe way. Each entry maps a string tag to an encoder/decoder pair and a constructor reference, enabling both forward lookup (value → tag) and reverse lookup (tag → value reconstruction).

The registry ships with 17 built-in entries covering the types most commonly lost by JSON.stringify: Date, Map, Set, BigInt, the six TypedArray variants, URL, RegExp, and three special sentinel tags for undefined, NaN, and ±Infinity.

Features

  • registerType(def) — add a custom type; throws SERIALIZE.REGISTRY.DUPLICATE_TAG on tag collision with a different constructor; idempotent for identical re-registrations
  • unregisterType(tag) — remove by tag; silent if absent (useful for test cleanup)
  • listTypes() — enumerate all current entries as an array of TypeDefinition
  • getType(tag) — look up by tag string; returns undefined if absent
  • getTypeByCtor(ctor) — look up by constructor; returns undefined if absent
  • 14 built-in type definitions registered at module load time
  • Three special sentinel definitions for undefined, NaN, and ±Infinity

Usage: Basic Example

import { registerType, unregisterType } from "@suaveplan/serialize";

class Color {
  constructor(public hex: string) {}
}

registerType({
  tag: "__Color",
  ctor: Color,
  encode: (v) => (v as Color).hex,
  decode: (enc) => new Color(enc as string),
});

// After registration, serialize/deserialize will handle Color instances.
// Clean up in tests:
unregisterType("__Color");

Usage: Advanced Example

import {
  getType,
  getTypeByCtor,
  listTypes,
  registerType,
} from "@suaveplan/serialize";

class Vector2 {
  constructor(public x: number, public y: number) {}
}

registerType({
  tag: "__Vector2",
  ctor: Vector2,
  encode: (v) => { const vec = v as Vector2; return [vec.x, vec.y]; },
  decode: (enc) => { const [x, y] = enc as [number, number]; return new Vector2(x, y); },
});

// Look up by tag
const def = getType("__Vector2");
console.log(def?.tag); // "__Vector2"

// Look up by ctor
const def2 = getTypeByCtor(Vector2);
console.log(def2?.tag); // "__Vector2"

// Enumerate all registered types
const all = listTypes();
console.log(all.map((d) => d.tag));

API Reference

registerType(def: TypeDefinition): void

Registers a custom type. Throws SerializationError with code SERIALIZE.REGISTRY.DUPLICATE_TAG if the same tag string is already registered for a different constructor. Registering the same tag+ctor pair again is a silent no-op (idempotent).

unregisterType(tag: string): void

Removes the registration for tag and its associated constructor from both internal maps. Silent if the tag was never registered.

listTypes(): TypeDefinition[]

Returns a snapshot array of all registered TypeDefinition objects at the time of the call.

getType(tag: string): TypeDefinition | undefined

Returns the TypeDefinition for the given tag, or undefined if no entry exists.

getTypeByCtor(ctor: Function): TypeDefinition | undefined

Returns the TypeDefinition whose ctor reference matches, or undefined if none is registered for that constructor.

TypeDefinition

interface TypeDefinition {
  tag: string;
  ctor: Function;
  encode: (value: unknown) => unknown;
  decode: (encoded: unknown) => unknown;
}

Implementation Notes

  • Two parallel Map instances (_byTag and _byCtor) are kept in sync to give O(1) lookup in both directions without requiring full list iteration.
  • Built-in registrations use an internal registerBuiltin helper that force-sets both maps, making them immune to the duplicate-tag guard that external callers hit. This allows the module to be loaded multiple times in test environments without throwing.
  • The sentinel entries for undefined, NaN, and ±Infinity use placeholder constructors (Object, Number, Boolean) that will never match a real value via getTypeByCtor; they are only reached via the getType(tag) path during deserialization.
  • TypedArray Float32Array and Float64Array encode to plain number arrays; precision loss for Float32Array is intentional and matches the original in-memory representation.