| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Call methods on any object over RPC with minimal fuss.
Create a "mirror" of an object on the server in the client. You can call any methods on the server object by calling the same method name on the client object. You can also subscribe to events on the client as if you were subscribing to events on the original API. Synchronous methods on the server object become asynchronous methods on the client-side. Properties on the server object become asynchronous getter methods on the client, e.g. for a server object { foo: 'bar' } the property foo can be read on the client via await clientApi.foo().
Unlike other RPC libraries, this does not require any boilerplate to define methods that are available over RPC. All methods and properties on the server object are "reflected" in client API automatically. Any method called on the client object will return a Promise, but methods that are not defined on the server will throw with a ReferenceError.
Most RPC libraries I could find require a lot of boilerplate to define the methods that are available over RPC. I wanted an easy way for an API on the server to be used from a client in exactly the same way as it is on the server, without needing to setup any RPC methods. Under-the-hood this uses a Proxy object.
npm install rpc-reflectorimport { createClient, createServer } from 'rpc-reflector'
const myApi = {
syncMethod: () => 'result1',
asyncMethod: () =>
new Promise((resolve) => {
setTimeout(() => resolve('result2'), 200)
}),
}
const { port1: serverPort, port2: clientPort } = new MessageChannel()
const server = createServer(myApi, serverPort)
const myApiOnClient =
/** @type {import('rpc-reflector').ClientApi<typeof myApi>} */ createClient(
clientPort,
)
;(async () => {
const result1 = await myApiOnClient.syncMethod()
const result2 = await myApiOnClient.asyncMethod()
console.log(result1) // 'result1'
console.log(result2) // 'result2'
// Tear down so the MessageChannel ports stop keeping the process alive.
createClient.close(myApiOnClient)
server.close()
serverPort.close()
clientPort.close()
})()api can be any object with any properties, methods and events that you want reflected in the client API.
channel can be a browser MessagePort, a Node Worker MessagePort or a MessagePort-like object that defines a postMessage() method and addEventListener('message', ...) / removeEventListener('message', ...) methods. The listener is called with a MessageEvent-like object, i.e. an object with the message on its data property.
If channel is a MessagePort you will need to manually call port.start() to start sending messages queued in the port.
options: an optional object with the following properties:
close() is used to remove event listeners from the channel. It will not close or destroy the MessagePort used as the channel.
channel: see above for createServer()
options: an optional object with the following properties:
Returns clientApi which can be called with any method on the api passed to createServer(). Events on api can be subscribed to via clientApi.on(eventName, handler) on the client. Properties/fields on the server api can be access by calling a method with the same name on the client API, e.g. to access the property api.myProp, on the client call await clientApi.myProp().
When using Typescript, you can pass the type of the server API as a generic e.g.
const clientApi = createClient<ServerApi>(channel)The returned clientApi will be correctly typed, with synchronous functions converted to synchronous.
The static method close() will remove all event listeners from the channel used to create the client. It will not close or destroy the MessagePort used as the channel.
Rejects every in-flight method call with error and returns the number of calls rejected. Use this when the transport to the server has dropped (e.g. the process hosting the server was killed) and pending calls can never be answered — without it they would hang until options.timeout. Unlike close(), the client remains fully usable afterwards: new calls can be made and event listeners stay registered. A response arriving later for a rejected call is ignored.
Note that a rejected call may still have executed on the server if the request was delivered before the transport dropped — whether it is safe to retry is the caller's judgement (reads generally are; mutations need care).
No-op returning 0 if nothing is pending or the client is closed.
Re-sends a subscription message to the server for every event — including events on nested sub-objects — that currently has at least one listener, and returns the number of subscription messages sent. Use this after the server has restarted: a restarted server has lost its subscription state, so it will not emit events until the client re-subscribes. Safe to call repeatedly — the server ignores duplicate subscriptions, so events are not double-delivered.
Only call this once the transport to the restarted server is connected again. Subscription messages written into a down transport are lost, and on some transports each write triggers a reconnect attempt, which can keep the transport busy while the server is still down.
No-op returning 0 if the client is closed.
The client can reject a call with one of the following error classes. Each carries a stable .code property so consumers can identify it without matching against the error message. Both are exported from the package and can also be checked with instanceof.
| Class | .code | Thrown when |
|---|---|---|
| ChannelClosedError | RPC_CHANNEL_CLOSED | A call is in flight when the client is closed, or a method is called after the client was closed. |
| TimeoutError | RPC_TIMEOUT | The server does not respond within options.timeout. |
import { createClient, ChannelClosedError } from 'rpc-reflector'
try {
await clientApi.someMethod()
} catch (err) {
if (err instanceof ChannelClosedError) {
// or: if (err.code === 'RPC_CHANNEL_CLOSED')
}
}PRs accepted.
Small note: If editing the README, please conform to the standard-readme specification.
MIT © 2020 Gregor MacLennan
| Back | FazBrowse Home | New Git URL |