leptos_sync_core/storage/
indexeddb.rs

1//! IndexedDB storage implementation
2
3use super::{LocalStorage, StorageError};
4use async_trait::async_trait;
5use serde::{de::DeserializeOwned, Serialize};
6use wasm_bindgen::JsCast;
7use web_sys::{window, Storage};
8
9/// IndexedDB storage implementation (currently falls back to localStorage)
10pub struct IndexedDbStorage {
11    db_name: String,
12    store_name: String,
13    fallback: super::memory::MemoryStorage,
14}
15
16impl IndexedDbStorage {
17    pub fn new(db_name: String, store_name: String) -> Self {
18        Self {
19            db_name,
20            store_name,
21            fallback: super::memory::MemoryStorage::new(),
22        }
23    }
24
25    /// Get the localStorage fallback
26    #[cfg(target_arch = "wasm32")]
27    fn get_local_storage() -> Result<Storage, StorageError> {
28        let window = window().ok_or_else(|| {
29            StorageError::OperationFailed("No window available".to_string())
30        })?;
31        
32        let storage = window.local_storage().map_err(|_| {
33            StorageError::OperationFailed("Failed to get localStorage".to_string())
34        })?.ok_or_else(|| {
35            StorageError::OperationFailed("localStorage not available".to_string())
36        })?;
37        
38        Ok(storage)
39    }
40
41    /// Get the localStorage fallback (non-WASM - always fails to trigger fallback)
42    #[cfg(not(target_arch = "wasm32"))]
43    fn get_local_storage() -> Result<Storage, StorageError> {
44        Err(StorageError::OperationFailed("localStorage not available in non-WASM environment".to_string()))
45    }
46
47    /// Get a value from localStorage
48    #[cfg(target_arch = "wasm32")]
49    fn get_from_local_storage<T: DeserializeOwned>(key: &str) -> Result<Option<T>, StorageError> {
50        let storage = Self::get_local_storage()?;
51        
52        match storage.get_item(key).map_err(|_| {
53            StorageError::OperationFailed("Failed to get item from localStorage".to_string())
54        })? {
55            Some(json_str) => {
56                let value = serde_json::from_str(&json_str)
57                    .map_err(|e| StorageError::Serialization(e))?;
58                Ok(Some(value))
59            }
60            None => Ok(None),
61        }
62    }
63
64    /// Set a value in localStorage
65    #[cfg(target_arch = "wasm32")]
66    fn set_to_local_storage<T: Serialize>(key: &str, value: &T) -> Result<(), StorageError> {
67        let storage = Self::get_local_storage()?;
68        
69        let json_str = serde_json::to_string(value)
70            .map_err(|e| StorageError::Serialization(e))?;
71        
72        storage.set_item(key, &json_str).map_err(|_| {
73            StorageError::OperationFailed("Failed to set item in localStorage".to_string())
74        })?;
75        
76        Ok(())
77    }
78
79    /// Remove a value from localStorage
80    #[cfg(target_arch = "wasm32")]
81    fn remove_from_local_storage(key: &str) -> Result<(), StorageError> {
82        let storage = Self::get_local_storage()?;
83        
84        storage.remove_item(key).map_err(|_| {
85            StorageError::OperationFailed("Failed to remove item from localStorage".to_string())
86        })?;
87        
88        Ok(())
89    }
90
91    /// Get all keys from localStorage
92    #[cfg(target_arch = "wasm32")]
93    fn get_keys_from_local_storage() -> Result<Vec<String>, StorageError> {
94        let storage = Self::get_local_storage()?;
95        let length = storage.length().map_err(|_| {
96            StorageError::OperationFailed("Failed to get localStorage length".to_string())
97        })?;
98        
99        let mut keys = Vec::new();
100        for i in 0..length {
101            if let Some(key) = storage.key(i).map_err(|_| {
102                StorageError::OperationFailed("Failed to get localStorage key".to_string())
103            })? {
104                keys.push(key);
105            }
106        }
107        
108        Ok(keys)
109    }
110
111    /// Get a value from localStorage (non-WASM - always fails to trigger fallback)
112    #[cfg(not(target_arch = "wasm32"))]
113    fn get_from_local_storage<T: DeserializeOwned>(_key: &str) -> Result<Option<T>, StorageError> {
114        Err(StorageError::OperationFailed("localStorage not available in non-WASM environment".to_string()))
115    }
116
117    /// Set a value in localStorage (non-WASM - always fails to trigger fallback)
118    #[cfg(not(target_arch = "wasm32"))]
119    fn set_to_local_storage<T: Serialize>(_key: &str, _value: &T) -> Result<(), StorageError> {
120        Err(StorageError::OperationFailed("localStorage not available in non-WASM environment".to_string()))
121    }
122
123    /// Remove a value from localStorage (non-WASM - always fails to trigger fallback)
124    #[cfg(not(target_arch = "wasm32"))]
125    fn remove_from_local_storage(_key: &str) -> Result<(), StorageError> {
126        Err(StorageError::OperationFailed("localStorage not available in non-WASM environment".to_string()))
127    }
128
129    /// Get all keys from localStorage (non-WASM - always fails to trigger fallback)
130    #[cfg(not(target_arch = "wasm32"))]
131    fn get_keys_from_local_storage() -> Result<Vec<String>, StorageError> {
132        Err(StorageError::OperationFailed("localStorage not available in non-WASM environment".to_string()))
133    }
134}
135
136impl Clone for IndexedDbStorage {
137    fn clone(&self) -> Self {
138        Self {
139            db_name: self.db_name.clone(),
140            store_name: self.store_name.clone(),
141            fallback: self.fallback.clone(),
142        }
143    }
144}
145
146#[async_trait]
147impl LocalStorage for IndexedDbStorage {
148    async fn set<T: Serialize + Send + Sync>(&self, key: &str, value: &T) -> Result<(), StorageError> {
149        // For now, use localStorage fallback since IndexedDB is not fully implemented
150        match Self::set_to_local_storage(key, value) {
151            Ok(()) => Ok(()),
152            Err(_) => {
153                // Fall back to in-memory storage
154                self.fallback.set(key, value).await
155            }
156        }
157    }
158
159    async fn get<T: DeserializeOwned + Send + Sync>(&self, key: &str) -> Result<Option<T>, StorageError> {
160        // For now, use localStorage fallback since IndexedDB is not fully implemented
161        match Self::get_from_local_storage(key) {
162            Ok(value) => Ok(value),
163            Err(_) => {
164                // Fall back to in-memory storage
165                self.fallback.get(key).await
166            }
167        }
168    }
169
170    async fn remove(&self, key: &str) -> Result<(), StorageError> {
171        // For now, use localStorage fallback since IndexedDB is not fully implemented
172        match Self::remove_from_local_storage(key) {
173            Ok(()) => Ok(()),
174            Err(_) => {
175                // Fall back to in-memory storage
176                self.fallback.remove(key).await
177            }
178        }
179    }
180
181    async fn keys(&self) -> Result<Vec<String>, StorageError> {
182        // For now, use localStorage fallback since IndexedDB is not fully implemented
183        match Self::get_keys_from_local_storage() {
184            Ok(keys) => Ok(keys),
185            Err(_) => {
186                // Fall back to in-memory storage
187                self.fallback.keys().await
188            }
189        }
190    }
191
192    async fn contains_key(&self, key: &str) -> Result<bool, StorageError> {
193        // For now, use localStorage fallback since IndexedDB is not fully implemented
194        match Self::get_from_local_storage::<()>(key) {
195            Ok(Some(_)) => Ok(true),
196            Ok(None) => Ok(false),
197            Err(_) => {
198                // Fall back to in-memory storage
199                self.fallback.contains_key(key).await
200            }
201        }
202    }
203
204    async fn len(&self) -> Result<usize, StorageError> {
205        // For now, use localStorage fallback since IndexedDB is not fully implemented
206        match Self::get_keys_from_local_storage() {
207            Ok(keys) => Ok(keys.len()),
208            Err(_) => {
209                // Fall back to in-memory storage
210                self.fallback.len().await
211            }
212        }
213    }
214
215    async fn is_empty(&self) -> Result<bool, StorageError> {
216        // For now, use localStorage fallback since IndexedDB is not fully implemented
217        match Self::get_keys_from_local_storage() {
218            Ok(keys) => Ok(keys.is_empty()),
219            Err(_) => {
220                // Fall back to in-memory storage
221                self.fallback.is_empty().await
222            }
223        }
224    }
225
226    async fn clear(&self) -> Result<(), StorageError> {
227        // For now, use localStorage fallback since IndexedDB is not fully implemented
228        let storage = Self::get_local_storage()?;
229        storage.clear().map_err(|_| {
230            StorageError::OperationFailed("Failed to clear localStorage".to_string())
231        })?;
232        Ok(())
233    }
234}
235
236// IndexedDB-specific methods (not yet fully implemented)
237impl IndexedDbStorage {
238    /// Create an IndexedDB database
239    pub async fn create_database(&self) -> Result<(), StorageError> {
240        // TODO: Implement actual IndexedDB creation
241        Err(StorageError::Unsupported("IndexedDB not yet fully implemented".to_string()))
242    }
243
244    /// Open an IndexedDB database
245    pub async fn open_database(&self) -> Result<(), StorageError> {
246        // TODO: Implement actual IndexedDB opening
247        Err(StorageError::Unsupported("IndexedDB not yet fully implemented".to_string()))
248    }
249
250    /// Create an object store
251    pub async fn create_object_store(&self) -> Result<(), StorageError> {
252        // TODO: Implement actual object store creation
253        Err(StorageError::Unsupported("IndexedDB not yet fully implemented".to_string()))
254    }
255
256    /// Get a value from IndexedDB
257    pub async fn indexeddb_get<T: DeserializeOwned>(&self, _key: &str) -> Result<Option<T>, StorageError> {
258        // TODO: Implement actual IndexedDB get
259        Err(StorageError::Unsupported("IndexedDB not yet fully implemented".to_string()))
260    }
261
262    /// Set a value in IndexedDB
263    pub async fn indexeddb_set<T: Serialize>(&self, _key: &str, _value: &T) -> Result<(), StorageError> {
264        // TODO: Implement actual IndexedDB set
265        Err(StorageError::Unsupported("IndexedDB not yet fully implemented".to_string()))
266    }
267
268    /// Delete a value from IndexedDB
269    pub async fn indexeddb_delete(&self, _key: &str) -> Result<(), StorageError> {
270        // TODO: Implement actual IndexedDB delete
271        Err(StorageError::Unsupported("IndexedDB not yet fully implemented".to_string()))
272    }
273
274    /// Get all keys from IndexedDB
275    pub async fn indexeddb_keys(&self) -> Result<Vec<String>, StorageError> {
276        // TODO: Implement actual IndexedDB keys
277        Err(StorageError::Unsupported("IndexedDB not yet fully implemented".to_string()))
278    }
279}
280
281#[cfg(test)]
282mod tests {
283    use super::*;
284
285    #[cfg(target_arch = "wasm32")]
286    #[tokio::test]
287    async fn test_indexeddb_storage_fallback() {
288        let storage = IndexedDbStorage::new(
289            "test_db".to_string(),
290            "test_store".to_string(),
291        );
292
293        // Test that operations work (falling back to localStorage or memory)
294        assert!(storage.set("key1", &"value1".to_string()).await.is_ok());
295        
296        let value = storage.get::<String>("key1").await.unwrap();
297        // Note: This might be None if localStorage is not available in test environment
298        // The important thing is that it doesn't panic
299        
300        // Test remove
301        assert!(storage.remove("key1").await.is_ok());
302    }
303
304    #[cfg(target_arch = "wasm32")]
305    #[tokio::test]
306    async fn test_indexeddb_storage_clone() {
307        let storage = IndexedDbStorage::new(
308            "test_db".to_string(),
309            "test_store".to_string(),
310        );
311        let cloned_storage = storage.clone();
312
313        // Test that cloned storage works
314        assert!(cloned_storage.set("key1", &"value1".to_string()).await.is_ok());
315    }
316}