# `protocol` _(no submodules)_ ## Purpose Defines the Zod-validated wire format for log frames travelling from a worker-isolated plugin to the main-process `@suaveplan/logger`. Frames are a small discriminated union — `log-batch`, `goodbye`, and `control` — so receivers branch on `kind` and reject malformed input cleanly via Zod parsing rather than ad-hoc shape checks. The protocol is bidirectional: workers send `log-batch` and `goodbye` frames to the main, the main sends `control` frames (`disable` / `enable`) back to the worker so it can stop emitting after the main-side logger has been disposed (design doc §5.5 dispose-in-reverse). ## Features - `LOGGER_PROXY_PROTOCOL_VERSION` — `"1.0.0"`. Embedded in every frame via the `v` field; servers reject on mismatch. - `LOG_LEVELS` / `LogLevel` — `trace | debug | info | warn | error | fatal`. Wider than `LoggerLike` to allow forward-compatibility; the main-side server narrows down before dispatch. - `PluginTag` / `pluginTagSchema` — `{ name, version }` attached to every batch. - `WireLogRecord` / `logRecordSchema` — single record: `level`, `msg`, `ts`, optional `ctx` map, optional `trace` (`trace_id` / `span_id`). - `LogBatchFrame` / `logBatchFrameSchema` — `{ kind: "log-batch", v, plugin, records: WireLogRecord[] }` with `min(1)` records. - `ControlFrame` / `controlFrameSchema` — `{ kind: "control", v, action: "disable" | "enable" }`. - `parseLoggerProxyFrame(input)` — validates and returns the discriminated union; throws `ZodError` on malformed input. ## Usage: Basic ```ts import { LOGGER_PROXY_PROTOCOL_VERSION, parseLoggerProxyFrame, } from "@suaveplan/api-logger-worker-proxy/protocol"; const frame = parseLoggerProxyFrame({ v: LOGGER_PROXY_PROTOCOL_VERSION, kind: "log-batch", plugin: { name: "demo", version: "0.1.0" }, records: [ { level: "info", msg: "hello", ts: new Date().toISOString(), ctx: { userId: 42 }, }, ], }); ``` ## Usage: Advanced Switch over `kind` with exhaustive narrowing: ```ts import { parseLoggerProxyFrame } from "@suaveplan/api-logger-worker-proxy/protocol"; function handle(input: unknown): void { const frame = parseLoggerProxyFrame(input); switch (frame.kind) { case "log-batch": for (const r of frame.records) dispatch(r, frame.plugin); break; case "control": applyControl(frame.action); break; case "goodbye": shutdown(frame.reason); break; } } ``` ## API Reference - `LOG_LEVELS: readonly LogLevel[]` — exported tuple for enumeration. - `logLevelSchema: ZodEnum` — re-use in downstream schemas. - `logRecordSchema`, `logBatchFrameSchema`, `controlFrameSchema`, `logGoodbyeFrameSchema`, `loggerProxyFrameSchema` — Zod schemas. - `parseLoggerProxyFrame(input: unknown): LoggerProxyFrame` — entry point for receivers. Throws on malformed input. ## Implementation Notes The schema accepts `ctx` as `Record` to keep the proxy transparent to the application. Main-side redaction normalises sensitive fields before they reach the sink. `ts` is required so the main-side stays a pure forwarder; the worker stamps wall-clock time at record creation. Frames flow over `MessagePort` and survive structured-clone — keeping records as plain data (no `Date`, no classes) is intentional.