Promise pipelining
Don't await a result before you use it. Chain dependent calls together and the whole chain resolves in a single network round trip — even over plain HTTP.
A JavaScript-native, object-capability RPC system. Chain dependent calls and the whole chain resolves in a single trip. No schemas, no boilerplate, under 16 kB.
Cap’n Web is a spiritual sibling to Cap’n Proto, created by the same author, but designed to play nice in the web stack. Like Cap’n Proto it is an object-capability protocol: “Cap’n” is short for “capabilities and”. Unlike Cap’n Proto, it has no schemas, almost no boilerplate, and its serialization is just JSON with a little pre- and post-processing.
Don't await a result before you use it. Chain dependent calls together and the whole chain resolves in a single network round trip — even over plain HTTP.
Sessions are symmetric: the client can call the server, and the server can call the client. Pass a function and the other side gets a stub that calls back.
Classes that extend RpcTarget travel as references, not copies. You hold a stub; method calls run where the object actually lives.
No .proto files, no build step, no generated clients. Types are just TypeScript — erased at runtime, and free.
Every major browser, Cloudflare Workers, Node, Deno, and Bun — over HTTP, WebSocket, MessagePort, or a transport you write yourself.
Minified and gzipped, with zero dependencies. The whole protocol is human-readable JSON — you can read it straight from the network tab.
Every call returns an RpcPromise. You can use that promise, or a property of it, as the input to
another call before it has resolved. The server substitutes the real value on arrival, so a chain
of dependent calls costs exactly one round trip:
// Authenticate, get the user's ID, fetch their profile, and
// fetch every friend's profile too. One request, one response.
let authed = api.authenticate(apiToken);
let profile = api.getUserProfile(authed.getUserId());
let friends = authed.getFriendIds().map(id => api.getUserProfile(id));
let [me, myFriends] = await Promise.all([profile, friends]);See how promise pipelining works → — a step-by-step tour, and the
record-replay trick behind .map().
Both examples run right here in the docs, with the source alongside them. They make the same point from opposite ends of the stack: the pipelined version issues one HTTP request where the ordinary version issues three.