@ch1/rpc
v1.3.1
Published
JavaScript Remote Procedure Call (RPC)
Downloads
7
Maintainers
Readme
CH1 RPC
This is not well maintained
This is not traditional RPC, but it is like it
Installation
yarn add @ch1/rpc
Usage
Convenient usage would be to use another library that consumes this library. Two examples are:
However this library can work over pretty much any stringifiable interface. Maybe binary one day.
Simplest Pure JS Example
Ideally process A/B would be literal separate processes like workers or sockets. However the usage can be "faked" in a single process as is done in [the tests]:(./spec/js-rpc.spec.ts "Testing Source Code")
Process A
const b = rpc.create({ /* config */}, {
sayOnA: (arg) => console.log(`Process B says ${arg}`);
});
b.ready.then(() => b.remote.sayOnB('hello world');
// will call sayOnB on process B
Process B
const a = rpc.create({ /* config */}, {
sayOnB: (arg) => console.log(`Process A says ${arg}`);
});
a.ready.then(() => a.remote.sayOnA('hello world');
// will call sayOnA on process A
How It Works
@ch1/rpc can work across any transport provided that the transport can be
simplified into on
and emit
functions.
What do we mean by on
and emit
functions? Let's imagine for a moment the
Web Worker API
note: @ch1/rpc-worker contains convenience functions that provide WebWorker support out of the box. The examples here are educational and do not need to be implemented
In our first process we might have a Web Worker:
// make a new worker
const myWorker = new Worker('worker.js');
// js-rpc only wants/needs to use the first parameter of emit consequently the
// WebWorker post message can be used out of the box
const emit = myWorker.postMessage.bind(myWorker);
// js-rpc's on message wants/needs data to be its first parameter. Since
// WebWorker.onmessage boxes passed data into an even we need to extract it
const on = myWorker.onmessage(event => event.data);
// make a RPC Object:
const worker = create({ on, emit } /* object to expose on worker process */);
In our second WebWorker process we have a slightly different API:
// WebWorkers use a variable called "self" to register their messages:
// self.postMessage has the same api as in our previous example
const emit = self.postMessage.bind(self);
// self.onmessage also boxes data into events
const on = self.onmessage(event => event.data);
// make a RPC Object:
const parentWindow = create(
{ on, emit } /* object to expose on window process */,
);
API
The create
call kicks everything off:
// RemoteType is a user defined type meant to inform TypeScript
// what the function signatures will be of `RPC.remote`
export function create<RemoteType>(
// The config object is more like the internal state of the system
// There are many config options
// This object will be changing in the future to better separate concerns
// Relevant config options will be documented below
config: RPCConfig,
// The optional "remote" object _in this context_ are the functions "local"
// to the `create` call that you wish to expose on the other side
// of the transport (worker, server, whatever)
remote?: Remote<any>,
// ignore this optional argument for now
remoteDesc?: RemoteDesc,
): RPC<RemoteType>;
The RPC<RemoteType>
interface is the full object you get back from the create
call:
export interface RPC<RemoteType> {
// The config object is more like the internal state of the system
// There are many config options
// This object will be changing in the future to better separate concerns
// Relevant config options will be documented below
config: RPCConfig;
// promises to free up the resources, takes an optional reason that
// propagates through to error messages received by any pending async
// results
destroy: (reason?: string) => Promise<void>;
// registers a callback and calls it in the event the object's `destroy`
// method is called. `onDestroy` returns a de-registration function
onDestroy: (callback: (reason?: string) => any) => () => void;
// this is a promise that resolves if/when the rpc system is ready
// it will time out and fail at a configurable threshold
ready: Promise<void>;
// this is the object that will expose all of the remote functions
// this object can have nested objects with functions
remote: RemoteType;
}
The RPCConfig Object is vast and it will be shrinking soon. The parts that will stay consistent are the mandatory configurations:
export interface RPCConfig {
/*************** Required ***************/
// the `emit` method must be defined by the user of `rpc`
// it is what sends stuff to the worker/socket/whatever
emit: (payload: any): any;
// the `on` method must be defined by the user of `rpc`
// it is what receives stuff from the worker/socket/whatever
on: (callback: (payload: any) => any): () => any;
}