# json-encode ## Purpose Implements the `serialize` function that converts any JavaScript value to a tagged JSON string preserving type information beyond what `JSON.stringify` supports. The encoder traverses the value graph recursively, wraps non-primitive types in `{ "__t": tag, "v": ... }` envelopes handled by the type registry, and detects both cycles and excessive nesting depth before they cause runtime errors. The tagged JSON format is self-describing: a receiver that imports `deserialize` from the same package can reconstruct the original value graph without out-of-band schema information. ## Features - Handles all types registered in the global registry (Date, Map, Set, BigInt, TypedArrays, URL, RegExp, custom classes) - Preserves `undefined` as `{"__t":"__undefined","v":null}` (normally silently dropped by `JSON.stringify`) - Preserves `NaN` as `{"__t":"__NaN","v":null}` and `±Infinity` as `{"__t":"__Infinity","v":"+"/"−"}` - Detects circular references via `WeakSet` and throws `CircularReferenceError` with the structural path - Configurable maximum depth (default 100) via `maxDepth` option; throws `MaxDepthExceededError` - Configurable maximum byte length via `maxSize` option; throws `MaxSizeExceededError` - Honour `AbortSignal` cancellation; checked every 256 nodes for minimal overhead - `seen` set cleared per-branch (re-entrant DAG nodes are allowed, only true cycles throw) ## Usage: Basic Example ```typescript import { serialize } from "@suaveplan/serialize"; const obj = { created: new Date("2024-01-01"), tags: new Set(["a", "b"]), count: BigInt(9007199254740993), }; const json = serialize(obj); // '{"created":{"__t":"__Date","v":"2024-01-01T00:00:00.000Z"},...}' ``` ## Usage: Advanced Example ```typescript import { serialize } from "@suaveplan/serialize"; const controller = new AbortController(); try { // Enforce max 50KB output and max 20 levels of nesting const json = serialize(hugeObject, { maxDepth: 20, maxSize: 50_000, signal: controller.signal, }); sendToNetwork(json); } catch (err) { if (err instanceof MaxSizeExceededError) { console.error("Payload too large:", err.context?.size, "bytes"); } else if (err instanceof MaxDepthExceededError) { console.error("Too deeply nested at depth:", err.context?.depth); } } // Cancel mid-way through a large serialization: controller.abort(); ``` ## API Reference ### `serialize(value: unknown, opts?: SerializeOptions): string` Converts `value` to a tagged JSON string. **Parameters:** - `value` — any JavaScript value; functions and symbols are silently dropped (same as `JSON.stringify`) - `opts.maxDepth` — maximum recursion depth before `MaxDepthExceededError` is thrown (default: `100`) - `opts.maxSize` — maximum byte length of the resulting JSON string; checked after encoding (default: unlimited) - `opts.signal` — an `AbortSignal`; checked every 256 nodes; throws the signal's reason if aborted **Returns:** a JSON string in the tagged wire format. **Throws:** - `CircularReferenceError` when a cycle is detected - `MaxDepthExceededError` when nesting exceeds `maxDepth` - `MaxSizeExceededError` when output exceeds `maxSize` bytes - The `AbortSignal`'s reason when `signal` is aborted ### `SerializeOptions` ```typescript interface SerializeOptions { maxDepth?: number; // default: 100 maxSize?: number; // default: unlimited signal?: AbortSignal; } ``` ## Implementation Notes - The cycle-detection `WeakSet` (`seen`) is managed with add-before/delete-after so that DAG structures (same object reachable via multiple paths) do not falsely trigger the cycle check. Only true back-edges to an ancestor in the current call stack throw. - Signal checking happens every 256 nodes rather than every node to keep the hot path free of extra branch overhead. - The `maxSize` check occurs **after** `JSON.stringify` on the fully encoded intermediate object, not during encoding. This trades one additional pass for implementation simplicity and avoids double-counting tag overhead. - `undefined` at object property positions is omitted by `JSON.stringify` but handled explicitly here by converting it to a tagged sentinel before the `JSON.stringify` call.