Files
storage/docs/architecture/decisions/047-honker-event-target.md
glm-5.1 412ad98f11 Pivot: fold drizzlebox as utils, HonkerEventTarget, OperationSpecs as repo surface
- Update architecture docs to reflect pivot from @libsql/client to Honker
- Fold @alkdev/drizzlebox Phase 0 into src/sqlite/utils/ (ADR-046)
- Add HonkerEventTarget adapter for pubsub TypedEventTarget (ADR-047)
- Replace hand-written CRUD with OperationSpec generation (ADR-048)
- Resolved OQ-26: Honker replaces Redis for single-node pub/sub (POC validated)
- Updated OQ-17, OQ-18, OQ-19 for OperationSpec repository surface
- Added OQ-30 (composite event target), OQ-31 (consumer naming), OQ-32 (Drizzle Kit)
- POC results: adapter buildable, same-process pub/sub works, transactional
  outbox semantics confirmed, concurrent listeners/streams work
- Research doc at docs/research/pivot-honker-sqlite-adapter.md
2026-06-01 16:31:40 +00:00

2.6 KiB

status, date, resolves
status date resolves
accepted 2026-06-01 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: addEventListenerdb.listen(), dispatchEventdb.notify(). Fire-and-forget semantics, no delivery guarantee.
  2. Durable mode: addEventListenerdb.stream().subscribe(), dispatchEventdb.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 semanticsdispatchEvent 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).