149 lines
5.8 KiB
Rust
149 lines
5.8 KiB
Rust
//! 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<Database>,
|
|
}
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
impl WasmStore {
|
|
pub async fn open(name: &str) -> Result<Self> {
|
|
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<Option<Vec<u8>>> {
|
|
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::<Vec<u8>>() {
|
|
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<bool> {
|
|
Ok(self.get(key).await?.is_some())
|
|
}
|
|
|
|
async fn keys(&self) -> Result<Vec<String>> {
|
|
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<Option<Vec<u8>>> {
|
|
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<bool> {
|
|
Err(KVError::Other("WasmStore is only available on wasm32 targets".to_string()))
|
|
}
|
|
}
|