Skip to content

zhangyx1998/rpc-magic-proxy

Repository files navigation

RPC Magic Proxy

Push any un-serializable object through an Node RPC channel!

Highlights

  • Translates functions into magic strings which can be proxied back.
  • Proxied function has access to remote thisArg (caller assigned).
  • Proxied function arguments and return values are automatically proxied.
  • Works with circular reference.
  • Retains strict equality of objects and arrays inside a "message".
  • Retains types of Map and Set, also retaining strict equality.
  • Carry side effects on arguments back to caller
  • Serialize 'pure' functions (those without no side effects)

Planned:

  • Convert Symbols, retaining strict equality on both sides (half done)
  • Proxy back Map and Set (and Objects) as AsyncMap, AsyncSet etc.
  • Keep track of object lifecycle across processes

Usage

import { Worker, isMainThread, parentPort, workerData } from "worker_threads";
import RPCContext from "rpc-magic-proxy";

async function main() {
  const ctx = new RPCContext();
  const data = {
    ping() {
      console.log("main: got request ping()");
      return "pong";
    },
    async hello(callback) {
      console.log("main: got request hello()");
      await callback("world");
    },
  };
  // This will serialize data and send it to worker
  const workerData = await ctx.serialize(data);
  ctx.bind(new Worker(new URL(import.meta.url), { workerData }));
}

async function worker() {
  const ctx = new RPCContext().bind(parentPort);
  const { ping, hello } = ctx.deserialize(workerData);
  // Proxy a function call
  console.log("client -> ping():", await ping());
  // Proxy a function call with callback as argument
  await hello((msg) => console.log("client -> hello():", msg));
  // This will unbind listeners and allow worker to exit
  ctx.reset();
}

isMainThread ? main() : worker();

Output:

main: got request ping()
client -> ping(): pong
main: got request hello()
client -> hello(): world

More examples: