//! WASM backend for kvstore using IndexedDB (idb crate) //! //! # Platform //! //! This backend is only available when compiling for `wasm32` (browser/WebAssembly). //! It uses the `idb` crate for async IndexedDB operations. //! //! # Usage //! //! This implementation is designed to run inside a browser environment and is not supported on native targets. //! //! # Example //! //! WASM backend for kvstore using IndexedDB (idb crate) //! Only compiled for wasm32 targets #[cfg(target_arch = "wasm32")] use crate::traits::KVStore; #[cfg(target_arch = "wasm32")] use crate::error::{KVError, Result}; #[cfg(target_arch = "wasm32")] use async_trait::async_trait; #[cfg(target_arch = "wasm32")] use idb::{Database, TransactionMode, Factory}; #[cfg(target_arch = "wasm32")] use wasm_bindgen::JsValue; // use wasm-bindgen directly for Uint8Array if needed #[cfg(target_arch = "wasm32")] use std::rc::Rc; #[cfg(target_arch = "wasm32")] const STORE_NAME: &str = "kv"; #[cfg(target_arch = "wasm32")] #[derive(Clone)] pub struct WasmStore { db: Rc, } #[cfg(target_arch = "wasm32")] impl WasmStore { pub async fn open(name: &str) -> Result { let factory = Factory::new().map_err(|e| KVError::Other(format!("IndexedDB factory error: {e:?}")))?; let mut open_req = factory.open(name, None) .map_err(|e| KVError::Other(format!("IndexedDB factory open error: {e:?}")))?; open_req.on_upgrade_needed(|event| { use idb::DatabaseEvent; let db = event.database().expect("Failed to get database in upgrade event"); if !db.store_names().iter().any(|n| n == STORE_NAME) { db.create_object_store(STORE_NAME, Default::default()).unwrap(); } }); let db = open_req.await.map_err(|e| KVError::Other(format!("IndexedDB open error: {e:?}")))?; Ok(Self { db: Rc::new(db) }) } } #[cfg(target_arch = "wasm32")] #[async_trait(?Send)] impl KVStore for WasmStore { async fn get(&self, key: &str) -> Result>> { let tx = self.db.transaction(&[STORE_NAME], TransactionMode::ReadOnly) .map_err(|e| KVError::Other(format!("idb transaction error: {e:?}")))?; let store = tx.object_store(STORE_NAME) .map_err(|e| KVError::Other(format!("idb object_store error: {e:?}")))?; use idb::Query; let val = store.get(Query::from(JsValue::from_str(key)))?.await .map_err(|e| KVError::Other(format!("idb get error: {e:?}")))?; if let Some(jsval) = val { match jsval.into_serde::>() { Ok(bytes) => Ok(Some(bytes)), Err(_) => Ok(None), } } else { Ok(None) } } async fn set(&self, key: &str, value: &[u8]) -> Result<()> { let tx = self.db.transaction(&[STORE_NAME], TransactionMode::ReadWrite) .map_err(|e| KVError::Other(format!("idb transaction error: {e:?}")))?; let store = tx.object_store(STORE_NAME) .map_err(|e| KVError::Other(format!("idb object_store error: {e:?}")))?; let js_value = JsValue::from_serde(&value).map_err(|e| KVError::Other(format!("serde error: {e:?}")))?; store.put(&js_value, Some(&JsValue::from_str(key)))?.await .map_err(|e| KVError::Other(format!("idb put error: {e:?}")))?; Ok(()) } async fn remove(&self, key: &str) -> Result<()> { let tx = self.db.transaction(&[STORE_NAME], TransactionMode::ReadWrite) .map_err(|e| KVError::Other(format!("idb transaction error: {e:?}")))?; let store = tx.object_store(STORE_NAME) .map_err(|e| KVError::Other(format!("idb object_store error: {e:?}")))?; use idb::Query; store.delete(Query::from(JsValue::from_str(key)))?.await .map_err(|e| KVError::Other(format!("idb delete error: {e:?}")))?; Ok(()) } async fn contains_key(&self, key: &str) -> Result { Ok(self.get(key).await?.is_some()) } async fn keys(&self) -> Result> { let tx = self.db.transaction(&[STORE_NAME], TransactionMode::ReadOnly) .map_err(|e| KVError::Other(format!("idb transaction error: {e:?}")))?; let store = tx.object_store(STORE_NAME) .map_err(|e| KVError::Other(format!("idb object_store error: {e:?}")))?; let js_keys = store.get_all_keys(None, None)?.await .map_err(|e| KVError::Other(format!("idb get_all_keys error: {e:?}")))?; let mut keys = Vec::new(); for key in js_keys.iter() { if let Some(s) = key.as_string() { keys.push(s); } } Ok(keys) } async fn clear(&self) -> Result<()> { let tx = self.db.transaction(&[STORE_NAME], TransactionMode::ReadWrite) .map_err(|e| KVError::Other(format!("idb transaction error: {e:?}")))?; let store = tx.object_store(STORE_NAME) .map_err(|e| KVError::Other(format!("idb object_store error: {e:?}")))?; store.clear()?.await .map_err(|e| KVError::Other(format!("idb clear error: {e:?}")))?; Ok(()) } } #[cfg(not(target_arch = "wasm32"))] pub struct WasmStore; #[cfg(not(target_arch = "wasm32"))] #[async_trait] impl KVStore for WasmStore { async fn get(&self, _key: &str) -> Result>> { Err(KVError::Other("WasmStore is only available on wasm32 targets".to_string())) } async fn set(&self, _key: &str, _value: &[u8]) -> Result<()> { Err(KVError::Other("WasmStore is only available on wasm32 targets".to_string())) } async fn delete(&self, _key: &str) -> Result<()> { Err(KVError::Other("WasmStore is only available on wasm32 targets".to_string())) } async fn exists(&self, _key: &str) -> Result { Err(KVError::Other("WasmStore is only available on wasm32 targets".to_string())) } }