pub struct Outbox { /* private fields */ }Expand description
A durable work queue over the Database port. Construct it with the table
the owning module declares (e.g. "<module>_outbox").
Implementations§
Source§impl Outbox
impl Outbox
pub fn new(table: impl Into<String>) -> Self
Sourcepub fn create_table_sql(&self) -> String
pub fn create_table_sql(&self) -> String
The portable DDL for the outbox table. The owning module ships this as a forward-only migration.
Sourcepub fn enqueue_statement(
&self,
id: &str,
topic: &str,
payload: &str,
subject: Option<&str>,
at: &str,
) -> Statement
pub fn enqueue_statement( &self, id: &str, topic: &str, payload: &str, subject: Option<&str>, at: &str, ) -> Statement
The INSERT that enqueues one unit of work. Return it into the module’s
own db.batch_atomic(..) alongside the state change, so the row is durable
exactly when the change is. id is a caller-supplied ULID; at is an
RFC 3339 timestamp used for both created_at and the initial
next_attempt_at (deliver as soon as possible).
subject is the person the work is for — an account id, a waitlist
entry id — or None for work that names nobody. Writing it is what
makes the queued row reachable for export and erasure (issue #266):
the payload JSON is the module’s own dialect and no predicate can
match into it. Pass it for every per-person job even when it feels
redundant with the payload; None rows drain identically but are
returned for nobody’s subject, so a forgotten Some is a silent
hole in the erasure catalogue rather than an error.
Sourcepub async fn claim_due(
&self,
db: &dyn Database,
now: &str,
lease_until: &str,
limit: u64,
) -> Result<Vec<OutboxRecord>, DbError>
pub async fn claim_due( &self, db: &dyn Database, now: &str, lease_until: &str, limit: u64, ) -> Result<Vec<OutboxRecord>, DbError>
Sourcepub async fn retry_later(
&self,
db: &dyn Database,
id: &str,
next_attempt_at: &str,
) -> Result<(), DbError>
pub async fn retry_later( &self, db: &dyn Database, id: &str, next_attempt_at: &str, ) -> Result<(), DbError>
Sourcepub async fn reschedule(
&self,
db: &dyn Database,
id: &str,
next_attempt_at: &str,
) -> Result<(), DbError>
pub async fn reschedule( &self, db: &dyn Database, id: &str, next_attempt_at: &str, ) -> Result<(), DbError>
Reschedules a record without counting an attempt: sets the next
attempt time and clears the lease. Two uses. First, the per-item
cursor for recurring work — one outbox row per thing to poll,
rescheduled by the handler to its next poll time, so “process N due
items per tick” is just a bounded claim. Second, how
drain_within releases claimed-but-unprocessed
rows when the budget runs out: a release is not a failure and must
not read as one.
§Errors
Returns DbError if the update fails.
Sourcepub async fn drain_within<F, Fut>(
&self,
db: &dyn Database,
clock: &dyn Clock,
budget: &ScheduledBudget,
opts: DrainOptions,
handle: F,
) -> Result<DrainReport, DbError>
pub async fn drain_within<F, Fut>( &self, db: &dyn Database, clock: &dyn Clock, budget: &ScheduledBudget, opts: DrainOptions, handle: F, ) -> Result<DrainReport, DbError>
Drains due records under a ScheduledBudget: process at most
opts.limit items this tick, and fewer when the budget cannot carry
them. This is the “N due items per tick” helper — a recurring poller
writes one outbox row per thing to poll and returns
Processed::NextAt with the row’s next poll time, which
reschedule applies without counting an
attempt, so the row comes back due exactly when it should and a
poll that keeps failing still counts toward the bounded retry the
RetryAt path enforces.
The budget is checked twice: the claim itself is capped at what the
remaining subrequests can carry (remaining / opts.subrequests_per_item, uncapped when subrequests are unbounded
or subrequests_per_item is 0), and each record is checked again
before its handler runs — a budget that runs out mid-batch releases
that record and every claimed one after it through
reschedule, so they are immediately claimable
on the next tick, and sets stopped_by_budget on the report.
§Errors
Returns DbError when a claim, complete or reschedule write
fails. Records already handled before the failure are finished; the
rest keep their lease until it lapses — at-least-once, as ever.