leptos_sync_core/storage/
indexeddb.rs1use super::{LocalStorage, StorageError};
4use async_trait::async_trait;
5use serde::{de::DeserializeOwned, Serialize};
6use wasm_bindgen::JsCast;
7use web_sys::{window, Storage};
8
9pub 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 match Self::set_to_local_storage(key, value) {
151 Ok(()) => Ok(()),
152 Err(_) => {
153 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 match Self::get_from_local_storage(key) {
162 Ok(value) => Ok(value),
163 Err(_) => {
164 self.fallback.get(key).await
166 }
167 }
168 }
169
170 async fn remove(&self, key: &str) -> Result<(), StorageError> {
171 match Self::remove_from_local_storage(key) {
173 Ok(()) => Ok(()),
174 Err(_) => {
175 self.fallback.remove(key).await
177 }
178 }
179 }
180
181 async fn keys(&self) -> Result<Vec<String>, StorageError> {
182 match Self::get_keys_from_local_storage() {
184 Ok(keys) => Ok(keys),
185 Err(_) => {
186 self.fallback.keys().await
188 }
189 }
190 }
191
192 async fn contains_key(&self, key: &str) -> Result<bool, StorageError> {
193 match Self::get_from_local_storage::<()>(key) {
195 Ok(Some(_)) => Ok(true),
196 Ok(None) => Ok(false),
197 Err(_) => {
198 self.fallback.contains_key(key).await
200 }
201 }
202 }
203
204 async fn len(&self) -> Result<usize, StorageError> {
205 match Self::get_keys_from_local_storage() {
207 Ok(keys) => Ok(keys.len()),
208 Err(_) => {
209 self.fallback.len().await
211 }
212 }
213 }
214
215 async fn is_empty(&self) -> Result<bool, StorageError> {
216 match Self::get_keys_from_local_storage() {
218 Ok(keys) => Ok(keys.is_empty()),
219 Err(_) => {
220 self.fallback.is_empty().await
222 }
223 }
224 }
225
226 async fn clear(&self) -> Result<(), StorageError> {
227 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
236impl IndexedDbStorage {
238 pub async fn create_database(&self) -> Result<(), StorageError> {
240 Err(StorageError::Unsupported("IndexedDB not yet fully implemented".to_string()))
242 }
243
244 pub async fn open_database(&self) -> Result<(), StorageError> {
246 Err(StorageError::Unsupported("IndexedDB not yet fully implemented".to_string()))
248 }
249
250 pub async fn create_object_store(&self) -> Result<(), StorageError> {
252 Err(StorageError::Unsupported("IndexedDB not yet fully implemented".to_string()))
254 }
255
256 pub async fn indexeddb_get<T: DeserializeOwned>(&self, _key: &str) -> Result<Option<T>, StorageError> {
258 Err(StorageError::Unsupported("IndexedDB not yet fully implemented".to_string()))
260 }
261
262 pub async fn indexeddb_set<T: Serialize>(&self, _key: &str, _value: &T) -> Result<(), StorageError> {
264 Err(StorageError::Unsupported("IndexedDB not yet fully implemented".to_string()))
266 }
267
268 pub async fn indexeddb_delete(&self, _key: &str) -> Result<(), StorageError> {
270 Err(StorageError::Unsupported("IndexedDB not yet fully implemented".to_string()))
272 }
273
274 pub async fn indexeddb_keys(&self) -> Result<Vec<String>, StorageError> {
276 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 assert!(storage.set("key1", &"value1".to_string()).await.is_ok());
295
296 let value = storage.get::<String>("key1").await.unwrap();
297 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 assert!(cloned_storage.set("key1", &"value1".to_string()).await.is_ok());
315 }
316}