capnwasm

Real Cap'n Proto C++ in the browser. Typed RPC, binary wire.

One typed-client toolchain for any backend — write a .capnp schema, get a fast typed JS client back. npx capnwasm gen user.capnp emits a reader/builder that runs against real upstream Cap'n Proto C++ statically compiled to wasm.

What this project is

capnwasm is me trying to learn Cap'n Proto and capnweb by building the opposite experiment: keep the real Cap'n Proto binary wire in the browser, compile the upstream C++ runtime to wasm, and measure what changes.

This is not a scoreboard where Cap'n Proto always wins. The useful question is the tradeoff boundary: when does zero-copy / sparse access / raw binary pay off, and when is JSON or capnweb the better choice?

JSON / capnweb is usually better for
  • tiny JS-to-JS objects
  • control-plane calls where payload is a few bytes
  • smallest bundle / no wasm runtime
Cap'n Proto / capnwasm is interesting for
  • raw binary data without base64
  • large payloads where you read only a few fields
  • wire interop with non-JS Cap'n Proto services

vs capnweb & REST/JSON

Every number below is measured either in this browser against the current Worker server, or generated at build time from the current assets. No hardcoded benchmark values.

RPC burst, 200 concurrent calls (browser→Worker WS) measuring… live
RPC 64 KB binary echo (browser→Worker WS) measuring… live
Wire bytes for 4 KB binary blob (gzip) loading… build
Bundle — full RPC client (JS + wasm, gzip) loading… build
Wasm runtime asset (gzip) loading… build
Fetch playground
REST/JSON vs capnweb vs capnwasm side-by-side, fetching static fixtures. Wire bytes, decode time, and render-to-DOM time for each. Tiny same-origin payloads often come out tied; the wire savings show in the egress row.
HTTP
🔁
RPC bench
Live WebSocket RPC against a Node server. Three workloads: 200-call burst, promise pipelining, 64 KB binary echo. Runs live in your browser so you can compare the current numbers.
Interactive
💬
Live chat demo
createClient + subscribeQuery + optimistic in a working chat. Open in two tabs to watch messages stream live across.
Interactive
🧩
Dynamic schema demo
No codegen: define the schema as JS data, build Cap'n Proto bytes, pick selected fields, and round-trip through the Worker.
No codegen
🔁
OpenAPI conversion
Paste OpenAPI JSON, convert to manifest, emit canonical .capnp, and round-trip back to canonical OpenAPI in the browser.
Contract
⚖️
vs capnweb
Where capnwasm wins, where it loses, where it’s tied. Reproducible numbers. Bundle size and cold start go to capnweb.
Tradeoffs
📊
Render bench
End-to-end full pipeline: capnweb × capnwasm crossed with WebSocket × HTTP-batch, three sizes, cold & warm. Both libraries win some, lose some — the whole picture.
Tradeoffs
🔍
Wire inspector
One line in DevTools, walks any Cap'n Proto frame. Schemaless or schema-aware. Hosted as a single /inspect.js on this site — not in the npm package.
Debug tool
📝
Notes from the trenches
Field report on the perf work: allocation traps, GC churn, a fast-path that wasn't, one negative SIMD result, and the tradeoff summary.
Write-up