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::any::TypeId;
10use std::sync::Arc;
11use std::time::Duration;
12
13use dex_protocol::dex::Value as ProtoValue;
14
15use crate::SubFlowOptions;
16
17/// Describes when Dex may invoke a Step's `execute` handler.
18///
19/// Build waits from durable timer and Channel [`Condition`] values. Combinators consume their
20/// inputs and preserve order, which determines timer indexes used by [`crate::TimerId`].
21///
22/// # Examples
23///
24/// ```
25/// use dex_sdk::{Channel, Timer, Wait};
26/// use std::time::Duration;
27///
28/// let approvals = Channel::<String>::new("approvals");
29/// let wait = Wait::any_of([
30///     approvals.for_one().with_id("approved"),
31///     Timer::by_duration(Duration::from_secs(300)).with_id("timeout"),
32/// ]);
33/// ```
34pub struct Wait {
35    pub(crate) kind: WaitKind,
36}
37
38pub(crate) enum WaitKind {
39    SkipImmediately,
40    AllOf(Vec<Condition>),
41    AnyOf(Vec<Condition>),
42    AnyCombinationOf(Vec<ConditionCombination>),
43}
44
45impl Wait {
46    /// Skips `wait_for` and asks Dex to invoke `execute` immediately.
47    pub fn skip_immediately() -> Self {
48        Self {
49            kind: WaitKind::SkipImmediately,
50        }
51    }
52
53    /// Waits until one condition is satisfied.
54    pub fn until(condition: Condition) -> Self {
55        Self::all_of([condition])
56    }
57
58    /// Waits until every condition is satisfied.
59    pub fn all_of(conditions: impl IntoIterator<Item = Condition>) -> Self {
60        Self {
61            kind: WaitKind::AllOf(conditions.into_iter().collect()),
62        }
63    }
64
65    /// Waits until at least one condition is satisfied.
66    pub fn any_of(conditions: impl IntoIterator<Item = Condition>) -> Self {
67        Self {
68            kind: WaitKind::AnyOf(conditions.into_iter().collect()),
69        }
70    }
71
72    /// Waits for every Condition in any combination.
73    ///
74    /// Every Condition needs a non-empty user ID. Clones of the same Condition
75    /// may be reused across combinations; distinct Conditions need unique IDs.
76    pub fn any_combination_of(
77        combinations: impl IntoIterator<Item = ConditionCombination>,
78    ) -> Self {
79        Self {
80            kind: WaitKind::AnyCombinationOf(combinations.into_iter().collect()),
81        }
82    }
83}
84
85/// Represents one durable timer, Channel, or SubFlow predicate.
86///
87/// Conditions are created by [`crate::Timer`], [`crate::Channel`], or [`crate::ChannelMap`]. Assign
88/// an ID when identifying a timer independently or using [`Wait::any_combination_of`].
89#[derive(Clone)]
90pub struct Condition {
91    pub(crate) identity: Arc<ConditionIdentity>,
92    pub(crate) id: Option<String>,
93    pub(crate) kind: ConditionKind,
94}
95
96pub(crate) struct ConditionIdentity;
97
98#[derive(Clone)]
99pub(crate) enum ConditionKind {
100    Timer(Duration),
101    Channel {
102        name: String,
103        instance: Option<String>,
104        at_least: Option<usize>,
105        at_most: Option<usize>,
106    },
107    SubFlow(Box<SubFlowDefinition>),
108}
109
110#[derive(Clone)]
111pub(crate) struct SubFlowDefinition {
112    pub(crate) flow_type: &'static str,
113    pub(crate) type_id: TypeId,
114    pub(crate) input: ProtoValue,
115    pub(crate) options: SubFlowOptions,
116}
117
118impl Condition {
119    pub(crate) fn timer(duration: Duration) -> Self {
120        Self {
121            identity: Arc::new(ConditionIdentity),
122            id: None,
123            kind: ConditionKind::Timer(duration),
124        }
125    }
126
127    pub(crate) fn channel(
128        name: String,
129        instance: Option<String>,
130        at_least: Option<usize>,
131        at_most: Option<usize>,
132    ) -> Self {
133        Self {
134            identity: Arc::new(ConditionIdentity),
135            id: None,
136            kind: ConditionKind::Channel {
137                name,
138                instance,
139                at_least,
140                at_most,
141            },
142        }
143    }
144
145    pub(crate) fn sub_flow(definition: SubFlowDefinition) -> Self {
146        let id = definition.options.condition_id.clone();
147        Self {
148            identity: Arc::new(ConditionIdentity),
149            id,
150            kind: ConditionKind::SubFlow(Box::new(definition)),
151        }
152    }
153
154    /// Assigns a stable ID used by [`crate::TimerId::by_condition_id`].
155    pub fn with_id(mut self, id: impl Into<String>) -> Self {
156        self.id = Some(id.into());
157        self
158    }
159}
160
161/// Groups conditions that must all succeed as one branch of [`Wait::any_combination_of`].
162pub struct ConditionCombination {
163    pub(crate) conditions: Vec<Condition>,
164}
165
166impl ConditionCombination {
167    /// Creates a branch satisfied only when all `conditions` are satisfied.
168    pub fn all_of(conditions: impl IntoIterator<Item = Condition>) -> Self {
169        Self {
170            conditions: conditions.into_iter().collect(),
171        }
172    }
173}