You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Lightweight [JSON-RPC 2.0](https://www.jsonrpc.org/specification) library for TypeScript with automatic request batching.
3
+
Lightweight [JSON-RPC 2.0](https://www.jsonrpc.org/specification) library for TypeScript.
4
4
5
-
- Type-safe client via TypeScript inference (no codegen)
6
-
- Supports batched requests
7
5
- Spec-compliant JSON-RPC 2.0
6
+
- Supports batched requests
7
+
- Supports bidirectional communication
8
+
- Works great with TypeScript
8
9
- Transport-agnostic core
9
10
- Zero dependencies
10
11
- Designed for Cloudflare Workers
@@ -16,76 +17,105 @@ This library was influenced by the designs of:
16
17
-[typed-rpc](https://github.com/fgnass/typed-rpc)
17
18
-[capnweb](https://github.com/cloudflare/capnweb)
18
19
20
+
## Comparison with Cap'n Web
21
+
22
+
Cap'n Web is an object-capabilities RPC library from [Kenton Varda](https://github.com/kentonv). Beyond its
23
+
support for object-capabilities, it is designed to integrate very nicely with TypeScript and Cloudflare Workers.
24
+
I couldn't find a JSON-RPC library that worked as nicely, so I ~~stole its design~~ used Cap'n Web as inspiration
25
+
for a JSON-RPC implementation.
26
+
27
+
Cap'n Web has a number of benefits over JSON-RPC
28
+
29
+
-**Object capabilities**. You can pass functions and classes by reference. This is more than a feature, it completely changes how expressive your API can be, and enables patterns that are not possible with a more limited RPC protocol.
30
+
-**Pipelining**. Invoke two methods and then pass their results into a third in one round-trip.
31
+
-**Support for non-JSON data types**: `ReadableStream`, `bigint`, `Date`
32
+
33
+
So why would you ever want to use JSON-RPC instead?
34
+
35
+
Mainly [**it's boring**](https://mcfunley.com/choose-boring-technology) and has been around for longer than a few months. The [JSON-RPC 2.0 Spec](https://www.jsonrpc.org/specification) was last updated in 2013. Even more impressive, you can sit down with a cup of coffee and read the whole thing. Your coffee might not even be cool enough to drink when you've finished.
36
+
37
+
A few more points:
38
+
39
+
- Widely used in [Language Server Protocol](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/) which likely drives your editor
40
+
- Used in [MCP spec](https://modelcontextprotocol.io/specification/2025-11-25)
41
+
- Easy to invoke with plain `curl` commands
42
+
- Many client implementations in many languages
43
+
44
+
Cap'n Web is strictly more powerful, and I look forward to seeing it grow and mature, but for many projects today JSON-RPC is a great fit.
45
+
19
46
## Basic usage
20
47
21
-
### Define a typescript interface
48
+
### Define a TypeScript interface
22
49
23
-
We will use this as a service definition and import it on both the client and server.
50
+
This serves as the service contract. Import it on both client and server for end-to-end type safety.
24
51
25
52
```ts
26
-
exportinterfaceMathService {
27
-
add(a:number, b:number):Promise<number>;
28
-
divide(a:number, b:number):Promise<number>;
29
-
}
53
+
exporttypeHelloService= {
54
+
hello(name:string):string;
55
+
};
30
56
```
31
57
32
-
### Define a service
33
-
34
-
It's not required to use a class here, anything that implements the service interface will work. A class allows you to pass the information beyond just the arguments to the method being invoked. Other examples would be the authenticated user, or set of feature flags.
`handleRpc` reads the request body, parses JSON, dispatches to your service, and returns a `Response`. POST only — other methods get a 405. Notifications return 204.
96
+
## JSON-RPC Spec Support
69
97
70
-
### Client
98
+
This library complies with the JSON-RPC 2.0 Spec, however it intentionally leaves out support for two features:
`newWebSocketRpcSession` creates a typed RPC session over a WebSocket. Pass a URL string and it connects for you, or pass an existing `WebSocket` instance.
119
149
120
-
Replace the default `fetch` transport entirely:
150
+
### Client-to-server calls
121
151
122
152
```ts
123
-
const client =rpcClient<MathService>({
124
-
transport: async (body:string) => {
125
-
// body is serialized JSON (single request or batch array)
Calling a remote method after close rejects with `"Session is closed"`. When the WebSocket drops unexpectedly, all pending calls reject and close handlers fire.
Fire-and-forget WebSocket handler for Cloudflare Workers. Creates a `WebSocketPair`, starts an RPC session as acceptor, and returns the `101` upgrade response. Use this when you don't need to call methods on the client.
Same as above, but returns `{ response, session }` so you can use `session.remote` to call methods on the client. See [Bidirectional RPC](#bidirectional-rpc) above.
146
252
147
253
### `processRpc(body, service, options?)`
148
254
149
-
Transport-agnostic core. Takes a parsed JSON body (not a `Request`), returns the response object(s) or `null` for notification-only requests.
255
+
Transport-agnostic core. Takes a parsed JSON body (not a `Request`), returns the response object(s) or `null` for notification-only requests. Use this if you need to handle the transport yourself.
150
256
151
257
```ts
152
-
// WebSocket example
258
+
import { processRpc } from"@jmorrell/jsonrpc";
259
+
153
260
ws.on("message", async (data) => {
154
261
const body =JSON.parse(data);
155
262
const result =awaitprocessRpc(body, myService);
156
263
if (result!==null) ws.send(JSON.stringify(result));
157
264
});
158
265
```
159
266
160
-
### Options
161
-
162
-
```ts
163
-
handleRpc(request, service, {
164
-
onError: (err) =>console.error(err), // Called when a handler throws
165
-
});
166
-
```
167
-
168
267
## Error handling
169
268
269
+
### `RpcError`
270
+
170
271
When a remote method returns a JSON-RPC error, the client throws an `RpcError` with `message`, `code`, and optional `data`:
This library uses by-position params only (arrays). Named params (objects) are rejected with `-32602 Invalid params`. This matches how TypeScript function calls naturally map to JSON-RPC.
198
-
199
-
If you want to use named params, use a single argument object:
- Properties that aren't functions on the service object
296
+
### `RpcProtocolError`
297
+
298
+
Protocol-level issues (malformed messages, unknown response IDs, transport failures) are reported via the `onError` callback as `RpcProtocolError` instances. Each has a `code` string:
0 commit comments