# `payload-codec` _(no submodules)_ ## Purpose Encodes and decodes the wire envelope used to ship a single event from the main-process `EventBus` across a `MessagePort` to a worker-tier subscriber. Includes a depth-bounded structured-clone safety probe so payloads that cannot survive the transfer are rejected at the source rather than producing opaque `DataCloneError` exceptions at runtime. The codec is intentionally conservative: anything beyond plain JSON-shaped data plus a small list of well-known cloneable host types (`Date`, `RegExp`, `Map`, `Set`, `ArrayBuffer`, typed arrays) is rejected with `EVENTS_PROXY.UNSERIALISABLE_PAYLOAD`. This matches the structured-clone algorithm but is checked synchronously and surfaces the offending path. ## Features - `EventEnvelopeSchema` Zod schema for the wire frame (`{ topic, payload, traceId? }`). - `encodeEnvelope(topic, payload, traceId?)` runs the probe, then constructs the envelope. Throws an `EventsProxyError` carrying the offending path on rejection. - `decodeEnvelope(raw)` Zod-validates an inbound frame. - `probeStructuredClone(value)` returns the offending path or `null`. Detects functions, symbols, `WeakMap`/`WeakSet`, class instances, cycles, and over-deep graphs (limit `MAX_PROBE_DEPTH = 64`). - `DEFAULT_BACKPRESSURE_HIGH_WATER = 1024` — the default per-subscription queue cap. ## Usage: Basic ```ts import { encodeEnvelope, decodeEnvelope, } from "@suaveplan/api-events-worker-proxy/payload-codec"; const wire = encodeEnvelope("billing.invoice.created", { invoiceId: "i_1" }); const decoded = decodeEnvelope(wire); console.log(decoded.topic, decoded.payload); ``` ## Usage: Advanced ```ts import { probeStructuredClone, encodeEnvelope, } from "@suaveplan/api-events-worker-proxy/payload-codec"; import { EventsProxyError, EVENTS_PROXY_CODES, } from "@suaveplan/api-events-worker-proxy/error-codes"; const payload = { invoiceId: "i_1", whenIssued: new Date(), handler: () => undefined, }; const offender = probeStructuredClone(payload); if (offender !== null) { throw new EventsProxyError( EVENTS_PROXY_CODES.UNSERIALISABLE_PAYLOAD, `payload rejected at ${offender}` ); } const wire = encodeEnvelope("invoice.flagged", payload); ``` ## API Reference - `EventEnvelopeSchema` — `z.object({ topic: z.string().min(1), traceId: z.string().optional(), payload: z.unknown() })`. - `WireEventEnvelope` — `z.infer`. - `encodeEnvelope(topic: string, payload: unknown, traceId?: string): WireEventEnvelope`. - `decodeEnvelope(raw: unknown): WireEventEnvelope`. - `probeStructuredClone(value: unknown): string | null` — returns the offending JSON-path-like string or `null`. - `MAX_PROBE_DEPTH: number` — `64`. - `DEFAULT_BACKPRESSURE_HIGH_WATER: number` — `1024`. ## Implementation Notes The probe walks the object graph synchronously with a `WeakSet` cycle detector, accumulating the offending JSON path. It deliberately rejects class instances (`Object.getPrototypeOf(value) !== Object.prototype`) because the receiving side reconstructs them as plain objects, breaking `instanceof`. `Date`, `RegExp`, `Map`, `Set`, `ArrayBuffer`, and `ArrayBuffer.isView`-typed values are recognised explicitly because they ARE structured-clone safe even though they have non-`Object` prototypes. The depth bound of 64 is generous for legitimate payloads but cuts off pathological inputs. Cycles are detected by `WeakSet`; the probe reports the path that re-entered the visited set rather than recursing infinitely.