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
Markdown
# 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;
birthDate: Date;
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.