UNPKG

universal-message

Version:

A universal message communication library for JavaScript/TypeScript that works across different environments including Worker threads, WebSocket, and other communication channels

458 lines (346 loc) β€’ 12.8 kB
# Universal Message [English](README.md) | [δΈ­ζ–‡](README.zh.md) A powerful universal message communication library for JavaScript/TypeScript that enables seamless communication across different environments including Worker threads, WebSocket, and other communication channels. ## Features - πŸš€ **Universal**: Works across different communication channels (Worker threads, WebSocket, MessagePort, etc.) - πŸ”Œ **Plugin System**: Extensible plugin architecture for custom message encoding/decoding with built-in support for complex types - 🎯 **Type Safe**: Full TypeScript support with intelligent path completion for deep object access (`obj.prop.array.0.field`) - πŸ“¦ **Zero Dependencies**: Lightweight with no external dependencies - πŸ”„ **Bidirectional**: Support for both client-server and peer-to-peer communication patterns - ⚑ **Promise-based**: Modern async/await API with Promise support and timeout handling - πŸ—οΈ **Remote Classes**: Full remote class instantiation and method invocation support - πŸ“‘ **Shared Objects**: Cross-environment object sharing and synchronization - πŸ›‘οΈ **Error Handling**: Comprehensive error handling with stack trace preservation - 🎨 **Custom Serialization**: Advanced JSON serialization supporting Date, Map, Set, RegExp, Buffer, BigInt, and more ## Installation ```bash npm install universal-message ``` ```bash yarn add universal-message ``` ```bash pnpm add universal-message ``` ## Quick Start ### Basic Usage ```typescript import { MessageServer, MessageClient } from 'universal-message'; // Server side const server = new MessageServer({ on: (callback) => { // Setup message listener (e.g., WebSocket onmessage, Worker onmessage) worker.onmessage = callback; }, send: (data) => { // Setup message sender (e.g., WebSocket send, Worker postMessage) worker.postMessage(data); } }); // Client side const client = new MessageClient({ on: (callback) => { // Setup message listener self.onmessage = callback; }, send: (data) => { // Setup message sender self.postMessage(data); } }); ``` ### Worker Thread Example ```typescript // main.ts import { Worker } from 'worker_threads'; import { MessageServer } from 'universal-message'; const worker = new Worker('./worker.js'); const server = new MessageServer({ on: (callback) => worker.on('message', callback), send: (data) => worker.postMessage(data) }); // Send a message and wait for response const result = await server.send('calculate', { a: 10, b: 20 }); console.log(result); // 30 // Create a remote class instance const calc = await server.newClass('Calculator'); const sum = await calc.call('add', 10, 20); // 30 // worker.js import { parentPort } from 'worker_threads'; import { MessageClient } from 'universal-message'; const client = new MessageClient({ on: (callback) => parentPort?.on('message', callback), send: (data) => parentPort?.postMessage(data) }); // Register a class for remote access class Calculator { add(a, b) { return a + b; } multiply(a, b) { return a * b; } } client.addClass(Calculator); // Listen for messages client.on('calculate', ({ a, b }) => { return a + b; }); ``` ### WebSocket Example ```typescript // Server side import { MessageServer } from 'universal-message'; const ws = new WebSocket('ws://localhost:8080'); const server = new MessageServer({ on: (callback) => { ws.onmessage = (event) => callback(JSON.parse(event.data)); }, send: (data) => { ws.send(JSON.stringify(data)); } }); // Use with type safety const user = await server.send<User>('getUser', { id: 123 }); // Client side (similar setup with WebSocket) ``` ### MessagePort Example ```typescript // Create a MessageChannel const channel = new MessageChannel(); // Port 1 - Server const server = new MessageServer({ on: (callback) => channel.port1.onmessage = callback, send: (data) => channel.port1.postMessage(data) }); // Port 2 - Client const client = new MessageClient({ on: (callback) => channel.port2.onmessage = callback, send: (data) => channel.port2.postMessage(data) }); ``` ## API Reference ### MessageServer The server-side message handler with enhanced remote capabilities. #### Methods - `send<T>(key: string, data?: any, timeout?: number): Promise<T>` - Send a message and wait for response - `on(key: string, callback: Function)` - Listen for messages - `hasOn(key: string): boolean` - Check if a message listener exists - `remove(key: string)` - Remove message listener - `removeAll(key: string)` - Remove listener and notify other side - `emit<T>(key: string, data?: any, callback?: Function): Promise<T>` - Alias for send - `newClass<T>(target: string | Class, constructorArgs?: any[]): Promise<RemoteClass<T>>` - Create remote class instance - `staticClass<T>(target: string | Class): StaticMethods<T>` - Access static class methods - `addShared<T>(key: string, value?: T): SharedObject<T>` - Create or access shared objects ### MessageClient The client-side message handler with class registration capabilities. #### Methods - `send<T>(key: string, data?: any, timeout?: number): Promise<T>` - Send a message and wait for response - `on(key: string, callback: Function)` - Listen for messages - `hasOn(key: string): boolean` - Check if a message listener exists - `remove(key: string)` - Remove message listener - `addClass<T>(className: string, classConstructor: T)` - Register a class for remote access - `addShared<T>(key: string, value?: T): SharedObject<T>` - Create or access shared objects ### MessageShared Base class providing shared object functionality for both server and client. #### Shared Objects ```typescript // Create or access shared objects across environments const sharedCounter = client.shared('counter', 0); // Get value const count = await sharedCounter.value(); // 0 // Set value await sharedCounter.set('', 5); // Call methods on shared objects const sharedArray = client.shared('myArray', [1, 2, 3]); await sharedArray.call('push', 4); // [1, 2, 3, 4] // Delete properties await sharedArray.del('0'); // Remove first element ``` ## Plugin System Universal Message supports a powerful plugin system for extending functionality: ```typescript import { MessagePlugin } from 'universal-message'; // Custom plugin example class LoggingPlugin extends MessagePlugin { name = 'logging-plugin'; async onSend(data) { console.log('Sending:', data); return data; } async onMessage(data) { console.log('Received:', data); return data; } } // Add plugin server.addPlugin(new LoggingPlugin()); // Remove plugin server.removePlugin('logging-plugin'); ``` ### Built-in Plugins - **MessageEcoderPlugin**: Handles encoding/decoding of complex data types including: - **ECoderAbortController**: AbortController serialization with signal propagation - **ECoderClassTransformer**: Automatic class instance serialization using class-transformer ### Custom Encoders Create custom encoders for specific data types: ```typescript import { MessageEcoder } from 'universal-message'; const CustomEncoder: MessageEcoder<MyClass> = { target: (data) => data instanceof MyClass, key: 'MyClass', encode: (data, tool) => { // Custom serialization logic return { customData: data.serialize() }; }, decode: (data, tool) => { // Custom deserialization logic return MyClass.deserialize(data.customData); }, update: (newValue, target, tool) => { // Handle updates if needed target.update(newValue); } }; ``` ## Remote Class Usage Universal Message provides powerful remote class instantiation and method invocation: ```typescript // Define a class with complex functionality class DatabaseManager { private connections = new Map(); constructor(config: DbConfig) { this.config = config; } async connect(database: string) { // Connection logic return `Connected to ${database}`; } async query(sql: string, params: any[]) { // Query logic return { rows: [], count: 0 }; } static getDrivers() { return ['mysql', 'postgres', 'sqlite']; } } // Client side - Register the class client.addClass('DatabaseManager', DatabaseManager); // Server side - Use remote class with full type safety const db = await server.newClass<DatabaseManager>('DatabaseManager', [{ host: 'localhost' }]); // Call instance methods const result = await db.call('connect', 'myapp'); const rows = await db.call('query', 'SELECT * FROM users', []); // Access static methods const drivers = await db.static.call('getDrivers'); // Deep property access with type safety const config = await db.get('config'); const host = await db.get('config.host'); // Type-safe deep access await db.set('config.timeout', 5000); ``` ### Advanced Type Safety With TypeScript, you get intelligent auto-completion for deep object paths: ```typescript class User { profile = { personal: { name: 'John', age: 30, addresses: [ { street: '123 Main St', city: 'NYC' } ] }, settings: { theme: 'dark', notifications: { email: true, push: false } } }; updateProfile(data: any) { /* ... */ } } const user = await server.newClass<User>('User'); // All these paths have full type checking and auto-completion: const name = await user.get('profile.personal.name'); // string const age = await user.get('profile.personal.age'); // number const street = await user.get('profile.personal.addresses.0.street'); // string const theme = await user.get('profile.settings.theme'); // string const emailNotif = await user.get('profile.settings.notifications.email'); // boolean // Method calls with parameter type checking await user.call('updateProfile', { name: 'Jane' }); ``` ## Advanced Features ### Complex Data Type Support Universal Message includes advanced JSON serialization supporting: ```typescript // Dates, Maps, Sets, RegExp, Errors, URLs const complexData = { timestamp: new Date(), userMap: new Map([['user1', { id: 1 }], ['user2', { id: 2 }]]), tags: new Set(['admin', 'user']), pattern: /^[a-z]+$/i, config: new URL('https://api.example.com'), lastError: new Error('Connection failed') }; await server.send('processData', complexData); // All types are automatically serialized and deserialized correctly // BigInt support const bigNumber = BigInt('12345678901234567890'); await server.send('calculateLarge', bigNumber); // Buffer/ArrayBuffer support (Node.js and Web) const buffer = Buffer.from('Hello World'); const arrayBuffer = new ArrayBuffer(16); await server.send('processBytes', { buffer, arrayBuffer }); ``` ### AbortController Support Built-in support for cancellable operations: ```typescript const controller = new AbortController(); // Send operation with abort controller const promise = server.send('longRunningTask', { signal: controller.signal }); // Cancel after 5 seconds setTimeout(() => controller.abort(), 5000); try { const result = await promise; } catch (error) { if (error.name === 'AbortError') { console.log('Operation was cancelled'); } } ``` ### Class-Transformer Integration Automatic class instance serialization: ```typescript import { Transform, Type } from 'class-transformer'; class Address { street: string; city: string; } class Person { name: string; @Type(() => Date) birthDate: Date; @Type(() => Address) address: Address; } // Instances are automatically serialized with decorators preserved const person = new Person(); person.name = 'John'; person.birthDate = new Date(); person.address = new Address(); await server.send('processPerson', person); // Person instance is reconstructed correctly on the other side ``` ### Error Handling with Stack Traces ```typescript try { const result = await server.send('riskyOperation', data, 10000); // 10s timeout } catch (error) { if (error.code === 'TIMEOUT') { console.log('Operation timed out'); } else { console.log('Error:', error.message); console.log('Stack:', error.stack); // Preserved across environments } } ``` ## Contributing Contributions are welcome! Please feel free to submit a Pull Request. ## License MIT License. See [LICENSE](LICENSE) for details.