1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
//! `reliar-outbox` is the storage-agnostic transactional outbox: the [`OutboxStore`]/
//! [`OutboxDeadLetters`] capability traits (plus `reliar_core::Publisher`, re-exported here for
//! convenience), the request and result types that cross their boundary, a pure [`RetryPolicy`],
//! the feature's [`OutboxSettings`], and the [`OutboxMetrics`] hook.
//!
//! **The object names the guarantee — there is no facade type joining the two** (ADR 0036
//! amendment B). A caller enqueues durably by calling [`OutboxEnqueue::enqueue`] on the
//! provider store, in its own transaction: the message becomes visible when that transaction
//! commits and is published later by an [`OutboxDispatcher`], at-least-once, with the duplicate
//! windows below. A caller that instead wants to send now, with no Reliar durability at all, calls
//! the transport's own [`Publisher::publish`] directly — `reliar-outbox` never wraps it. Nothing
//! decides between the two at runtime, and no setting can.
//!
//! This slice ships the traits and types a provider builds against, a host configures, and the
//! [`OutboxDispatcher`] worker loop. Store behaviour is proven only against real Postgres (ADR
//! 0043): this crate constructs no store of its own, so the example below is a compiled generic
//! call shape, never invoked — `reliar-store-postgres`'s own rustdoc has the runnable version
//! over `PgPool::connect`.
//!
//! ```
//! use reliar_core::{Message, Publisher, SerializedEnvelope};
//! use reliar_outbox::OutboxEnqueue;
//!
//! # #[derive(serde::Serialize, serde::Deserialize)]
//! # struct OrderCreated;
//! # impl Message for OrderCreated {
//! # const TYPE: &'static str = "orders.created";
//! # const VERSION: u16 = 1;
//! # }
//! #
//! /// The durable path: any provider store implementing `OutboxEnqueue<Tx>` enqueues in the
//! /// caller's own transaction — no facade type in between (ADR 0036 amendment B). A bare
//! /// message becomes an envelope with default metadata and a freshly rooted conversation;
//! /// published later by an `OutboxDispatcher`. Use `enqueue_envelope` with `Envelope::builder(..)`
//! /// instead when an id must propagate from an inbound request.
//! async fn enqueue_order<S, Tx>(store: &S, tx: &mut Tx, body: OrderCreated) -> Result<(), S::Error>
//! where
//! S: OutboxEnqueue<Tx>,
//! {
//! store.enqueue(tx, body).await?;
//! Ok(())
//! }
//!
//! /// The bypass path needs no store: any `reliar_core::Publisher` — a real transport, or a
//! /// harness-local stand-in — sends now, with none of the outbox's guarantees.
//! async fn publish_now<P: Publisher>(
//! publisher: &P,
//! envelope: &SerializedEnvelope,
//! ) -> Result<(), P::Error> {
//! publisher.publish(envelope).await
//! }
//! ```
//!
//! # Guarantees
//!
//! - **Durable at-least-once publication. Never exactly-once.** Duplicate delivery is expected
//! and must be handled by an idempotent consumer. Three distinct windows produce a duplicate,
//! and all three are unavoidable:
//! 1. **The crash window:** a publish reaches the broker, the worker crashes before
//! `complete` persists, the lease expires, and another worker republishes the same message.
//! 2. **The slow-batch window:** no crash at all — a worker claims a large batch under
//! a lease shorter than the batch takes to drain, the lease expires while the worker is
//! still healthily publishing, a second worker reclaims and republishes the tail, and the
//! first worker's later `complete`/`fail` is fenced out by the row's `claim_token` (ADR 0046 A) —
//! it affects zero rows.
//! 3. **The drain window:** on cancellation, `run()` drains in-flight publishes for at
//! most `DispatcherSettings::drain_timeout`; a publish still unresolved at the timeout is
//! released rather than awaited further, and its outcome — success or failure — is the same
//! duplicate risk as the other two windows, just triggered by shutdown instead of a lease.
//! - **No ordering by default.** [`Ordering::Unordered`] (the default) guarantees **nothing**
//! about order — not globally, not per `conversation_id`, not per aggregate, not
//! approximately. `SKIP LOCKED`, concurrent publishing, per-message backoff and multiple
//! workers each reorder freely (ADR 0013). [`Ordering::PerKey`] is a configuration error in
//! this release — see [`Ordering::validate`].
//! - **Pure retry.** [`RetryPolicy`] is I/O-free and clock-free: it returns a [`core::time::Duration`],
//! never a timestamp. The store applies it as `available_at = now() + delay` in SQL, so a
//! worker's clock skew can never hot-loop a row or park it in the future (ADR 0009).
//! - **The library never reads the environment implicitly.** Only [`OutboxSettings::from_env`]
//! touches `std::env`, and only when called (ADR 0019).
//! - **Calling the transport publisher directly bypasses the outbox.** A call to a transport's
//! [`Publisher::publish`] carries **none** of the above: one attempt, no retry, no backoff, no
//! dead state, no duplicate window — only as much retry as the transport itself performs, and
//! no relationship to any transaction the caller has open. Use [`OutboxEnqueue::enqueue`]/
//! [`OutboxEnqueue::enqueue_envelope`] for the durable path instead (ADR 0036 amendment B).
//! See `docs/guides/outbox-enqueue-and-publish.md` for the full comparison.
pub use ClaimToken;
pub use ;
pub use OutboxEnqueue;
pub use ConfigError;
pub use ;
pub use Ordering;
pub use ;
pub use OutboxRecordId;
/// Re-exported from `reliar-core` (ADR 0032): a store author's or a publisher's `Classify`
/// bound, a publish/store failure's `FailureKind`, the `Publisher` capability trait, and the
/// shared `SettingsError` all live in core now. New code should name `reliar_core::` directly;
/// this re-export keeps existing `use reliar_outbox::{…}` imports one line.
pub use ;
pub use ;
pub use ;
pub use ;
pub use WorkerId;