
Security News
AI Agent Lands PRs in Major OSS Projects, Targets Maintainers via Cold Outreach
An AI agent is merging PRs into major OSS projects and cold-emailing maintainers to drum up more work.
@ggoodman/rpc
Advanced tools
A fully-typed, transport-agnostic, bi-directional RPC framework that also supports passing functions as parameters and returning functions as results.
A fully-typed, transport-agnostic, bi-directional RPC framework that also supports passing functions as parameters and returning functions as results.
In JavaScript, passing around first-class functions is a basic part of writing idiomatic code. Unfortunately, as soon as a process (or context) boundary is introduced between the caller and callee, this breaks down. JavaScript Functions are not Transferable objects. This library aims to help with situations where two processes or contexts need to invoke functions between themselves and may even want to pass around callback functions.
Here is an example of one side of an RPC use-case. Imagine we are writing an in-browser editor (something like the Velcro Playground) where we have some expensive logic we want to delegate to a Worker. Here, let's imagine we want to ask the worker to acquire all typings files for a set of dependencies. For each dependency that is found, we provide a callback that should be falled with a { path, content } object.
Note: It is interesting to point out that even though this logic is crossing a process boundary, we are freely passing callback functions in the arguments. The local peer passes a callback and the remote peer receives a function while the library handles piping the two together.
workerClient.ts:
import { connect, Transport } from '@ggoodman/rpc';
import { WorkerApi } from './workerImpl';
// Let's imagine we're using https://github.com/GoogleChromeLabs/worker-plugin in a Webpack setup
const worker = new Worker('./workerImpl', { type: 'module' });
// Expose our local api and connect to the peer in the worker over a DOM worker transport. We
// indicate the shape of the API the peer exposes as a template type to the connect function.
// This gives us full intellisense on calls to `Peer#invoke()` later.
const workerPeer = connect<WorkerApi>(Transport.fromDomWorker(worker));
/**
* Acquire types for a set of dependencies
*
* @param dependencies The mapping of dependency modules names to semver ranges
* @param onDependency A callback function that will be fired for each discovered typing file
*/
export function acquireTypes(
dependencies: Record<string, string>,
onDependency: (file: { path: string; content: string }) => void
) {
// We're going to delegate this call to the worker. Note that we're passing in the `onDependency`
// function without any gymnastics. The RPC library makes this sort of workflow frictionless.
return workerPeer.invoke('acquireTypes', dependencies, onDependency);
}
workerImpl.ts:
import { connect, Transport } from '@ggoodman/rpc';
const workerApi = {
acquireTypes: async (
dependencies: Record<string, string>,
onDependency: (file: { path: string; content: string }) => void
) => {
// Actually acquire types here. Let's pretend that this logic makes sense even though it is
// total nonsense.
for (const dependency in dependencies) {
// Here is where reality conflicts a bit with developer ergonomics. Since `onDependency` is
// a function whose implementation lives on another Peer, we *should* await its invocation
// if we want to enforce strict ordering. Otherwise, the Promise returned from the Peer's
// call to `acquireTypes` will likely settle before all calls to `onDependency` have
// completed. If fire-and-forget behaviour is acceptable, you can skip the `await` here.
await onDependency({
path: `/node_modules/${dependency}/index.d.ts`,
content: `declare module "${dependency}" {}`,
});
}
},
};
npm install @ggoodman/rpc
Creating RPC instances uses a Builder Pattern.
expose(localApi): Builder<typeof localApi>Exposes a local API where localApi is a map of function names to function implementations. Returns a Builder instance.
You can then obtain an API<RemoteApiType, typeof localApi> instance by connecting to a Transport:
const api = expose(localApi).connect<RemoteApiType>(transport);
connect<RemoteApiType>(transport): API<RemoteApiType>Connects to a remote API over the given transport and returns an API instance.
APIRepresents an instance of a connected RPC API.
invoke(methodName, ...args): Promise<ReturnType>Invoke methodName on the other side of the transport with args ...args. This will always return a Promise.
dispose()Frees up resources, disposes the transport and any installed Codecs.
TransportExposes factory functions for constructing Transports for various use-cases. Transport is also an interface describing the required API for compatible transports that can be used with this library.
/**
* The interface for rpc-compatible transports.
*/
export interface Transport {
/**
* Dispose of this transport and free any allocated resources.
*/
dispose(): void;
/**
* Register a RPC message listener with the transport
*
* @param handler A handler function to be called with each RPC message received from a peer
*/
onMessage(handler: (msg: unknown[]) => void): { dispose(): void };
/**
* Send an RPC message to the peer over this transport
*
* @param msg The array-encoded message that should be sent to the peer over the transport
* @param transfer An optional array of objects that should be marked as transferrable when the transport supports it
*/
sendMessage(msg: unknown[], transfer?: unknown[]): void;
}
fromNodeMessagePort(port): TransportConstruct a Transport from a Node-compatible MessagePort.
fromNodeDomPort(port): TransportConstruct a Transport from a browser-compatible MessagePort.
fromNodeDomWorker(worker): TransportConstruct a Transport from a browser-compatible Worker.
FAQs
A fully-typed, transport-agnostic, bi-directional RPC framework that also supports passing functions as parameters and returning functions as results.
We found that @ggoodman/rpc demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
An AI agent is merging PRs into major OSS projects and cold-emailing maintainers to drum up more work.

Research
/Security News
Chrome extension CL Suite by @CLMasters neutralizes 2FA for Facebook and Meta Business accounts while exfiltrating Business Manager contact and analytics data.

Security News
After Matplotlib rejected an AI-written PR, the agent fired back with a blog post, igniting debate over AI contributions and maintainer burden.