# `envelope` — wire-frame Zod schema _(no submodules)_ ## Purpose `@suaveplan/api-ws/envelope` owns the canonical Zod schema for every WebSocket frame that flows between the host and a plugin. Engines parse incoming frames against this schema before the message reaches plugin code, so unknown top-level keys never leak past the host boundary and every frame is guaranteed to carry the correlation id and discriminator the subscription bus, ack pipeline, and reconnect-resume path require. The shape is deliberately minimal — `id`, `type`, optional `topic`, optional `payload`, optional `ts` — so plugins can compose their own Zod schemas on top of `payload` without fighting the wire format. ## Features - Strict object schema — extra keys are a parse failure, not a silent drop. - `isWsEnvelope(value): value is WsEnvelope` predicate for ergonomic narrowing inside engine adapters. - `parseWsEnvelope(value)` throws the underlying `ZodError` for callers that want to convert to a `WsError` at the boundary. - Always uses the default-import form `import z from "zod"` per the project Zod policy. ## Usage: Basic ```ts import { isWsEnvelope, parseWsEnvelope } from "@suaveplan/api-ws/envelope"; const frame = parseWsEnvelope(JSON.parse(rawText)); console.log(frame.id, frame.type); ``` ## Usage: Advanced ```ts import { isWsEnvelope, type WsEnvelope } from "@suaveplan/api-ws/envelope"; import { WS_CODES, WsError } from "@suaveplan/api-ws/error-codes"; import z from "zod"; const orderPayload = z.object({ orderId: z.string() }); function handle(raw: unknown): void { if (!isWsEnvelope(raw)) { throw new WsError(WS_CODES.HANDSHAKE_FAILED, "invalid envelope"); } const envelope: WsEnvelope = raw; if (envelope.type === "orders.ack") { const payload = orderPayload.parse(envelope.payload); // narrow + branch... void payload; } } ``` ## API Reference - `wsEnvelopeSchema: z.ZodSchema` — strict object schema. - `type WsEnvelope = z.infer`. - `isWsEnvelope(value: unknown): value is WsEnvelope` — type predicate. - `parseWsEnvelope(value: unknown): WsEnvelope` — throwing parser. ## Implementation Notes - `strict()` mode is non-negotiable. Engines that need to allow custom extensions MUST do so by defining their own discriminated union of `wsEnvelopeSchema.extend({ ... })` shapes per `type`, not by relaxing the base schema. - `payload` is `z.unknown()` rather than `z.any()` so downstream code is forced to narrow before use. - The Zod default-import form (`import z from "zod"`) is the only Bun-safe form per the project policy and is enforced by a repo-wide grep test.