behest_store/memory/composite.rs
1//! Composite in-memory store that coordinates cascading operations
2//! across all storage domains.
3
4use uuid::Uuid;
5
6use crate::memory::{
7 MemoryArtifactStore, MemoryEmbeddingStore, MemoryExecutionStore, MemorySessionStore,
8};
9use crate::{ArtifactStore, EmbeddingStore, ExecutionStore, SessionStore, StoreResult};
10
11/// A composite in-memory store aggregating all four storage domains
12/// with coordinated operations, including cascading deletes.
13///
14/// Provides direct access to each sub-store through public fields for
15/// fine-grained control, plus `delete_session_cascading()` for atomic
16/// cross-domain cleanup.
17///
18/// # Example
19///
20/// ```rust
21/// use behest_store::memory::MemoryStore;
22///
23/// # async fn example() -> Result<(), behest_core::error::StorageError> {
24/// let store = MemoryStore::new();
25/// // Use store.sessions, store.executions, store.embeddings, store.artifacts
26/// // individually, or use store.delete_session_cascading() for full cleanup.
27/// # Ok(())
28/// # }
29/// ```
30#[derive(Default)]
31pub struct MemoryStore {
32 /// Session and message storage.
33 pub sessions: MemorySessionStore,
34 /// Embedding storage.
35 pub embeddings: MemoryEmbeddingStore,
36 /// Artifact storage.
37 pub artifacts: MemoryArtifactStore,
38 /// Tool execution and usage storage.
39 pub executions: MemoryExecutionStore,
40}
41
42impl MemoryStore {
43 /// Creates a new empty composite memory store with all four sub-stores.
44 #[must_use]
45 pub fn new() -> Self {
46 Self::default()
47 }
48
49 /// Deletes a session and all related data across all sub-stores.
50 ///
51 /// This is semantically equivalent to what a real database would do
52 /// with foreign-key `ON DELETE CASCADE`, but across in-memory collections.
53 /// Collects errors from all sub-stores instead of short-circuiting.
54 ///
55 /// # Errors
56 ///
57 /// Returns [`StorageError::BackendError`](behest_core::error::StorageError::BackendError)
58 /// aggregating errors from any sub-stores that failed.
59 pub async fn delete_session_cascading(&self, id: &Uuid) -> StoreResult<()> {
60 // Collect errors from all sub-stores
61 let mut errors: Vec<String> = Vec::new();
62
63 if let Err(e) = self.sessions.delete_session(id).await {
64 errors.push(format!("sessions: {e}"));
65 }
66 if let Err(e) = self.embeddings.delete_by_session(id).await {
67 errors.push(format!("embeddings: {e}"));
68 }
69 if let Err(e) = self.artifacts.delete_by_session(id).await {
70 errors.push(format!("artifacts: {e}"));
71 }
72 if let Err(e) = self.executions.delete_by_session(id).await {
73 errors.push(format!("executions: {e}"));
74 }
75
76 if errors.is_empty() {
77 Ok(())
78 } else {
79 Err(behest_core::error::StorageError::BackendError {
80 backend: "memory".to_owned(),
81 message: format!("cascade delete failed: {}", errors.join("; ")),
82 source: None,
83 })
84 }
85 }
86}