- 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
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
- Bidirectional —
dispatchEventsends over WS,addEventListenerreceives 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
EventEnvelopeJSON
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
- Send path — dispatchEvent serializes envelope and calls ws.send
- Receive path — ws.onmessage parses envelope, creates CustomEvent, dispatches to listeners
- Topic scoping — type:id topics correctly formed from envelope
- Connection close — ws.onclose propagates to listeners (error event?)
- Multiple listeners — multiple addEventListener on same topic