Codecs
Every message crosses encode/decode, so the wire format is a first-class
config option — not middleware:
interface Codec { encode(data: unknown): string | BufferSource | Blob; decode(data: unknown): unknown;}encode’s return type deliberately mirrors WebSocket.send’s parameter, so
the codec contract can never drift looser than what the socket accepts.
The default: JSON
Section titled “The default: JSON”With no codec configured, jsonCodec applies:
- encode — strings pass through as-is; binary (
ArrayBuffer,ArrayBufferView,Blob) passes through untouched; everything else isJSON.stringify-ed. - decode — strings are
JSON.parse-d with a safe fallback to the raw string when they aren’t JSON; binary frames pass through untouched.
That means client.send({ type: "hello" }) and client.send(bytes) both do
the right thing with zero setup.
A custom codec
Section titled “A custom codec”import { decode, encode } from "@msgpack/msgpack";import { defineClient, type Codec } from "durablews";
const msgpackCodec: Codec = { encode: (data) => encode(data), decode: (data) => data instanceof ArrayBuffer ? decode(new Uint8Array(data)) : data};
const client = defineClient({ url, codec: msgpackCodec });Where the codec sits
Section titled “Where the codec sits”inbound: frame → codec.decode → schema validation → middleware → messageoutbound: send() → [queue] → outbound middleware → codec.encode → socketTwo consequences worth knowing:
- Schema validation runs on decoded values — your schema describes application messages, not wire bytes.
- Outbound middleware runs before encode, so middleware transforms plain values and the codec owns serialization. A token-stamping middleware and a msgpack codec compose without knowing about each other.