Loomup Docs
Guide /docs/events

Durable event journal

Every mutation to a captured application resource writes one canonical _lb_events row in the same SQLite transaction as the application row. Direct writes from standard SQLite clients are captured too; Loomup-originated writes additionally record actor and idempotency metadata.

The journal is product data. Realtime delivery does not delete history and one failed capability does not stop another.

Independent consumers

Each consumer has its own cursor, retry/dead-letter state, and expiring worker lease:

  • realtime delivers live changes and maintains the legacy _cdc_log compatibility marker.
  • push creates notification outbox work when push is enabled.
  • audit materializes a compact actor/resource/operation audit projection.
  • usage materializes idempotent daily mutation counts.
  • webhook:<name> signs and delivers one declarative webhook stream.

Leases prevent two runtime processes from concurrently owning the same cursor. They expire after a crash, so another runtime can resume at-least-once delivery. Projection rows are keyed by event sequence and usage keeps an applied-sequence ledger, making replay safe without double-counting.

Studio’s Events view shows cursor lag, lease activity, failures, dead letters, recent events, and verified recovery points. Replaying a consumer rewinds only that consumer:

text
Studio → Events → Retries and dead letters → Replay

Open export

Export ordered, resumable NDJSON:

bash
loomup events export --output events.ndjson
loomup events export --after-sequence 1000 --limit 500

Each line is a versioned envelope:

json
{"format":"loomup.event.v1","event":{"sequence":1001,"event_id":"…"}}

The SQLite database and event stream remain usable without a Loomup cloud account.

Retention and recovery

Journal compaction requires a verified snapshot and refuses to cross an active consumer or sync-client cursor. Sync clients older than the configured cursor TTL are pruned and receive a safe reset_required bootstrap on return. Abandoned consumers older than events.consumer_cursor_ttl_secs are pruned the same way. Disabling realtime, push, or a webhook retires that consumer at startup; Studio and the admin API can retire a cursor explicitly so it no longer blocks compaction. See History, snapshots, and recovery.

Delivery to external systems is ordered per consumer and at least once. Receivers must deduplicate with event_id; Loomup does not claim exactly-once delivery across a network boundary.