1 | # Async Call
|
2 |
|
3 | `async-call-rpc` is a [JSON RPC](https://www.jsonrpc.org/specification) server and client written in TypeScript for any ES6+ environment.
|
4 |
|
5 | [![Code coverage](https://codecov.io/gh/Jack-Works/async-call-rpc/branch/master/graph/badge.svg)](https://codecov.io/gh/Jack-Works/async-call-rpc)
|
6 | [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/Jack-Works/async-call-rpc/build)](https://github.com/Jack-Works/async-call-rpc/actions)
|
7 | [![npm](https://img.shields.io/npm/v/async-call-rpc)](https://npmjs.org/async-call-rpc)
|
8 | ![ES2015+](https://img.shields.io/badge/ECMAScript-2015%2B-brightgreen)
|
9 |
|
10 | ## Links
|
11 |
|
12 | [CHANGELOG.md](./CHANGELOG.md) | [Document of AsyncCall](https://jack-works.github.io/async-call-rpc/async-call-rpc.asynccall.html) | [Document of AsyncGeneratorCall](https://jack-works.github.io/async-call-rpc/async-call-rpc.asyncgeneratorcall.html) | [Playground](https://jack-works.github.io/async-call-rpc/)
|
13 |
|
14 | Chapters:
|
15 |
|
16 | - [The first concept: `channel`](#the-first-concept-messagechannel)
|
17 | - [Example](#example)
|
18 | - [Notifications and Batch requests](#notifications-and-batch-requests)
|
19 | - [Installation](#installation)
|
20 | - [Entries](#entries)
|
21 | - [Utils available if both server and client are created by this library](#utils-available-if-both-server-and-client-are-created-by-this-library)
|
22 | - [Builtin `channels` (including WebSocket)](#builtin-channels)
|
23 | - [Implemented JSON RPC internal methods](#implemented-json-rpc-internal-methods)
|
24 | - [Non-standard extension to JSON RPC specification](#non-standard-extension-to-json-rpc-specification)
|
25 |
|
26 | ## Features
|
27 |
|
28 | - Zero dependencies!
|
29 | - Running in any ES6+ environment (+`globalThis`), no requirement on any Web or Node API
|
30 | - Simple to define a server and simple to use as a client
|
31 | - Full TypeScript support
|
32 | - Support custom serializer to pass complex data types
|
33 | - Support async generator (Require both server and client supports 4 JSON RPC internal methods, and `Symbol.asyncIterator`, `(async function* () {}).constructor.prototype` available)
|
34 |
|
35 | ## Cautions
|
36 |
|
37 | - NOT support ECMAScript 5 (ES6 `Proxy` is the core of this library)
|
38 | - This package is shipping ECMAScript 2018 syntax (including `async function`).
|
39 | - The async generator mode might leak memory on the server. Use it by your caution.
|
40 | - NOT support JSON RPC 1.0
|
41 |
|
42 | ## The first concept: `channel`
|
43 |
|
44 | <a id="channel"></a>
|
45 |
|
46 | The `channel` is the only thing you need to learn to use this library.
|
47 |
|
48 | This library is designed to not rely on any specific platform. Only require things defined in the ECMAScript specification.
|
49 | In the ES spec, there is no I/O related API so it's impossible to communicate with the outer world.
|
50 |
|
51 | You need to implement one of the following interfaces:
|
52 |
|
53 | - [CallbackBasedChannel](https://jack-works.github.io/async-call-rpc/async-call-rpc.callbackbasedchannel.html), generally used in the server. [Example](https://github.com/Jack-Works/async-call-rpc/blob/master/utils-src/web/websocket.client.ts).
|
54 | - [EventBasedChannel](https://jack-works.github.io/async-call-rpc/async-call-rpc.eventbasedchannel.html), generally used in the client. [Example](https://github.com/Jack-Works/async-call-rpc/blob/master/utils-src/node/websocket.server.ts)
|
55 |
|
56 | There are some [built-in channel](#builtin-channels) you can simplify the usage.
|
57 |
|
58 | The following document will assume you have defined your `channel`.
|
59 |
|
60 | ## Example
|
61 |
|
62 | ### Server example
|
63 |
|
64 | ```ts
|
65 | // server.ts
|
66 | export function add(x: number, y: number) {
|
67 | return x + y
|
68 | }
|
69 | export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
70 |
|
71 | // init.ts
|
72 | import { AsyncCall } from 'async-call-rpc'
|
73 | import * as server from './server'
|
74 | // create a server
|
75 | AsyncCall(server, { channel })
|
76 | ```
|
77 |
|
78 | ### Client example
|
79 |
|
80 | ```ts
|
81 | import { AsyncCall } from 'async-call-rpc'
|
82 | const server = AsyncCall<typeof server>({}, { channel })
|
83 | server.add(2, 40).then(console.log) // 42
|
84 | ```
|
85 |
|
86 | ### Isomorphic API
|
87 |
|
88 | You can notice from the above example,
|
89 | define a server is using `AsyncCall(serverImplementation, opt)`,
|
90 | define a client is using `AsyncCall<typeof serverImplementation>({}, opt)`.
|
91 | So it is possible to define a server and a client at the same time.
|
92 |
|
93 | ## Notifications and Batch requests
|
94 |
|
95 | AsyncCall can send [Notifications](https://www.jsonrpc.org/specification#notification).
|
96 |
|
97 | Using notifications means results or remote errors will be dropped. Local errors won't be omitted, e.g. serializer error or network error.
|
98 |
|
99 | ```ts
|
100 | import { AsyncCall, notify } from 'async-call-rpc'
|
101 | const server = notify(AsyncCall<typeof server>({}, { channel }))
|
102 | server.online().then(console.log) // undefined
|
103 | ```
|
104 |
|
105 | AsyncCall can send [batch request](https://www.jsonrpc.org/specification#batch) too.
|
106 |
|
107 | ```ts
|
108 | import { AsyncCall, batch } from 'async-call-rpc'
|
109 | const [server, emit, drop] = batch(AsyncCall<typeof server>({}, { channel }))
|
110 | const a = server.req1() // pending
|
111 | const b = server.req2() // pending
|
112 | const c = server.req3() // pending
|
113 | emit() // to send all pending requests
|
114 | // request a, b, c sent
|
115 |
|
116 | const d = server.req1() // pending
|
117 | drop() // to drop all pending requests (and corresponding Promises)
|
118 | // d rejected
|
119 | ```
|
120 |
|
121 | ## Installation
|
122 |
|
123 | ### Install through npm
|
124 |
|
125 | > npm i async-call-rpc
|
126 |
|
127 | > yarn add async-call-rpc
|
128 |
|
129 | ### Import from browser or Deno
|
130 |
|
131 | You can access https://www.jsdelivr.com/package/npm/async-call-rpc?path=out to get the latest URL and SRI.
|
132 |
|
133 | (Supports type definition for deno out-of-box!)
|
134 |
|
135 | ```js
|
136 | import { AsyncCall } from 'https://cdn.jsdelivr.net/npm/async-call-rpc@latest/out/base.mjs'
|
137 | ```
|
138 |
|
139 | ### UMD
|
140 |
|
141 | ```html
|
142 | <script src="https://cdn.jsdelivr.net/npm/async-call-rpc@2.0.1/out/base.js"></script>
|
143 | <script>
|
144 | const { AsyncCall } = globalThis.AsyncCall
|
145 | </script>
|
146 | ```
|
147 |
|
148 | ### In other JS environment
|
149 |
|
150 | Load the `out/base.mjs` (ES Module) or `out/base.js` (UMD, CommonJS or AMD) to your project.
|
151 |
|
152 | ## Entries
|
153 |
|
154 | This library has 2 entry. `base` and `full`. `base` is the default entry point. The `full` version includes the `AsyncGeneratorCall` but the base version doesn't.
|
155 |
|
156 | ### Browser / Deno
|
157 |
|
158 | Please check out https://www.jsdelivr.com/package/npm/async-call-rpc?path=out
|
159 |
|
160 | ### Node:
|
161 |
|
162 | ```js
|
163 | // Full version
|
164 | require('async-rpc-call/full') // or
|
165 | import * as RPC from 'async-rpc-call/full'
|
166 |
|
167 | // Base version
|
168 | require('async-rpc-call/base') // or
|
169 | import * as RPC from 'async-rpc-call/base'
|
170 | ```
|
171 |
|
172 | ## Builtin channels
|
173 |
|
174 | They're not part of the core library but provided as utils to increase usability.
|
175 |
|
176 | ### (Node) WebSocket
|
177 |
|
178 | | | Server | Client |
|
179 | | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
|
180 | | Entry point | `async-call-rpc/utils/node/websocket.server.js`<br />[(Source code)](https://github.com/Jack-Works/async-call-rpc/blob/master/utils-src/node/websocket.server.ts) | TBD |
|
181 | | Entry point type | CommonJS | CommonJS |
|
182 | | Dependencies | [ws](https://npmjs.com/ws) | [ws](https://npmjs.com/ws) |
|
183 | | Example | [./examples/node.websocket.server.js](https://github.com/Jack-Works/async-call-rpc/blob/master/examples/node.websocket.server.js) | TBD |
|
184 |
|
185 | ### (Deno) WebSocket
|
186 |
|
187 | | | Server | Client |
|
188 | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- |
|
189 | | Entry point | `https://cdn.jsdelivr.net/npm/async-call-rpc@latest/utils/deno/websocket.server.ts`<br />[(Source code)](https://github.com/Jack-Works/async-call-rpc/blob/master/utils/deno/websocket.server.ts) | TBD |
|
190 | | Entry point type | ES Module | ES Module |
|
191 | | Dependencies | Deno std | Deno std |
|
192 | | Example | [./examples/deno.websocket.server.ts](https://github.com/Jack-Works/async-call-rpc/blob/master/examples/deno.websocket.server.ts) | TBD |
|
193 |
|
194 | ### (Web) WebSocket
|
195 |
|
196 | | | Client |
|
197 | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
198 | | Entry point | `https://cdn.jsdelivr.net/npm/async-call-rpc@latest/utils/web/websocket.client.js`<br />[(Source code)](https://github.com/Jack-Works/async-call-rpc/blob/master/utils-src/web/websocket.client.ts) |
|
199 | | Entry point type | ES Module |
|
200 | | Dependencies | Nothing |
|
201 | | Example | [./examples/browser.websocket.client.js](https://github.com/Jack-Works/async-call-rpc/blob/master/examples/browser.websocket.client.js) |
|
202 |
|
203 | ### (Web) [BroadcastChannel](https://mdn.io/BroadcastChannel)
|
204 |
|
205 | > ⚠️ Broadcast Channel is not supported by Safari yet ⚠️
|
206 |
|
207 | | | Server & Client |
|
208 | | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
209 | | Entry point | `https://cdn.jsdelivr.net/npm/async-call-rpc@latest/utils/web/broadcast.channel.js`<br />[(Source code)](https://github.com/Jack-Works/async-call-rpc/blob/master/utils-src/web/broadcast.channel.ts) |
|
210 | | Entry point type | ES Module |
|
211 | | Dependencies | Nothing |
|
212 | | Example | TBD |
|
213 |
|
214 | ### (Web) [Worker](https://mdn.io/Worker)
|
215 |
|
216 | > ⚠️ Import a ES Module in a Web Worker is only supported by Chrome yet! ⚠️
|
217 |
|
218 | | | Host & Worker |
|
219 | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
220 | | Entry point | `https://cdn.jsdelivr.net/npm/async-call-rpc@latest/utils/web/worker.js`<br />[(Source code)](https://github.com/Jack-Works/async-call-rpc/blob/master/utils-src/web/worker.ts) |
|
221 | | Entry point type | ES Module |
|
222 | | Dependencies | Nothing |
|
223 | | Example | Main frame: [./examples/browser.worker-main.js](https://github.com/Jack-Works/async-call-rpc/blob/master/examples/browser.worker-main.js) <br /> Worker: [./examples/browser.worker-worker.js](https://github.com/Jack-Works/async-call-rpc/blob/master/examples/browser.worker-worker.js) |
|
224 |
|
225 | Main frame: `new WorkerChannel(new Worker(...))`
|
226 |
|
227 | Worker: `new WorkerChannel()`
|
228 |
|
229 | ## Builtin serializers
|
230 |
|
231 | ### (Web, Deno and Node) BSON
|
232 |
|
233 | | | Server |
|
234 | | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
235 | | Entry point Node | `async-call-rpc/utils/node/bson.js`<br />[(Source code)](https://github.com/Jack-Works/async-call-rpc/blob/master/utils-src/node/bson.ts) |
|
236 | | Entry point Browser/Deno | `https://cdn.jsdelivr.net/npm/async-call-rpc@latest/utils/web/bson.js`<br />[(Source code)](https://github.com/Jack-Works/async-call-rpc/blob/master/utils-src/web/bson.ts) |
|
237 | | Dependencies | [bson](https://npmjs.com/bson) |
|
238 |
|
239 | ### (Web, Deno and Node) Msgpack
|
240 |
|
241 | | | Server |
|
242 | | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
243 | | Entry point Node | `async-call-rpc/utils/node/msgpack.js`<br />[(Source code)](https://github.com/Jack-Works/async-call-rpc/blob/master/utils-src/node/msgpack.ts) |
|
244 | | Entry point Browser/Deno | `https://cdn.jsdelivr.net/npm/async-call-rpc@latest/utils/web/msgpack.js`<br />[(Source code)](https://github.com/Jack-Works/async-call-rpc/blob/master/utils-src/web/msgpack.ts) |
|
245 | | Dependencies | [@msgpack/msgpack](https://npmjs.com/@msgpack/msgpack) |
|
246 | | Example (Node) | [./examples/node.websocket.server.js](https://github.com/Jack-Works/async-call-rpc/blob/master/examples/node.websocket.server.js) |
|
247 | | Example (Deno) | [./examples/deno.websocket.server.ts](https://github.com/Jack-Works/async-call-rpc/blob/master/examples/deno.websocket.server.ts) |
|
248 | | Example (Web) | [./examples/browser.websocket.client.js](https://github.com/Jack-Works/async-call-rpc/blob/master/examples/browser.websocket.client.js) |
|
249 |
|
250 | ## Utils available if both server and client are created by this library
|
251 |
|
252 | AsyncCall has some non-standard extensions to the JSON RPC specification that can help the library easier to use. Those features aren't enabled by default.
|
253 |
|
254 | - Send call stack of Error response or send call stack of caller's request. See [remoteStack on Request object](#remotestack-on-request-object)
|
255 | - Try to keep the "undefined" result when using JSONSerialization. See ["undef" on response object](#undef-on-response-object)
|
256 |
|
257 | ## Implemented JSON RPC internal methods
|
258 |
|
259 | These four methods are used to implement `AsyncGeneratorCall` support.
|
260 |
|
261 | ```ts
|
262 | interface JSONRPC_Internal_Methods {
|
263 | // These 4 methods represent the Async Iterator protocol in ECMAScript
|
264 | // this method starts an async iterator, return the id
|
265 | 'rpc.async-iterator.start'(method: string, params: unknown[]): Promise<string>
|
266 | // this method executes `next` method on the previous iterator started by `rpc.async-iterator.start`
|
267 | 'rpc.async-iterator.next'(id: string, value: unknown): Promise<IteratorResult<unknown>>
|
268 | // this method executes `return` method on the previous iterator started by `rpc.async-iterator.start`
|
269 | 'rpc.async-iterator.return'(id: string, value: unknown): Promise<IteratorResult<unknown>>
|
270 | // this method executes `throw` method on the previous iterator started by `rpc.async-iterator.start`
|
271 | 'rpc.async-iterator.throw'(id: string, value: unknown): Promise<IteratorResult<unknown>>
|
272 | }
|
273 | ```
|
274 |
|
275 | ## Non-standard extension to JSON RPC specification
|
276 |
|
277 | ### remoteStack on Request object
|
278 |
|
279 | This library can send the client the call stack to the server to make the logger better.
|
280 |
|
281 | Controlled by [`option.log.sendLocalStack`](https://jack-works.github.io/async-call-rpc/async-call-rpc.asynccallloglevel.sendlocalstack.html). Default to `false`.
|
282 |
|
283 | ```ts
|
284 | interface JSONRPC_Request_object {
|
285 | // This property include the caller's stack.
|
286 | remoteStack?: string
|
287 | }
|
288 | ```
|
289 |
|
290 | ### "undef" on Response object
|
291 |
|
292 | This is a non-standard property appears when using JSONSerialization due to JSON doesn't support `undefined`. It's a hint to the client, that the result is `undefined`.
|
293 |
|
294 | This behavior is controlled by the 3rd parameter of [JSONSerialization(replacerAndReceiver?, space?, undefinedKeepingBehavior?: false | "keep" | "null" = "null")](https://jack-works.github.io/async-call-rpc/async-call-rpc.jsonserialization.html). Default to `"null"`. To turn on this feature to "keep" undefined values, change the 3rd option to "keep".
|
295 |
|
296 | ```ts
|
297 | interface JSONRPC_Response_object {
|
298 | // This property is a hint.
|
299 | // If the client is run in JavaScript, it should treat "result: null" as "result: undefined"
|
300 | undef?: boolean
|
301 | }
|
302 | ```
|
303 |
|
304 | ### The implementation-defined Error data
|
305 |
|
306 | In the JSON RPC specification, this is implementation-defined. This is controlled by the option [`options.mapError`](https://jack-works.github.io/async-call-rpc/async-call-rpc.errormapfunction.html)
|
307 |
|
308 | This library will try to "Recover" the Error object if there is enough information from another side.
|
309 |
|
310 | ```ts
|
311 | interface JSONRPC_Error_object {
|
312 | // This property will help client to build a better Error object.
|
313 | data?: {
|
314 | stack?: string
|
315 | // Supported value for "type" field (Defined in ECMAScript specification):
|
316 | type?:
|
317 | | string
|
318 | | 'Error'
|
319 | | 'EvalError'
|
320 | | 'RangeError'
|
321 | | 'ReferenceError'
|
322 | | 'SyntaxError'
|
323 | | 'TypeError'
|
324 | | 'URIError'
|
325 | // Defined in HTML specification, only supported in Web
|
326 | | 'DOMException'
|
327 | }
|
328 | }
|
329 | ```
|