Transactional Outbox
Atomic event emission with content mutations — polling, backoff, webhook fan-out.
On this page
SveltyCMS uses the transactional outbox pattern so domain events (webhooks, automations, internal pub/sub) are not lost when a write succeeds but side-effect delivery fails (or vice versa).
Flow
- Write path (collection create/update/delete) uses
persistWithOutbox:- Prefer one DB transaction for entity write +
outboxService.emit(SQL adapters). - Fall back to sequential write→emit when
transaction()is unavailable (e.g. Mongo without a replica set).
- Prefer one DB transaction for entity write +
- Event types:
entry:create|entry:update|entry:delete. afterMutationstill invalidates cache and publishesentryUpdated(outbox already emitted inside the TX when possible).- Background
outboxService.startPolling()(hooks cold-start) runsprocessBatch(). - Delivery: internal
pubSub+ optionalwebhookService.triggerfor mapped event types. - Success →
status: delivered; failure → exponential backoff retry; after 5 attempts →failed.
Storage
| Adapter | Table / collection |
|---|---|
| SQLite / PostgreSQL / MariaDB | svelty_outbox (migrations + Drizzle schema) |
| MongoDB | svelty_outbox model (OutboxEvent) |
API
import { outboxService } from "@src/services/outbox";
await outboxService.emit(
"entry:create",
"entry",
entryId,
{ collection, data },
tenantId,
{ transaction }, // optional — same DB transaction as the write
);
await outboxService.processBatch(50);
await outboxService.cleanup(olderThanISO);
Backoff
outboxBackoffMs(attempts) → 1s, 2s, 4s, … capped at 5 minutes, based on updatedAt after each failure. Pending events inside the window are skipped until ready.
Kill switches
| Env | Effect |
|---|---|
DISABLE_OUTBOX=true |
Skip emit + polling |
BENCHMARK_MODE=true |
Same |
Tests
- Unit:
tests/unit/services/outbox-service.test.ts - Integration:
tests/integration/api/outbox-plugin-storage.test.ts - Testing API:
outbox-emit,outbox-process-batch,outbox-tx-rollback, …
Implementation
- Service:
src/services/outbox/outbox-service.ts - Boot:
hooks.server.ts→outboxService.startPolling(5000) - Emit:
collections-namespace.tspersistWithOutbox(+afterMutationfallback)
Was this page helpful?