Status: Experimental - This library is under active development. APIs may change between minor versions. Install with
npm install @ably/rpc@beta.
Run RPC protocols over Ably pub/sub channels.
Most RPC setups need you to run and scale your own WebSocket servers or message brokers. With Ably as the transport layer, the infrastructure problem goes away:
- Message ordering guarantees - Ably delivers messages in the order they were published, per channel. RPC request/response sequencing just works.
- Connection recovery - Clients reconnect and resume automatically. No dropped calls, no retry logic in your application code.
- Global edge routing - Messages route through the nearest Ably edge node. Low latency without deploying servers in every region.
- No infrastructure to manage - Your RPC logic lives entirely on the client and server. No middleware, no WebSocket servers, no message brokers to operate.
The result: a reliable transport for RPC in decoupled, distributed systems - which is most stateful internet applications.
- JSON-RPC 2.0 - Standard request/response protocol with typed proxies. Cross-platform — works with any JSON-RPC 2.0 implementation in any language.
- Cap'n Web - Object-capability RPC with promise pipelining and pass-by-reference via capnweb. JavaScript/TypeScript only.
- Protocol-agnostic transport -
AblyTransportworks with any RPC protocol that needs send/receive semantics - Echo filtering - Messages from your own connection are automatically ignored
- TypeScript-first - Full type inference for local and remote method signatures
- Dual-format - Ships ESM and CJS builds
npm install @ably/rpc ablyFor Cap'n Web support, also install capnweb:
npm install @ably/rpc ably capnwebimport Ably from 'ably';
import { AblyTransport } from '@ably/rpc';
import { JsonRpcSession } from '@ably/rpc/json-rpc';
// Use token auth — never expose your API key client-side
const ably = new Ably.Realtime({ authUrl: '/api/token' });
const channel = ably.channels.get('rpc:my-session');
const transport = new AblyTransport(channel, false, ably);
await transport.waitReady();
// Server side - expose methods
const session = new JsonRpcSession(transport, {
async add(a: number, b: number) { return a + b; },
async greet(name: string) { return `Hello, ${name}!`; },
});
// Client side - call remote methods
const remote = session.getRemoteMain();
await remote.add(2, 3); // 5
await remote.greet('World'); // "Hello, World!"import Ably from 'ably';
import { RpcSession } from 'capnweb';
import { AblyTransport } from '@ably/rpc';
// Use token auth — never expose your API key client-side
const ably = new Ably.Realtime({ authUrl: '/api/token' });
const channel = ably.channels.get('rpc:my-session');
const transport = new AblyTransport(channel, false, ably);
await transport.waitReady();
// Pass AblyTransport directly to capnweb's RpcSession
const session = new RpcSession(transport, {
async increment() { return ++counter; },
async getValue() { return counter; },
});
const remote = session.getRemoteMain();
await remote.increment(); // promise pipelining, pass-by-reference┌──────────────────┐ ┌──────────────────┐
│ Client A │ │ Client B │
│ │ │ │
│ JsonRpcSession │ │ JsonRpcSession │
│ or capnweb │ │ or capnweb │
│ │ │ │ │ │
│ AblyTransport │ │ AblyTransport │
└─────────┬────────┘ └─────────┬────────┘
│ ┌──────────────┐ │
│ │ │ │
└───► Ably Channel ◄────┘
│ │
│ • ordering │
│ • recovery │
│ • edge CDN │
└──────────────┘
Both clients publish to and subscribe on the same Ably channel. AblyTransport handles serialization, message queuing, and echo filtering. The RPC session (JSON-RPC or Cap'n Web) handles protocol framing on top.
Sizes below are gzipped, measured with esbuild (minified, browser ESM). The Ably SDK (ably) is a peer dependency you already have — these sizes show the additional cost of adding RPC support.
| Protocol | Import | Gzipped |
|---|---|---|
| JSON-RPC 2.0 | @ably/rpc + @ably/rpc/json-rpc |
~5 KB |
| Cap'n Web | @ably/rpc + capnweb |
~14 KB |
For reference, the ably SDK itself is ~51 KB gzipped.
Which protocol should I pick?
- JSON-RPC 2.0 (~5 KB) is the right choice for most use cases — simple request/response with typed methods. Because it uses the standard JSON-RPC 2.0 wire format, your server can be written in any language.
- Cap'n Web (~14 KB) via capnweb gives you promise pipelining and pass-by-reference semantics, which reduce round trips in complex RPC chains. Both sides must be JavaScript/TypeScript.
Bridges an Ably RealtimeChannel to a send/receive transport interface.
new AblyTransport(channel: Ably.RealtimeChannel, debug?: boolean, ably?: Ably.Realtime)waitReady()- Wait for the channel to attachsend(message: string)- Send a message (serialized to preserve ordering)receive()- Receive the next message (queues if none waiting)abort(reason)- Abort the transport with an errorclose()- Close the transport cleanly
The ably parameter enables echo filtering (messages from your own connection are ignored).
JSON-RPC 2.0 session over the transport. Registers local methods, provides a proxy for remote calls.
new JsonRpcSession(transport: AblyTransport, localApi: Local)getRemoteMain()- Returns a typed proxy; property access maps to JSON-RPC requestsclose()- Stop the receive loop and reject pending requests
Interface satisfied by both JsonRpcSession and capnweb's RpcSession:
interface ProtocolSession<Remote> {
getRemoteMain(): Remote;
close?: () => void;
}Use this type when writing protocol-agnostic code.
The demo/ directory contains a full working example: a counter app with bidirectional RPC between browser tabs, supporting both JSON-RPC 2.0 and Cap'n Web.
cd demo
cp .env.example .env # add your Ably API key
npm install
npm run dev- Fork the repo
- Create a feature branch (
git checkout -b my-feature) - Run tests (
npm test) and build (npm run build) - Open a pull request
