Wire Protocol
Experimental
This package is in an experimental phase. The API may change without following semver until it reaches a stable release.
Moost WS uses a simple JSON-over-WebSocket protocol. Messages are plain JSON objects sent as text frames — no custom framing, no binary encoding.
For the full Wooks protocol reference, see the Wooks Wire Protocol Documentation.
Message Types
Client to Server
Every client message has an event and path for routing, optional data for payload, and an optional id for RPC correlation:
interface WsClientMessage {
event: string // Router method (e.g. "message", "join", "query")
path: string // Route path (e.g. "/chat/general")
data?: unknown // Payload
id?: string | number // Correlation ID — present for RPC, absent for fire-and-forget
}Fire-and-forget (no reply expected):
{ "event": "message", "path": "/chat/general", "data": { "text": "Hello!" } }RPC (reply expected):
{ "event": "join", "path": "/chat/general", "data": { "name": "Alice" }, "id": 1 }Server to Client: Reply
Sent in response to a client message that included an id. Exactly one reply per request.
interface WsReplyMessage {
id: string | number // Matches client's correlation ID
data?: unknown // Handler return value
error?: { code: number; message: string } // Error details (if handler threw)
}Success reply:
{ "id": 1, "data": { "joined": true, "room": "general" } }Error reply:
{ "id": 1, "error": { "code": 400, "message": "Name is required" } }Server to Client: Push
Server-initiated messages from broadcasts, subscriptions, or direct sends. No id field.
interface WsPushMessage {
event: string // Event type
path: string // Concrete path
params?: Record<string, string> // Route params extracted by router
data?: unknown // Payload
}Push example:
{
"event": "message",
"path": "/chat/general",
"data": { "from": "Alice", "text": "Hello!" }
}Routing Logic
Messages are routed by two dimensions:
- Event — the
eventfield acts as the "method" (like HTTP GET/POST) - Path — the
pathfield acts as the URL path
The router matches against registered handlers:
// Server-side handler
@Message('join', '/chat/:room')
join(@Param('room') room: string) { /* ... */ }// Client message that matches
{ "event": "join", "path": "/chat/general", "data": { "name": "Alice" }, "id": 1 }The event must match exactly. The path supports parametric patterns (:param) and wildcards (*).
Error Responses
When a handler throws an error, the server replies with an error object (only for RPC messages with an id):
| Scenario | Code | Message |
|---|---|---|
Handler throws WsError(code, msg) | Custom code | Custom message |
| No matching handler found | 404 | "Not found" |
| Unhandled exception in handler | 500 | "Internal Error" |
{ "id": 1, "error": { "code": 404, "message": "Not found" } }For fire-and-forget messages (no id), errors are logged server-side but not sent to the client.
Heartbeat
The server sends periodic WebSocket ping frames to detect stale connections. Clients that don't respond with a pong within the timeout are disconnected.
Configure the heartbeat interval:
const ws = new MoostWs({
httpApp: http.getHttpApp(),
wooksWs: {
heartbeatInterval: 30000, // milliseconds (default: 30000)
},
})Set to 0 to disable heartbeat.
Custom Serialization
Both server and client support pluggable serialization for formats like MessagePack or CBOR:
Server:
const ws = new MoostWs({
wooksWs: {
messageParser: (raw: string) => myCustomParse(raw),
messageSerializer: (msg: unknown) => myCustomSerialize(msg),
},
})Client:
const client = createWsClient('ws://localhost:3000/ws', {
messageParser: (raw: string) => myCustomParse(raw),
messageSerializer: (msg: unknown) => myCustomSerialize(msg),
})Both sides must use the same serialization format.