- Create docs/architecture/event-targets/ with individual specs: in-process, redis, websocket-client, websocket-server, worker, iroh-spoke, iroh-hub - Update event-targets.md to serve as index with topology model (symmetric vs fan-out) and adapter status table - Update architecture.md index to reference new directory
3.5 KiB
status, last_updated
| status | last_updated |
|---|---|
| draft | 2026-05-07 |
WebSocket Server Event Target
Import: @alkdev/pubsub/event-target-websocket-server
Peer dep: none (WebSocket is a web standard, but a server framework like Hono may be needed for upgrade handling)
Status: Not yet implemented.
Manages multiple WebSocket connections for the server (hub) side. Handles fan-out: dispatchEvent sends to all connected spokes; addEventListener aggregates subscriptions across all connections.
createWebSocketServerEventTarget
function createWebSocketServerEventTarget<TEvent extends TypedEvent>(
options: CreateWebSocketServerEventTargetArgs,
): TypedEventTarget<TEvent>;
CreateWebSocketServerEventTargetArgs
| Field | Type | Required | Description |
|---|---|---|---|
onConnection |
callback | No | Called when a new spoke connects. Receives the spoke's event target for per-connection customization. |
onDisconnection |
callback | No | Called when a spoke disconnects. Receives the spoke's event target for cleanup. |
How It Works
Unlike the client adapter, the server adapter manages a Set<WebSocket> of active connections:
dispatchEvent→ iterates all connected WebSockets, sends JSON envelope to eachaddEventListener→ registers local listeners. The server doesn't subscribe to individual spokes — it listens for events from any spokeremoveEventListener→ removes local listeners
Incoming Messages
Each connected spoke sends JSON envelopes. The server listens on ws.onmessage for each connection:
for (const ws of this.connections) {
ws.onmessage = (msg) => {
const envelope = JSON.parse(msg.data);
const topic = `${envelope.type}:${envelope.id}`;
const event = new CustomEvent(topic, { detail: envelope });
this.dispatchEvent(event); // dispatches to local listeners
};
}
Outgoing Messages (Fan-out)
dispatchEvent(event) {
const message = JSON.stringify(event.detail);
for (const ws of this.connections) {
ws.send(message);
}
return true;
}
Per-Connection Spoke Targets
The server adapter creates a WebSocketClientEventTarget for each incoming connection. This allows the hub to target specific spokes if needed (e.g., responding to a specific request).
Key Properties
- Fan-out — dispatchEvent sends to all connected spokes
- Aggregate subscription — addEventListener listens for events from any spoke
- Connection lifecycle — manages add/remove of WebSocket connections
- No native deps — works with any WebSocket server (Node ws, Bun, Deno, Hono)
Open Questions
- Server framework coupling — Should this adapter take a raw
WebSocketServeror just handleWebSocketinstances? RawWebSocketinstances keeps it framework-agnostic. The caller (hub code) handles the HTTP upgrade and passes connectedWebSockets to the adapter. - Backpressure — What happens when
ws.send()blocks or buffers? Should there be a max buffer size per connection? - Selective fan-out — Should
dispatchEventalways send to all connections, or should there be a way to target a specific spoke?
Test Plan
- Fan-out — dispatchEvent sends to all connected WebSockets
- Incoming aggregation — messages from any spoke dispatch to local listeners
- Connection add/remove — new connections are tracked, disconnections are cleaned up
- Mixed topology — server adapter and client adapters can communicate bidirectionally