UNPKG

20.3 kBMarkdownView Raw
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
14Chapters:
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
46The `channel` is the only thing you need to learn to use this library.
47
48This library is designed to not rely on any specific platform. Only require things defined in the ECMAScript specification.
49In the ES spec, there is no I/O related API so it's impossible to communicate with the outer world.
50
51You 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
56There are some [built-in channel](#builtin-channels) you can simplify the usage.
57
58The following document will assume you have defined your `channel`.
59
60## Example
61
62### Server example
63
64```ts
65// server.ts
66export function add(x: number, y: number) {
67 return x + y
68}
69export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
70
71// init.ts
72import { AsyncCall } from 'async-call-rpc'
73import * as server from './server'
74// create a server
75AsyncCall(server, { channel })
76```
77
78### Client example
79
80```ts
81import { AsyncCall } from 'async-call-rpc'
82const server = AsyncCall<typeof server>({}, { channel })
83server.add(2, 40).then(console.log) // 42
84```
85
86### Isomorphic API
87
88You can notice from the above example,
89define a server is using `AsyncCall(serverImplementation, opt)`,
90define a client is using `AsyncCall<typeof serverImplementation>({}, opt)`.
91So it is possible to define a server and a client at the same time.
92
93## Notifications and Batch requests
94
95AsyncCall can send [Notifications](https://www.jsonrpc.org/specification#notification).
96
97Using 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
100import { AsyncCall, notify } from 'async-call-rpc'
101const server = notify(AsyncCall<typeof server>({}, { channel }))
102server.online().then(console.log) // undefined
103```
104
105AsyncCall can send [batch request](https://www.jsonrpc.org/specification#batch) too.
106
107```ts
108import { AsyncCall, batch } from 'async-call-rpc'
109const [server, emit, drop] = batch(AsyncCall<typeof server>({}, { channel }))
110const a = server.req1() // pending
111const b = server.req2() // pending
112const c = server.req3() // pending
113emit() // to send all pending requests
114// request a, b, c sent
115
116const d = server.req1() // pending
117drop() // 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
131You 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
136import { 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
150Load the `out/base.mjs` (ES Module) or `out/base.js` (UMD, CommonJS or AMD) to your project.
151
152## Entries
153
154This 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
158Please check out https://www.jsdelivr.com/package/npm/async-call-rpc?path=out
159
160### Node:
161
162```js
163// Full version
164require('async-rpc-call/full') // or
165import * as RPC from 'async-rpc-call/full'
166
167// Base version
168require('async-rpc-call/base') // or
169import * as RPC from 'async-rpc-call/base'
170```
171
172## Builtin channels
173
174They'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
225Main frame: `new WorkerChannel(new Worker(...))`
226
227Worker: `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
252AsyncCall 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
259These four methods are used to implement `AsyncGeneratorCall` support.
260
261```ts
262interface 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
279This library can send the client the call stack to the server to make the logger better.
280
281Controlled by [`option.log.sendLocalStack`](https://jack-works.github.io/async-call-rpc/async-call-rpc.asynccallloglevel.sendlocalstack.html). Default to `false`.
282
283```ts
284interface JSONRPC_Request_object {
285 // This property include the caller's stack.
286 remoteStack?: string
287}
288```
289
290### "undef" on Response object
291
292This 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
294This 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
297interface 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
306In 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
308This library will try to "Recover" the Error object if there is enough information from another side.
309
310```ts
311interface 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```