Skip to main content

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}