2.7 KiB
2.7 KiB
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 WsEnvelopepredicate for ergonomic narrowing inside engine adapters.parseWsEnvelope(value)throws the underlyingZodErrorfor callers that want to convert to aWsErrorat the boundary.- Always uses the default-import form
import z from "zod"per the project Zod policy.
Usage: Basic
import { isWsEnvelope, parseWsEnvelope } from "@suaveplan/api-ws/envelope";
const frame = parseWsEnvelope(JSON.parse(rawText));
console.log(frame.id, frame.type);
Usage: Advanced
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<WsEnvelope>— strict object schema.type WsEnvelope = z.infer<typeof wsEnvelopeSchema>.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 ofwsEnvelopeSchema.extend({ ... })shapes pertype, not by relaxing the base schema.payloadisz.unknown()rather thanz.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.