--- status: accepted date: 2026-06-01 resolves: OQ-26 --- # ADR-047: HonkerEventTarget Adapter for pubsub ## Context `@alkdev/pubsub` defines a `TypedEventTarget` interface that all transport adapters implement: in-process `EventTarget`, Redis, WebSocket client/server, and Worker. This provides transport-agnostic pub/sub — consumers call `createPubSub({ eventTarget })` without knowing the underlying transport. Honker provides SQLite with built-in pub/sub primitives: - `db.notify(channel, payload)` / `db.listen(channel)` — ephemeral, fire-and-forget - `db.stream(name).publish(payload)` / `db.stream(name).subscribe(consumer)` — durable, offset-tracked POC 2-4 (2026-06-01) validated: - Same-process notify→listen works. ~17ms median latency. - Multiple concurrent listeners on different channels work. - `tx.notify()` only fires on `tx.commit()`. Rollback suppresses notification. - `queue.enqueueTx(tx, payload)` only visible after commit. Rollback suppresses. - Stream publish/subscribe works with consumer offset tracking. These results confirm Honker can back the pubsub `TypedEventTarget` interface for single-node deployments. ## Decision Implement `HonkerEventTarget` in `src/sqlite/event-target.ts`. It adapts `@alkdev/pubsub`'s `TypedEventTarget` to Honker primitives with two modes: 1. **Ephemeral mode**: `addEventListener` → `db.listen()`, `dispatchEvent` → `db.notify()`. Fire-and-forget semantics, no delivery guarantee. 2. **Durable mode**: `addEventListener` → `db.stream().subscribe()`, `dispatchEvent` → `db.stream().publish()`. Per-consumer offset tracking, crash recovery replays from last saved offset. `@alkdev/pubsub` is a peer dependency (needed only when using HonkerEventTarget). The `graphs/` module remains zero-dep. ## Consequences - **Single-node hub can use Honker instead of Redis** — no separate Redis deployment needed for pub/sub. - **Transactional outbox semantics** — `dispatchEvent` inside a Drizzle transaction (via `tx.notify()` or `stream.publishTx()`) commits atomically with data writes. No dual-write problem. - **Hub-spoke symmetry** — both hub and spoke use the same `createPubSub()` call. Different event target instances determine routing. - **Multi-node still needs Redis or WebSocket** — Honker events don't cross process boundaries. For multi-node, the `WebSocketServerEventTarget` (hub) and `WebSocketClientEventTarget` (spoke) handle cross-process routing. - **Latency trade-off** — ~17ms Honker round-trip vs sub-ms in-process. For hot-path call protocol, pair with in-process `EventTarget`. Design of a composite event target is an open question (OQ-30).