Skip to content

Jack-Works/async-call-rpc

Repository files navigation

Async Call

async-call-rpcis aJSON RPCserver and client written in TypeScript for any ECMAScript environment.

Code coverage GitHub Workflow Status npm@works/json-rpc on jsr ES2015+

Links

CHANGELOG.md|Document of AsyncCall|Document of AsyncGeneratorCall|Playground

Chapters:

Features

  • 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 supports4 JSON RPC internal methods listed belowand async generator exists in the environment)

Cautions

  • 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

Installation

Install via npm

npm i async-call-rpc

yarn add async-call-rpc

pnpm i async-call-rpc

Install viajsr

This package is published as@works/json-rpcon 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

Import from browser

You can accesshttps:// jsdelivr /package/npm/async-call-rpc?path=outto get the latest URL andSRI.

Note: theutils/entrypoint is not published on the jsr.

import{AsyncCall}from'https://cdn.jsdelivr.net/npm/async-call-rpc@latest/out/base.mjs'

UMD

<scriptsrc= "https://cdn.jsdelivr.net/npm/[email protected]/out/base.js"></script>
<script>
const{AsyncCall}=globalThis.AsyncCall
</script>

Other JS environments

Load theout/base.mjs(ES Module) orout/base.js(UMD, CommonJS, or AMD) to your project.

channel

To communicate with the server/client, you need to implement one of the following interfaces:

There are somebuilt-in channelsyou can simplify the usage.

The following document will assume you have defined yourchannel.

encoder

This library does not have any opinionated data transmitting format. You need to implement one of the following interfaces:

Example

Server example

// server.ts
exportfunctionadd(x:number,y:number){
returnx+y
}
exportconstsleep=(ms:number)=>newPromise((resolve)=>setTimeout(resolve,ms))

// init.ts
import{AsyncCall}from'async-call-rpc'
import*asserverfrom'./server.ts'
// create a server
AsyncCall(server,{channel})

Client example

import{AsyncCall}from'async-call-rpc'
importtype*asserverfrom'./server.ts'
constserver=AsyncCall<typeofserver>({},{channel})
server.add(2,40).then(console.log)// 42

Notifications and Batch requests

AsyncCall can sendnotifications.

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'
constserver=notify(AsyncCall<typeofserver>({},{channel}))
server.add(1,2).then(console.log)// undefined

AsyncCall can sendbatch requesttoo.

import{AsyncCall,batch}from'async-call-rpc'
const[server,emit,drop]=batch(AsyncCall<typeofserver>({},{channel}))
consta=server.req1()// pending
constb=server.req2()// pending
constc=server.req3()// pending
emit()// to send all pending requests
// request a, b, c sent

constd=server.req1()// pending
drop()// to drop all pending requests (and reject corresponding Promises)
// d rejected

Package entries

This library has 2 entries.baseandfull.baseis the default entry point. Thefullversion includes theAsyncGeneratorCallbut thebaseversion doesn't.

Browser / Deno

Please check outhttps:// jsdelivr /package/npm/async-call-rpc?path=out

Node:

// 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'

Builtin channels

They're not part of the core library but are provided as utils to increase usability.

(Node) WebSocket

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

(Deno) WebSocket

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

(Web) WebSocket

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
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()

Implemented JSON RPC internal methods

These four methods are used to implementAsyncGeneratorCallsupport.

interfaceJSONRPC_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>>
}

Non-standard extension to JSON RPC specification

remoteStack on the Request object

This library can send the client the call stack to the server to make the logger better.

Controlled byoption.log.sendLocalStack.Default tofalse.

interfaceJSONRPC_Request_object{
// This property includes the caller's stack.
remoteStack?:string
}

"undef" on Response object

This is a non-standard property that appears when using the deprecated JSONSerialization due to JSON doesn't supportundefined.It's a hint to the client, that the result isundefined.

This behavior is controlled by the 3rd parameter ofJSONSerialization(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".

interfaceJSONRPC_Response_object{
// This property is a hint.
// If the client is run in JavaScript, it should treat "result: null" as "result: undefined"
undef?:boolean
}

The implementation-defined Error data

In the JSON RPC specification, this is implementation-defined. This is controlled by the optionoptions.mapError

This library will try to "Recover" the Error object if there is enough information from another side.

interfaceJSONRPC_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'
}
}