Files
pubsub/docs/architecture/event-targets.md
glm-5.1 bc0c2589c7 Resolve WebSocket event target open questions, add subscription control protocol
- Resolve OQ1: WS server accepts raw WebSocket instances via
  addConnection/removeConnection (framework-agnostic, not coupled to
  Hono/Express/Bun/Deno)
- Resolve OQ2: Backpressure handled by disconnecting slow consumers at
  configurable threshold (default 1MB), with onBackpressure callback
  for observability
- Resolve OQ3: Topic-based fan-out with subscription tracking instead
  of broadcast-all; spokes send __subscribe/__unsubscribe control
  events; direct messaging via 'direct:' topic pattern

Add ADR-003 for subscription control protocol decision. Update all
fan-out adapters (WS server, Iroh hub) and spoke adapters (WS client,
Iroh spoke) with subscription tracking/forwarding. Fix routing key
ambiguity (full topic string, not event type alone). Add error
handling, composition, and reserved type sections. Clarify Worker as
symmetric-only.
2026-05-08 03:29:27 +00:00

4.0 KiB

status, last_updated
status last_updated
draft 2026-05-08

Event Target Adapters

All adapters implement the TypedEventTarget<TEvent> interface and use the EventEnvelope format ({ type, id, payload }) as the serialization contract.

Interface Contract

Every adapter must implement:

Method Behavior
addEventListener(type, callback) Register listener for event type. Callback receives CustomEvent with typed detail (an EventEnvelope).
dispatchEvent(event) Send/dispatch event. Returns boolean (always true for non-cancelable events).
removeEventListener(type, callback) Unregister listener. Clean up underlying subscription when no listeners remain for a topic.

Topology Model

Adapters come in two shapes:

  • Symmetric (single connection) — wraps one connection. Same interface on both sides. Examples: Redis, Iroh spoke, WebSocket client, Worker.
  • Fan-out (multi-connection) — manages multiple connections. dispatchEvent sends only to connections subscribed to the event type (topic-based fan-out); addEventListener aggregates from all. Examples: WebSocket server, Iroh hub.

Worker is a symmetric adapter (point-to-point). A Worker pool manager would be a fan-out concern, but that belongs in @alkdev/operations, not in this package.

The createPubSub layer is topology-agnostic. A hub composes multiple adapters and uses operators to combine streams — this is downstream application logic, not a package boundary.

Composing Adapters

Adapters compose at the createPubSub level, not by nesting adapters. A common pattern:

Client (WS spoke) → Server (WS hub) → Redis → Other servers

In this topology:

  • The WS hub adapter manages spoke connections with topic-based fan-out
  • The hub's createPubSub instance also has a Redis event target for cross-server communication
  • Subscription state at the hub level aggregates: when a spoke subscribes to topic X, the hub may also subscribe to X on Redis (if no other spoke is already subscribed)

This composition is handled by createPubSub bridging multiple event targets, not by adapters wrapping each other. The fan-out adapter's subscription tracking is local to its connected spokes; upstream subscriptions are a createPubSub concern.

Subscription Control Protocol

Fan-out adapters must know which connections are interested in which topics. Symmetric adapters that connect to a fan-out adapter must declare their subscriptions. This is done through control eventsEventEnvelope messages with reserved __-prefixed types:

  • __subscribe — sent when a spoke adds the first listener for a topic
  • __unsubscribe — sent when a spoke removes the last listener for a topic

The id field in control events is the empty string ("") by convention. Control events use the topic field in their payload for routing instead. Control events are handled internally by adapters and never dispatched to user-facing listeners. Event types starting with __ are reserved — user code must not define event types with this prefix.

See ADR-003.

This is analogous to Redis's SUBSCRIBE/UNSUBSCRIBE commands — control messages share the same wire format and connection as data.

Adapter Docs

Adapter Import Status
In-Process (default, no import) Implemented (built-in EventTarget)
Redis @alkdev/pubsub/event-target-redis Implemented. Needs tests.
WebSocket Client @alkdev/pubsub/event-target-websocket-client Not yet implemented
WebSocket Server @alkdev/pubsub/event-target-websocket-server Not yet implemented
Worker @alkdev/pubsub/event-target-worker Not yet implemented (R&D on Node vs Web Worker)
Iroh Spoke @alkdev/pubsub/event-target-iroh-spoke Not yet implemented (R&D on binding)
Iroh Hub @alkdev/pubsub/event-target-iroh-hub Not yet implemented (R&D on binding)