Skip to main content

soothe_client/appkit/
session_store.rs

1//! Session ↔ loop persistence.
2
3use std::collections::HashMap;
4use std::sync::Arc;
5
6use serde_json::Value;
7use tokio::sync::Mutex;
8
9/// Persisted session record.
10#[derive(Debug, Clone, Default)]
11pub struct SessionRecord {
12    /// Application session id.
13    pub session_id: String,
14    /// Bound loop id.
15    pub loop_id: Option<String>,
16    /// Workspace id.
17    pub workspace_id: String,
18    /// User id.
19    pub user_id: String,
20    /// Reset counter.
21    pub reset_count: u32,
22    /// Message log.
23    pub messages: Vec<Value>,
24}
25
26/// Session ↔ loop persistence.
27pub trait SessionStore: Send + Sync {
28    /// Load session.
29    fn get_session(
30        &self,
31        session_id: &str,
32    ) -> impl std::future::Future<Output = Option<SessionRecord>> + Send;
33
34    /// Create session.
35    fn create_session(
36        &self,
37        record: SessionRecord,
38    ) -> impl std::future::Future<Output = SessionRecord> + Send;
39
40    /// Touch last-used (no-op ok).
41    fn update_last_used(&self, session_id: &str) -> impl std::future::Future<Output = ()> + Send;
42
43    /// Increment reset count.
44    fn increment_reset_count(
45        &self,
46        session_id: &str,
47    ) -> impl std::future::Future<Output = ()> + Send;
48
49    /// Lookup loop id.
50    fn get_loop_id_for_session(
51        &self,
52        session_id: &str,
53    ) -> impl std::future::Future<Output = Option<String>> + Send;
54
55    /// Bind loop id.
56    fn set_loop_id(
57        &self,
58        session_id: &str,
59        loop_id: &str,
60    ) -> impl std::future::Future<Output = ()> + Send;
61
62    /// Append a message.
63    fn append_message(
64        &self,
65        session_id: &str,
66        message: Value,
67    ) -> impl std::future::Future<Output = ()> + Send;
68}
69
70/// Process-local store.
71#[derive(Clone, Default)]
72pub struct InMemorySessionStore {
73    inner: Arc<Mutex<HashMap<String, SessionRecord>>>,
74}
75
76impl InMemorySessionStore {
77    /// Create empty store.
78    pub fn new() -> Self {
79        Self::default()
80    }
81}
82
83impl SessionStore for InMemorySessionStore {
84    async fn get_session(&self, session_id: &str) -> Option<SessionRecord> {
85        self.inner.lock().await.get(session_id).cloned()
86    }
87
88    async fn create_session(&self, record: SessionRecord) -> SessionRecord {
89        let mut map = self.inner.lock().await;
90        map.insert(record.session_id.clone(), record.clone());
91        record
92    }
93
94    async fn update_last_used(&self, _session_id: &str) {}
95
96    async fn increment_reset_count(&self, session_id: &str) {
97        if let Some(rec) = self.inner.lock().await.get_mut(session_id) {
98            rec.reset_count += 1;
99        }
100    }
101
102    async fn get_loop_id_for_session(&self, session_id: &str) -> Option<String> {
103        self.inner
104            .lock()
105            .await
106            .get(session_id)
107            .and_then(|r| r.loop_id.clone())
108    }
109
110    async fn set_loop_id(&self, session_id: &str, loop_id: &str) {
111        let mut map = self.inner.lock().await;
112        if let Some(rec) = map.get_mut(session_id) {
113            rec.loop_id = Some(loop_id.to_string());
114        } else {
115            map.insert(
116                session_id.to_string(),
117                SessionRecord {
118                    session_id: session_id.to_string(),
119                    loop_id: Some(loop_id.to_string()),
120                    ..Default::default()
121                },
122            );
123        }
124    }
125
126    async fn append_message(&self, session_id: &str, message: Value) {
127        if let Some(rec) = self.inner.lock().await.get_mut(session_id) {
128            rec.messages.push(message);
129        }
130    }
131}