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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
//! # durare
//!
//! A [DBOS](https://docs.dbos.dev)-style **durable execution** library for
//! Rust.
//!
//! Write ordinary async code; wrap each side-effecting unit in a step. Every
//! step's result is checkpointed to a [`StateProvider`] (Postgres, SQLite, or
//! in-memory). If the process crashes or restarts, call
//! [`DurableEngine::recover`] and every unfinished workflow resumes exactly
//! where it stopped — completed steps are served from their checkpoints instead
//! of re-running.
//!
//! There is **no separate server**: the engine is a library that runs inside
//! your worker and talks directly to the database, using the same `dbos` system
//! schema as the DBOS Transact SDKs for Python, Go, and TypeScript — the SDKs
//! interoperate on one database.
//!
//! The problem this solves: any multi-step operation has crash windows — after
//! the card is charged but before the receipt is sent, the process dies, and a
//! naive retry charges twice. In `durare` the workflow id is an idempotency key
//! and every step is checkpointed, so a duplicate trigger (a retried webhook, a
//! double-click, a crashed-and-rerun caller) attaches to the same run instead
//! of repeating its effects. This example is a test — the assertions at the
//! bottom hold on every commit:
//!
//! ```
//! use durare::{DurableContext, DurableEngine, InMemoryProvider, Result, WorkflowOptions};
//! use std::sync::Arc;
//! use std::sync::atomic::{AtomicU32, Ordering};
//!
//! /// Stand-in for a payment API we must never call twice for the same order.
//! static CHARGES: AtomicU32 = AtomicU32::new(0);
//!
//! #[durare::step]
//! async fn charge_card(ctx: &DurableContext, order_id: String) -> Result<String> {
//! CHARGES.fetch_add(1, Ordering::SeqCst);
//! Ok(format!("ch_{order_id}"))
//! }
//!
//! #[durare::step]
//! async fn send_receipt(ctx: &DurableContext, charge_id: String) -> Result<()> {
//! // A crash between the two steps does NOT re-charge: on restart,
//! // `charge_card` is served from its checkpoint and the workflow
//! // resumes right here.
//! println!("emailing receipt for {charge_id}");
//! Ok(())
//! }
//!
//! #[durare::workflow]
//! async fn process_order(ctx: DurableContext, order_id: String) -> Result<String> {
//! // Reads like ordinary async code; each step checkpoints once.
//! let charge_id = charge_card(&ctx, order_id).await?;
//! send_receipt(&ctx, charge_id.clone()).await?;
//! Ok(charge_id)
//! }
//!
//! # #[tokio::main(flavor = "current_thread")]
//! # async fn main() -> Result<()> {
//! let engine = DurableEngine::new(Arc::new(InMemoryProvider::new())).await?;
//! engine.recover().await?; // after a crash: resume every unfinished workflow
//!
//! let handle = engine
//! .start_with(ProcessOrder, "1001".into(), WorkflowOptions::with_id("order-1001"))
//! .await?;
//! assert_eq!(handle.await?, "ch_1001");
//!
//! // The same trigger arrives again — same workflow id, no second charge.
//! let duplicate = engine
//! .start_with(ProcessOrder, "1001".into(), WorkflowOptions::with_id("order-1001"))
//! .await?;
//! assert_eq!(duplicate.await?, "ch_1001"); // served from the checkpoint
//! assert_eq!(CHARGES.load(Ordering::SeqCst), 1); // charged exactly once
//! # Ok(())
//! # }
//! ```
//!
//! Swap [`InMemoryProvider`] for [`PostgresProvider`] and the same guarantees
//! hold across processes, restarts, and a fleet of workers. For the full
//! crash-and-recover demo — a process killed mid-workflow, restarted, and
//! finishing without repeating work — run
//! [`examples/order.rs`](https://github.com/SamuelXing/durare/blob/main/examples/order.rs).
//!
//! # What's in the crate
//!
//! - **Workflows and steps** — [`DurableEngine`], [`DurableContext::step`] with
//! retry policies ([`StepOptions`]), typed starts via
//! [`DurableEngine::start_with`], durable [`sleep`](DurableContext::sleep).
//! - **Queues** — [`WorkflowQueue`]: worker and global concurrency, rate
//! limits, priorities, deduplication, partitions.
//! - **Messaging and events** — durable [`send`](DurableContext::send) /
//! [`recv`](DurableContext::recv) between workflows,
//! [`set_event`](DurableContext::set_event) /
//! [`get_event`](DurableContext::get_event) for observable state.
//! - **Streams** — append-only durable streams:
//! [`write_stream`](DurableContext::write_stream), read whole
//! ([`DurableEngine::read_stream`]) or incrementally
//! ([`read_stream_values`](DurableEngine::read_stream_values)).
//! - **Scheduling** — cron workflows via `#[durare::workflow(schedule = "…")]`,
//! plus managed schedules ([`DurableEngine::create_schedule`]: pause, resume,
//! trigger, backfill).
//! - **Transactions** — [`DurableContext::transaction`] commits your SQL and
//! the step checkpoint atomically, making the step exactly-once.
//! - **Composition** — child workflows
//! ([`start_workflow`](DurableContext::start_workflow)), durable
//! [`select`](DurableContext::select), code evolution with
//! [`patch`](DurableContext::patch).
//! - **Management and operations** — list / cancel / resume / fork, timeouts,
//! [`Debouncer`], the registry-less [`Client`] for other processes,
//! [`AdminServer`] (feature `admin`), [`Conductor`] (feature `conductor`).
//! - **Backends** — [`PostgresProvider`] (feature `postgres`),
//! [`SqliteProvider`] (feature `sqlite`), and [`InMemoryProvider`], all behind
//! the [`StateProvider`] seam.
//!
//! # Guides
//!
//! Five module-level guides explain the concepts in depth, `std`-style, each
//! with tested examples: start with [`durability`] (checkpoints, replay, and
//! the determinism contract — read this first), then its companion
//! [`determinism`] (the rules for writing a correct workflow body — deterministic
//! control flow, durable-safe data, and dependencies), and [`queues`],
//! [`messaging`], and [`transactions`]. Eleven runnable, end-to-end examples
//! live in [`examples/`](https://github.com/SamuelXing/durare/tree/main/examples).
//!
//! # Cargo features
//!
//! Backends are compiled behind features, all on by default; enable just one to
//! drop the other's driver. **At least one backend is required** — a
//! zero-backend build is a compile error. [`InMemoryProvider`] is always
//! available (no feature).
//!
//! - **`postgres`** *(default)* — the [`PostgresProvider`] backend.
//! - **`sqlite`** *(default)* — the [`SqliteProvider`] backend (a bundled C
//! library; drop it with `default-features = false, features = ["postgres"]`
//! for a pure-Postgres build that needs no C toolchain).
//! - **`conductor`** *(off by default)* — the DBOS Conductor client
//! ([`Conductor`], [`ConductorConfig`], [`AlertHandler`]): a websocket client
//! for the DBOS control plane, behind a feature because it pulls in a TLS
//! websocket stack and gzip framing.
//! - **`admin`** *(off by default)* — the [`AdminServer`] HTTP control surface
//! (health, recovery, and workflow management for the DBOS console/conductor
//! and health probes), behind a feature because it pulls in the axum/hyper/tower
//! HTTP stack.
// Render `#[doc(cfg(...))]` "available on feature X" banners on docs.rs (which
// builds with `--cfg docsrs`, see Cargo.toml). Inert on stable and CI builds.
// A SQL backend is required: the transaction layer and the error type are built
// on sqlx driver types that exist only when a backend is compiled. The in-memory
// backend is always available but isn't enough on its own.
compile_error!;
// Concept guides — std-style module pages (think `std::pin`) that each explain
// one subsystem, with tested examples. Implementation lives in the private
// modules below; the guides re-export the relevant types with
// `#[doc(no_inline)]` so every item's canonical documentation stays at the
// crate root.
pub use AdminServer;
pub use Client;
pub use ;
pub use ;
pub use ;
/// Macro plumbing referenced by `#[durare::workflow]`; not public API.
pub use WorkflowResult;
pub use ;
pub use ;
/// Re-exported so callers can consume the asynchronous stream returned by
/// `read_stream_values` (`StreamExt::next`) without depending on `futures` directly.
pub use ;
pub use WorkflowHandle;
pub use InMemoryProvider;
pub use PostgresProvider;
pub use ;
pub use ;
pub use ;
pub use ;
pub use SqliteProvider;
pub use ;
/// The `#[workflow]` attribute macro. Annotate an
/// `async fn(DurableContext, Input) -> Result<Output>` to have it
/// auto-registered with every [`DurableEngine`] in the binary.
pub use workflow;
/// The `#[step]` attribute macro. Annotate an
/// `async fn(&DurableContext, args..) -> Result<T>` to have its body run as a
/// durable [`DurableContext::step`] — checkpointed once, replayed thereafter —
/// so it reads like an ordinary async call.
pub use step;
/// The `#[transaction]` attribute macro. Annotate an
/// `async fn(&DurableContext, &mut Tx, args..) -> Result<T>` to have its body
/// run as a durable [`DurableContext::transaction`] — the SQL writes and the
/// checkpoint commit atomically — without the `|tx| Box::pin(..)` wrapper.
pub use transaction;
/// Re-exported so the `#[workflow]` macro can reference `durare::inventory::*`
/// from user crates without them depending on `inventory` directly.
pub use inventory;