SYSTEM Cited by 2 sources
Cap'n Web¶
Cap'n Web is a pure-TypeScript
object-capability RPC library from Cloudflare (GitHub,
MIT, announced 2025-09-22).
Minify+gzip is under 10 kB with no dependencies; runs in every major
browser, Workers, Node.js, and other modern JS
runtimes; ships transports for HTTP batch, WebSocket, and
postMessage() out of the box, and is extensible to others.
What it is¶
A JS-native RPC protocol that inherits the capability + promise-pipelining design of Cap'n Proto but drops the schema language and binary wire format. Instead:
- No schemas, no codegen. You define RPC servers by extending
RpcTarget; you call remote methods on a client stub as if it were a local object. TypeScript interfaces can give you end-to-end type safety at compile time. - Wire format is JSON post-processed for escape-array encoding of
non-JSON types (
["date", 1758...]is aDate;[["Alice","Bob"]]is a literal array). Human-readable; you cantail -fa session and understand it. - Symmetric protocol. No client/server distinction at the protocol level — both peers maintain import + export tables and can invoke each other's exported objects.
The four-message protocol¶
| Message | Meaning |
|---|---|
push |
Evaluate an expression; store result in the peer's export table at a predictable positive ID. |
pull |
Please serialize and send back the value at this ID (issued only if the caller actually awaits). |
resolve |
Here's the value (in response to a pull). |
reject |
Call threw / rejected. |
The caller predicts the positive export ID of each push — that's
what enables promise pipelining without
waiting for the resolve. See the
protocol spec
for details.
Capability features (vs typical RPC)¶
- Pass-by-reference for functions. Passing a function over RPC gives the recipient a stub; invoking the stub makes an RPC back to the creator → bidirectional RPC with callbacks as first-class arguments.
- Pass-by-reference for objects. Classes extending
RpcTargetare passed by reference; method calls route back to the origin. - Promise pipelining. The Promise
returned by a call is a JS
Proxy— methods invoked on it are sent immediately as speculative dependent calls. - Capability-based security.
authenticate()returning a session stub is the idiomatic auth pattern. The session object is unforgeable at the protocol layer — clients can only obtain one via a successfulauthenticate()call.
The .map() trick¶
Cap'n Web supports promise.map(callback) for pipelined
transformations over a promised array — this is the feature that
makes it a serious alternative to
GraphQL for the canonical "N+1 query" case. The callback is run
once client-side against a Proxy placeholder (must be synchronous);
the interceptor records the pipelined calls as a DSL expression that
is just the RPC protocol itself, and ships it to the server to
replay per array element. No round trips, no schema.
(patterns/record-replay-dsl)
Setup¶
Client (WebSocket):
import { newWebSocketRpcSession } from "capnweb";
const api = newWebSocketRpcSession("wss://example.com/api");
const result = await api.hello("World");
Client (HTTP batch — once you await, the batch is done):
const batch = newHttpBatchRpcSession("https://example.com/api");
const [a, b] = await Promise.all([batch.hello("Alice"), batch.hello("Bob")]);
Server (Cloudflare Worker):
import { RpcTarget, newWorkersRpcResponse } from "capnweb";
class MyApiServer extends RpcTarget {
hello(name) { return `Hello, ${name}!` }
}
export default {
fetch(req) {
if (new URL(req.url).pathname === "/api")
return newWorkersRpcResponse(req, new MyApiServer());
return new Response("Not found", {status: 404});
}
}
Production use¶
- Wrangler remote bindings (GA
2025-09-16)
— local
workerdtest instances speak Cap'n Web RPC to production services. First shipped consumer, announced ~6 days before the launch post. - Cloudflare states internal experiments in frontend apps.
Trade-offs¶
- Experimental. Cloudflare explicitly flags "willingness to live on the cutting edge may be required."
- No runtime type checking. TypeScript is compile-time only; malicious clients can still send wrong-shaped args — pair with runtime validators like Zod at handlers.
.map()lambda must be synchronous. Anyawaitinside the callback breaks the DSL capture. Users will hit this and have to restructure.- Capability lifetime / GC. A stub held by a remote peer pins the underlying object. The post notes IDs are never reused but doesn't walk through the disposal discipline — a real consideration for long-lived WebSocket sessions.
Seen in¶
- sources/2025-09-22-cloudflare-capn-web-rpc-for-browsers-and-web-servers — announcement post; full design walkthrough + protocol spec. Canonical wiki instance of concepts/object-capability-rpc, concepts/promise-pipelining, and patterns/record-replay-dsl.
Related¶
- systems/capnproto — schema-based ancestor, same model, binary wire
- systems/grpc — contrast: schema-first, HTTP/2, no capability/pipelining
- systems/github-graphql-api — the GraphQL solution to the same waterfall problem Cap'n Web solves with pipelining
As a TypeScript-as-schema precedent¶
Cap'n Web's "define RPC in TypeScript types, not a schema DSL"
posture is the direct precedent for Cloudflare's 2026-04-13
decision to make TypeScript the source of truth for generating
the cf CLI, Workers bindings, SDKs, Terraform
provider, and MCP Code Mode server from one
unified interface schema.
See patterns/typescript-as-codegen-source (Source:
sources/2026-04-13-cloudflare-building-a-cli-for-all-of-cloudflare).