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

3.5 KiB

status, last_updated
status last_updated
draft 2026-05-08

Iroh Spoke Event Target

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

P2P QUIC event target for the spoke (client) side. The spoke initiates the connection and opens the bidirectional stream.

createIrohSpokeEventTarget

async function createIrohSpokeEventTarget<TEvent extends TypedEvent>(
  args: CreateIrohSpokeEventTargetArgs,
): Promise<TypedEventTarget<TEvent>>;

CreateIrohSpokeEventTargetArgs

Field Type Required Description
endpoint Endpoint Yes iroh endpoint (created with Endpoint.create())
hubNodeId string | NodeId Yes The hub's public key (Ed25519)
alpn string No Application-layer protocol. Default: "alkpubsub/1"

Protocol

Single bidirectional QUIC stream per connection. Length-prefixed JSON messages:

[4 bytes: length N][N bytes: JSON payload]

The JSON payload is the EventEnvelope:

{ "type": "call.responded", "id": "uuid-123", "payload": { "output": 42 } }

Connection Flow

  1. Spoke creates Endpoint
  2. Spoke calls endpoint.connect(hubNodeId, alpn)Connection
  3. Spoke calls connection.openBi()SendStream + RecvStream
  4. Spoke wraps streams in IrohSpokeEventTarget
  5. On disconnect: RecvStream.readExact() throws, spoke must reconnect

Subscription Forwarding

Same pattern as WebSocket Client: when addEventListener is called for the first listener on a topic, the spoke sends a __subscribe control event to the hub. When removeEventListener removes the last listener for a topic, the spoke sends __unsubscribe. Reference counting ensures __subscribe is sent only on the first addEventListener for a topic, and __unsubscribe only when the last removeEventListener for that topic is called. This enables the hub's topic-based fan-out (see ADR-003).

Control events use the existing EventEnvelope format:

{ "type": "__subscribe", "id": "", "payload": { "topic": "message.sent:conv-123" } }
{ "type": "__unsubscribe", "id": "", "payload": { "topic": "message.sent:conv-123" } }

Key Properties

  • NAT traversal — spoke dials hub by NodeId, no public IP needed
  • Cryptographic identityConnection.remoteNodeId() verifies the hub
  • BidirectionaldispatchEvent writes to SendStream, addEventListener reads from RecvStream
  • Per-connection — one event target per QUIC connection
  • Subscription forwardingaddEventListener/removeEventListener send __subscribe/__unsubscribe control events to the hub (see ADR-003)

R&D Needed

  1. Binding stability@rayhanadev/iroh has one author and no tests. API surface is small (10 methods) but needs validation.
  2. NAPI under Deno — NAPI-RS .node binaries need testing under Deno 2.x.
  3. Stream multiplexing — multiple openBi() streams on one connection vs. single stream with multiplexed events. Single stream + JSON framing is simpler.
  4. ReconnectionRecvStream.readExact() throws on connection close. Need to propagate this to listeners and support reconnect.

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