Defjs

Connect, send one typed envelope, receive once, then close and await closed.

Open a WebSocket session

Each direction is one struct. You own unsubscribe, close, and await session.closed. Invalid frames go to onRuntimeError.

See WebSocket for details.

import {
  createClient,
  defineWebSocket,
  struct,
  withEndpoint,
} from "@defjs/core";

const client = createClient(withEndpoint("https://chat.example.com"));

const room = defineWebSocket({
  maxIncomingQueueSize: 100,
  path: "/chat",
  incoming: struct.object({
    type: struct.literal("message"),
    text: struct.string(),
  }),
  outgoing: struct.object({
    type: struct.literal("send"),
    text: struct.string(),
  }),
});

async function main() {
  const [error, session] = await room(client);
  if (error) {
    console.error(error.code);
    return;
  }
  const unsubscribe = session.onRuntimeError((cause) =>
    console.error("runtime", cause),
  );
  try {
    session.send({ type: "send", text: "Hello" });
    for await (const message of session.receive) {
      console.log(message.type, message.text);
      break;
    }
  } finally {
    unsubscribe();
    session.close(1000, "consumer-finished");
    await session.closed;
  }
}

void main();

chat.example.com is a placeholder. With a server that answers { "type": "send", "text": "Hello" } with { "type": "message", "text": "Hello" }, the snippet prints:

message Hello

Reconnect is opt-in (withWebSocketReconnect). Heartbeat is opt-in too. Browser WebSockets can’t take arbitrary handshake headers — use cookies, subprotocols, or a short-lived ticket.

Edit this page on GitHub
Released under the MIT License. · Copyright © 2026 MunMunMiao

On this page