OpOutbox

interface OpOutbox

The SECOND outbox shape: a durable, FIFO, append-only operation log — distinct from SubmitOutbox, which is a keyed latest-write-wins draft store. Reach for this one when order matters and every mutation must reach the server exactly once, surviving process death:

  • append-onlyenqueue logs an opaque (type, payload) op and returns its stable id, which doubles as an idempotency key the caller sends to the server so a re-delivered op is collapsed.

  • FIFO replayreplay drains oldest-first through a caller-supplied send. It stops at the first transient failure (offline / 5xx / timeout) to preserve order and let the next trigger resume, rather than reordering the queue or hammering a down server.

  • dead-lettering — an op the server rejects (classified permanent by isPermanent, e.g. a 4xx) or one that exhausts maxAttempts transient retries is marked dead (kept, not deleted) so replay moves past it instead of getting stuck behind a poison row. Dead ops are inspectable via deadLetters and re-runnable via requeue.

Payload-agnostic by design: it stores opaque type + serialized-payload strings, and the caller owns both serialization and the send transport — so this stays decoupled from any HTTP client or DTO.

Inheritors

Types

Link copied to clipboard
object Companion

Functions

Link copied to clipboard
abstract suspend fun deadLetters(): List<OpEntry>

Dead-lettered ops, for inspection or a manual-retry UI.

Link copied to clipboard
abstract suspend fun enqueue(type: String, payload: String): String

Append an op; returns its id (= idempotency key). Triggering replay afterwards is the caller's job.

Link copied to clipboard
abstract fun pending(): Flow<List<OpEntry>>

Observe live (non-dead) queue contents, oldest-first — drives a "N pending" sync indicator.

Link copied to clipboard
abstract suspend fun replay(maxAttempts: Int = DEFAULT_MAX_ATTEMPTS, isPermanent: (Throwable) -> Boolean = { false }, send: suspend (OpEntry) -> Unit)

Drain PENDING ops oldest-first. For each, calls send; on success the op is deleted. On failure: if isPermanent returns true for the error, or the op has now failed maxAttempts times, it is dead-lettered and replay continues; otherwise the failure is recorded and replay STOPS (preserving order) — the next trigger resumes from the same op.

Link copied to clipboard
abstract suspend fun requeue(id: String)

Requeue a dead-lettered op (reset attempts + status to PENDING) for a manual retry.