dex_sdk/wait.rs
1// Copyright (c) 2026 Super Durable, Inc.
2//
3// Licensed under the Super Durable Source License 1.0.
4// You may not use this file except in compliance with the License.
5// See the LICENSE file in the repository root.
6//
7// SPDX-License-Identifier: LicenseRef-Super-Durable-1.0
8
9use std::sync::Arc;
10use std::time::Duration;
11
12/// Describes when Dex may invoke a Step's `execute` handler.
13///
14/// Build waits from durable timer and Channel [`Condition`] values. Combinators consume their
15/// inputs and preserve order, which determines timer indexes used by [`crate::TimerId`].
16///
17/// # Examples
18///
19/// ```
20/// use dex_sdk::{Channel, Timer, Wait};
21/// use std::time::Duration;
22///
23/// let approvals = Channel::<String>::new("approvals");
24/// let wait = Wait::any_of([
25/// approvals.for_one().with_id("approved"),
26/// Timer::by_duration(Duration::from_secs(300)).with_id("timeout"),
27/// ]);
28/// ```
29pub struct Wait {
30 pub(crate) kind: WaitKind,
31}
32
33pub(crate) enum WaitKind {
34 SkipImmediately,
35 AllOf(Vec<Condition>),
36 AnyOf(Vec<Condition>),
37 AnyCombinationOf(Vec<ConditionCombination>),
38}
39
40impl Wait {
41 /// Skips `wait_for` and asks Dex to invoke `execute` immediately.
42 pub fn skip_immediately() -> Self {
43 Self {
44 kind: WaitKind::SkipImmediately,
45 }
46 }
47
48 /// Waits until one condition is satisfied.
49 pub fn until(condition: Condition) -> Self {
50 Self::all_of([condition])
51 }
52
53 /// Waits until every condition is satisfied.
54 pub fn all_of(conditions: impl IntoIterator<Item = Condition>) -> Self {
55 Self {
56 kind: WaitKind::AllOf(conditions.into_iter().collect()),
57 }
58 }
59
60 /// Waits until at least one condition is satisfied.
61 pub fn any_of(conditions: impl IntoIterator<Item = Condition>) -> Self {
62 Self {
63 kind: WaitKind::AnyOf(conditions.into_iter().collect()),
64 }
65 }
66
67 /// Waits for every Condition in any combination.
68 ///
69 /// Every Condition needs a non-empty user ID. Clones of the same Condition
70 /// may be reused across combinations; distinct Conditions need unique IDs.
71 pub fn any_combination_of(
72 combinations: impl IntoIterator<Item = ConditionCombination>,
73 ) -> Self {
74 Self {
75 kind: WaitKind::AnyCombinationOf(combinations.into_iter().collect()),
76 }
77 }
78}
79
80/// Represents one durable timer or Channel predicate.
81///
82/// Conditions are created by [`crate::Timer`], [`crate::Channel`], or [`crate::ChannelMap`]. Assign
83/// an ID when identifying a timer independently or using [`Wait::any_combination_of`].
84#[derive(Clone)]
85pub struct Condition {
86 pub(crate) identity: Arc<ConditionIdentity>,
87 pub(crate) id: Option<String>,
88 pub(crate) kind: ConditionKind,
89}
90
91pub(crate) struct ConditionIdentity;
92
93#[derive(Clone)]
94pub(crate) enum ConditionKind {
95 Timer(Duration),
96 Channel {
97 name: String,
98 instance: Option<String>,
99 at_least: Option<usize>,
100 at_most: Option<usize>,
101 },
102}
103
104impl Condition {
105 pub(crate) fn timer(duration: Duration) -> Self {
106 Self {
107 identity: Arc::new(ConditionIdentity),
108 id: None,
109 kind: ConditionKind::Timer(duration),
110 }
111 }
112
113 pub(crate) fn channel(
114 name: String,
115 instance: Option<String>,
116 at_least: Option<usize>,
117 at_most: Option<usize>,
118 ) -> Self {
119 Self {
120 identity: Arc::new(ConditionIdentity),
121 id: None,
122 kind: ConditionKind::Channel {
123 name,
124 instance,
125 at_least,
126 at_most,
127 },
128 }
129 }
130
131 /// Assigns a stable ID used by [`crate::TimerId::by_condition_id`].
132 pub fn with_id(mut self, id: impl Into<String>) -> Self {
133 self.id = Some(id.into());
134 self
135 }
136}
137
138/// Groups conditions that must all succeed as one branch of [`Wait::any_combination_of`].
139pub struct ConditionCombination {
140 pub(crate) conditions: Vec<Condition>,
141}
142
143impl ConditionCombination {
144 /// Creates a branch satisfied only when all `conditions` are satisfied.
145 pub fn all_of(conditions: impl IntoIterator<Item = Condition>) -> Self {
146 Self {
147 conditions: conditions.into_iter().collect(),
148 }
149 }
150}