One contract, both sides typed
Define a Channel on the server; the browser client infers every event, command, RPC, and stream from typeof channel — no codegen step required.
Declare one channel — get a typed client with RPC, streams, presence, and live cursors. Socket.IO's runtime, tRPC-grade types.
Define a Channel on the server; the browser client infers every event, command, RPC, and stream from typeof channel — no codegen step required.
await client.request("getRoom", input) resolves with the typed output, or rejects with a typed, discriminated error. Timeouts and idempotency keys built in.
Async-iterable server streams, a per-room typed presence roster, and per-room event history/rewind — all first-class, all typed.
A volatile presence channel plus the opt-in @ws-asyncapi/cursors package gives you smoothed multiplayer cursors in a few lines.
Auto-reconnect with backoff, heartbeats, offline buffering, and connection-state recovery (replay missed events) — single-node or across a Redis backplane.
Thin bindings over a framework-agnostic query-core map RPCs to TanStack Query and bridge presence/streams into hooks and signals.
import { Channel } from "ws-asyncapi";
import { createNodeWsServer } from "@ws-asyncapi/adapter-node";
import { z } from "zod";
export const chat = new Channel("/chat/:room", "chat")
// server → client event
.serverMessage("message", z.object({ from: z.string(), text: z.string() }))
// client → server RPC with a typed reply
.rpc(
"send",
z.object({ text: z.string() }),
z.object({ id: z.string() }),
async ({ message, ws }) => {
const id = crypto.randomUUID();
ws.publish("room", "message", { from: "me", text: message.text });
return { id };
},
)
.onOpen(({ ws }) => ws.subscribe("room"));
createNodeWsServer([chat], { port: 3000 });import { createClient } from "@ws-asyncapi/client";
import type { chat } from "./server";
// fully typed from the server contract — no codegen
const client = createClient<typeof chat>("ws://localhost:3000", "/chat/general");
client.onEvent("message", (m) => {
// m: { from: string; text: string }
console.log(`${m.from}: ${m.text}`);
});
const { id } = await client.request("send", { text: "hello!" });
// ^ string — inferred from the contractPrefer a generated client over
typeof channel? ws-asyncapi emits an AsyncAPI 3.0 document and ships a CLI that generates an equivalent typed client. See Channels & the contract.