Skip to main content

cachekit/l1/
mod.rs

1use moka::sync::Cache;
2use moka::Expiry;
3use std::time::Duration;
4
5#[derive(Clone)]
6struct L1Entry {
7    data: Vec<u8>,
8    ttl: Duration,
9}
10
11struct L1Expiry;
12
13impl Expiry<String, L1Entry> for L1Expiry {
14    fn expire_after_create(
15        &self,
16        _key: &String,
17        value: &L1Entry,
18        _created_at: std::time::Instant,
19    ) -> Option<Duration> {
20        Some(value.ttl)
21    }
22}
23
24/// In-process LRU cache with per-entry TTL, backed by [`moka`].
25///
26/// Used as the L1 layer in the dual-layer cache architecture.
27pub struct L1Cache {
28    store: Cache<String, L1Entry>,
29}
30
31impl L1Cache {
32    /// Create a new L1 cache with the given maximum entry capacity.
33    pub fn new(capacity: usize) -> Self {
34        Self {
35            store: Cache::builder()
36                .max_capacity(u64::try_from(capacity).unwrap_or(u64::MAX))
37                .expire_after(L1Expiry)
38                .build(),
39        }
40    }
41
42    /// Retrieve cached bytes by key, or `None` if absent or expired.
43    pub fn get(&self, key: &str) -> Option<Vec<u8>> {
44        self.store.get(key).map(|entry| entry.data.clone())
45    }
46
47    /// Insert or overwrite an entry with the given TTL.
48    pub fn set(&self, key: &str, value: &[u8], ttl: Duration) {
49        self.store.insert(
50            key.to_string(),
51            L1Entry {
52                data: value.to_vec(),
53                ttl,
54            },
55        );
56    }
57
58    /// Remove an entry by key.
59    pub fn delete(&self, key: &str) {
60        self.store.invalidate(key);
61    }
62
63    /// Drive moka's internal eviction machinery. Useful in tests to force
64    /// pending invalidations and expiry checks to complete synchronously.
65    pub fn run_pending_tasks(&self) {
66        self.store.run_pending_tasks();
67    }
68}