xz-rag 0.1.1

Multi-channel Retrieval-Augmented Generation engine
Documentation
use moka::future::Cache;

use crate::types::retrieval::RetrieveResult;

/// In-memory cache for retrieval results using moka.
pub struct RagMemoryCache {
    cache: Cache<String, RetrieveResult>,
}

impl RagMemoryCache {
    /// Create a new in-memory cache with the given capacity and TTL.
    pub fn new(max_entries: usize, ttl_seconds: u64) -> Self {
        let cache = Cache::builder()
            .max_capacity(max_entries as u64)
            .time_to_live(std::time::Duration::from_secs(ttl_seconds))
            .build();

        Self { cache }
    }

    /// Retrieve a cached result by key.
    pub async fn get(&self, key: &str) -> Option<RetrieveResult> {
        self.cache.get(key).await
    }

    /// Store a result in the cache.
    pub async fn set(&self, key: &str, value: RetrieveResult) {
        self.cache.insert(key.to_string(), value).await;
    }

    /// Invalidate all cache entries.
    ///
    /// Note: moka does not support namespace-based invalidation natively,
    /// so this invalidates the entire cache. Entries are also auto-evicted
    /// by TTL and capacity limits.
    pub fn invalidate(&self, _namespace: &str) {
        self.cache.invalidate_all();
    }
}

#[cfg(all(test, feature = "caching"))]
mod tests {
    use super::*;

    fn make_result() -> RetrieveResult {
        RetrieveResult {
            hits: vec![],
            channel_report: std::collections::HashMap::new(),
            latency_ms: 42,
            effective_query: "test query".to_string(),
        }
    }

    #[tokio::test]
    async fn invalidate_evicts_entries() {
        let cache = RagMemoryCache::new(100, 3600);

        cache.set("key1", make_result()).await;
        assert!(cache.get("key1").await.is_some());

        cache.invalidate("test_namespace");
        assert!(cache.get("key1").await.is_none());
    }

    #[tokio::test]
    async fn invalidate_evicts_all_entries() {
        let cache = RagMemoryCache::new(100, 3600);

        cache.set("key1", make_result()).await;
        cache.set("key2", make_result()).await;
        assert!(cache.get("key1").await.is_some());
        assert!(cache.get("key2").await.is_some());

        cache.invalidate("test_namespace");
        assert!(cache.get("key1").await.is_none());
        assert!(cache.get("key2").await.is_none());
    }

    #[tokio::test]
    async fn set_after_invalidate_still_works() {
        let cache = RagMemoryCache::new(100, 3600);

        cache.set("key1", make_result()).await;
        cache.invalidate("test_namespace");

        // After invalidation, new entries should still be settable/gettable
        cache.set("key1", make_result()).await;
        assert!(cache.get("key1").await.is_some());
    }
}