Skip to main content

nodedb_lite/nodedb/
definitions.rs

1//! Lite catalog for function, trigger, and procedure definitions.
2//!
3//! Stores definitions in `Namespace::Meta` using typed keys:
4//! - `function:{name}` → serialized StoredFunction
5//! - `trigger:{name}` → serialized StoredTrigger
6//! - `procedure:{name}` → serialized StoredProcedure
7//!
8//! Definitions are synced from Origin via CRDT (definitions only — execution
9//! is local). Triggers fire on Lite-originated writes only, not on CRDT sync
10//! merges (origin-only rule).
11
12use nodedb_types::error::{NodeDbError, NodeDbResult};
13
14use super::NodeDbLite;
15use crate::storage::engine::StorageEngine;
16
17/// A stored function definition (mirrors Origin's StoredFunction).
18#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
19pub struct LiteStoredFunction {
20    pub name: String,
21    pub parameters: Vec<LiteFunctionParam>,
22    pub return_type: String,
23    pub body_sql: String,
24    pub owner: String,
25    pub created_at: u64,
26}
27
28#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
29pub struct LiteFunctionParam {
30    pub name: String,
31    pub data_type: String,
32}
33
34/// A stored trigger definition (mirrors Origin's StoredTrigger).
35#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
36pub struct LiteStoredTrigger {
37    pub name: String,
38    pub collection: String,
39    pub timing: String,
40    pub events: Vec<String>,
41    pub granularity: String,
42    pub when_condition: Option<String>,
43    pub body_sql: String,
44    pub priority: i32,
45    pub enabled: bool,
46    pub execution_mode: String,
47    pub owner: String,
48    pub created_at: u64,
49}
50
51/// A stored procedure definition (mirrors Origin's StoredProcedure).
52#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
53pub struct LiteStoredProcedure {
54    pub name: String,
55    pub parameters: Vec<LiteProcedureParam>,
56    pub body_sql: String,
57    pub max_iterations: u64,
58    pub timeout_secs: u64,
59    pub owner: String,
60    pub created_at: u64,
61}
62
63#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
64pub struct LiteProcedureParam {
65    pub name: String,
66    pub data_type: String,
67    pub direction: String,
68}
69
70// ── Generic CRUD helpers ────────────────────────────────────────────────
71
72impl<S: StorageEngine> NodeDbLite<S> {
73    /// Store a definition in the local catalog under `{prefix}:{name}`.
74    async fn put_definition(
75        &self,
76        prefix: &str,
77        name: &str,
78        def: &impl serde::Serialize,
79    ) -> NodeDbResult<()> {
80        let key = format!("{prefix}:{name}");
81        let bytes = sonic_rs::to_vec(def).map_err(|e| NodeDbError::storage(e.to_string()))?;
82        self.storage
83            .put(nodedb_types::Namespace::Meta, key.as_bytes(), &bytes)
84            .await?;
85        Ok(())
86    }
87
88    /// Get a definition by key prefix and name.
89    async fn get_definition<T: serde::de::DeserializeOwned>(
90        &self,
91        prefix: &str,
92        name: &str,
93    ) -> NodeDbResult<Option<T>> {
94        let key = format!("{prefix}:{name}");
95        let bytes = self
96            .storage
97            .get(nodedb_types::Namespace::Meta, key.as_bytes())
98            .await?;
99        match bytes {
100            Some(b) => {
101                let def: T =
102                    sonic_rs::from_slice(&b).map_err(|e| NodeDbError::storage(e.to_string()))?;
103                Ok(Some(def))
104            }
105            None => Ok(None),
106        }
107    }
108
109    /// List all definitions with a given key prefix.
110    async fn list_definitions<T: serde::de::DeserializeOwned>(
111        &self,
112        prefix: &str,
113    ) -> NodeDbResult<Vec<T>> {
114        let scan_prefix = format!("{prefix}:");
115        let pairs = self
116            .storage
117            .scan_prefix(nodedb_types::Namespace::Meta, scan_prefix.as_bytes())
118            .await?;
119        let mut defs = Vec::with_capacity(pairs.len());
120        for (_key, value) in pairs {
121            if let Ok(d) = sonic_rs::from_slice::<T>(&value) {
122                defs.push(d);
123            }
124        }
125        Ok(defs)
126    }
127
128    /// Delete a definition by key prefix and name.
129    async fn delete_definition(&self, prefix: &str, name: &str) -> NodeDbResult<()> {
130        let key = format!("{prefix}:{name}");
131        self.storage
132            .delete(nodedb_types::Namespace::Meta, key.as_bytes())
133            .await?;
134        Ok(())
135    }
136}
137
138// ── Type-specific convenience methods ───────────────────────────────────
139
140impl<S: StorageEngine> NodeDbLite<S> {
141    /// Store a function definition in the local catalog.
142    pub async fn put_function(&self, func: &LiteStoredFunction) -> NodeDbResult<()> {
143        self.put_definition("function", &func.name, func).await
144    }
145
146    /// Get a function definition by name.
147    pub async fn get_function(&self, name: &str) -> NodeDbResult<Option<LiteStoredFunction>> {
148        self.get_definition("function", name).await
149    }
150
151    /// List all function definitions.
152    pub async fn list_functions(&self) -> NodeDbResult<Vec<LiteStoredFunction>> {
153        self.list_definitions("function").await
154    }
155
156    /// Delete a function definition.
157    pub async fn delete_function(&self, name: &str) -> NodeDbResult<()> {
158        self.delete_definition("function", name).await
159    }
160
161    /// Store a trigger definition in the local catalog.
162    pub async fn put_trigger(&self, trigger: &LiteStoredTrigger) -> NodeDbResult<()> {
163        self.put_definition("trigger", &trigger.name, trigger).await
164    }
165
166    /// Get a trigger definition by name.
167    pub async fn get_trigger(&self, name: &str) -> NodeDbResult<Option<LiteStoredTrigger>> {
168        self.get_definition("trigger", name).await
169    }
170
171    /// List all trigger definitions.
172    pub async fn list_triggers(&self) -> NodeDbResult<Vec<LiteStoredTrigger>> {
173        self.list_definitions("trigger").await
174    }
175
176    /// List triggers for a specific collection.
177    pub async fn list_triggers_for_collection(
178        &self,
179        collection: &str,
180    ) -> NodeDbResult<Vec<LiteStoredTrigger>> {
181        let all = self.list_triggers().await?;
182        Ok(all
183            .into_iter()
184            .filter(|t| t.collection == collection)
185            .collect())
186    }
187
188    /// Delete a trigger definition.
189    pub async fn delete_trigger(&self, name: &str) -> NodeDbResult<()> {
190        self.delete_definition("trigger", name).await
191    }
192
193    /// Store a procedure definition in the local catalog.
194    pub async fn put_procedure(&self, proc: &LiteStoredProcedure) -> NodeDbResult<()> {
195        self.put_definition("procedure", &proc.name, proc).await
196    }
197
198    /// Get a procedure definition by name.
199    pub async fn get_procedure(&self, name: &str) -> NodeDbResult<Option<LiteStoredProcedure>> {
200        self.get_definition("procedure", name).await
201    }
202
203    /// List all procedure definitions.
204    pub async fn list_procedures(&self) -> NodeDbResult<Vec<LiteStoredProcedure>> {
205        self.list_definitions("procedure").await
206    }
207
208    /// Delete a procedure definition.
209    pub async fn delete_procedure(&self, name: &str) -> NodeDbResult<()> {
210        self.delete_definition("procedure", name).await
211    }
212}