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:
realtimedelivers live changes and maintains the legacy_cdc_logcompatibility marker.pushcreates notification outbox work when push is enabled.auditmaterializes a compact actor/resource/operation audit projection.usagematerializes 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:
Studio → Events → Retries and dead letters → Replay
Open export
Export ordered, resumable NDJSON:
loomup events export --output events.ndjson
loomup events export --after-sequence 1000 --limit 500
Each line is a versioned envelope:
{"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.