Files
pubsub/docs/architecture/event-targets/iroh-hub.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.3 KiB

status, last_updated
status last_updated
draft 2026-05-08

Iroh Hub Event Target

Import: @alkdev/pubsub/event-target-iroh-hub Peer dep: @rayhanadev/iroh (optional, NAPI-RS native addon) Status: Not yet implemented. Needs R&D on binding stability, NAPI under Deno.

P2P QUIC event target for the hub (server) side. The hub accepts incoming connections and bidirectional streams. Manages multiple connected spokes.

createIrohHubEventTarget

async function createIrohHubEventTarget<TEvent extends TypedEvent>(
  args: CreateIrohHubEventTargetArgs,
): Promise<TypedEventTarget<TEvent>>;

CreateIrohHubEventTargetArgs

Field Type Required Description
endpoint Endpoint Yes iroh endpoint (created with Endpoint.create())
alpn string No Application-layer protocol. Default: "alkpubsub/1"

How It Works

Similar to the WebSocket server adapter, the Iroh hub adapter manages multiple connections:

  • dispatchEvent → writes JSON envelope to all connected spokes' SendStreams
  • addEventListener → registers local listeners for events from any spoke
  • On incoming connection → endpoint.accept()connection.acceptBi() → new spoke tracked

Each spoke gets its own read loop that parses length-prefixed JSON messages from RecvStream and dispatches locally.

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
  4. Hub creates per-spoke read loop
  5. On disconnect → RecvStream.readExact() throws → remove spoke from set
  6. Hub continues accepting new connections

Fan-Out

As a fan-out adapter, the Iroh hub must implement topic-based fan-outdispatchEvent 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 and WebSocket server adapter for the established pattern.

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);

  // 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;
}

Key Properties

  • Multi-connection — manages a set of 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

R&D Needed

  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. 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 NodeIds.

See ../iroh-transport.md for full protocol details, identity, and comparison with WebSocket.