mubit-sdk 0.7.0

Umbrella Rust SDK for Mubit core/control planes
Documentation
//! MuBit Learn Lesson Cache.
//!
//! Async-safe TTL cache for `get_context()` results keyed by (session_id, query_hash).

use std::collections::HashMap;
use std::time::{Duration, Instant};
use tokio::sync::RwLock;

pub struct LessonCache {
    store: RwLock<HashMap<String, (Instant, String)>>,
    ttl: Duration,
    max_entries: usize,
}

impl LessonCache {
    pub fn new(ttl: Duration, max_entries: usize) -> Self {
        Self {
            store: RwLock::new(HashMap::new()),
            ttl,
            max_entries,
        }
    }

    pub async fn get(&self, session_id: &str, query: &str) -> Option<String> {
        let key = Self::key(session_id, query);
        let store = self.store.read().await;
        if let Some((ts, value)) = store.get(&key) {
            if ts.elapsed() < self.ttl {
                return Some(value.clone());
            }
        }
        None
    }

    pub async fn set(&self, session_id: &str, query: &str, context_block: String) {
        let key = Self::key(session_id, query);
        let mut store = self.store.write().await;
        if store.len() >= self.max_entries {
            // Evict oldest
            if let Some(oldest_key) = store
                .iter()
                .min_by_key(|(_, (ts, _))| *ts)
                .map(|(k, _)| k.clone())
            {
                store.remove(&oldest_key);
            }
        }
        store.insert(key, (Instant::now(), context_block));
    }

    #[allow(dead_code)]
    pub async fn clear(&self) {
        self.store.write().await.clear();
    }

    fn key(session_id: &str, query: &str) -> String {
        use std::hash::{Hash, Hasher};
        let mut hasher = std::collections::hash_map::DefaultHasher::new();
        query.hash(&mut hasher);
        let qh = format!("{:x}", hasher.finish());
        format!("{}:{}", session_id, &qh[..12.min(qh.len())])
    }
}