ironflow_store/memory/mod.rs
1//! In-memory [`Store`](crate::store::Store) implementation for development and testing.
2//!
3//! [`InMemoryStore`] uses `Arc<RwLock<..>>` internally, making it safe to share
4//! across tasks. Data is lost when the process exits.
5//!
6//! # Examples
7//!
8//! ```no_run
9//! use std::collections::HashMap;
10//! use ironflow_store::prelude::*;
11//! use serde_json::json;
12//!
13//! # async fn example() -> Result<(), ironflow_store::error::StoreError> {
14//! let store = InMemoryStore::new();
15//!
16//! let run = store.create_run(NewRun {
17//! workflow_name: "test".to_string(),
18//! trigger: TriggerKind::Manual,
19//! payload: json!({}),
20//! max_retries: 3,
21//! handler_version: None,
22//! labels: HashMap::new(),
23//! scheduled_at: None,
24//! }).await?;
25//!
26//! assert_eq!(run.status.state, RunStatus::Pending);
27//! # Ok(())
28//! # }
29//! ```
30
31use std::collections::HashMap;
32use std::sync::Arc;
33
34use tokio::sync::RwLock;
35use uuid::Uuid;
36
37use crate::entities::User;
38
39mod api_key_store;
40mod audit_log_store;
41mod run_store;
42mod secret_store;
43mod user_store;
44
45#[derive(Debug, Default)]
46pub(super) struct State {
47 pub(super) runs: HashMap<Uuid, crate::entities::Run>,
48 pub(super) steps: HashMap<Uuid, crate::entities::Step>,
49 pub(super) step_dependencies: Vec<crate::entities::StepDependency>,
50 pub(super) users: HashMap<Uuid, User>,
51 pub(super) api_keys: HashMap<Uuid, crate::entities::ApiKey>,
52 pub(super) secrets: HashMap<String, EncryptedSecret>,
53 pub(super) audit_logs: Vec<crate::entities::AuditLogEntry>,
54}
55
56#[derive(Debug, Clone)]
57pub(super) struct EncryptedSecret {
58 pub(super) id: Uuid,
59 pub(super) key: String,
60 #[cfg(feature = "secret-store")]
61 pub(super) encrypted_value: Vec<u8>,
62 #[cfg(feature = "secret-store")]
63 pub(super) nonce: Vec<u8>,
64 pub(super) created_at: chrono::DateTime<chrono::Utc>,
65 pub(super) updated_at: chrono::DateTime<chrono::Utc>,
66}
67
68/// In-memory store backed by `Arc<RwLock<..>>`.
69///
70/// Thread-safe and cheap to clone. All data is held in memory and lost on drop.
71/// Implements [`Store`](crate::store::Store) so a single `Arc<InMemoryStore>`
72/// covers runs, users, API keys, and secrets.
73///
74/// # Examples
75///
76/// ```
77/// use ironflow_store::memory::InMemoryStore;
78///
79/// let store = InMemoryStore::new();
80/// let store2 = store.clone(); // cheap Arc clone
81/// ```
82#[derive(Debug, Clone)]
83pub struct InMemoryStore {
84 pub(super) state: Arc<RwLock<State>>,
85 #[cfg(feature = "secret-store")]
86 pub(super) master_key: Option<Arc<crate::crypto::MasterKey>>,
87}
88
89impl InMemoryStore {
90 /// Create a new empty in-memory store.
91 ///
92 /// # Examples
93 ///
94 /// ```
95 /// use ironflow_store::memory::InMemoryStore;
96 ///
97 /// let store = InMemoryStore::new();
98 /// ```
99 pub fn new() -> Self {
100 Self {
101 state: Arc::new(RwLock::new(State::default())),
102 #[cfg(feature = "secret-store")]
103 master_key: None,
104 }
105 }
106
107 /// Set the master key for secret encryption.
108 ///
109 /// Required before using [`SecretStore`](crate::secret_store::SecretStore)
110 /// methods that read/write secret values. Without a master key, those
111 /// methods return [`StoreError::Crypto`](crate::error::StoreError::Crypto).
112 ///
113 /// Listing and deleting secrets works without a master key.
114 ///
115 /// # Examples
116 ///
117 /// ```
118 /// use ironflow_store::memory::InMemoryStore;
119 /// use ironflow_store::crypto::MasterKey;
120 ///
121 /// # fn example() -> Result<(), ironflow_store::crypto::CryptoError> {
122 /// let mut store = InMemoryStore::new();
123 /// let key = MasterKey::from_hex(
124 /// "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
125 /// )?;
126 /// store.set_master_key(key);
127 /// # Ok(())
128 /// # }
129 /// ```
130 #[cfg(feature = "secret-store")]
131 pub fn set_master_key(&mut self, key: crate::crypto::MasterKey) {
132 self.master_key = Some(Arc::new(key));
133 }
134}
135
136impl Default for InMemoryStore {
137 fn default() -> Self {
138 Self::new()
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use std::collections::HashMap;
145
146 use serde_json::json;
147
148 use crate::entities::{NewRun, TriggerKind};
149
150 use super::InMemoryStore;
151
152 pub(crate) fn new_run_req(name: &str) -> NewRun {
153 NewRun {
154 workflow_name: name.to_string(),
155 trigger: TriggerKind::Manual,
156 payload: json!({}),
157 max_retries: 3,
158 handler_version: None,
159 labels: HashMap::new(),
160 scheduled_at: None,
161 }
162 }
163
164 pub(crate) async fn create_terminal_run(
165 store: &InMemoryStore,
166 name: &str,
167 status: crate::entities::RunStatus,
168 ) -> crate::entities::Run {
169 use crate::store::RunStore;
170
171 let run = store.create_run(new_run_req(name)).await.unwrap();
172 store
173 .update_run_status(run.id, crate::entities::RunStatus::Running)
174 .await
175 .unwrap();
176 store.update_run_status(run.id, status).await.unwrap();
177 store.get_run(run.id).await.unwrap().unwrap()
178 }
179}