rs_query/
client.rs

1//! QueryClient - central cache and query manager
2
3use crate::{QueryKey, QueryOptions};
4use std::any::{Any, TypeId};
5use std::collections::HashMap;
6use std::sync::{Arc, RwLock};
7use std::time::Instant;
8
9/// Cache entry for type-erased storage
10struct CacheEntry {
11    data: Box<dyn Any + Send + Sync>,
12    type_id: TypeId,
13    fetched_at: Instant,
14    #[allow(dead_code)]
15    last_accessed: Instant,
16    options: QueryOptions,
17    is_stale: bool,
18}
19
20/// Central query client managing cache and query execution.
21///
22/// Create one per application and share with all components.
23///
24/// # Example
25///
26/// ```rust,ignore
27/// struct App {
28///     query_client: QueryClient,
29/// }
30///
31/// impl App {
32///     fn new() -> Self {
33///         Self {
34///             query_client: QueryClient::new(),
35///         }
36///     }
37/// }
38/// ```
39pub struct QueryClient {
40    cache: Arc<RwLock<HashMap<String, CacheEntry>>>,
41    in_flight: Arc<RwLock<HashMap<String, ()>>>,
42}
43
44impl QueryClient {
45    pub fn new() -> Self {
46        Self {
47            cache: Arc::new(RwLock::new(HashMap::new())),
48            in_flight: Arc::new(RwLock::new(HashMap::new())),
49        }
50    }
51
52    /// Get cached data if available and not stale
53    pub fn get_query_data<T: Clone + Send + Sync + 'static>(&self, key: &QueryKey) -> Option<T> {
54        let cache = self.cache.read().ok()?;
55        let entry = cache.get(&key.cache_key())?;
56
57        if entry.type_id != TypeId::of::<T>() {
58            return None;
59        }
60
61        // Check if stale
62        let age = entry.fetched_at.elapsed();
63        if age > entry.options.stale_time && !entry.is_stale {
64            return None;
65        }
66
67        entry.data.downcast_ref::<T>().cloned()
68    }
69
70    /// Set cached data
71    pub fn set_query_data<T: Clone + Send + Sync + 'static>(
72        &self,
73        key: &QueryKey,
74        data: T,
75        options: QueryOptions,
76    ) {
77        let mut cache = self.cache.write().unwrap();
78        cache.insert(
79            key.cache_key(),
80            CacheEntry {
81                data: Box::new(data),
82                type_id: TypeId::of::<T>(),
83                fetched_at: Instant::now(),
84                last_accessed: Instant::now(),
85                options,
86                is_stale: false,
87            },
88        );
89    }
90
91    /// Invalidate queries matching the key pattern
92    pub fn invalidate_queries(&self, pattern: &QueryKey) {
93        let mut cache = self.cache.write().unwrap();
94        let pattern_str = pattern.cache_key();
95
96        for (key, entry) in cache.iter_mut() {
97            if key.starts_with(&pattern_str) || key == &pattern_str {
98                entry.is_stale = true;
99            }
100        }
101    }
102
103    /// Check if a query is in flight (for deduplication)
104    pub fn is_in_flight(&self, key: &QueryKey) -> bool {
105        self.in_flight
106            .read()
107            .unwrap()
108            .contains_key(&key.cache_key())
109    }
110
111    /// Mark query as in flight
112    pub fn set_in_flight(&self, key: &QueryKey, in_flight: bool) {
113        let mut map = self.in_flight.write().unwrap();
114        if in_flight {
115            map.insert(key.cache_key(), ());
116        } else {
117            map.remove(&key.cache_key());
118        }
119    }
120
121    /// Clear all cached data
122    pub fn clear(&self) {
123        self.cache.write().unwrap().clear();
124    }
125
126    /// Run garbage collection on stale entries
127    #[allow(dead_code)]
128    pub fn gc(&self) {
129        let mut cache = self.cache.write().unwrap();
130        cache.retain(|_, entry| {
131            let age = entry.last_accessed.elapsed();
132            age < entry.options.gc_time
133        });
134    }
135}
136
137impl Default for QueryClient {
138    fn default() -> Self {
139        Self::new()
140    }
141}
142
143impl Clone for QueryClient {
144    fn clone(&self) -> Self {
145        Self {
146            cache: Arc::clone(&self.cache),
147            in_flight: Arc::clone(&self.in_flight),
148        }
149    }
150}