Files
pubsub/docs/architecture/event-targets/websocket-client.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

2.3 KiB

status, last_updated
status last_updated
draft 2026-05-07

WebSocket Client Event Target

Import: @alkdev/pubsub/event-target-websocket-client Peer dep: none (WebSocket is a web standard) Status: Not yet implemented.

Wraps a single WebSocket connection for the client (spoke) side. Bidirectional — can both send and receive events.

createWebSocketClientEventTarget

function createWebSocketClientEventTarget<TEvent extends TypedEvent>(
  ws: WebSocket,
): TypedEventTarget<TEvent>;

Takes an already-connected WebSocket. The caller is responsible for connection lifecycle (including reconnection — see below).

Protocol

WebSocket provides native message boundaries (no length-prefix needed). Each message is a JSON-serialized EventEnvelope:

{ "type": "call.responded", "id": "uuid-123", "payload": { "output": 42 } }

Sending (dispatchEvent)

dispatchEvent(event) {
  this.ws.send(JSON.stringify(event.detail));
  // event.detail is the EventEnvelope { type, id, payload }
  return true;
}

Receiving (addEventListener)

ws.onmessage = (msg) => {
  const envelope = JSON.parse(msg.data);
  const topic = `${envelope.type}:${envelope.id}`;
  const event = new CustomEvent(topic, { detail: envelope });
  // dispatch to local listeners
};

Key Properties

  • BidirectionaldispatchEvent sends over WS, addEventListener receives from WS
  • Per-connection — one event target per WebSocket connection
  • JSON framing — WebSocket provides native message boundaries
  • No native deps — works in browsers and Node
  • Envelope serialization — sends/receives the full EventEnvelope JSON

Reconnection

WebSocket connections drop. On reconnect, the spoke must create a new WebSocket and a new WebSocketClientEventTarget. Reconnection logic belongs to the spoke lifecycle, not the event target.

The event target itself is per-connection. A new connection means a new instance.

Test Plan

  1. Send path — dispatchEvent serializes envelope and calls ws.send
  2. Receive path — ws.onmessage parses envelope, creates CustomEvent, dispatches to listeners
  3. Topic scoping — type:id topics correctly formed from envelope
  4. Connection close — ws.onclose propagates to listeners (error event?)
  5. Multiple listeners — multiple addEventListener on same topic