Skip to main content

dex_sdk/
persistence.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 crate::{Attribute, AttributeIndex, AttributeMap, Channel, ChannelMap};
10
11#[derive(Clone, Debug, Default, Eq, PartialEq)]
12/// Declares the Attributes and Channels a Flow persists.
13///
14/// Return a schema from [`crate::Flow::persistence`]. Definitions must have unique names,
15/// and all values accessed by Step or RPC code must appear in the schema.
16///
17/// # Examples
18///
19/// ```
20/// use dex_sdk::{Attribute, Channel, PersistenceSchema};
21///
22/// let status = Attribute::<String>::new("status");
23/// let commands = Channel::<String>::new("commands");
24/// let schema = PersistenceSchema::new().attribute(&status).channel(&commands);
25/// ```
26pub struct PersistenceSchema {
27    definitions: Vec<PersistenceDefinition>,
28}
29
30#[derive(Clone, Debug, Eq, PartialEq)]
31pub(crate) struct PersistenceDefinition {
32    pub(crate) name: String,
33    pub(crate) kind: PersistenceKind,
34    pub(crate) index: Option<AttributeIndex>,
35    pub(crate) sync_to_attribute_store: bool,
36}
37
38#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39pub(crate) enum PersistenceKind {
40    Attribute,
41    AttributeMap,
42    Channel,
43    ChannelMap,
44}
45
46impl PersistenceSchema {
47    /// Creates an empty schema.
48    pub fn new() -> Self {
49        Self::default()
50    }
51
52    /// Adds one single-value Attribute definition.
53    pub fn attribute<T>(mut self, attribute: &Attribute<T>) -> Self {
54        self.add(
55            attribute.name(),
56            PersistenceKind::Attribute,
57            attribute.index().cloned(),
58            attribute.is_sync_to_attribute_store(),
59        );
60        self
61    }
62
63    /// Adds one keyed Attribute-map definition.
64    pub fn attribute_map<T>(mut self, attribute: &AttributeMap<T>) -> Self {
65        self.add(
66            attribute.name(),
67            PersistenceKind::AttributeMap,
68            attribute.index().cloned(),
69            attribute.is_sync_to_attribute_store(),
70        );
71        self
72    }
73
74    /// Adds one Channel definition.
75    pub fn channel<T>(mut self, channel: &Channel<T>) -> Self {
76        self.add(channel.name(), PersistenceKind::Channel, None, false);
77        self
78    }
79
80    /// Adds one keyed Channel-map definition.
81    pub fn channel_map<T>(mut self, channel: &ChannelMap<T>) -> Self {
82        self.add(channel.name(), PersistenceKind::ChannelMap, None, false);
83        self
84    }
85
86    pub(crate) fn definitions(&self) -> &[PersistenceDefinition] {
87        &self.definitions
88    }
89
90    fn add(
91        &mut self,
92        name: &str,
93        kind: PersistenceKind,
94        index: Option<AttributeIndex>,
95        sync_to_attribute_store: bool,
96    ) {
97        self.definitions.push(PersistenceDefinition {
98            name: name.to_string(),
99            kind,
100            index,
101            sync_to_attribute_store,
102        });
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::PersistenceSchema;
109    use crate::{Attribute, AttributeMap};
110
111    #[test]
112    fn registration_metadata_retains_sync_configuration() {
113        let attribute = Attribute::<String>::new("plain");
114        let attribute_map = AttributeMap::<String>::new("map").sync_to_attribute_store();
115        let schema = PersistenceSchema::new()
116            .attribute(&attribute)
117            .attribute_map(&attribute_map);
118
119        assert!(!schema.definitions[0].sync_to_attribute_store);
120        assert!(schema.definitions[1].sync_to_attribute_store);
121    }
122}