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
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
//! DBOS Transact for Rust — lightweight durable workflows on Postgres.
//!
//! This crate is the Rust port of [DBOS Transact](https://docs.dbos.dev/), joining the
//! Python, TypeScript, Go, and Java implementations. All five share one Postgres system
//! database schema, so a Rust application can run alongside applications written in any
//! of the others.
//!
//! # Status
//!
//! Under construction. The system database layer, the lifecycle, registration, workflows,
//! steps, events, messaging, recovery, queues and the client are here; the scheduler is not yet.
//!
//! # Cargo features
//!
//! - `engine` *(default)* — the durable execution engine: registry, contexts, workflows,
//! steps, queues, messaging, and the client. Turning it off leaves the system database
//! layer, which is the surface a future FFI host would consume.
//! - `macros` *(default)* — [`select_step!`], the durable race, which is a procedural macro and so
//! costs `syn` and `quote` at build time. Implies `engine`, because the expansion is engine
//! code; nothing it generates reaches the binary.
// A procedural macro has no `$crate`, so `select_step!`'s expansion names `::dbos` absolutely;
// this is what lets that path resolve inside this crate's own tests and doctests.
extern crate self as dbos;
// Flattened deliberately: the crate path is the branding, so these are `dbos::Config` and
// `dbos::Error` rather than `dbos::config::Config`. `DBOS` is the one type that spells the brand,
// because it *is* the brand — nobody writes `tokio::TOKIO`.
pub use PendingStep;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use WorkflowHandle;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use sleep;
pub use ;
pub use ;
// Re-exported because [`cancellation_token`] hands one back: naming what it returns should not
// oblige an application to declare `tokio-util` itself. It is the same foreign type either way, so
// this makes `tokio-util` a public dependency rather than sparing anyone one — a major bump of it
// is a breaking change here, and an application on a different major sees a type mismatch the
// moment it passes a token across this boundary. The alternative, a newtype, would have to
// reimplement the half of `CancellationToken` a body actually waits on, to hide a coupling that
// watching a token is anyway.
pub use CancellationToken;
pub use ;
/// Races these durable calls and runs the arm belonging to the one that finishes first.
///
/// The durable race, and the reason a plain `tokio::select!` over steps is a trap rather than a
/// shortcut. Since a step takes its id when it is *built*, a `select!` over steps allocates ids
/// deterministically and so looks fixed — and it is still not durable, because nothing records
/// which branch won and a replay is free to choose again, taking a path the first execution never
/// took. This records the winning position, and a replay polls only that branch, which then
/// replays from its own checkpoint without running.
///
/// ```no_run
/// # async fn charge(cents: u32) -> dbos::Result<u32> { Ok(cents) }
/// # async fn timer() -> dbos::Result<()> { Ok(()) }
/// # async fn f(cents: u32) -> dbos::Result<String> {
/// use dbos::step;
/// dbos::select_step! {
/// charged = step("charge", || charge(cents)) => {
/// let cents = charged?;
/// format!("charged {cents}")
/// }
/// expired = step("expire", || timer()) => { expired?; "timed out".to_owned() }
/// }
/// # }
/// ```
///
/// # This crate may not be renamed
///
/// The expansion names `::dbos` — this crate, by that name — so a dependency renamed with
/// `dbos_sdk = { package = "dbos" }` puts only `dbos_sdk` in the extern prelude and the expansion
/// resolves to nothing. Resolving the downstream name instead means `proc_macro_crate`, which
/// reads the dependent's manifest and so brings a TOML parser into the build graph — against the
/// point of the `macros` feature. `tokio` makes the same trade for `#[tokio::main]`. If a rename
/// ever has to be supported, the cheap way is `tokio`'s: an optional `crate = path` at the head of
/// the macro, which costs no dependency.
///
/// # Arms, and the one place this is not `match`
///
/// An arm is `binding = call => expression`, and the comma between arms follows `match`'s rule
/// exactly: optional after a body that ends in a block, required otherwise.
///
/// # A branch is any pending call that observes
///
/// A branch is any [`PendingStep`]: a [`step`](step()), a handle's
/// [`result`](WorkflowHandle::result), a wait over workflows
/// ([`select_workflow`](fn@select_workflow) or [`join_workflows`](fn@join_workflows)), a
/// [`get_event`], a [`set_event`], a [`sleep`](sleep()), or a checkpointed management call on
/// [`DBOS`]. Each checkpoints itself under the id it was built with and replays from its own row
/// when it is the recorded winner, so a race between a step and the await of a child is as durable
/// as one between two steps.
///
/// **Where *every* branch is a workflow's outcome, reach for
/// [`select_workflow!`](macro@crate::select_workflow) instead.** Both are durable; the difference
/// is what they cost. One wait settles the whole set — one query per poll interval however many
/// handles it is given — where N awaits raced here are N pollers asking separately. This macro is
/// for a race whose branches are *not* all of one kind: a step against an await, a sleep against
/// an event.
///
/// **A [`start`](WorkflowRef::start) and a [`run`](WorkflowRef::run) are refused, by type.** They
/// hand back a [`PendingStart`] and a [`PendingWorkflow`] rather than a `PendingStep`, and both
/// *create* a workflow: only
/// the winner is polled on a replay, so whether a child exists at all would follow the timing of
/// another branch, and a run additionally holds two ids and would leave a started, recorded child
/// whose outcome the parent never learns. Start outside the race, and race the handle's
/// [`result`](WorkflowHandle::result) — which observes rather than creates, and is the branch that
/// was meant.
///
/// **What losing means.** A losing step is dropped mid-body and records nothing, so a replay never
/// runs it; its [`cancellation_token`] fires on the way out, as it does for a timeout, so work it
/// handed to a blocking thread learns to stop. A losing [`sleep`](sleep()) is the exception that
/// does leave a row: it checkpoints the instant it will wake at *before* waiting on it, so what
/// stays behind records an abandoned wait rather than an outcome. A losing *await* is a dropped
/// wait on a child that keeps going, durably, with nobody watching it — losing the race does not
/// cancel it. Cancel from the winning arm if abandoning the loser is the intent.
///
/// **A control signal winning is not a decision.** A branch that resolves to a cancellation, an
/// interruption or a database failure has recorded nothing, as a step ending that way never does,
/// and the race records nothing either: the error is returned, the workflow stays pending, and a
/// recovery races every branch afresh. Recording that branch as the winner would pin every
/// recovery to one that never ran its body. An application error is the branch's own recorded
/// outcome, and winning with one is recorded and replayed like any other win.
///
/// **Every branch fails the same way, and is made to say so before the race.** The branches are
/// tied to one error type when they are pushed, which is at the build — so `map_err` on an arm's
/// body is too late, and the conversion belongs on the call. A call in the engine's channel joins
/// with [`lift`](PendingStep::lift), which the compiler writes itself because
/// [`EngineOnly`] is uninhabited; a call with an application error type of its
/// own joins with [`map_error`](PendingStep::map_error), which the caller writes because only the
/// caller knows what one failure means in terms of the other.
///
/// # A branch is an expression, and exactly one call
///
/// **An expression, where [`select_workflow!`](macro@crate::select_workflow) needs a variable.**
/// That looks like the pair disagreeing and is not: a workflow handle is named twice there, once
/// for its id and once to be consumed, so an expression would be evaluated twice. A branch here is
/// named once — this macro builds it, owns it, polls it, drops it — so the hazard does not exist
/// and the form that reads like `tokio::select!` is available.
///
/// **Exactly one call, not a block containing several.** The branches are built before any is
/// polled, which is what fixes their ids; an `async` block would defer the calls inside it to its
/// first poll and put their ids back on poll order. Work needing several steps in one branch is a
/// child workflow, which has a counter of its own — and the handle's `result` can be the branch.
///
/// # What it refuses, and why each refusal is the design
///
/// **Guards (`if cond`), `else` and `complete` arms.** All three change which branches exist
/// between runs, and the checkpoint records a *position* among them: a guard true on the first run
/// and false on the replay would make the recorded index name a branch that is no longer there.
///
/// **`biased`.** Not an option to offer, because fixed source order is the contract. Tokio
/// randomises for fairness, and fairness is exactly what a replay cannot reproduce.
///
/// **Fewer than two branches.** One branch is not a race: await the call, which is shorter and
/// durable on its own. There is no upper bound — a procedural macro names a slot per branch and
/// runs out of nothing — though a race wide enough to want one is usually a child workflow per
/// branch, which has a step counter of its own.
///
/// Each refusal is a `compile_error!` spanned on the offending tokens, which is better than
/// [`select_workflow!`](macro@crate::select_workflow) manages: it takes a slice and cannot see an
/// empty one until it runs.
///
/// # Inside and outside a workflow
///
/// Inside one, the winning position is recorded under
/// [`SELECT_STEP`](crate::sysdb::types::step_names::SELECT_STEP) and the branches checkpoint
/// themselves under the ids they were built with. Outside one — or inside another step — nothing
/// is recorded and the branches run plainly, which is the same fall-through an ordinary step takes
/// and what keeps a function built from steps ordinarily testable.
///
/// The value is a [`Result`], because the race itself can fail. Each arm binds its own branch's
/// outcome, so an arm decides for itself whether to `?` it, match it, or report it.
pub use select_step;
pub use ;
/// What [`select_step!`](crate::select_step) expands into, and **not public API**.
///
/// A procedural macro has no `$crate`, so its expansion has to name an absolute path that the
/// calling crate can resolve — which means everything the expansion calls must be `pub`. This
/// module is where that surface lives, so that being `pub` for the macro's sake is not the same as
/// being part of the crate's API: nothing here is documented, nothing here is stable, and calling
/// any of it by hand is writing an expansion by hand.
///
/// The durable race itself is documented on [`select_step!`](crate::select_step); the reasoning
/// behind what it records is in `select.rs`.
///
/// Gated on `engine` rather than on `macros`, which is what actually reaches it: these are the
/// only paths into the race's core, and without them an engine-only build has a module of `pub`
/// items nothing can call.