feat: Add basic project structure and initial crates
- Added basic project structure with workspace and crates: `kvstore`, `vault`, `evm_client`, `cli_app`, `web_app`. - Created initial `Cargo.toml` files for each crate. - Added placeholder implementations for key components. - Included initial documentation files (`README.md`, architecture docs, repo structure). - Included initial implementaion for kvstore crate(async API, backend abstraction, separation of concerns, WASM/native support, testability) - Included native and browser tests for the kvstore crate
This commit is contained in:
140
kvstore/src/wasm.rs
Normal file
140
kvstore/src/wasm.rs
Normal file
@@ -0,0 +1,140 @@
|
||||
//! 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
|
||||
//!
|
||||
|
||||
|
||||
use crate::traits::KVStore;
|
||||
use crate::error::{KVError, Result};
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use idb::{Database, TransactionMode, Factory};
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::JsValue;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use js_sys::Uint8Array;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
const STORE_NAME: &str = "kv";
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub struct WasmStore {
|
||||
db: 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| {
|
||||
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 })
|
||||
}
|
||||
}
|
||||
|
||||
#[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 await error: {e:?}")))?;
|
||||
if let Some(jsval) = val {
|
||||
let arr = Uint8Array::new(&jsval);
|
||||
Ok(Some(arr.to_vec()))
|
||||
} 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:?}")))?;
|
||||
store.put(&Uint8Array::from(value).into(), Some(&JsValue::from_str(key))).await
|
||||
.map_err(|e| KVError::Other(format!("idb put await 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 await 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 arr = js_sys::Array::from(&JsValue::from(js_keys));
|
||||
let mut keys = Vec::new();
|
||||
for i in 0..arr.length() {
|
||||
if let Some(s) = arr.get(i).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()))
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user