JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 530
  • Score
    100M100P100Q123102F
  • License MIT

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

Package Exports

  • universal-message

Readme

Universal Message

English | δΈ­ζ–‡

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

npm install universal-message
yarn add universal-message
pnpm add universal-message

Quick Start

Basic Usage

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

// 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

// 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

// 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

// 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:

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:

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:

// 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:

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:

// 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:

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:

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

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 for details.