Overview
The @proj-airi/server-sdk package provides a client SDK for connecting to the AIRI server runtime. It handles WebSocket communication, authentication, heartbeat management, and event routing.
Installation
npm install @proj-airi/server-sdk
Quick Start
Connect to a server runtime:
import { Client } from '@proj-airi/server-sdk'
const client = new Client ({
url: 'ws://localhost:6121/ws' ,
name: 'my-module' ,
token: 'your-auth-token'
})
// Listen for events
client . onEvent ( 'registry:modules:sync' , ( event ) => {
console . log ( 'Modules:' , event . data . modules )
})
// Send events
client . send ({
type: 'custom:event' ,
data: { message: 'Hello' }
})
Client Class
Constructor
Create a new client instance.
const client = new Client < CustomData >( options )
options.url
string
default: "ws://localhost:6121/ws"
WebSocket server URL
Module name for identification
Authentication token (if server requires auth)
Custom module identity (auto-generated if not provided)
List of event types this module can emit
Configuration schema for this module
Connect automatically on construction
Reconnect automatically on disconnect
options.maxReconnectAttempts
Maximum reconnection attempts (-1 for unlimited)
options.heartbeat.readTimeout
Heartbeat interval in milliseconds
options.heartbeat.message
MessageHeartbeat | string
Custom heartbeat message
Connection close callback
options.onAnyMessage
(data: WebSocketEvent) => void
Handler for all incoming messages
options.onAnySend
(data: WebSocketEvent) => void
Handler for all outgoing messages
Methods
connect()
Manually connect to the server.
send()
Send an event to the server.
client . send ({
type: 'custom:event' ,
data: { message: 'Hello' },
route: {
destinations: [ 'target-module' ]
}
})
Event metadata (auto-populated if not provided)
sendRaw()
Send raw data without serialization.
client . sendRaw ( 'raw string data' )
client . sendRaw ( new Uint8Array ([ 1 , 2 , 3 ]))
onEvent()
Register an event handler.
client . onEvent ( 'module:configure' , async ( event ) => {
const config = event . data . config
// Handle configuration
})
offEvent()
Unregister an event handler.
const handler = ( event ) => {
// Handle event
}
client . onEvent ( 'custom:event' , handler )
// Later: remove specific handler
client . offEvent ( 'custom:event' , handler )
// Or remove all handlers for an event
client . offEvent ( 'custom:event' )
close()
Close the connection.
TypeScript Generics
Type your custom event data:
interface CustomEvents {
'custom:greeting' : { name : string }
'custom:data' : { value : number }
}
const client = new Client < CustomEvents >({
url: 'ws://localhost:6121/ws' ,
name: 'typed-module'
})
// Type-safe event handling
client . onEvent ( 'custom:greeting' , ( event ) => {
const name = event . data . name // TypeScript knows this is a string
})
// Type-safe sending
client . send ({
type: 'custom:greeting' ,
data: { name: 'Alice' } // TypeScript enforces correct shape
})
Event Types
The SDK re-exports event types from @proj-airi/server-shared:
import type {
WebSocketEvent ,
MetadataEventSource ,
ModuleDependency ,
ModuleConfigSchema
} from '@proj-airi/server-sdk'
import {
WebSocketEventSource ,
ContextUpdateStrategy ,
MessageHeartbeat ,
MessageHeartbeatKind
} from '@proj-airi/server-sdk'
Connection Lifecycle
The client manages connection lifecycle automatically:
Authentication Flow
const client = new Client ({
url: 'ws://localhost:6121/ws' ,
name: 'secure-module' ,
token: 'secret-token'
})
// Client automatically:
// 1. Connects to server
// 2. Sends module:authenticate with token
// 3. Waits for module:authenticated response
// 4. Announces module to registry
// 5. Receives registry:modules:sync
Heartbeat Management
Heartbeats maintain connection health:
const client = new Client ({
url: 'ws://localhost:6121/ws' ,
name: 'monitored-module' ,
heartbeat: {
readTimeout: 30000 , // Send ping every 30s
message: MessageHeartbeat . Ping
}
})
// Client automatically sends heartbeat pings
// Server responds with pongs
// Connection is maintained
Error Handling
const client = new Client ({
url: 'ws://localhost:6121/ws' ,
name: 'resilient-module' ,
onError : ( error ) => {
console . error ( 'Client error:' , error )
},
onClose : () => {
console . log ( 'Connection closed' )
},
autoReconnect: true ,
maxReconnectAttempts: 10
})
// Handle specific error events
client . onEvent ( 'error' , ( event ) => {
console . error ( 'Server error:' , event . data . message )
if ( event . data . message === 'not authenticated' ) {
// Handle authentication failure
}
})
Reconnection with Backoff
The client implements exponential backoff:
// Attempt 1: 1s delay
// Attempt 2: 2s delay
// Attempt 3: 4s delay
// Attempt 4: 8s delay
// Attempt 5: 16s delay
// Attempt 6+: 30s delay (capped)
Configure reconnection:
const client = new Client ({
url: 'ws://localhost:6121/ws' ,
name: 'persistent-module' ,
autoReconnect: true ,
maxReconnectAttempts: - 1 // Unlimited attempts
})
Event Routing
Route events to specific destinations:
// Route to specific modules
client . send ({
type: 'custom:request' ,
data: { query: 'data' },
route: {
destinations: [ 'processor-module' , 'logger-module' ]
}
})
// Route by module instance
client . send ({
type: 'custom:request' ,
data: { query: 'data' },
route: {
destinations: [
{ name: 'worker-module' , index: 0 },
{ name: 'worker-module' , index: 1 }
]
}
})
// Route by labels
client . send ({
type: 'custom:broadcast' ,
data: { message: 'Hello' },
route: {
destinations: [
{ labels: { role: 'processor' } }
]
}
})
// Broadcast to all (default)
client . send ({
type: 'custom:broadcast' ,
data: { message: 'Everyone' }
})
Module Registry
Receive and track connected modules:
const modules = new Map ()
client . onEvent ( 'registry:modules:sync' , ( event ) => {
for ( const module of event . data . modules ) {
modules . set ( module . identity . id , module )
}
console . log ( 'Known modules:' , Array . from ( modules . keys ()))
})
Configuration Handling
client . onEvent ( 'module:configure' , async ( event ) => {
const config = event . data . config
try {
// Validate configuration
if ( ! config . apiKey ) {
throw new Error ( 'API key required' )
}
// Apply configuration
await applyConfig ( config )
// Acknowledge
client . send ({
type: 'module:configuration:configured' ,
data: {
identity: client . identity ,
config
}
})
} catch ( error ) {
// Report error
client . send ({
type: 'error' ,
data: {
message: error . message
}
})
}
})
Message Interception
Intercept all messages for logging or debugging:
const client = new Client ({
url: 'ws://localhost:6121/ws' ,
name: 'debug-module' ,
onAnyMessage : ( event ) => {
console . log ( 'Received:' , event . type , event . data )
},
onAnySend : ( event ) => {
console . log ( 'Sending:' , event . type , event . data )
}
})
Node.js Utilities
For Node.js environments:
import { /* node utilities */ } from '@proj-airi/server-sdk/utils/node'
// Node-specific utilities (implementation details in source)
Complete Example
AI module connecting to server:
import { Client } from '@proj-airi/server-sdk'
interface AIModuleEvents {
'ai:generate' : { prompt : string , model : string }
'ai:response' : { text : string , tokens : number }
}
const client = new Client < AIModuleEvents >({
url: 'ws://localhost:6121/ws' ,
name: 'ai-module' ,
token: process . env . AUTH_TOKEN ,
identity: {
kind: 'plugin' ,
plugin: {
id: 'ai-module' ,
version: '1.0.0' ,
labels: { role: 'processor' , tier: 'premium' }
},
id: 'ai-module-1'
},
possibleEvents: [ 'ai:response' ],
onError : ( error ) => {
console . error ( 'Connection error:' , error )
},
autoReconnect: true
})
// Handle generation requests
client . onEvent ( 'ai:generate' , async ( event ) => {
const { prompt , model } = event . data
try {
// Generate response
const response = await generateText ( prompt , model )
// Send response
client . send ({
type: 'ai:response' ,
data: {
text: response . text ,
tokens: response . tokens
},
metadata: {
event: {
parentId: event . metadata . event . id
}
}
})
} catch ( error ) {
// Send error
client . send ({
type: 'error' ,
data: {
message: error . message
},
metadata: {
event: {
parentId: event . metadata . event . id
}
}
})
}
})
// Graceful shutdown
process . on ( 'SIGTERM' , () => {
console . log ( 'Shutting down...' )
client . close ()
process . exit ( 0 )
})
Best Practices
Always enable autoReconnect for production services to handle network issues gracefully.
Use event IDs for tracing
Include parentId in responses to enable request-response correlation and distributed tracing.
Configure appropriate heartbeat intervals based on your network conditions (default: 30s).
Use TypeScript generics to type-check event data and catch errors at compile time.
Handle authentication errors
Listen for authentication errors and handle them appropriately (retry with new token, notify user, etc.).
Next Steps
Server Runtime Learn about the server runtime
WebSocket Protocol Understand the WebSocket protocol