first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,115 @@
# registry
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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
```typescript
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
```typescript
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`
```typescript
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.