WebSocket Typed Message Protocol

Layer a typed message protocol over WebSocket with Zod parsing per message type, versioned envelopes, and safe close-frame handling.

TypescriptData Fetching

Code

snippet
import { z } from 'zod';
import { WebSocket } from 'ws';

const Envelope = z.object({
  v: z.literal(1),
  type: z.string(),
  payload: z.unknown(),
});

const Messages = {
  'tick.update': z.object({ symbol: z.string(), price: z.number() }),
  'alert.raise': z.object({ level: z.enum(['warn', 'critical']), text: z.string() }),
};

type MessageType = keyof typeof Messages;

function parseMessage(raw: string) {
  const envelope = Envelope.safeParse(JSON.parse(raw));
  if (!envelope.success) return { ok: false, reason: 'bad envelope' };

  const schema = Messages[envelope.data.type as MessageType];
  if (!schema) return { ok: false, reason: 'unknown type ' + envelope.data.type };

  const payload = schema.safeParse(envelope.data.payload);
  return payload.success
    ? { ok: true, type: envelope.data.type, payload: payload.data }
    : { ok: false, reason: 'bad payload for ' + envelope.data.type };
}

const socket = new WebSocket('wss://stream.example.com/v1');

socket.on('message', (data) => {
  const parsed = parseMessage(data.toString());
  if (!parsed.ok) {
    socket.close(1002, 'protocol violation');
    return;
  }
  if (parsed.type === 'tick.update') {
    console.log(parsed.payload.symbol, parsed.payload.price);
  }
});

socket.on('close', (code, reason) => {
  console.log('closed', code, reason.toString());
});