sal-modular/kvstore/src/wasm.rs

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()))
}
}