Skip to content

Commit d29ee75

Browse files
committed
Add a typed RPC client
1 parent d1cb569 commit d29ee75

File tree

5 files changed

+294
-56
lines changed

5 files changed

+294
-56
lines changed

README.md

Lines changed: 65 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,36 +18,83 @@ npm install io-ts fp-ts --save
1818

1919
## Usage
2020

21+
This package makes no assumptions about the transportation layer, for client or server.
22+
23+
### Methods
24+
25+
The methods definition uses [`io-ts`](https://github.com/gcanti/io-ts) to encode and decode requests or responses.
26+
2127
```ts
22-
import { createHandler } from "@borderlesslabs/json-rpc";
2328
import * as t from "io-ts";
2429

25-
const rpc = createRpc(
26-
{
27-
hello: {
28-
// `request` is required, even when empty.
29-
request: t.type({}),
30-
response: t.string
31-
},
32-
echo: {
33-
// Specify `request` parameters as keys of the object.
34-
request: t.type({ arg: t.string }),
35-
response: t.string
36-
}
30+
const methods = {
31+
hello: {
32+
// `request` is required, even when empty.
33+
request: t.type({}),
34+
response: t.string
3735
},
38-
{
39-
hello: _ => "Hello World!",
40-
echo: ({ arg }) => arg
36+
echo: {
37+
// Specify `request` parameters as keys of the object.
38+
request: t.type({ arg: t.string }),
39+
response: t.string
4140
}
42-
);
41+
};
42+
```
4343

44-
const res = await rpc({
44+
### Server
45+
46+
The server takes the methods and a dictionary of matching resolvers.
47+
48+
```ts
49+
import { createClient } from "@borderlesslabs/json-rpc";
50+
51+
const server = createServer(methods, {
52+
hello: _ => "Hello World!",
53+
echo: ({ arg }) => arg
54+
});
55+
56+
const res = await server({
4557
jsonrpc: "2.0",
4658
id: "test",
4759
method: "hello"
4860
}); //=> { jsonrpc: "2.0", id: "test", result: "Hello World!" }
4961
```
5062

63+
### Client
64+
65+
The client takes the methods and a function to `send` the JSON-RPC request.
66+
67+
```ts
68+
import { createClient } from "@borderlesslabs/json-rpc";
69+
70+
const client = createClient(methods, async x => {
71+
const res = await fetch("...", {
72+
body: JSON.stringify(x),
73+
headers: {
74+
"Content-Type": "application/json"
75+
}
76+
});
77+
78+
return res.json();
79+
});
80+
81+
const result = await client({
82+
method: "hello",
83+
params: {}
84+
}); //=> "Hello World!"
85+
86+
const results = await client.batch(
87+
{
88+
method: "hello",
89+
params: {}
90+
},
91+
{
92+
method: "echo",
93+
params: { arg: "Test" }
94+
}
95+
); //=> ["Hello World!", "Test"]
96+
```
97+
5198
## License
5299

53100
MIT

package-lock.json

Lines changed: 1 addition & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,8 @@
9696
"peerDependencies": {
9797
"io-ts": "^2.0.4",
9898
"fp-ts": "^2.4.0"
99+
},
100+
"dependencies": {
101+
"make-error": "^1.3.5"
99102
}
100103
}

src/index.spec.ts

Lines changed: 82 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
import * as t from "io-ts";
2-
import { createRpc, parse } from "./index";
3-
import { isLeft, isRight } from "fp-ts/lib/Either";
2+
import { createServer, parse, createClient } from "./index";
3+
import { isLeft, isRight, either } from "fp-ts/lib/Either";
44

55
describe("json rpc", () => {
6-
const rpc = createRpc(
7-
{
8-
hello: {
9-
request: t.type({}),
10-
response: t.string
11-
},
12-
echo: {
13-
request: t.type({ arg: t.string }),
14-
response: t.string
15-
}
6+
const methods = {
7+
hello: {
8+
request: t.type({}),
9+
response: t.string
1610
},
17-
{
18-
hello: _ => "Hello World!",
19-
echo: ({ arg }) => arg
11+
echo: {
12+
request: t.type({ arg: t.string }),
13+
response: t.string
2014
}
21-
);
15+
};
16+
17+
const server = createServer(methods, {
18+
hello: _ => "Hello World!",
19+
echo: ({ arg }) => arg
20+
});
21+
22+
const client = createClient(methods, x => server(x, undefined));
2223

2324
describe("parse", () => {
2425
it("should parse json", () => {
@@ -42,10 +43,10 @@ describe("json rpc", () => {
4243
});
4344
});
4445

45-
describe("rpc", () => {
46+
describe("server", () => {
4647
describe("object", () => {
4748
it("should respond", async () => {
48-
const res = await rpc({
49+
const res = await server({
4950
jsonrpc: "2.0",
5051
id: "test",
5152
method: "hello"
@@ -59,7 +60,7 @@ describe("json rpc", () => {
5960
});
6061

6162
it("should not respond to notification", async () => {
62-
const res = await rpc({
63+
const res = await server({
6364
jsonrpc: "2.0",
6465
method: "hello"
6566
});
@@ -68,7 +69,7 @@ describe("json rpc", () => {
6869
});
6970

7071
it("should accept an array of parameters", async () => {
71-
const res = await rpc({
72+
const res = await server({
7273
jsonrpc: "2.0",
7374
id: "test",
7475
method: "echo",
@@ -83,7 +84,7 @@ describe("json rpc", () => {
8384
});
8485

8586
it("should accept an object of parameters", async () => {
86-
const res = await rpc({
87+
const res = await server({
8788
jsonrpc: "2.0",
8889
id: "test",
8990
method: "echo",
@@ -100,7 +101,7 @@ describe("json rpc", () => {
100101

101102
describe("array", () => {
102103
it("should respond", async () => {
103-
const res = await rpc([
104+
const res = await server([
104105
{
105106
jsonrpc: "2.0",
106107
id: 1,
@@ -130,7 +131,7 @@ describe("json rpc", () => {
130131

131132
describe("invalid", () => {
132133
it("should fail on malformed request", async () => {
133-
const res = await rpc(123);
134+
const res = await server(123);
134135

135136
expect(res).toEqual({
136137
jsonrpc: "2.0",
@@ -143,7 +144,7 @@ describe("json rpc", () => {
143144
});
144145

145146
it("should fail on malformed request object", async () => {
146-
const res = await rpc({});
147+
const res = await server({});
147148

148149
expect(res).toEqual({
149150
jsonrpc: "2.0",
@@ -156,7 +157,7 @@ describe("json rpc", () => {
156157
});
157158

158159
it("should fail on empty batch request", async () => {
159-
const res = await rpc([]);
160+
const res = await server([]);
160161

161162
expect(res).toEqual({
162163
jsonrpc: "2.0",
@@ -169,7 +170,7 @@ describe("json rpc", () => {
169170
});
170171

171172
it("should fail on invalid request rpc id", async () => {
172-
const res = await rpc({
173+
const res = await server({
173174
jsonrpc: "2.0",
174175
id: {}
175176
});
@@ -185,7 +186,7 @@ describe("json rpc", () => {
185186
});
186187

187188
it("should fail on fractional numeric request rpc id", async () => {
188-
const res = await rpc({
189+
const res = await server({
189190
jsonrpc: "2.0",
190191
id: 123.5
191192
});
@@ -201,7 +202,7 @@ describe("json rpc", () => {
201202
});
202203

203204
it("should fail on method not found", async () => {
204-
const res = await rpc({
205+
const res = await server({
205206
jsonrpc: "2.0",
206207
id: "test",
207208
method: "missing"
@@ -218,7 +219,7 @@ describe("json rpc", () => {
218219
});
219220

220221
it("should fail on missing parameters", async () => {
221-
const res = await rpc({
222+
const res = await server({
222223
jsonrpc: "2.0",
223224
id: "test",
224225
method: "echo"
@@ -236,7 +237,7 @@ describe("json rpc", () => {
236237
});
237238

238239
it("should fail on invalid parameters", async () => {
239-
const res = await rpc({
240+
const res = await server({
240241
jsonrpc: "2.0",
241242
id: "test",
242243
method: "echo",
@@ -254,7 +255,7 @@ describe("json rpc", () => {
254255
});
255256

256257
it("should fail on invalid parameters", async () => {
257-
const res = await rpc({
258+
const res = await server({
258259
jsonrpc: "2.0",
259260
id: "test",
260261
method: "echo",
@@ -272,4 +273,54 @@ describe("json rpc", () => {
272273
});
273274
});
274275
});
276+
277+
describe("client", () => {
278+
describe("request", () => {
279+
it("should make a request", async () => {
280+
const result = await client({ method: "hello", params: {} });
281+
282+
expect(result).toEqual("Hello World!");
283+
});
284+
285+
it("should make a notification request", async () => {
286+
const result = await client({
287+
method: "hello",
288+
params: {},
289+
async: true
290+
});
291+
292+
expect(result).toEqual(undefined);
293+
});
294+
});
295+
296+
describe("batch", () => {
297+
it("should make a batch request", async () => {
298+
const result = await client.batch(
299+
{ method: "hello", params: {} },
300+
{ method: "echo", params: { arg: "test" } }
301+
);
302+
303+
expect(result).toEqual(["Hello World!", "test"]);
304+
});
305+
306+
it("should make a batch notification request", async () => {
307+
const result = await client.batch(
308+
{ method: "hello", params: {}, async: true },
309+
{ method: "echo", params: { arg: "test" }, async: true }
310+
);
311+
312+
expect(result).toEqual([undefined, undefined]);
313+
});
314+
315+
it("should handle mixed batch responses", async () => {
316+
const result = await client.batch(
317+
{ method: "hello", params: {}, async: true },
318+
{ method: "echo", params: { arg: "test" } },
319+
{ method: "hello", params: {}, async: true }
320+
);
321+
322+
expect(result).toEqual([undefined, "test", undefined]);
323+
});
324+
});
325+
});
275326
});

0 commit comments

Comments
 (0)