Files
pubsub/docs/architecture/event-targets/websocket-server.md
glm-5.1 371dabc20d Add per-adapter architecture docs in event-targets/ directory
- 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
2026-05-07 14:49:50 +00:00

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 each
  • addEventListener → registers local listeners. The server doesn't subscribe to individual spokes — it listens for events from any spoke
  • removeEventListener → 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

  1. Server framework coupling — Should this adapter take a raw WebSocketServer or just handle WebSocket instances? Raw WebSocket instances keeps it framework-agnostic. The caller (hub code) handles the HTTP upgrade and passes connected WebSockets to the adapter.
  2. Backpressure — What happens when ws.send() blocks or buffers? Should there be a max buffer size per connection?
  3. Selective fan-out — Should dispatchEvent always send to all connections, or should there be a way to target a specific spoke?

Test Plan

  1. Fan-out — dispatchEvent sends to all connected WebSockets
  2. Incoming aggregation — messages from any spoke dispatch to local listeners
  3. Connection add/remove — new connections are tracked, disconnections are cleaned up
  4. Mixed topology — server adapter and client adapters can communicate bidirectionally