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.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-05-07
|
||||
last_updated: 2026-05-08
|
||||
---
|
||||
|
||||
# Iroh Hub Event Target
|
||||
@@ -38,6 +38,8 @@ Each spoke gets its own read loop that parses length-prefixed JSON messages from
|
||||
|
||||
## Connection Lifecycle
|
||||
|
||||
Unlike the WebSocket server adapter (where the caller passes connections via `addConnection`), the Iroh hub adapter manages connections automatically via `endpoint.accept()`. This is a deliberate design difference: Iroh QUIC connections are accepted by the endpoint, not passed in by a framework. The hub has no `addConnection`/`removeConnection` API — connections are internal to the adapter.
|
||||
|
||||
1. Hub creates `Endpoint` and starts accepting
|
||||
2. Spoke connects → hub gets `Connection` from `endpoint.accept()`
|
||||
3. Hub accepts stream → `connection.acceptBi()` → `SendStream` + `RecvStream`
|
||||
@@ -47,11 +49,20 @@ Each spoke gets its own read loop that parses length-prefixed JSON messages from
|
||||
|
||||
## Fan-Out
|
||||
|
||||
As a fan-out adapter, the Iroh hub must implement **topic-based fan-out** — `dispatchEvent` sends only to spokes subscribed to that topic, not to all connected spokes. This requires a `subscriptions` map (`Map<string, Set<Spoke>>`) updated by `__subscribe`/`__unsubscribe` control events from spokes. See [ADR-003](../decisions/003-subscription-control-protocol.md) and [WebSocket server adapter](websocket-server.md) for the established pattern.
|
||||
|
||||
```ts
|
||||
dispatchEvent(event) {
|
||||
// event.type is the full topic string, e.g. "message.sent:conv-123"
|
||||
// This matches the topics that spokes subscribe to via __subscribe
|
||||
const message = encodeEnvelope(event.detail);
|
||||
for (const spoke of this.spokes) {
|
||||
spoke.sendStream.writeAll(message);
|
||||
|
||||
// Send only to spokes subscribed to this topic
|
||||
const subscribers = this.subscriptions.get(event.type);
|
||||
if (subscribers) {
|
||||
for (const spoke of subscribers) {
|
||||
spoke.sendStream.writeAll(message);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -60,7 +71,8 @@ dispatchEvent(event) {
|
||||
## Key Properties
|
||||
|
||||
- **Multi-connection** — manages a set of connected spokes
|
||||
- **Fan-out** — dispatchEvent sends to all connected spokes
|
||||
- **Topic-based fan-out** — dispatchEvent sends only to spokes subscribed to the event type
|
||||
- **Subscription tracking** — maintains topic-to-spoke mapping, updated by `__subscribe`/`__unsubscribe` control events
|
||||
- **Accepts incoming** — endpoint.accept() loop runs continuously
|
||||
- **Cryptographic identity** — each spoke verified by Ed25519 NodeId
|
||||
|
||||
@@ -69,7 +81,7 @@ dispatchEvent(event) {
|
||||
1. **Binding stability** — same as spoke adapter. `@rayhanadev/iroh` needs testing.
|
||||
2. **Concurrent accept** — can `endpoint.accept()` handle multiple simultaneous connections?
|
||||
3. **Stream vs. Connection per spoke** — current design: one bidirectional stream per spoke on a single connection. Alternative: one connection per spoke. Need to benchmark which is better for the expected workload.
|
||||
4. **1:N fan-out** — for hub to N spokes, each spoke gets its own stream. For true broadcast, `iroh-gossip` would be better (not yet available in TS).
|
||||
4. **iroh-gossip** — for true broadcast to many spokes, `iroh-gossip` would be more efficient than per-spoke streams. Not yet available in TS. The current subscription-tracked fan-out design works for moderate fan-out; gossip would be an optimization for very large fan-out later.
|
||||
5. **Connection rejection** — how to reject connections from unknown `NodeId`s.
|
||||
|
||||
See [../iroh-transport.md](../iroh-transport.md) for full protocol details, identity, and comparison with WebSocket.
|
||||
Reference in New Issue
Block a user