Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,218 @@
import { SERIALIZE_CODES } from "../errors/codes.js";
import { SerializationError } from "../errors/serialize-error.js";
import type { TypeDefinition } from "./registry.types.js";
export type { TypeDefinition };
// ---------------------------------------------------------------------------
// Global singleton registry
// ---------------------------------------------------------------------------
const _byTag = new Map<string, TypeDefinition>();
// biome-ignore lint/complexity/noBannedTypes: intentional Function key for ctor lookup
const _byCtor = new Map<Function, TypeDefinition>();
/**
* Register a custom type for round-trip serialization.
*
* Throws `SERIALIZE.REGISTRY.DUPLICATE_TAG` if the tag is already registered
* for a *different* constructor (re-registering the same tag+ctor is a no-op).
*/
export function registerType(def: TypeDefinition): void {
const existing = _byTag.get(def.tag);
if (existing !== undefined && existing.ctor !== def.ctor) {
throw new SerializationError(
SERIALIZE_CODES["REGISTRY.DUPLICATE_TAG"],
"serialize.registry.duplicate_tag",
{ context: { tag: def.tag } }
);
}
_byTag.set(def.tag, def);
_byCtor.set(def.ctor, def);
}
/** Remove a previously registered type by tag. Silent if not found. */
export function unregisterType(tag: string): void {
const existing = _byTag.get(tag);
if (existing !== undefined) {
_byCtor.delete(existing.ctor);
_byTag.delete(tag);
}
}
/** Return all currently registered `TypeDefinition` objects. */
export function listTypes(): TypeDefinition[] {
return Array.from(_byTag.values());
}
/** Look up a `TypeDefinition` by its tag string, or `undefined` if absent. */
export function getType(tag: string): TypeDefinition | undefined {
return _byTag.get(tag);
}
/** Look up a `TypeDefinition` by constructor reference, or `undefined`. */
// biome-ignore lint/complexity/noBannedTypes: intentional Function for ctor lookup
export function getTypeByCtor(ctor: Function): TypeDefinition | undefined {
return _byCtor.get(ctor);
}
// ---------------------------------------------------------------------------
// Built-in type registrations
// ---------------------------------------------------------------------------
function registerBuiltin(def: TypeDefinition, skipCtorIndex = false): void {
// Built-ins always win; force-set even if already present (idempotent).
_byTag.set(def.tag, def);
if (!skipCtorIndex) {
_byCtor.set(def.ctor, def);
}
}
registerBuiltin({
tag: "__Date",
ctor: Date,
encode: (v) => (v as Date).toISOString(),
decode: (enc) => new Date(enc as string),
});
registerBuiltin({
tag: "__Map",
ctor: Map,
encode: (v) => Array.from((v as Map<unknown, unknown>).entries()),
decode: (enc) => new Map(enc as [unknown, unknown][]),
});
registerBuiltin({
tag: "__Set",
ctor: Set,
encode: (v) => Array.from((v as Set<unknown>).values()),
decode: (enc) => new Set(enc as unknown[]),
});
registerBuiltin({
tag: "__BigInt",
ctor: BigInt,
encode: (v) => (v as bigint).toString(),
decode: (enc) => BigInt(enc as string),
});
registerBuiltin({
tag: "__Uint8Array",
ctor: Uint8Array,
encode: (v) =>
Array.from(v as Uint8Array)
.map((b) => b.toString(16).padStart(2, "0"))
.join(""),
decode: (enc) => {
const hex = enc as string;
const bytes = new Uint8Array(hex.length / 2);
for (let i = 0; i < bytes.length; i++) {
bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
}
return bytes;
},
});
registerBuiltin({
tag: "__Int8Array",
ctor: Int8Array,
encode: (v) => Array.from(v as Int8Array),
decode: (enc) => new Int8Array(enc as number[]),
});
registerBuiltin({
tag: "__Int16Array",
ctor: Int16Array,
encode: (v) => Array.from(v as Int16Array),
decode: (enc) => new Int16Array(enc as number[]),
});
registerBuiltin({
tag: "__Int32Array",
ctor: Int32Array,
encode: (v) => Array.from(v as Int32Array),
decode: (enc) => new Int32Array(enc as number[]),
});
registerBuiltin({
tag: "__Uint16Array",
ctor: Uint16Array,
encode: (v) => Array.from(v as Uint16Array),
decode: (enc) => new Uint16Array(enc as number[]),
});
registerBuiltin({
tag: "__Uint32Array",
ctor: Uint32Array,
encode: (v) => Array.from(v as Uint32Array),
decode: (enc) => new Uint32Array(enc as number[]),
});
registerBuiltin({
tag: "__Float32Array",
ctor: Float32Array,
encode: (v) => Array.from(v as Float32Array),
decode: (enc) => new Float32Array(enc as number[]),
});
registerBuiltin({
tag: "__Float64Array",
ctor: Float64Array,
encode: (v) => Array.from(v as Float64Array),
decode: (enc) => new Float64Array(enc as number[]),
});
registerBuiltin({
tag: "__URL",
ctor: URL,
encode: (v) => (v as URL).href,
decode: (enc) => new URL(enc as string),
});
registerBuiltin({
tag: "__RegExp",
ctor: RegExp,
encode: (v) => {
const re = v as RegExp;
return { source: re.source, flags: re.flags };
},
decode: (enc) => {
const { source, flags } = enc as { source: string; flags: string };
return new RegExp(source, flags);
},
});
// Special sentinel registrations — these are detected by value, not by ctor.
// We register tag-only entries (skipCtorIndex=true) so getType() can return
// a decode handler without polluting the ctor index.
registerBuiltin(
{
tag: "__undefined",
ctor: Object, // placeholder; never used for ctor lookup
encode: (_v) => null,
decode: (_enc) => undefined,
},
true // skip ctor index
);
registerBuiltin(
{
tag: "__NaN",
ctor: Object, // placeholder; never used for ctor lookup
encode: (_v) => null,
decode: (_enc) => Number.NaN,
},
true // skip ctor index
);
registerBuiltin(
{
tag: "__Infinity",
ctor: Object, // placeholder; never used for ctor lookup
encode: (v) => (v === Number.POSITIVE_INFINITY ? "+" : "-"),
decode: (enc) =>
enc === "+" ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY,
},
true // skip ctor index
);