Skip to content

Commit 70f6cf4

Browse files
committed
Remove Proxy API as default API
1 parent 347afd8 commit 70f6cf4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1246
-1012
lines changed

README.md

Lines changed: 114 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,177 +1,178 @@
11
# gentle_rpc
22

3-
JSON-RPC 2.0 library with WebSockets and HTTP support for
3+
JSON-RPC 2.0 library with HTTP and WebSockets support for
44
[deno](https://github.com/denoland/deno) and the browser.
55

66
## Server
77

88
### respond
99

10-
Takes the arguments `methods`, `req` and `options`. You can set options for an
11-
additional server argument, protocol, headers and public error stacks.
10+
Takes `Methods`, `ServerRequest` and `Options`.
1211

1312
```typescript
14-
import { serve } from "https://deno.land/std@0.97.0/http/server.ts";
15-
import { respond } from "https://deno.land/x/gentle_rpc/mod.ts";
13+
import { serve } from "https://deno.land/std@0.97.0/http/server.ts"
14+
import { respond } from "https://deno.land/x/gentle_rpc/mod.ts"
1615

17-
const server = serve("0.0.0.0:8000");
16+
const server = serve("0.0.0.0:8000")
1817
const rpcMethods = {
19-
sayHello: (w: [string]) => `Hello ${w}`,
18+
sayHello: ([w]: [string]) => `Hello ${w}`,
2019
callNamedParameters: ({ a, b, c }: { a: number; b: number; c: string }) =>
2120
`${c} ${a * b}`,
2221
animalsMakeNoise: (noise: string[]) =>
2322
noise.map((el) => el.toUpperCase()).join(" "),
24-
};
23+
}
2524

26-
console.log("listening on 0.0.0.0:8000");
25+
console.log("listening on 0.0.0.0:8000")
2726

2827
for await (const req of server) {
2928
// HTTP:
30-
await respond(rpcMethods, req);
29+
respond(rpcMethods, req)
3130
// WebSockets:
32-
await respond(rpcMethods, req, { proto: "ws" });
31+
respond(rpcMethods, req, { proto: "ws" })
3332
}
3433
```
3534

36-
#### custom errors
35+
#### CustomError
3736

3837
Throw a `CustomError` to send a server-defined error response.
3938

4039
```typescript
41-
import { CustomError, respond } from "https://deno.land/x/gentle_rpc/mod.ts";
40+
import { CustomError, respond } from "https://deno.land/x/gentle_rpc/mod.ts"
4241

4342
//..
44-
await respond({
45-
throwError: () => {
46-
throw new CustomError(
47-
-32000, // the JSON-RPC error code. Note, must be -32000 to -32099
48-
"An error occurred", // the error message, a short sentence
49-
{ details: "..." }, // optional additional details, can be an object, primitive, etc (any)
50-
);
43+
await respond(
44+
{
45+
throwError: () => {
46+
throw new CustomError(
47+
-32040, // the JSON-RPC error code. Note, must be -32040 to -32099
48+
"An error occurred", // the error message, a short sentence
49+
{ details: "..." } // optional additional details, can be any `JsonValue`
50+
)
51+
},
5152
},
52-
}, req);
53+
req
54+
)
5355
//..
5456
```
5557

5658
## Client
5759

5860
#### createRemote
5961

60-
Takes a `resource` for HTTP or a `WebSocket` for WebSockets and returns a
61-
TypeScript `Proxy` or `Promise<Proxy>` which we will call `remote` from now on.
62+
Takes a `Resource` for HTTP or a `WebSocket` for WebSockets and returns
63+
`Remote`.
6264

6365
```typescript
64-
import { createRemote } from "https://deno.land/x/gentle_rpc/mod.ts";
66+
import { createRemote } from "https://deno.land/x/gentle_rpc/mod.ts"
6567
// Or import directly into the browser with:
66-
import { createRemote } from "https://cdn.jsdelivr.net/gh/timonson/gentle_rpc@v2.8/client/dist/remote.js";
68+
import { createRemote } from "https://cdn.jsdelivr.net/gh/timonson/gentle_rpc@v2.8/client/dist/remote.js"
6769

6870
// HTTP:
69-
const remote = createRemote("http://0.0.0.0:8000");
71+
const remote = createRemote("http://0.0.0.0:8000")
7072

7173
// WebSocket:
72-
const remote = await createRemote(new WebSocket("ws://0.0.0.0:8000"));
74+
const remote = await createRemote(new WebSocket("ws://0.0.0.0:8000"))
7375
```
7476

7577
### HTTP
7678

77-
#### remote
79+
#### call
7880

79-
All `remote` methods take an `Array<JsonValue>` or `Record<string, JsonValue>`
80-
object and return `Promise<JsonValue | undefined>`.
81+
Takes a string and an `Array<JsonValue>` or `Record<string, JsonValue>` object
82+
and returns `Promise<JsonValue>`.
8183

8284
```typescript
83-
const greeting = await remote.sayHello(["World"]);
85+
const greeting = await remote.call("sayHello", ["World"])
8486
// Hello World
8587

86-
const namedParams = await remote.callNamedParameters({
88+
const namedParams = await remote.call("callNamedParameters", {
8789
a: 5,
8890
b: 10,
8991
c: "result:",
90-
});
92+
})
9193
// result: 50
9294
```
9395

9496
##### notification
9597

98+
Using the option `{ isNotification: true }` will retun `Promise<undefined>`.
99+
96100
```typescript
97-
const notification = await remote.sayHello.notify(["World"]);
101+
const notification = await remote.call("sayHello", ["World"], {
102+
isNotification: true,
103+
})
98104
// undefined
99105
```
100106

101-
##### auth
107+
##### jwt
102108

103-
This method will set the `Authorization` header to `` `Bearer ${jwt}` ``.
109+
Adding a jwt to the optional property `jwt` will set the `Authorization` header
110+
to `` `Bearer ${jwt}` ``.
104111

105112
```typescript
106-
const greeting = await remote.sayHello.auth(jwt)(["World"]);
107-
// Hello World
113+
const user = await remote.call("login", undefined, { jwt })
114+
// Bob
108115
```
109116

110-
##### batch
117+
#### batch
111118

112119
```typescript
113-
const noise1 = await remote.animalsMakeNoise.batch([
114-
["miaaow"],
115-
["wuuuufu", "wuuuufu"],
116-
["iaaaiaia", "iaaaiaia", "iaaaiaia"],
117-
["fiiiiire"],
118-
]);
119-
// [ "MIAAOW", "WUUUUFU WUUUUFU", "IAAAIAIA IAAAIAIA IAAAIAIA", "FIIIIIRE" ]
120+
const noise1 = await remote.batch([
121+
{
122+
animalsMakeNoise: [
123+
["miaaow"],
124+
["wuuuufu", "wuuuufu"],
125+
["iaaaiaia", "iaaaiaia", "iaaaiaia"],
126+
["fiiiiire"],
127+
],
128+
sayHello: [["World"], undefined, ["World"]],
129+
},
130+
])
131+
// [ "MIAAOW", "WUUUUFU WUUUUFU", "IAAAIAIA IAAAIAIA IAAAIAIA", "FIIIIIRE", "Hello World", "Hello ", "Hello World" ]
120132
```
121133

122-
##### batch with different methods
123-
124-
Takes either a `batchObject` or a `batchArray` as argument and returns a
125-
promise.
134+
The following example uses the object keys `cat`, `dog`, `donkey`, `dragon` as
135+
RPC _request object ids_ under the hood. The returned _RPC result_ values will
136+
be assigned to these keys.
126137

127138
```typescript
128-
await remote.batch({
139+
let noise2 = await remote.batch({
129140
cat: ["sayHello", ["miaaow"]],
130141
dog: ["animalsMakeNoise", ["wuuuufu"]],
131142
donkey: ["sayHello"],
132143
dragon: ["animalsMakeNoise", ["fiiiiire", "fiiiiire"]],
133-
});
144+
})
134145
// { cat: "Hello miaaow", dog: "WUUUUFU", donkey: "Hello ", dragon: "FIIIIIRE FIIIIIRE" }
135146
```
136147

137-
The example above uses the object keys `cat`, `dog`, `donkey`, `dragon` as RPC
138-
_request object ids_ under the hood. The returned _RPC result_ values will be
139-
assigned to these keys.
140-
141-
For other use cases you might prefer the following example:
142-
143-
```typescript
144-
await remote.batch([
145-
"animalsMakeNoise",
146-
["miaaow"],
147-
["wuuuufu", "wuuuufu"],
148-
["iaaaiaia", "iaaaiaia", "iaaaiaia"],
149-
["fiiiiire"],
150-
]);
151-
// [ "MIAAOW", "WUUUUFU WUUUUFU", "IAAAIAIA IAAAIAIA IAAAIAIA", "FIIIIIRE" ]
152-
```
153-
154148
### WebSockets
155149

156150
The support for WebSockets is still experimental and has not been fully tested
157151
yet.
158152

159-
#### remote
153+
#### call
160154

161-
All `remote` methods take an `Array<JsonValue>` or `Record<string, JsonValue>`
162-
object and return `Promise<JsonValue | undefined>`.
155+
Takes a string and an `Array<JsonValue>` or `Record<string, JsonValue>` object
156+
and returns `Promise<JsonValue | undefined>`.
163157

164158
```typescript
165-
const noise = await remote.animalsMakeNoise(["wuufff"]);
166-
console.log(noise);
159+
const noise = await remote.call("callNamedParameters", {
160+
a: 10,
161+
b: 20,
162+
c: "The result is:",
163+
})
164+
// The result is: 200
165+
166+
const notification = await remote.call("sayHello", ["World"], true)
167+
// undefined
167168

168-
remote.socket.close();
169+
remote.socket.close()
169170
```
170171

171172
##### notification
172173

173174
```typescript
174-
const notification = await remote.animalsMakeNoise.notify(["wuufff"]);
175+
const notification = await remote.animalsMakeNoise.notify(["wuufff"])
175176
```
176177

177178
##### messaging between multiple clients
@@ -185,41 +186,61 @@ Other clients can _listen to_ and _emit_ messages by _subscribing_ to the same
185186
method.
186187

187188
```typescript
188-
// First client
189-
export async function run(iter: AsyncGenerator<unknown>) {
189+
const firstClient = await createRemote(new WebSocket("ws://0.0.0.0:8000"))
190+
const secondClient = await createRemote(new WebSocket("ws://0.0.0.0:8000"))
191+
192+
async function run(iter: AsyncGenerator<unknown>) {
190193
try {
191194
for await (let x of iter) {
192-
console.log(x);
195+
console.log(x)
193196
}
194197
} catch (err) {
195-
console.log(err.message, err.code);
198+
console.log(err.message, err.code, err.data)
196199
}
197200
}
198201

199-
const greeting = remote.sayHello.subscribe();
200-
run(greeting.generator);
201-
greeting.emit(["first"]);
202+
const greeting = firstClient.subscribe("sayHello")
203+
const second = secondClient.subscribe("sayHello")
204+
205+
run(greeting.generator)
206+
run(second.generator)
207+
greeting.emit({ w: "first" })
208+
second.emitBatch([{ w: "second" }, { w: "third" }])
209+
// Hello first
202210
// Hello first
203211
// Hello second
212+
// Hello second
213+
// Hello third
204214
// Hello third
205215
```
206216

217+
## Proxy API
218+
219+
Optionally, you can import _syntactical sugar_ and use a more friendly API
220+
supported by `Proxy` objects.
221+
207222
```typescript
208-
// Second client
209-
const greeting = remote.sayHello.subscribe();
210-
run(greeting.generator);
211-
greeting.emitBatch([["second"], ["third"]]);
212-
// Hello first
213-
// Hello second
214-
// Hello third
223+
import { createRemote, HttpProxy, httpProxyHandler } from "../../mod.ts"
224+
225+
const remote = new Proxy<HttpProxy>(
226+
createRemote("http://0.0.0.0:8000"),
227+
httpProxyHandler
228+
)
215229

216-
// You can optionally unsubscribe:
217-
greeting.unsubscribe();
230+
let greeting = await remote.sayHello(["World"])
231+
// Hello World
232+
233+
const namedParams = await remote.callNamedParameters({
234+
a: 5,
235+
b: 10,
236+
c: "result:",
237+
})
238+
// result: 50
218239
```
219240

220241
## Examples and Tests
221242

222-
Checkout the
243+
Please checkout the
223244
[examples](https://github.com/timonson/gentle_rpc/tree/master/examples) and
224245
[tests](https://github.com/timonson/gentle_rpc/tree/master/tests) folders for
225246
more detailed examples.

client/creation.ts

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { generate as generateV4Uuid } from "./deps.ts";
2-
31
import type { RpcBatchRequest, RpcRequest } from "../json_rpc_types.ts";
2+
import type { BatchArrayInput, BatchObjectInput } from "./http.ts";
43

5-
export type BatchArrayInput = [string, ...RpcRequest["params"][]];
6-
export type BatchObjectInput = Record<string, [string, RpcRequest["params"]?]>;
4+
function generateId() {
5+
return window.crypto.getRandomValues(new Uint32Array(1))[0].toString(16);
6+
}
77

88
export function createRequest({
99
method,
@@ -21,7 +21,7 @@ export function createRequest({
2121
method,
2222
};
2323
params && (rpcRequest.params = params);
24-
id = isNotification ? undefined : id !== undefined ? id : generateV4Uuid();
24+
id = isNotification ? undefined : id !== undefined ? id : generateId();
2525
id !== undefined && (rpcRequest.id = id);
2626
return rpcRequest;
2727
}
@@ -31,15 +31,11 @@ export function createRequestBatch(
3131
isNotification = false,
3232
): RpcBatchRequest {
3333
return Array.isArray(batchObj)
34-
? batchObj
35-
.map((el, _, array) =>
36-
createRequest({
37-
method: array[0] as string,
38-
params: el as RpcRequest["params"],
39-
isNotification,
40-
})
34+
? batchObj.map((el) =>
35+
Object.entries(el).map(([method, arr]) =>
36+
arr.map((params) => createRequest({ method, params, isNotification }))
4137
)
42-
.slice(1)
38+
).flat(2)
4339
: Object.entries(batchObj).map(([key, value]) =>
4440
createRequest({
4541
method: value[0],

client/deps.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)