Skip to main content

kevy_embedded/
config.rs

1//! Embedded-store configuration. Builder-style — every knob has a sane
2//! default so `Config::default()` works for the simplest use case
3//! (in-memory, no persistence, background TTL reaper).
4
5use std::path::PathBuf;
6use std::time::Duration;
7
8pub use kevy_persist::Fsync as AppendFsync;
9pub use kevy_store::EvictionPolicy;
10
11/// How the active TTL reaper runs.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum TtlReaperMode {
14    /// Spawn a background thread that ticks at the configured interval
15    /// (default 100 ms / 10 Hz, matching Redis's `hz=10`). Default.
16    Background,
17    /// Caller-driven via [`crate::Store::tick`]. Required for WASM
18    /// targets (no threads) and single-threaded apps that don't want a
19    /// background worker.
20    Manual,
21}
22
23/// Embedded-store config. Build by chaining `with_*` methods on
24/// [`Config::default`].
25#[derive(Debug, Clone)]
26pub struct Config {
27    /// Soft memory ceiling in bytes. `0` (default) = unlimited.
28    pub maxmemory: u64,
29    /// Eviction policy when over `maxmemory`. Default `NoEviction`.
30    pub eviction_policy: EvictionPolicy,
31    /// Persistence directory. `None` = pure in-memory (no AOF, no snapshot).
32    pub data_dir: Option<PathBuf>,
33    /// AOF on/off when `data_dir` is set. Defaults to `true` (on) when
34    /// `with_persist` was called; ignored if `data_dir` is `None`.
35    pub aof: bool,
36    /// AOF fsync policy. Default `EverySec` (matches Redis: ≤ 1 s loss).
37    pub appendfsync: AppendFsync,
38    /// Snapshot file name inside `data_dir`. Default `"dump-0.rdb"`.
39    pub snapshot_filename: String,
40    /// AOF file name inside `data_dir`. Default `"aof-0.aof"`.
41    pub aof_filename: String,
42    /// TTL reaper mode. Default `Background`.
43    pub ttl_reaper: TtlReaperMode,
44    /// Reaper tick interval. Default 100 ms (10 Hz).
45    pub reaper_interval: Duration,
46    /// `tick_expire` samples per round. Default 20 (matches Redis).
47    pub reaper_samples: usize,
48    /// Max sample rounds per tick. Default 16.
49    pub reaper_max_rounds: u32,
50}
51
52impl Default for Config {
53    fn default() -> Self {
54        Self {
55            maxmemory: 0,
56            eviction_policy: EvictionPolicy::NoEviction,
57            data_dir: None,
58            aof: true,
59            appendfsync: AppendFsync::EverySec,
60            snapshot_filename: String::from("dump-0.rdb"),
61            aof_filename: String::from("aof-0.aof"),
62            ttl_reaper: TtlReaperMode::Background,
63            reaper_interval: Duration::from_millis(100),
64            reaper_samples: 20,
65            reaper_max_rounds: 16,
66        }
67    }
68}
69
70impl Config {
71    /// Enable persistence under `dir` — snapshot file + AOF land inside.
72    /// AOF defaults on; turn it off with [`Self::without_aof`] for pure
73    /// snapshot-only durability.
74    pub fn with_persist(mut self, dir: impl Into<PathBuf>) -> Self {
75        self.data_dir = Some(dir.into());
76        self
77    }
78
79    /// Disable the AOF (snapshot-only persistence — explicit `save_snapshot`
80    /// calls are the only way data survives restart).
81    pub fn without_aof(mut self) -> Self {
82        self.aof = false;
83        self
84    }
85
86    /// Soft memory ceiling in bytes. `0` keeps the default (unlimited).
87    pub fn with_max_memory(mut self, bytes: u64) -> Self {
88        self.maxmemory = bytes;
89        self
90    }
91
92    /// Eviction policy when over [`Self::with_max_memory`].
93    pub fn with_eviction(mut self, policy: EvictionPolicy) -> Self {
94        self.eviction_policy = policy;
95        self
96    }
97
98    /// AOF fsync policy. Default [`AppendFsync::EverySec`].
99    pub fn with_appendfsync(mut self, fsync: AppendFsync) -> Self {
100        self.appendfsync = fsync;
101        self
102    }
103
104    /// Caller-driven TTL reaping — disables the background thread.
105    /// Required for WASM (no threads available). Call
106    /// [`crate::Store::tick`] yourself from your event loop.
107    pub fn with_ttl_reaper_manual(mut self) -> Self {
108        self.ttl_reaper = TtlReaperMode::Manual;
109        self
110    }
111
112    /// Override the background reaper interval. Default 100 ms.
113    pub fn with_reaper_interval(mut self, iv: Duration) -> Self {
114        self.reaper_interval = iv;
115        self
116    }
117
118    /// Override the snapshot file name inside `data_dir`.
119    pub fn with_snapshot_filename(mut self, name: impl Into<String>) -> Self {
120        self.snapshot_filename = name.into();
121        self
122    }
123
124    /// Override the AOF file name inside `data_dir`.
125    pub fn with_aof_filename(mut self, name: impl Into<String>) -> Self {
126        self.aof_filename = name.into();
127        self
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn default_is_pure_in_memory() {
137        let c = Config::default();
138        assert_eq!(c.maxmemory, 0);
139        assert!(c.data_dir.is_none());
140        assert_eq!(c.ttl_reaper, TtlReaperMode::Background);
141        assert!(c.aof);
142    }
143
144    #[test]
145    fn builder_chains() {
146        let c = Config::default()
147            .with_persist("/tmp/foo")
148            .with_max_memory(1024)
149            .with_eviction(EvictionPolicy::AllKeysLru)
150            .with_ttl_reaper_manual()
151            .with_appendfsync(AppendFsync::Always);
152        assert_eq!(c.data_dir.as_deref(), Some(std::path::Path::new("/tmp/foo")));
153        assert_eq!(c.maxmemory, 1024);
154        assert_eq!(c.eviction_policy, EvictionPolicy::AllKeysLru);
155        assert_eq!(c.ttl_reaper, TtlReaperMode::Manual);
156    }
157
158    #[test]
159    fn without_aof_disables_logging_path() {
160        let c = Config::default().with_persist("/tmp/foo").without_aof();
161        assert!(c.data_dir.is_some());
162        assert!(!c.aof);
163    }
164}