@aionbuilders/helios-protocol
v1.1.0
Published
Core protocol implementation for Helios - a lightweight, runtime-agnostic WebSocket messaging protocol with request/response and pub/sub patterns
Maintainers
Readme
@aionbuilders/helios-protocol
Core protocol implementation for Helios - a lightweight, runtime-agnostic WebSocket messaging protocol
Features
- 🚀 Lightweight - Zero runtime dependencies, pure JavaScript
- 🔄 Request/Response Pattern - RPC-style async request/response with timeout support
- 📡 Pub/Sub Events - Topic-based event system with wildcard matching
- 🎯 Type-Safe - Full TypeScript definitions with JSDoc annotations
- ⚡ Runtime Agnostic - Works with Bun, Node.js, Deno, and browsers
- 🔒 Validation - Built-in message validation with detailed error reporting
- 🧪 Well Tested - 245+ test cases covering all core functionality
Installation
# Using npm
npm install @aionbuilders/helios-protocol
# Using bun
bun add @aionbuilders/helios-protocol
# Using yarn
yarn add @aionbuilders/helios-protocolQuick Start
import { Request, Event, Parser, Serializer } from '@aionbuilders/helios-protocol';
// Create a request
const request = Request.outgoing(
{ userId: 123 },
{ method: 'user.get', timeout: 5000 }
);
// Serialize for transport
const serialized = Serializer.serialize(request);
// Parse incoming messages
const message = Parser.parse(serialized);
// Create an event
const event = Event.outgoing(
{ message: 'Hello, World!' },
{ topic: 'chat.room.general', reliable: true }
);API Reference
Core Components
- Messages - Request, Response, Event message types
- MethodManager - RPC method routing with middleware
- EventManager - Pub/sub event system with topic subscriptions
- Parser/Serializer - Message parsing and serialization
- Errors - Structured error hierarchy
- Utils - PatternMatcher and CapturePatternMatcher
Messages
The protocol supports three message types: Request, Response, and Event.
Request
RPC-style request messages with timeout support.
import { Request } from '@aionbuilders/helios-protocol';
// Create outgoing request
const request = Request.outgoing(
{ userId: 123, action: 'update' }, // payload
{
method: 'user.update', // required
timeout: 5000, // optional (ms)
metadata: { trace: 'abc' }, // optional
peer: { service: 'user-service' } // optional routing
}
);
// Access properties
console.log(request.method); // 'user.update'
console.log(request.timeout); // 5000
console.log(request.payload); // { userId: 123, action: 'update' }
console.log(request.id); // auto-generated UUIDMethod format: Alphanumeric characters, dots, dashes, and underscores (e.g., user.get, chat-room.join)
Response
Response to a request message.
import { Response } from '@aionbuilders/helios-protocol';
// Success response
const success = Response.outgoing(
{ user: { id: 123, name: 'Alice' } }, // payload
{
requestId: request.id, // required
status: 200 // required
}
);
// Error response
const error = Response.outgoing(
null,
{
requestId: request.id,
status: 404,
error: 'User not found'
}
);
// Access properties
console.log(success.status); // 200
console.log(success.requestId); // matches request.id
console.log(success.payload); // { user: {...} }
console.log(error.error); // 'User not found'Status codes: Follow HTTP conventions (200, 404, 500, etc.)
Event
Pub/sub style event messages with topic routing.
import { Event } from '@aionbuilders/helios-protocol';
// Create event
const event = Event.outgoing(
{ message: 'Hello!', user: 'Alice' }, // data
{
topic: 'chat.room.general', // required
reliable: true, // optional (default: false)
metadata: { priority: 'high' } // optional
}
);
// Topic matching with wildcards
Event.matchTopic('chat.room.general', 'chat.*'); // true (single level)
Event.matchTopic('chat.room.general', 'chat.**'); // true (multi level)
Event.matchTopic('chat.room.general', 'user.*'); // false
Event.matchTopic('chat.room.general.dm', 'chat.*'); // false
Event.matchTopic('chat.room.general.dm', 'chat.**'); // true
// Access properties
console.log(event.topic); // 'chat.room.general'
console.log(event.reliable); // true
console.log(event.data); // { message: 'Hello!', user: 'Alice' }Topic format: Alphanumeric characters, dots, dashes, underscores, and wildcards (*, **)
*matches exactly one level (e.g.,chat.*matcheschat.roombut notchat.room.general)**matches one or more levels (e.g.,chat.**matcheschat.room.general)
MethodManager
RPC method routing with middleware support and pattern matching.
import { MethodManager, Request } from '@aionbuilders/helios-protocol';
const methods = new MethodManager();
// Register methods
methods.register('user.get', async (context) => {
// Access request data
const userId = context.payload.userId;
// Return response data directly
return { id: userId, name: 'Alice', email: '[email protected]' };
});
methods.register('user.create', async (context) => {
// Return custom status code using context.createResponse
return context.createResponse({ id: 456 }, 201);
});
// Register with options
methods.register('user.update', async (context) => {
return { updated: true };
}, { timeout: 10000 });
// Handle incoming requests
const request = Request.outgoing({ userId: 123 }, { method: 'user.get' });
const response = await methods.handle(request);
console.log(response.status); // 200
console.log(response.data); // { id: 123, name: 'Alice', ... }Middleware
Add cross-cutting concerns like logging, auth, validation:
// Global middleware (runs for all methods)
methods.use('**', async (context, next) => {
console.log(`[${context.method}] Start`);
const result = await next();
console.log(`[${context.method}] Done`);
return result;
});
// Pattern-based middleware (runs for matching methods)
methods.use('user.*', async (context, next) => {
// Auth check for all user.* methods
if (!context.clientId) {
throw new Error('Unauthorized');
}
return await next();
});
// Specific method middleware
methods.use('user.delete', async (context, next) => {
// Audit log for user deletions
console.log('Deleting user:', context.payload.userId);
return await next();
});Pattern matching:
user.get- Exact matchuser.*- Single level wildcard (matchesuser.get,user.create)user.**- Multi-level wildcard (matchesuser.get,user.settings.update)**.admin- Prefix wildcard (matchesuser.admin,system.admin)
Context Data
Pass additional data to handlers (like client ID, auth info):
const response = await methods.handle(request, {
clientId: 'client-123',
userId: 456,
permissions: ['read', 'write']
});
methods.register('post.create', async (context) => {
// Access custom context data
const { clientId, userId, permissions } = context;
if (!permissions.includes('write')) {
return context.createResponse(null, 403);
}
return { postId: 789, author: userId };
});Namespaces
Organize methods into logical groups:
// Create namespace
const userMethods = methods.namespace('user');
// Register methods with automatic prefix
userMethods.register('get', async (context) => {
return { id: 123 };
});
userMethods.register('create', async (context) => {
return { id: 456 };
});
// Accessible as 'user.get' and 'user.create'
const response = await methods.handle(
Request.outgoing({}, { method: 'user.get' })
);EventManager
Pub/sub event system with topic routing and middleware.
import { EventManager, Event } from '@aionbuilders/helios-protocol';
const events = new EventManager();
// Subscribe to events
events.on('user:created', async (context) => {
console.log('New user:', context.data.userId);
});
events.on('user:deleted', async (context) => {
console.log('User deleted:', context.data.userId);
});
// Subscribe with wildcards
events.on('user:*', async (context) => {
// Matches user:created, user:deleted, user:updated, etc.
console.log('User event:', context.topic);
});
events.on('chat:**', async (context) => {
// Matches chat:message, chat:room:join, chat:room:leave, etc.
console.log('Chat event:', context.data);
});
// Dispatch events
const event = Event.outgoing(
{ userId: 123, name: 'Alice' },
{ topic: 'user:created' }
);
await events.handle(event);One-time Listeners
Subscribe to events that auto-remove after first execution:
events.once('system:ready', async (context) => {
console.log('System initialized');
// This listener will be removed after first execution
});Event Middleware
Add cross-cutting logic to event handling:
// Global middleware
events.use('**', async (context, next) => {
console.log(`Event: ${context.topic}`);
await next();
});
// Pattern-based middleware
events.use('user:*', async (context, next) => {
// Log all user events
console.log('User event data:', context.data);
await next();
});
// Specific topic middleware
events.use('chat:message', async (context, next) => {
// Filter profanity for chat messages
context.data.message = filterProfanity(context.data.message);
await next();
});Context Data
Pass additional context when handling events:
await events.handle(event, {
clientId: 'client-123',
server: 'ws-server-1'
});
events.on('user:action', async (context) => {
// Access custom context
console.log('Client:', context.clientId);
console.log('Server:', context.server);
});Event Namespaces
Organize event listeners into logical groups:
// Create namespace
const userEvents = events.namespace('user');
// Register listeners with automatic prefix
userEvents.on('created', async (context) => {
console.log('User created');
});
userEvents.on('deleted', async (context) => {
console.log('User deleted');
});
// Accessible as 'user:created' and 'user:deleted'
await events.handle(
Event.outgoing({}, { topic: 'user:created' })
);Management API
// List all registered topics
const topics = events.topics(); // ['user:created', 'chat:message', ...]
// Get listeners for a topic
const listeners = events.listeners('user:created');
// Check if topic has listeners
if (events.has('user:created')) {
console.log('Has listeners');
}
// Remove specific listener
events.off('user:created', myHandler);
// Remove all listeners for topic
events.offAll('user:created');
// Clear all listeners
events.clear();Parser & Serializer
Serializer
Convert messages to wire format (JSON or binary).
import { Serializer } from '@aionbuilders/helios-protocol';
// Serialize to JSON (default)
const jsonString = Serializer.serialize(message);
// Serialize to binary (Uint8Array)
const binary = Serializer.serialize(message, 'binary');
// Check if message can be serialized
if (Serializer.canSerialize(message)) {
const data = Serializer.serialize(message);
}Parser
Parse incoming data into typed message instances.
import { Parser } from '@aionbuilders/helios-protocol';
// Parse from different formats
const message = Parser.parse(jsonString); // from JSON string
const message = Parser.parse(uint8Array); // from Uint8Array
const message = Parser.parse(arrayBuffer); // from ArrayBuffer
// Returns the correct message type
if (message instanceof Request) {
console.log('Received request:', message.method);
} else if (message instanceof Response) {
console.log('Received response:', message.status);
} else if (message instanceof Event) {
console.log('Received event:', message.topic);
}Errors
The protocol provides a structured error hierarchy.
import {
HeliosError, // Base error class
ValidationError, // Invalid data provided (4xx equivalent)
ProtocolError, // Invalid message format (4xx equivalent)
TimeoutError // Request timeout (5xx equivalent)
} from '@aionbuilders/helios-protocol';
try {
const request = Request.outgoing({}, { /* missing method */ });
} catch (error) {
if (error instanceof ValidationError) {
console.log(error.message); // "Request requires a method"
console.log(error.details); // [{ field: "method", message: "Method is required" }]
}
}
try {
const message = Parser.parse(invalidData);
} catch (error) {
if (error instanceof ProtocolError) {
console.log(error.code); // "INVALID_MESSAGE_TYPE"
console.log(error.message); // "Invalid message type"
}
}Message Properties
All messages share common properties:
// Common to all messages
message.id // Unique identifier (UUID v4)
message.type // 'request' | 'response' | 'event'
message.protocol // Protocol version (e.g., 'helios/1.0.0')
message.timestamp // Unix timestamp in milliseconds
message.direction // 'outgoing' | 'incoming'
message.metadata // Optional metadata object
message.peer // Optional peer routing { id?, service?, metadata? }
// Methods
message.clone() // Create a deep copy
message.validate() // Validate message integrity
message.toJSON() // Convert to plain objectComplete Export List
// Messages
import {
Message,
Request,
Response,
Event
} from '@aionbuilders/helios-protocol';
// Method Management (RPC)
import {
MethodManager,
MethodHandler,
RequestContext,
MethodNamespaceManager
} from '@aionbuilders/helios-protocol';
// Event Management (Pub/Sub)
import {
EventManager,
EventListener,
EventContext,
EventNamespaceManager
} from '@aionbuilders/helios-protocol';
// Parser & Serializer
import {
Parser,
Serializer
} from '@aionbuilders/helios-protocol';
// Errors
import {
HeliosError,
ValidationError,
ProtocolError,
TimeoutError
} from '@aionbuilders/helios-protocol';
// Utils
import {
PatternMatcher,
CapturePatternMatcher
} from '@aionbuilders/helios-protocol';TypeScript Support
Full TypeScript definitions are included:
import type {
MessageType,
MessageHeaders,
RequestHeaders,
ResponseHeaders,
EventHeaders,
MessageOptions,
PeerRouting,
SerializedMessage
} from '@aionbuilders/helios-protocol';
// All classes are fully typed
const request = Request.outgoing<{ userId: number }>(
{ userId: 123 },
{ method: 'user.get' }
);
// Type-safe access
const userId: number = request.payload.userId;Peer Routing
Messages can include peer routing information for multi-peer scenarios:
// Route by peer ID
const request = Request.outgoing(
{ action: 'ping' },
{
method: 'health.check',
peer: { id: 'client-abc-123' }
}
);
// Route by service type
const request = Request.outgoing(
{ userId: 123 },
{
method: 'user.get',
peer: { service: 'user-service' }
}
);
// Route with metadata
const request = Request.outgoing(
{ query: 'data' },
{
method: 'search',
peer: {
service: 'search-service',
metadata: { region: 'eu-west-1', version: '2.0' }
}
}
);Note: At least one of id or service is required when using peer routing.
Validation
The protocol uses a two-phase validation approach:
Outgoing (Creation) - Strict
When creating messages, validation is strict and throws immediately:
// ❌ Throws ValidationError: method is required
Request.outgoing({}, {});
// ❌ Throws ValidationError: invalid method format
Request.outgoing({}, { method: 'invalid method!' });
// ❌ Throws ValidationError: timeout must be positive
Request.outgoing({}, { method: 'test', timeout: -1 });Incoming (Parsing) - Permissive
When parsing received messages, validation is permissive and only throws for critical errors:
// Parses successfully, logs warning for unusual values
const message = Parser.parse(slightlyInvalidData);Manual Validation
You can manually validate a message at any time:
try {
message.validate();
console.log('Message is valid');
} catch (error) {
console.error('Validation failed:', error.details);
}Protocol Version
Current protocol version: helios/1.0.0
The protocol follows semantic versioning:
- Major version changes indicate breaking protocol changes
- Minor version changes add backward-compatible features
- Patch version changes are bug fixes
Messages with incompatible major versions are rejected automatically.
Examples
Full Request/Response Cycle
import { Request, Response, Parser, Serializer } from '@aionbuilders/helios-protocol';
// Client side: create and send request
const request = Request.outgoing(
{ userId: 123 },
{ method: 'user.get', timeout: 5000 }
);
const requestData = Serializer.serialize(request);
// Send requestData over WebSocket...
// Server side: receive and parse request
const receivedRequest = Parser.parse(requestData);
// Process request and create response
const response = Response.outgoing(
{ id: 123, name: 'Alice', email: '[email protected]' },
{ requestId: receivedRequest.id, status: 200 }
);
const responseData = Serializer.serialize(response);
// Send responseData back over WebSocket...
// Client side: receive response
const receivedResponse = Parser.parse(responseData);
console.log(receivedResponse.payload); // { id: 123, name: 'Alice', ... }Pub/Sub with Events
import { Event, Parser, Serializer } from '@aionbuilders/helios-protocol';
// Publisher: create and broadcast event
const event = Event.outgoing(
{ message: 'New user joined!', user: 'Bob' },
{ topic: 'chat.room.lobby', reliable: true }
);
const eventData = Serializer.serialize(event);
// Broadcast eventData to all subscribers...
// Subscriber: receive and handle event
const receivedEvent = Parser.parse(eventData);
if (receivedEvent instanceof Event) {
// Check topic match
if (Event.matchTopic(receivedEvent.topic, 'chat.**')) {
console.log('Chat event:', receivedEvent.data);
}
}Runtime Compatibility
This protocol package is runtime agnostic and works with:
- ✅ Bun (recommended, optimized for Bun runtime)
- ✅ Node.js (v16+)
- ✅ Deno
- ✅ Browsers (modern browsers with ES2020+ support)
Related Packages
- @aionbuilders/helios - Server implementation (Bun)
- @aionbuilders/starling - Client implementation (browser + Bun)
Contributing
Contributions are welcome! Please read DEVELOPMENT.md for development guidelines and architectural decisions.
License
MIT © Killian Di Vincenzo (AION Builders)
Made with ❤️ by Killian Di Vincenzo with AION Builders
