async-call-rpc
is a JSON RPC server and client written in TypeScript for any ECMAScript environment.
CHANGELOG.md | Document of AsyncCall | Document of AsyncGeneratorCall | Playground
Chapters:
- Installation
channel
, how to communicateencoder
, how to use complex data types- Example
- Notifications and Batch requests
- Package entries
- Builtin
channels
(including WebSocket) - Implemented JSON RPC internal methods
- Non-standard extension to JSON RPC specification
- Zero dependencies!
- Running in any ES6 environment, no requirement on any Web or Node API
- Simple to define a RPC server and simple to use as a RPC client
- Full TypeScript support
- Use a custom encoder to communicate with complex data types
- Support async generator (Require both server and client supports 4 JSON RPC internal methods listed below and async generator exists in the environment)
- This package is shipping ECMAScript 2018 syntax (
async function
). - The async generator support leaks memory on the server. Use it with caution.
- NOT support ES5.
- NOT support JSON RPC 1.0
npm i async-call-rpc
yarn add async-call-rpc
pnpm i async-call-rpc
Install via jsr
This package is published as @works/json-rpc
on the jsr.
If you install it via jsr, you should import this package from "@works/json-rpc"
instead of "async-call-rpc"
deno add @works/json-rpc
npx jsr add @works/json-rpc
yarn dlx jsr add @works/json-rpc
pnpm dlx jsr add @works/json-rpc
bunx jsr add @works/json-rpc
You can access https://www.jsdelivr.com/package/npm/async-call-rpc?path=out to get the latest URL and SRI.
Note: the utils/
entrypoint is not published on the jsr.
import { AsyncCall } from 'https://cdn.jsdelivr.net/npm/async-call-rpc@latest/out/base.mjs'
<script src="https://cdn.jsdelivr.net/npm/[email protected]/out/base.js"></script>
<script>
const { AsyncCall } = globalThis.AsyncCall
</script>
Load the out/base.mjs
(ES Module) or out/base.js
(UMD, CommonJS, or AMD) to your project.
To communicate with the server/client, you need to implement one of the following interfaces:
- CallbackBasedChannel, generally used in the server. Example: WebSocket on Node.
- EventBasedChannel, generally used in the client. Example: WebSocket on Web
There are some built-in channels you can simplify the usage.
The following document will assume you have defined your channel
.
This library does not have any opinionated data transmitting format. You need to implement one of the following interfaces:
// server.ts
export function add(x: number, y: number) {
return x + y
}
export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
// init.ts
import { AsyncCall } from 'async-call-rpc'
import * as server from './server.ts'
// create a server
AsyncCall(server, { channel })
import { AsyncCall } from 'async-call-rpc'
import type * as server from './server.ts'
const server = AsyncCall<typeof server>({}, { channel })
server.add(2, 40).then(console.log) // 42
AsyncCall can send notifications.
Using notifications means results or remote errors are never sent back. Local errors will not be omitted, e.g. encoder errors or network errors.
import { AsyncCall, notify } from 'async-call-rpc'
const server = notify(AsyncCall<typeof server>({}, { channel }))
server.add(1, 2).then(console.log) // undefined
AsyncCall can send batch request too.
import { AsyncCall, batch } from 'async-call-rpc'
const [server, emit, drop] = batch(AsyncCall<typeof server>({}, { channel }))
const a = server.req1() // pending
const b = server.req2() // pending
const c = server.req3() // pending
emit() // to send all pending requests
// request a, b, c sent
const d = server.req1() // pending
drop() // to drop all pending requests (and reject corresponding Promises)
// d rejected
This library has 2 entries. base
and full
. base
is the default entry point. The full
version includes the AsyncGeneratorCall
but the base
version doesn't.
Please check out https://www.jsdelivr.com/package/npm/async-call-rpc?path=out
// Full version
require('async-rpc-call/full') // or
import { } from 'async-rpc-call/full'
// Base version
require('async-rpc-call') // or
import { } from 'async-rpc-call'
They're not part of the core library but are provided as utils to increase usability.
Server | Client | |
---|---|---|
Entry point | async-call-rpc/utils/node/websocket.server.js (Source code) |
TBD |
Entry point type | CommonJS | CommonJS |
Dependencies | ws | ws |
Example | ./examples/node.websocket.server.js | TBD |
Server | Client | |
---|---|---|
Entry point | https://cdn.jsdelivr.net/npm/async-call-rpc@latest/utils/deno/websocket.server.ts (Source code) |
TBD |
Entry point type | ES Module | ES Module |
Dependencies | Deno std | Deno std |
Example | ./examples/deno.websocket.server.ts | TBD |
Client | |
---|---|
Entry point | https://cdn.jsdelivr.net/npm/async-call-rpc@latest/utils/web/websocket.client.js (Source code) |
Entry point type | ES Module |
Dependencies | Nothing |
Example | ./examples/browser.websocket.client.js |
(Web) BroadcastChannel
Server & Client | |
---|---|
Entry point | https://cdn.jsdelivr.net/npm/async-call-rpc@latest/utils/web/broadcast.channel.js (Source code) |
Entry point type | ES Module |
Dependencies | Nothing |
Example | TBD |
(Web) Worker
Host & Worker | |
---|---|
Entry point | https://cdn.jsdelivr.net/npm/async-call-rpc@latest/utils/web/worker.js (Source code) |
Entry point type | ES Module |
Dependencies | Nothing |
Example | Main frame: ./examples/browser.worker-main.js Worker: ./examples/browser.worker-worker.js |
Main thread: new WorkerChannel(new Worker(...))
Worker: new WorkerChannel()
These four methods are used to implement AsyncGeneratorCall
support.
interface JSONRPC_Internal_Methods {
// These 4 methods represent the Async Iterator protocol in ECMAScript
//This method starts an async iterator, returns the id
'rpc.async-iterator.start'(method: string, params: unknown[]): Promise<string>
//This method executes the `next` method on the previous iterator started by `rpc.async-iterator.start`
'rpc.async-iterator.next'(id: string, value: unknown): Promise<IteratorResult<unknown>>
//This method executes the `return` method on the previous iterator started by `rpc.async-iterator.start`
'rpc.async-iterator.return'(id: string, value: unknown): Promise<IteratorResult<unknown>>
//This method executes the `throw` method on the previous iterator started by `rpc.async-iterator.start`
'rpc.async-iterator.throw'(id: string, value: unknown): Promise<IteratorResult<unknown>>
}
This library can send the client the call stack to the server to make the logger better.
Controlled by option.log.sendLocalStack
. Default to false
.
interface JSONRPC_Request_object {
// This property includes the caller's stack.
remoteStack?: string
}
This is a non-standard property that appears when using the deprecated JSONSerialization due to JSON doesn't support undefined
. It's a hint to the client, that the result is undefined
.
This behavior is controlled by the 3rd parameter of JSONSerialization(replacerAndReceiver?, space?, undefinedKeepingBehavior?: false | "keep" | "null" = "null"). Default to "null"
. To turn on this feature to "keep" undefined values, change the 3rd option to "keep".
interface JSONRPC_Response_object {
// This property is a hint.
// If the client is run in JavaScript, it should treat "result: null" as "result: undefined"
undef?: boolean
}
In the JSON RPC specification, this is implementation-defined. This is controlled by the option options.mapError
This library will try to "Recover" the Error object if there is enough information from another side.
interface JSONRPC_Error_object {
// This property will help the client to build a better Error object.
data?: {
stack?: string
// Supported value for "type" field (Defined in ECMAScript specification):
type?:
| string
| 'Error'
| 'EvalError'
| 'RangeError'
| 'ReferenceError'
| 'SyntaxError'
| 'TypeError'
| 'URIError'
// Defined in HTML specification, only supported in Web
| 'DOMException'
}
}