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
This commit is contained in:
86
docs/architecture/event-targets/websocket-server.md
Normal file
86
docs/architecture/event-targets/websocket-server.md
Normal file
@@ -0,0 +1,86 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 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`
|
||||
|
||||
```ts
|
||||
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:
|
||||
|
||||
```ts
|
||||
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)
|
||||
|
||||
```ts
|
||||
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 `WebSocket`s 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
|
||||
Reference in New Issue
Block a user