capnwasm chat

Two independent chats, one per library, sharing only the input box and the send button. Every click fires capnwasm.post(...) AND capnweb.post(...) in parallel (Promise.all); each call lands in its own pane via its own endpoint. The capnwasm pane talks to /chat-ws or /chat-http; the capnweb pane talks to /capnweb-chat-ws or /capnweb-chat-http. A single Durable Object hosts both sides of state independently, which is what makes broadcast actually work on workerd. Toggle transport: WebSocket (push) or REST (poll). Source: capnwasm-pane.ts, capnweb-pane.ts, chat_room.mjs.

Transport:

capnwasm

Cap'n Proto wire · PostParams in, ChatMessage(s) out
Connecting…

capnweb

JSON wire · cap.post(author, text) + callback stub or getMessagesSince
Connecting…

What's the same / what's different

capnwasm
// Browser
const r = cap.callBuilder(IFC, M_POST, PostParamsBuilder);
r.params.author = author;
r.params.text   = text;
await r.send().promise;

// WebSocket subscribe
for await (const chunk of sub.updates) {
  const m = openChatMessage(cpp, chunk);
  render({ id: Number(m.id), author: m.author, text: m.text });
}

// REST poll
await r.send({
  resultsReader: ChatMessageListReader,
  extract: (rdr) => { for (let i = 0; i < rdr.items.length; i++) ... },
}).promise;
Schema-driven binary frames, fields read from wasm linear memory by precomputed offset, no JSON in the chat path.
capnweb
// Browser
const main = newWebSocketRpcSession("/capnweb-chat-ws");
//        OR newHttpBatchRpcSession("/capnweb-chat-http");
await main.post(author, text);

// WebSocket subscribe (callback stub)
class Sub extends RpcTarget {
  onMessage(m) { render(m); }
}
await main.subscribe(new Sub());

// REST poll
const items = await main.getMessagesSince(cursor);
JSON-shaped messages, server holds callback.dup() and invokes onMessage per broadcast — the cap-passing pattern.

Both panes are served by one ChatRoom Durable Object (src/chat_room.mjs), with two independent state buckets (history, id sequence, subscribers) — one per library. The DO is also what makes broadcast work on Cloudflare: every WebSocket event runs in the same I/O context, so a POST on socket A can wake the subscribe stream on socket B without crossing workerd's per-event request boundary.

← back to landing