leptos_query_rs/persistence/
mod.rs1use crate::retry::QueryError;
2use async_trait::async_trait;
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::sync::Arc;
6use std::time::Instant;
7
8#[cfg(target_arch = "wasm32")]
9use web_sys::Storage;
10
11#[async_trait]
13pub trait StorageBackend: Send + Sync {
14 async fn store(&self, key: &str, data: &[u8]) -> Result<(), QueryError>;
16
17 async fn retrieve(&self, key: &str) -> Result<Option<Vec<u8>>, QueryError>;
19
20 async fn remove(&self, key: &str) -> Result<(), QueryError>;
22
23 async fn list_keys(&self) -> Result<Vec<String>, QueryError>;
25
26 async fn clear(&self) -> Result<(), QueryError>;
28
29 async fn size(&self) -> Result<usize, QueryError>;
31}
32
33pub struct MemoryBackend {
35 data: Arc<parking_lot::RwLock<HashMap<String, Vec<u8>>>>,
36}
37
38impl Default for MemoryBackend {
39 fn default() -> Self {
40 Self::new()
41 }
42}
43
44impl MemoryBackend {
45 pub fn new() -> Self {
46 Self {
47 data: Arc::new(parking_lot::RwLock::new(HashMap::new())),
48 }
49 }
50}
51
52#[async_trait]
53impl StorageBackend for MemoryBackend {
54 async fn store(&self, key: &str, data: &[u8]) -> Result<(), QueryError> {
55 let mut map = self.data.write();
56 map.insert(key.to_string(), data.to_vec());
57 Ok(())
58 }
59
60 async fn retrieve(&self, key: &str) -> Result<Option<Vec<u8>>, QueryError> {
61 let map = self.data.read();
62 Ok(map.get(key).cloned())
63 }
64
65 async fn remove(&self, key: &str) -> Result<(), QueryError> {
66 let mut map = self.data.write();
67 map.remove(key);
68 Ok(())
69 }
70
71 async fn list_keys(&self) -> Result<Vec<String>, QueryError> {
72 let map = self.data.read();
73 Ok(map.keys().cloned().collect())
74 }
75
76 async fn clear(&self) -> Result<(), QueryError> {
77 let mut map = self.data.write();
78 map.clear();
79 Ok(())
80 }
81
82 async fn size(&self) -> Result<usize, QueryError> {
83 let map = self.data.read();
84 Ok(map.len())
85 }
86}
87
88#[cfg(feature = "persistence")]
90pub struct LocalStorageBackend {
91 prefix: String,
92 #[cfg(not(target_arch = "wasm32"))]
94 data: std::cell::RefCell<std::collections::HashMap<String, Vec<u8>>>,
95}
96
97#[cfg(feature = "persistence")]
98impl LocalStorageBackend {
99 pub fn new(prefix: String) -> Self {
100 Self {
101 prefix,
102 #[cfg(not(target_arch = "wasm32"))]
103 data: std::cell::RefCell::new(std::collections::HashMap::new()),
104 }
105 }
106
107 pub fn prefix(&self) -> &str {
108 &self.prefix
109 }
110
111 fn make_key(&self, key: &crate::types::QueryKey) -> String {
112 format!("{}_{}", self.prefix, key.to_string())
113 }
114
115 pub fn store<T: Serialize>(&self, key: &crate::types::QueryKey, data: &T) -> Result<(), QueryError> {
116 let serialized = bincode::serialize(data)
117 .map_err(|e| QueryError::SerializationError(e.to_string()))?;
118
119 #[cfg(target_arch = "wasm32")]
120 {
121 let window = web_sys::window().ok_or_else(|| {
122 QueryError::StorageError("window not available".to_string())
123 })?;
124
125 let storage = window.local_storage().map_err(|_| {
126 QueryError::StorageError("localStorage not available".to_string())
127 })?.ok_or_else(|| {
128 QueryError::StorageError("localStorage not available".to_string())
129 })?;
130
131 let encoded = base64::encode(&serialized);
132 let full_key = self.make_key(key);
133 storage.set_item(&full_key, &encoded).map_err(|_| {
134 QueryError::StorageError("Failed to store data".to_string())
135 })?;
136 }
137
138 #[cfg(not(target_arch = "wasm32"))]
139 {
140 let full_key = self.make_key(key);
142 self.data.borrow_mut().insert(full_key, serialized);
143 }
144
145 Ok(())
146 }
147
148 pub fn retrieve<T: serde::de::DeserializeOwned>(&self, key: &crate::types::QueryKey) -> Result<Option<T>, QueryError> {
149 #[cfg(target_arch = "wasm32")]
150 {
151 let window = web_sys::window().ok_or_else(|| {
152 QueryError::StorageError("window not available".to_string())
153 })?;
154
155 let storage = window.local_storage().map_err(|_| {
156 QueryError::StorageError("localStorage not available".to_string())
157 })?.ok_or_else(|| {
158 QueryError::StorageError("localStorage not available".to_string())
159 })?;
160
161 let full_key = self.make_key(key);
162 let encoded = storage.get_item(&full_key).map_err(|_| {
163 QueryError::StorageError("Failed to retrieve data".to_string())
164 })?;
165
166 match encoded {
167 Some(encoded) => {
168 let data = base64::decode(&encoded).map_err(|_| {
169 QueryError::StorageError("Failed to decode data".to_string())
170 })?;
171 let deserialized: T = bincode::deserialize(&data)
172 .map_err(|e| QueryError::DeserializationError(e.to_string()))?;
173 Ok(Some(deserialized))
174 }
175 None => Ok(None),
176 }
177 }
178
179 #[cfg(not(target_arch = "wasm32"))]
180 {
181 let full_key = self.make_key(key);
183 if let Some(data) = self.data.borrow().get(&full_key) {
184 let deserialized: T = bincode::deserialize(data)
185 .map_err(|e| QueryError::DeserializationError(e.to_string()))?;
186 Ok(Some(deserialized))
187 } else {
188 Ok(None)
189 }
190 }
191 }
192
193 pub fn remove(&self, key: &crate::types::QueryKey) -> Result<(), QueryError> {
194 #[cfg(target_arch = "wasm32")]
195 {
196 let window = web_sys::window().ok_or_else(|| {
197 QueryError::StorageError("window not available".to_string())
198 })?;
199
200 let storage = window.local_storage().map_err(|_| {
201 QueryError::StorageError("localStorage not available".to_string())
202 })?.ok_or_else(|| {
203 QueryError::StorageError("localStorage not available".to_string())
204 })?;
205
206 let full_key = self.make_key(key);
207 storage.remove_item(&full_key).map_err(|_| {
208 QueryError::StorageError("Failed to remove data".to_string())
209 })?;
210 }
211
212 #[cfg(not(target_arch = "wasm32"))]
213 {
214 let full_key = self.make_key(key);
216 self.data.borrow_mut().remove(&full_key);
217 }
218
219 Ok(())
220 }
221
222 pub fn clear(&self) -> Result<(), QueryError> {
223 #[cfg(target_arch = "wasm32")]
224 {
225 let window = web_sys::window().ok_or_else(|| {
226 QueryError::StorageError("window not available".to_string())
227 })?;
228
229 let storage = window.local_storage().map_err(|_| {
230 QueryError::StorageError("localStorage not available".to_string())
231 })?.ok_or_else(|| {
232 QueryError::StorageError("localStorage not available".to_string())
233 })?;
234
235 let length = storage.length().map_err(|_| {
237 QueryError::StorageError("Failed to get storage length".to_string())
238 })?;
239
240 for i in 0..length {
241 if let Ok(Some(key)) = storage.key(i) {
242 if key.starts_with(&self.prefix) {
243 storage.remove_item(&key).map_err(|_| {
244 QueryError::StorageError("Failed to remove item".to_string())
245 })?;
246 }
247 }
248 }
249 }
250
251 #[cfg(not(target_arch = "wasm32"))]
252 {
253 self.data.borrow_mut().clear();
255 }
256
257 Ok(())
258 }
259}
260
261#[cfg(feature = "persistence")]
263pub struct IndexedDBBackend {
264 db_name: String,
265 store_name: String,
266 data: std::cell::RefCell<std::collections::HashMap<String, Vec<u8>>>,
268}
269
270#[cfg(feature = "persistence")]
271impl IndexedDBBackend {
272 pub fn new(db_name: String, store_name: String) -> Self {
273 Self {
274 db_name,
275 store_name,
276 data: std::cell::RefCell::new(std::collections::HashMap::new()),
277 }
278 }
279
280 pub fn db_name(&self) -> &str {
281 &self.db_name
282 }
283
284 pub fn store_name(&self) -> &str {
285 &self.store_name
286 }
287
288 pub fn store<T: Serialize>(&self, key: &crate::types::QueryKey, data: &T) -> Result<(), QueryError> {
289 let serialized = bincode::serialize(data)
292 .map_err(|e| QueryError::SerializationError(e.to_string()))?;
293
294 let key_str = key.to_string();
295 self.data.borrow_mut().insert(key_str, serialized);
296 Ok(())
297 }
298
299 pub fn retrieve<T: serde::de::DeserializeOwned>(&self, key: &crate::types::QueryKey) -> Result<Option<T>, QueryError> {
300 let key_str = key.to_string();
303
304 if let Some(data) = self.data.borrow().get(&key_str) {
305 let deserialized: T = bincode::deserialize(data)
306 .map_err(|e| QueryError::DeserializationError(e.to_string()))?;
307 Ok(Some(deserialized))
308 } else {
309 Ok(None)
310 }
311 }
312
313 pub fn remove(&self, key: &crate::types::QueryKey) -> Result<(), QueryError> {
314 let key_str = key.to_string();
317
318 self.data.borrow_mut().remove(&key_str);
319 Ok(())
320 }
321
322 pub fn clear(&self) -> Result<(), QueryError> {
323 self.data.borrow_mut().clear();
327 Ok(())
328 }
329}
330
331#[derive(Clone, Debug, Serialize, Deserialize)]
335pub struct PersistenceConfig {
336 pub enabled: bool,
338 pub backend: PersistenceBackend,
340 pub max_size: Option<usize>,
342 pub compress: bool,
344 pub encryption_key: Option<String>,
346 pub persist_offline_queue: bool,
348}
349
350impl Default for PersistenceConfig {
351 fn default() -> Self {
352 Self {
353 enabled: true,
354 backend: PersistenceBackend::Memory,
355 max_size: Some(10 * 1024 * 1024), compress: false,
357 encryption_key: None,
358 persist_offline_queue: true,
359 }
360 }
361}
362
363#[derive(Clone, Debug, Serialize, Deserialize)]
365pub enum PersistenceBackend {
366 Memory,
368 LocalStorage,
370 IndexedDB,
372}
373
374pub struct PersistenceManager {
376 #[allow(dead_code)]
377 config: PersistenceConfig,
378 backend: Box<dyn StorageBackend + Send + Sync>,
379}
380
381impl PersistenceManager {
382 pub async fn new(config: PersistenceConfig) -> Result<Self, QueryError> {
384 let backend = Self::create_backend(&config).await?;
385
386 Ok(Self {
387 config,
388 backend,
389 })
390 }
391
392 async fn create_backend(config: &PersistenceConfig) -> Result<Box<dyn StorageBackend + Send + Sync>, QueryError> {
394 match &config.backend {
395 PersistenceBackend::Memory => {
396 Ok(Box::new(MemoryBackend::new()))
397 }
398 PersistenceBackend::LocalStorage => {
399 #[cfg(target_arch = "wasm32")]
400 {
401 LocalStorageBackend::new().map(|b| Box::new(b) as Box<dyn StorageBackend + Send + Sync>)
402 }
403 #[cfg(not(target_arch = "wasm32"))]
404 {
405 Err(QueryError::StorageError("localStorage not available on this platform".to_string()))
406 }
407 }
408 PersistenceBackend::IndexedDB => {
409 Err(QueryError::StorageError("IndexedDB backend not yet implemented".to_string()))
410 }
411 }
412 }
413
414 pub async fn store_cache_entry(&self, key: &crate::types::QueryKey, entry: &crate::client::CacheEntry) -> Result<(), QueryError> {
416 let data = bincode::serialize(entry)
417 .map_err(|e| QueryError::StorageError(format!("Serialization failed: {}", e)))?;
418
419 let key_str = key.to_string();
420 self.backend.store(&key_str, &data).await
421 }
422
423 pub async fn retrieve_cache_entry(&self, key: &crate::types::QueryKey) -> Result<Option<crate::client::CacheEntry>, QueryError> {
425 let key_str = key.to_string();
426 if let Some(data) = self.backend.retrieve(&key_str).await? {
427 let entry: crate::client::CacheEntry = bincode::deserialize(&data)
428 .map_err(|e| QueryError::StorageError(format!("Deserialization failed: {}", e)))?;
429 Ok(Some(entry))
430 } else {
431 Ok(None)
432 }
433 }
434
435 pub async fn remove_cache_entry(&self, key: &crate::types::QueryKey) -> Result<(), QueryError> {
437 let key_str = key.to_string();
438 self.backend.remove(&key_str).await
439 }
440
441 pub async fn list_cached_keys(&self) -> Result<Vec<crate::types::QueryKey>, QueryError> {
443 let keys = self.backend.list_keys().await?;
444 let mut query_keys = Vec::new();
445
446 for key_str in keys {
447 if let Ok(key) = serde_json::from_str(&key_str) {
449 query_keys.push(key);
450 }
451 }
452
453 Ok(query_keys)
454 }
455
456 pub async fn clear_cache(&self) -> Result<(), QueryError> {
458 self.backend.clear().await
459 }
460
461 pub async fn get_stats(&self) -> Result<StorageStats, QueryError> {
463 let size = self.backend.size().await?;
464 Ok(StorageStats {
465 total_entries: size,
466 total_size_bytes: 0, })
468 }
469
470 pub async fn add_to_offline_queue(&self, request: OfflineRequest) -> Result<(), QueryError> {
472 let data = bincode::serialize(&request)
473 .map_err(|e| QueryError::StorageError(format!("Serialization failed: {}", e)))?;
474
475 let key = format!("offline_queue_{}", request.timestamp.elapsed().as_millis());
476 self.backend.store(&key, &data).await
477 }
478
479 pub async fn process_offline_queue(&self) -> Result<Vec<OfflineRequest>, QueryError> {
481 let keys = self.backend.list_keys().await?;
482 let mut requests = Vec::new();
483
484 for key in keys {
485 if key.starts_with("offline_queue_") {
486 if let Some(data) = self.backend.retrieve(&key).await? {
487 if let Ok(request) = bincode::deserialize::<OfflineRequest>(&data) {
488 requests.push(request);
489 }
490 }
491 let _ = self.backend.remove(&key).await;
493 }
494 }
495
496 Ok(requests)
497 }
498
499 pub fn get_offline_queue(&self) -> Vec<OfflineRequest> {
501 Vec::new()
504 }
505
506 pub fn is_cache_persisted(&self) -> bool {
508 true
511 }
512}
513
514#[derive(Clone, Debug, Serialize, Deserialize)]
516pub struct StorageStats {
517 pub total_entries: usize,
519 pub total_size_bytes: usize,
521}
522
523#[derive(Clone, Debug, Serialize, Deserialize)]
525pub struct OfflineRequest {
526 pub request_type: OfflineRequestType,
528 pub data: Vec<u8>,
530 #[serde(with = "instant_serde")]
532 pub timestamp: Instant,
533 pub retry_count: u32,
535}
536
537#[derive(Clone, Debug, Serialize, Deserialize)]
539pub enum OfflineRequestType {
540 Query,
542 Mutation,
544 Invalidate,
546 Remove,
548}
549
550mod instant_serde {
552 use serde::{Deserialize, Deserializer, Serialize, Serializer};
553 use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
554
555 pub fn serialize<S>(instant: &Instant, serializer: S) -> Result<S::Ok, S::Error>
556 where
557 S: Serializer,
558 {
559 let system_time = SystemTime::now() - instant.elapsed();
561 let duration = system_time.duration_since(UNIX_EPOCH).unwrap_or(Duration::ZERO);
562 duration.serialize(serializer)
563 }
564
565 pub fn deserialize<'de, D>(deserializer: D) -> Result<Instant, D::Error>
566 where
567 D: Deserializer<'de>,
568 {
569 let duration = Duration::deserialize(deserializer)?;
570 let system_time = UNIX_EPOCH + duration;
571 let now = SystemTime::now();
572 let elapsed = now.duration_since(system_time).unwrap_or(Duration::ZERO);
573 Ok(Instant::now() - elapsed)
574 }
575}
576
577#[cfg(test)]
578mod tests {
579 use super::*;
580
581 #[tokio::test]
582 async fn test_memory_backend() {
583 let backend = MemoryBackend::new();
584
585 backend.store("test_key", b"test_data").await.unwrap();
587 let data = backend.retrieve("test_key").await.unwrap();
588 assert_eq!(data, Some(b"test_data".to_vec()));
589
590 backend.remove("test_key").await.unwrap();
592 let data = backend.retrieve("test_key").await.unwrap();
593 assert_eq!(data, None);
594
595 backend.store("key1", b"data1").await.unwrap();
597 backend.store("key2", b"data2").await.unwrap();
598 let keys = backend.list_keys().await.unwrap();
599 assert_eq!(keys.len(), 2);
600 assert!(keys.contains(&"key1".to_string()));
601 assert!(keys.contains(&"key2".to_string()));
602
603 backend.clear().await.unwrap();
605 let keys = backend.list_keys().await.unwrap();
606 assert_eq!(keys.len(), 0);
607 }
608
609 #[tokio::test]
610 async fn test_persistence_manager() {
611 let config = PersistenceConfig::default();
612 let manager = PersistenceManager::new(config).await.unwrap();
613
614 let stats = manager.get_stats().await.unwrap();
616 assert_eq!(stats.total_entries, 0);
617 }
618
619 #[tokio::test]
620 async fn test_offline_queue() {
621 let config = PersistenceConfig::default();
622 let manager = PersistenceManager::new(config).await.unwrap();
623
624 let request = OfflineRequest {
625 request_type: OfflineRequestType::Query,
626 data: b"test_data".to_vec(),
627 timestamp: Instant::now(),
628 retry_count: 0,
629 };
630
631 manager.add_to_offline_queue(request.clone()).await.unwrap();
632 let requests = manager.process_offline_queue().await.unwrap();
633 assert_eq!(requests.len(), 1);
634 }
635}