@nookuio/rpc
v1.0.5
Published
A lightweight, type-safe RPC (Remote Procedure Call) library for bidirectional communication between processes. Built with TypeScript and designed for use in Electron applications, web workers, iframes, or any scenario requiring cross-context communicatio
Downloads
238
Readme
@nookuio/rpc
A lightweight, type-safe RPC (Remote Procedure Call) library for bidirectional communication between processes. Built with TypeScript and designed for use in Electron applications, web workers, iframes, or any scenario requiring cross-context communication.
Features
- Type-safe: Full TypeScript support with generic types for remote and local contexts
- Bidirectional: Both sides can expose methods and emit/listen to events
- Promise-based: All remote calls return promises automatically
- Event system: Built-in event emitter for real-time communication
- Flexible transport: Works with any message-passing mechanism (IPC, postMessage, etc.)
- Performance optimized: Proxy caching to minimize overhead
- Configurable: Custom serialization, timeouts, and error handling
Installation
pnpm add @nookuio/rpcBasic Usage
Creating a Client
import { createClient } from '@nookuio/rpc';
// Define your local context (methods available to remote)
const localContext = {
greet: (name: string) => `Hello, ${name}!`,
calculate: (a: number, b: number) => a + b
};
// Define your remote context type (methods available from remote)
interface RemoteContext {
getData: () => Promise<string>;
saveData: (data: string) => Promise<void>;
}
// Create the client
const client = createClient<RemoteContext, typeof localContext, {}, {}>(localContext, {
invoke: async (request) => {
// Send request to remote
return await ipcRenderer.invoke('rpc-request', request);
},
handle: (handler) => {
// Handle incoming requests from remote
ipcRenderer.on('rpc-request', async (event, request) => {
const response = await handler(request);
event.sender.send('rpc-response', response);
});
},
emit: (event) => {
// Emit events to remote
ipcRenderer.send('rpc-event', event);
},
handleEvent: (handler) => {
// Handle events from remote
ipcRenderer.on('rpc-event', (_, event) => handler(event));
}
});
// Use remote methods
const data = await client.getData();
await client.saveData('new data');
// Access local context
console.log(client.$context.greet('World'));API Reference
createClient<R, L, RE, LE>(localContext, options)
Creates an RPC client with type-safe remote method calls.
Type Parameters:
R- Remote context type (methods available from remote)L- Local context type (methods available to remote)RE- Remote events type (events emitted by remote)LE- Local events type (events emitted locally)
Parameters:
localContext- Object containing methods exposed to the remote processoptions- Configuration object
Options:
interface ClientOptions {
// Send requests to remote process
invoke: (request: ContextRequest) => Promise<ContextResponse>;
// Handle incoming requests from remote
handle: (handler: (request: ContextRequest) => Promise<ContextResponse>) => void;
// Emit events to remote process
emit: (request: EventRequest) => void;
// Handle events from remote process
handleEvent: (handler: (request: EventRequest) => void) => void;
// Custom serialization (default: JSON.stringify)
serialize?: (data: any) => any;
// Custom deserialization (default: JSON.parse)
deserialize?: (data: any) => any;
// Request timeout in milliseconds (default: 5000)
timeout?: number;
// Cleanup function
destroy?: () => void | Promise<void>;
// Handle timeout errors
onTimeout?: (request: ContextRequest) => void;
}Client Methods
$context
Access the local context object.
client.$context.myMethod();$destroy()
Cleanup the client, remove all listeners, and call the destroy function.
client.$destroy();on(eventName, listener)
Listen to events from the remote process.
client.on('dataUpdated', (data) => {
console.log('Data updated:', data);
});off(eventName, listener)
Remove an event listener.
const listener = (data) => console.log(data);
client.on('dataUpdated', listener);
client.off('dataUpdated', listener);emit(eventName, ...args)
Emit an event to the remote process.
client.emit('userAction', { action: 'click', target: 'button' });removeAllListeners()
Remove all event listeners.
client.removeAllListeners();Examples
With Events
// Define event types
interface RemoteEvents extends RpcEvents {
dataChanged: (data: string) => void;
error: (error: Error) => void;
}
interface LocalEvents extends RpcEvents {
userAction: (action: string) => void;
}
const client = createClient<RemoteContext, LocalContext, RemoteEvents, LocalEvents>(localContext, options);
// Listen to remote events
client.on('dataChanged', (data) => {
console.log('Remote data changed:', data);
});
client.on('error', (error) => {
console.error('Remote error:', error);
});
// Emit local events
client.emit('userAction', 'button-click');Custom Serialization
import superjson from 'superjson';
const client = createClient<RemoteContext, LocalContext, {}, {}>(localContext, {
// ... other options
serialize: (data) => superjson.stringify(data),
deserialize: (data) => superjson.parse(data)
});How It Works
- Proxy-based API: The client uses JavaScript Proxies to intercept property access and method calls
- Request/Response: Method calls are serialized into requests, sent to the remote process, and responses are deserialized
- Event System: Built-in event emitter allows both sides to emit and listen to events
- Caching: Proxies are cached by path to optimize performance
- Type Safety: TypeScript generics ensure type safety across the RPC boundary
Performance
- Proxy caching minimizes object creation overhead
- Configurable timeouts prevent hanging requests
- Efficient serialization with custom serializer support
- No polling - event-driven architecture
