robinpath_modules/modules/
cache_mod.rs1use robinpath::{RobinPath, Value};
2use std::collections::HashMap;
3use std::sync::Mutex;
4use std::time::{Duration, Instant};
5
6struct CacheEntry {
7 value: Value,
8 expires_at: Option<Instant>,
9}
10
11struct GlobalCache {
12 entries: HashMap<String, CacheEntry>,
13}
14
15impl GlobalCache {
16 fn new() -> Self {
17 Self {
18 entries: HashMap::new(),
19 }
20 }
21
22 fn cleanup(&mut self) {
23 let now = Instant::now();
24 self.entries.retain(|_, entry| {
25 entry.expires_at.map_or(true, |exp| exp > now)
26 });
27 }
28}
29
30static CACHE: std::sync::LazyLock<Mutex<GlobalCache>> =
31 std::sync::LazyLock::new(|| Mutex::new(GlobalCache::new()));
32
33pub fn register(rp: &mut RobinPath) {
34 rp.register_builtin("cache.set", |args, _| {
35 let key = args.first().map(|v| v.to_display_string()).unwrap_or_default();
36 let value = args.get(1).cloned().unwrap_or(Value::Null);
37 let ttl_secs = args.get(2).map(|v| v.to_number());
38 let expires_at = ttl_secs.map(|secs| Instant::now() + Duration::from_secs_f64(secs));
39 let mut cache = CACHE.lock().unwrap();
40 cache.entries.insert(key, CacheEntry { value, expires_at });
41 Ok(Value::Bool(true))
42 });
43
44 rp.register_builtin("cache.get", |args, _| {
45 let key = args.first().map(|v| v.to_display_string()).unwrap_or_default();
46 let default = args.get(1).cloned().unwrap_or(Value::Null);
47 let mut cache = CACHE.lock().unwrap();
48 let now = Instant::now();
49 if let Some(entry) = cache.entries.get(&key) {
50 if entry.expires_at.map_or(true, |exp| exp > now) {
51 return Ok(entry.value.clone());
52 }
53 cache.entries.remove(&key);
55 }
56 Ok(default)
57 });
58
59 rp.register_builtin("cache.has", |args, _| {
60 let key = args.first().map(|v| v.to_display_string()).unwrap_or_default();
61 let mut cache = CACHE.lock().unwrap();
62 let now = Instant::now();
63 if let Some(entry) = cache.entries.get(&key) {
64 if entry.expires_at.map_or(true, |exp| exp > now) {
65 return Ok(Value::Bool(true));
66 }
67 cache.entries.remove(&key);
68 }
69 Ok(Value::Bool(false))
70 });
71
72 rp.register_builtin("cache.delete", |args, _| {
73 let key = args.first().map(|v| v.to_display_string()).unwrap_or_default();
74 let mut cache = CACHE.lock().unwrap();
75 let removed = cache.entries.remove(&key).is_some();
76 Ok(Value::Bool(removed))
77 });
78
79 rp.register_builtin("cache.clear", |_args, _| {
80 let mut cache = CACHE.lock().unwrap();
81 cache.entries.clear();
82 Ok(Value::Bool(true))
83 });
84
85 rp.register_builtin("cache.keys", |_args, _| {
86 let mut cache = CACHE.lock().unwrap();
87 cache.cleanup();
88 let keys: Vec<Value> = cache
89 .entries
90 .keys()
91 .map(|k| Value::String(k.clone()))
92 .collect();
93 Ok(Value::Array(keys))
94 });
95
96 rp.register_builtin("cache.values", |_args, _| {
97 let mut cache = CACHE.lock().unwrap();
98 cache.cleanup();
99 let values: Vec<Value> = cache
100 .entries
101 .values()
102 .map(|entry| entry.value.clone())
103 .collect();
104 Ok(Value::Array(values))
105 });
106
107 rp.register_builtin("cache.size", |_args, _| {
108 let mut cache = CACHE.lock().unwrap();
109 cache.cleanup();
110 Ok(Value::Number(cache.entries.len() as f64))
111 });
112
113 rp.register_builtin("cache.ttl", |args, _| {
114 let key = args.first().map(|v| v.to_display_string()).unwrap_or_default();
115 let cache = CACHE.lock().unwrap();
116 if let Some(entry) = cache.entries.get(&key) {
117 match entry.expires_at {
118 Some(exp) => {
119 let now = Instant::now();
120 if exp > now {
121 Ok(Value::Number((exp - now).as_secs_f64()))
122 } else {
123 Ok(Value::Number(-1.0))
124 }
125 }
126 None => Ok(Value::Number(-1.0)), }
128 } else {
129 Ok(Value::Null)
130 }
131 });
132
133 rp.register_builtin("cache.setMany", |args, _| {
134 let obj = args.first().cloned().unwrap_or(Value::Null);
135 let ttl_secs = args.get(1).map(|v| v.to_number());
136 let expires_at = ttl_secs.map(|secs| Instant::now() + Duration::from_secs_f64(secs));
137 if let Value::Object(map) = &obj {
138 let mut cache = CACHE.lock().unwrap();
139 for (key, value) in map {
140 cache.entries.insert(
141 key.clone(),
142 CacheEntry {
143 value: value.clone(),
144 expires_at,
145 },
146 );
147 }
148 }
149 Ok(Value::Bool(true))
150 });
151
152 rp.register_builtin("cache.getMany", |args, _| {
153 let keys = args.first().cloned().unwrap_or(Value::Null);
154 if let Value::Array(arr) = &keys {
155 let mut cache = CACHE.lock().unwrap();
156 let now = Instant::now();
157 let mut result = indexmap::IndexMap::new();
158 for key_val in arr {
159 let key = key_val.to_display_string();
160 if let Some(entry) = cache.entries.get(&key) {
161 if entry.expires_at.map_or(true, |exp| exp > now) {
162 result.insert(key, entry.value.clone());
163 } else {
164 cache.entries.remove(&key);
165 result.insert(key, Value::Null);
166 }
167 } else {
168 result.insert(key, Value::Null);
169 }
170 }
171 Ok(Value::Object(result))
172 } else {
173 Ok(Value::Object(indexmap::IndexMap::new()))
174 }
175 });
176
177 rp.register_builtin("cache.deleteMany", |args, _| {
178 let keys = args.first().cloned().unwrap_or(Value::Null);
179 if let Value::Array(arr) = &keys {
180 let mut cache = CACHE.lock().unwrap();
181 let mut count = 0;
182 for key_val in arr {
183 let key = key_val.to_display_string();
184 if cache.entries.remove(&key).is_some() {
185 count += 1;
186 }
187 }
188 Ok(Value::Number(count as f64))
189 } else {
190 Ok(Value::Number(0.0))
191 }
192 });
193}