Skip to main content

dex_sdk/
channel.rs

1// Copyright (c) 2026 Super Durable, Inc.
2//
3// Licensed under the Sustainable Use 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-Sustainable-Use-1.0
8
9use std::marker::PhantomData;
10
11use crate::{Condition, Context, HandlerResult, Value};
12
13#[derive(Clone, Debug, Eq, PartialEq)]
14pub(crate) struct ChannelLoad {
15    pub(crate) name: String,
16}
17
18/// Selects one ChannelMap instance's pending messages for a handler invocation.
19///
20/// Create instance loads with [`ChannelMap::load_messages`], then attach them with
21/// [`Rpc::load_channel_map_instance`](crate::Rpc::load_channel_map_instance).
22#[derive(Clone, Debug, Eq, PartialEq)]
23pub struct ChannelMapLoad {
24    pub(crate) name: String,
25    pub(crate) instance: Option<String>,
26}
27
28/// Identifies one typed value that is still pending in a Channel.
29#[derive(Clone, Debug, Eq, PartialEq)]
30pub struct ChannelMessage<T> {
31    /// UUIDv7 assigned by Dex when the message was published.
32    pub message_id: String,
33    /// Decoded Channel value.
34    pub value: T,
35}
36
37/// Defines one durable FIFO queue of typed messages.
38///
39/// Add the Channel to [`crate::PersistenceSchema`]. Clients and handlers may publish messages;
40/// Steps create [`Condition`] values that wait for queue-size bounds and read the messages consumed
41/// by the satisfied condition.
42///
43/// # Examples
44///
45/// ```
46/// use dex_sdk::{Channel, Wait};
47///
48/// let approvals = Channel::<String>::new("approvals");
49/// let wait = Wait::until(approvals.for_one());
50/// ```
51pub struct Channel<T> {
52    name: String,
53    marker: PhantomData<fn() -> T>,
54}
55
56impl<T> Channel<T> {
57    /// Defines a Channel with a stable `name`.
58    ///
59    /// Slash is prohibited because it is a reserved character.
60    pub fn new(name: impl Into<String>) -> Self {
61        Self {
62            name: name.into(),
63            marker: PhantomData,
64        }
65    }
66
67    /// Stages one message from the current Step or RPC invocation.
68    ///
69    /// # Errors
70    ///
71    /// Returns a [`crate::HandlerError`] when `value` cannot be encoded.
72    pub fn publish(&self, context: &mut Context, value: T) -> HandlerResult<()>
73    where
74        T: Value,
75    {
76        context.publish(self, value)
77    }
78
79    /// Stages best-effort deletion of one pending message from a handler.
80    pub fn delete(&self, context: &mut Context, message_id: &str) -> HandlerResult<()> {
81        context.delete_channel_message(self, message_id)
82    }
83
84    /// Returns the invocation snapshot's queued-message count.
85    pub fn size(&self, context: &Context) -> HandlerResult<usize> {
86        context.channel_size(self)
87    }
88
89    /// Returns loaded pending messages from the invocation snapshot in FIFO order.
90    ///
91    /// # Errors
92    ///
93    /// Returns a [`crate::HandlerError`] when this Channel was not selected by the handler options
94    /// or when decoding fails.
95    pub fn pending_messages(&self, context: &Context) -> HandlerResult<Vec<ChannelMessage<T>>>
96    where
97        T: Value,
98    {
99        context.pending_channel_messages(self)
100    }
101
102    /// Finds one pending message by its Dex-assigned ID.
103    ///
104    /// Returns `Ok(None)` when the loaded snapshot does not contain `message_id`.
105    ///
106    /// # Errors
107    ///
108    /// Returns the same errors as [`Self::pending_messages`].
109    pub fn find_pending_message(
110        &self,
111        context: &Context,
112        message_id: &str,
113    ) -> HandlerResult<Option<ChannelMessage<T>>>
114    where
115        T: Value,
116    {
117        Ok(self
118            .pending_messages(context)?
119            .into_iter()
120            .find(|message| message.message_id == message_id))
121    }
122
123    /// Decodes messages consumed by this Channel's satisfied condition.
124    ///
125    /// # Errors
126    ///
127    /// Returns a [`crate::HandlerError`] when a message cannot be decoded.
128    pub fn condition_results(&self, context: &Context) -> HandlerResult<Vec<T>>
129    where
130        T: Value,
131    {
132        context.channel_results(self)
133    }
134
135    /// Creates a condition that consumes exactly one queued message.
136    pub fn for_one(&self) -> Condition {
137        self.range(Some(1), Some(1))
138    }
139
140    /// Creates a condition that consumes exactly `count` queued messages.
141    pub fn for_n(&self, count: usize) -> Condition {
142        self.range(Some(count), Some(count))
143    }
144
145    /// Creates a condition satisfied when at least `count` messages are queued.
146    pub fn at_least(&self, count: usize) -> Condition {
147        self.range(Some(count), None)
148    }
149
150    /// Creates a non-blocking condition consuming up to `count` queued messages.
151    ///
152    /// When its surrounding Wait completes, it consumes messages queued then. An empty queue yields
153    /// no messages.
154    pub fn at_most(&self, count: usize) -> Condition {
155        self.range(None, Some(count))
156    }
157
158    /// Creates a condition that waits for the lower bound, then consumes up to the upper bound.
159    ///
160    /// Omitting the lower bound makes the condition complete immediately. Consumption uses only
161    /// messages queued when the condition completes.
162    pub fn range(&self, at_least: Option<usize>, at_most: Option<usize>) -> Condition {
163        Condition::channel(self.name.clone(), None, at_least, at_most)
164    }
165
166    /// Creates a guard used by conditional Flow completion to require an empty queue.
167    pub fn when_empty(&self) -> ChannelGuard {
168        ChannelGuard {
169            name: self.name.clone(),
170            instance: None,
171        }
172    }
173
174    pub(crate) fn name(&self) -> &str {
175        &self.name
176    }
177}
178
179impl<T> Clone for Channel<T> {
180    fn clone(&self) -> Self {
181        Self {
182            name: self.name.clone(),
183            marker: PhantomData,
184        }
185    }
186}
187
188/// Defines independently queued Channel instances under one name.
189///
190/// Supply an instance string for every publish, read, condition, and completion guard. Add the map
191/// definition once to [`crate::PersistenceSchema`].
192/// Slash is prohibited in instance keys because it is a reserved character.
193pub struct ChannelMap<T> {
194    name: String,
195    marker: PhantomData<fn() -> T>,
196}
197
198impl<T> ChannelMap<T> {
199    /// Defines a Channel map with a stable `name`.
200    ///
201    /// Slash is prohibited because it is a reserved character.
202    pub fn new(name: impl Into<String>) -> Self {
203        Self {
204            name: name.into(),
205            marker: PhantomData,
206        }
207    }
208
209    /// Selects one slash-free map `instance`'s pending messages for a handler Context.
210    pub fn load_messages(&self, instance: impl Into<String>) -> ChannelMapLoad {
211        ChannelMapLoad {
212            name: self.name.clone(),
213            instance: Some(instance.into()),
214        }
215    }
216
217    /// Stages one message for `instance` from the current invocation.
218    ///
219    /// # Errors
220    ///
221    /// Returns a [`crate::HandlerError`] when `value` cannot be encoded.
222    pub fn publish(&self, context: &mut Context, instance: &str, value: T) -> HandlerResult<()>
223    where
224        T: Value,
225    {
226        context.publish_map(self, instance, value)
227    }
228
229    /// Stages best-effort deletion of one pending message from a Channel-map instance.
230    pub fn delete(
231        &self,
232        context: &mut Context,
233        instance: &str,
234        message_id: &str,
235    ) -> HandlerResult<()> {
236        context.delete_channel_map_message(self, instance, message_id)
237    }
238
239    /// Returns the invocation snapshot's message count for `instance`.
240    pub fn size(&self, context: &Context, instance: &str) -> HandlerResult<usize> {
241        context.channel_map_size(self, instance)
242    }
243
244    /// Returns one instance's loaded pending messages in FIFO order.
245    ///
246    /// # Errors
247    ///
248    /// Returns a [`crate::HandlerError`] when the instance was not selected by the handler options
249    /// or when a Value cannot be decoded.
250    pub fn pending_messages(
251        &self,
252        context: &Context,
253        instance: &str,
254    ) -> HandlerResult<Vec<ChannelMessage<T>>>
255    where
256        T: Value,
257    {
258        context.pending_channel_map_messages(self, instance)
259    }
260
261    /// Finds one pending message by ID within one loaded map instance.
262    ///
263    /// Returns `Ok(None)` when the loaded snapshot does not contain `message_id`.
264    ///
265    /// # Errors
266    ///
267    /// Returns the same errors as [`Self::pending_messages`].
268    pub fn find_pending_message(
269        &self,
270        context: &Context,
271        instance: &str,
272        message_id: &str,
273    ) -> HandlerResult<Option<ChannelMessage<T>>>
274    where
275        T: Value,
276    {
277        Ok(self
278            .pending_messages(context, instance)?
279            .into_iter()
280            .find(|message| message.message_id == message_id))
281    }
282
283    /// Returns the number of non-empty instances visible to the current handler.
284    pub fn map_size(&self, context: &Context) -> HandlerResult<usize> {
285        Ok(self.all_instance_keys(context)?.len())
286    }
287
288    /// Returns sorted decoded non-empty keys, including buffered publishes.
289    pub fn all_instance_keys(&self, context: &Context) -> HandlerResult<Vec<String>> {
290        context.channel_map_keys(self)
291    }
292
293    /// Decodes messages consumed by `instance`'s satisfied condition.
294    ///
295    /// # Errors
296    ///
297    /// Returns a [`crate::HandlerError`] when a message cannot be decoded.
298    pub fn condition_results(&self, context: &Context, instance: &str) -> HandlerResult<Vec<T>>
299    where
300        T: Value,
301    {
302        context.channel_map_results(self, instance)
303    }
304
305    /// Creates an `instance` condition that consumes exactly one message.
306    pub fn for_one(&self, instance: &str) -> Condition {
307        self.range(instance, Some(1), Some(1))
308    }
309
310    /// Creates an `instance` condition that consumes exactly `count` messages.
311    pub fn for_n(&self, instance: &str, count: usize) -> Condition {
312        self.range(instance, Some(count), Some(count))
313    }
314
315    /// Creates an `instance` condition with an inclusive lower queue-size bound.
316    pub fn at_least(&self, instance: &str, count: usize) -> Condition {
317        self.range(instance, Some(count), None)
318    }
319
320    /// Creates a non-blocking `instance` condition consuming up to `count` queued messages.
321    ///
322    /// When its surrounding Wait completes, it consumes messages queued then. An empty queue yields
323    /// no messages.
324    pub fn at_most(&self, instance: &str, count: usize) -> Condition {
325        self.range(instance, None, Some(count))
326    }
327
328    /// Creates an `instance` condition that waits for the lower bound, then consumes to its upper.
329    ///
330    /// Omitting the lower bound makes the condition complete immediately. Consumption uses only
331    /// messages queued when the condition completes.
332    pub fn range(
333        &self,
334        instance: &str,
335        at_least: Option<usize>,
336        at_most: Option<usize>,
337    ) -> Condition {
338        crate::registry::assert_map_instance(instance);
339        Condition::channel(
340            self.name.clone(),
341            Some(instance.to_string()),
342            at_least,
343            at_most,
344        )
345    }
346
347    /// Creates a conditional-completion guard requiring `instance` to be empty.
348    pub fn when_empty(&self, instance: &str) -> ChannelGuard {
349        crate::registry::assert_map_instance(instance);
350        ChannelGuard {
351            name: self.name.clone(),
352            instance: Some(instance.to_string()),
353        }
354    }
355
356    pub(crate) fn name(&self) -> &str {
357        &self.name
358    }
359}
360
361impl<T> Clone for ChannelMap<T> {
362    fn clone(&self) -> Self {
363        Self {
364            name: self.name.clone(),
365            marker: PhantomData,
366        }
367    }
368}
369
370#[derive(Clone, Debug, Eq, PartialEq)]
371/// Identifies a Channel or Channel-map instance that must be empty before conditional completion.
372pub struct ChannelGuard {
373    name: String,
374    instance: Option<String>,
375}
376
377impl ChannelGuard {
378    pub(crate) fn physical_name(&self) -> String {
379        match self.instance.as_deref() {
380            Some(instance) => crate::registry::physical_name(&self.name, instance),
381            None => self.name.clone(),
382        }
383    }
384}