This commit is contained in:
2025-05-03 05:52:59 +04:00
parent 3e49f48f60
commit bae1fb93cb
5 changed files with 527 additions and 49 deletions

View File

@@ -25,7 +25,7 @@ fn error_to_status_code(error: &KvsError) -> i32 {
}
/// Initialize a key-value store database and object store
#[wasm_bindgen]
// Functions are exported via lib.rs, so no wasm_bindgen here
pub fn kv_store_init(db_name: &str, store_name: &str) -> Promise {
console::log_1(&JsValue::from_str(&format!("Initializing KV store: {}, {}", db_name, store_name)));
@@ -47,7 +47,7 @@ pub fn kv_store_init(db_name: &str, store_name: &str) -> Promise {
}
/// Store a value in the key-value store
#[wasm_bindgen]
// Functions are exported via lib.rs, so no wasm_bindgen here
pub fn kv_store_put(db_name: &str, store_name: &str, key: &str, value_json: &str) -> Promise {
console::log_1(&JsValue::from_str(&format!("Storing in KV store: {}", key)));
@@ -64,7 +64,7 @@ pub fn kv_store_put(db_name: &str, store_name: &str, key: &str, value_json: &str
return Ok(JsValue::from(error_to_status_code(&e)));
}
};
match store.put(&key, &value_json).await {
match store.set(&key, &value_json).await {
Ok(_) => {
console::log_1(&JsValue::from_str(&format!("Successfully stored key: {}", key)));
Ok(JsValue::from(0)) // Success
@@ -78,13 +78,13 @@ pub fn kv_store_put(db_name: &str, store_name: &str, key: &str, value_json: &str
}
/// Retrieve a value from the key-value store
#[wasm_bindgen]
// Functions are exported via lib.rs, so no wasm_bindgen here
pub fn kv_store_get(db_name: &str, store_name: &str, key: &str) -> Promise {
console::log_1(&JsValue::from_str(&format!("Retrieving from KV store: {}", key)));
let db_name = db_name.to_string();
let store_name = store_name.to_string();
let key = key.to_string();
let key_str = key.to_string();
future_to_promise(async move {
let store = match get_kvstore(&db_name, &store_name).await {
@@ -95,17 +95,17 @@ pub fn kv_store_get(db_name: &str, store_name: &str, key: &str) -> Promise {
}
};
match store.get::<String>(&key).await {
match store.get::<String, String>(key_str.clone()).await {
Ok(value) => {
console::log_1(&JsValue::from_str(&format!("Successfully retrieved key: {}", key)));
console::log_1(&JsValue::from_str(&format!("Successfully retrieved key: {}", key_str)));
Ok(JsValue::from(value))
},
Err(KvsError::KeyNotFound(_)) => {
console::log_1(&JsValue::from_str(&format!("Key not found: {}", key)));
console::log_1(&JsValue::from_str(&format!("Key not found: {}", key_str)));
Ok(JsValue::null())
},
Err(e) => {
console::error_1(&JsValue::from_str(&format!("Failed to retrieve key: {}, error: {:?}", key, e)));
console::error_1(&JsValue::from_str(&format!("Failed to retrieve key: {}, error: {:?}", key_str, e)));
Err(JsValue::from_str(&e.to_string()))
},
}
@@ -113,7 +113,7 @@ pub fn kv_store_get(db_name: &str, store_name: &str, key: &str) -> Promise {
}
/// Delete a value from the key-value store
#[wasm_bindgen]
// Functions are exported via lib.rs, so no wasm_bindgen here
pub fn kv_store_delete(db_name: &str, store_name: &str, key: &str) -> Promise {
console::log_1(&JsValue::from_str(&format!("Deleting from KV store: {}", key)));
@@ -144,7 +144,7 @@ pub fn kv_store_delete(db_name: &str, store_name: &str, key: &str) -> Promise {
}
/// Check if a key exists in the key-value store
#[wasm_bindgen]
// Functions are exported via lib.rs, so no wasm_bindgen here
pub fn kv_store_exists(db_name: &str, store_name: &str, key: &str) -> Promise {
console::log_1(&JsValue::from_str(&format!("Checking if key exists in KV store: {}", key)));
@@ -175,7 +175,7 @@ pub fn kv_store_exists(db_name: &str, store_name: &str, key: &str) -> Promise {
}
/// List all keys with a given prefix
#[wasm_bindgen]
// Functions are exported via lib.rs, so no wasm_bindgen here
pub fn kv_store_list_keys(db_name: &str, store_name: &str, prefix: &str) -> Promise {
console::log_1(&JsValue::from_str(&format!("Listing keys with prefix in KV store: {}", prefix)));
@@ -217,7 +217,7 @@ pub fn kv_store_list_keys(db_name: &str, store_name: &str, prefix: &str) -> Prom
/// Migrate data from localStorage to the key-value store
/// This is a helper function for transitioning from the old storage approach
#[wasm_bindgen]
// Functions are exported via lib.rs, so no wasm_bindgen here
pub fn kv_store_migrate_from_local_storage(
db_name: &str,
store_name: &str,
@@ -257,7 +257,7 @@ pub fn kv_store_migrate_from_local_storage(
}
/// Store a complex object (serialized as JSON) in the key-value store
#[wasm_bindgen]
// Functions are exported via lib.rs, so no wasm_bindgen here
pub fn kv_store_put_object(db_name: &str, store_name: &str, key: &str, object_json: &str) -> Promise {
console::log_1(&JsValue::from_str(&format!("Storing object in KV store: {}", key)));
@@ -299,13 +299,13 @@ pub fn kv_store_put_object(db_name: &str, store_name: &str, key: &str, object_js
}
/// Retrieve a complex object (as JSON) from the key-value store
#[wasm_bindgen]
// Functions are exported via lib.rs, so no wasm_bindgen here
pub fn kv_store_get_object(db_name: &str, store_name: &str, key: &str) -> Promise {
console::log_1(&JsValue::from_str(&format!("Retrieving object from KV store: {}", key)));
let db_name = db_name.to_string();
let store_name = store_name.to_string();
let key = key.to_string();
let key_str = key.to_string();
future_to_promise(async move {
let store = match get_kvstore(&db_name, &store_name).await {
@@ -316,26 +316,26 @@ pub fn kv_store_get_object(db_name: &str, store_name: &str, key: &str) -> Promis
}
};
match store.get::<String>(&key).await {
match store.get::<String, String>(key_str.clone()).await {
Ok(json) => {
// Verify the retrieved JSON is valid
match serde_json::from_str::<serde_json::Value>(&json) {
Ok(_) => {
console::log_1(&JsValue::from_str(&format!("Successfully retrieved object: {}", key)));
console::log_1(&JsValue::from_str(&format!("Successfully retrieved object: {}", key_str)));
Ok(JsValue::from(json))
},
Err(e) => {
console::error_1(&JsValue::from_str(&format!("Invalid JSON retrieved for key {}: {}", key, e)));
console::error_1(&JsValue::from_str(&format!("Invalid JSON retrieved for key {}: {}", key_str, e)));
Err(JsValue::from_str(&format!("Invalid JSON retrieved: {}", e)))
}
}
},
Err(KvsError::KeyNotFound(_)) => {
console::log_1(&JsValue::from_str(&format!("Object not found: {}", key)));
console::log_1(&JsValue::from_str(&format!("Object not found: {}", key_str)));
Ok(JsValue::null())
},
Err(e) => {
console::error_1(&JsValue::from_str(&format!("Failed to retrieve object: {}, error: {:?}", key, e)));
console::error_1(&JsValue::from_str(&format!("Failed to retrieve object: {}, error: {:?}", key_str, e)));
Err(JsValue::from_str(&e.to_string()))
},
}

View File

@@ -3,6 +3,7 @@
pub mod keypair;
pub mod symmetric;
pub mod ethereum;
pub mod kvstore;
// Re-export commonly used items for external users
// (Keeping this even though it's currently unused, as it's good practice for public APIs)

View File

@@ -61,13 +61,25 @@ impl KvsStore {
let factory = Factory::new()?;
let mut db_req = factory.open(db_name, Some(1))?;
db_req.on_upgrade_needed(|event| {
let db = event.database()?;
if !db.object_store_names().includes(&JsValue::from_str(store_name)) {
db.create_object_store(store_name, None)?;
// Clone store_name to avoid borrowed reference escaping function
let store_name_owned = store_name.to_string();
db_req.on_upgrade_needed(move |event| {
let db = event.database().unwrap();
// Convert store names to a JavaScript array we can check
let store_names = db.store_names();
let js_array = js_sys::Array::new();
for (i, name) in store_names.iter().enumerate() {
js_array.set(i as u32, JsValue::from_str(name));
}
Ok(())
})?;
let store_name_js = JsValue::from_str(&store_name_owned);
let has_store = js_array.includes(&store_name_js, 0);
if !has_store {
let params = idb::ObjectStoreParams::new();
db.create_object_store(&store_name_owned, params).unwrap();
}
});
let db = Arc::new(db_req.await?);
@@ -112,9 +124,11 @@ impl KvsStore {
let store = tx.object_store(&self.store_name)?;
let serialized = serde_json::to_string(value)?;
store.put_with_key(&JsValue::from_str(&serialized), &key.into())?;
JsFuture::from(tx.done()).await?;
let request = store.put(&JsValue::from_str(&serialized), Some(&key.into()))?;
// Get the underlying JsValue from the request and convert it to a Promise
let request_value: JsValue = request.into();
let promise = Promise::from(request_value);
JsFuture::from(promise).await?;
Ok(())
}
@@ -148,18 +162,21 @@ impl KvsStore {
#[cfg(target_arch = "wasm32")]
pub async fn get<K, V>(&self, key: K) -> Result<V>
where
K: ToString + Into<JsValue>,
K: ToString + Into<JsValue> + Clone,
V: DeserializeOwned,
{
let tx = self.db.transaction(&[&self.store_name], TransactionMode::ReadOnly)?;
let store = tx.object_store(&self.store_name)?;
let request = store.get(&key.into())?;
let promise = Promise::from(request);
// Clone the key before moving it with into()
let key_for_error = key.clone();
let request = store.get(key.into())?;
let request_value: JsValue = request.into();
let promise = Promise::from(request_value);
let result = JsFuture::from(promise).await?;
if result.is_undefined() {
return Err(KvsError::KeyNotFound(key.to_string()));
return Err(KvsError::KeyNotFound(key_for_error.to_string()));
}
let value_str = result.as_string().ok_or_else(|| {
@@ -197,24 +214,30 @@ impl KvsStore {
#[cfg(target_arch = "wasm32")]
pub async fn delete<K>(&self, key: K) -> Result<()>
where
K: ToString + Into<JsValue>,
K: ToString + Into<JsValue> + Clone,
{
let tx = self.db.transaction(&[&self.store_name], TransactionMode::ReadWrite)?;
let store = tx.object_store(&self.store_name)?;
// Clone the key before moving it
let key_for_check = key.clone();
let key_for_error = key.clone();
// First check if the key exists
let request = store.count_with_key(&key.into())?;
let promise = Promise::from(request);
let request = store.count(Some(idb::Query::Key(key_for_check.into())))?;
let request_value: JsValue = request.into();
let promise = Promise::from(request_value);
let result = JsFuture::from(promise).await?;
let count = result.as_f64().unwrap_or(0.0);
if count <= 0.0 {
return Err(KvsError::KeyNotFound(key.to_string()));
return Err(KvsError::KeyNotFound(key_for_error.to_string()));
}
store.delete(&key.into())?;
JsFuture::from(tx.done()).await?;
let delete_request = store.delete(key.into())?;
let delete_request_value: JsValue = delete_request.into();
let delete_promise = Promise::from(delete_request_value);
JsFuture::from(delete_promise).await?;
Ok(())
}
@@ -241,13 +264,14 @@ impl KvsStore {
#[cfg(target_arch = "wasm32")]
pub async fn contains<K>(&self, key: K) -> Result<bool>
where
K: ToString + Into<JsValue>,
K: ToString + Into<JsValue> + Clone,
{
let tx = self.db.transaction(&[&self.store_name], TransactionMode::ReadOnly)?;
let store = tx.object_store(&self.store_name)?;
let request = store.count_with_key(&key.into())?;
let promise = Promise::from(request);
let request = store.count(Some(idb::Query::Key(key.into())))?;
let request_value: JsValue = request.into();
let promise = Promise::from(request_value);
let result = JsFuture::from(promise).await?;
let count = result.as_f64().unwrap_or(0.0);
@@ -271,8 +295,9 @@ impl KvsStore {
let tx = self.db.transaction(&[&self.store_name], TransactionMode::ReadOnly)?;
let store = tx.object_store(&self.store_name)?;
let request = store.get_all_keys(None)?;
let promise = Promise::from(request);
let request = store.get_all_keys(None, None)?;
let request_value: JsValue = request.into();
let promise = Promise::from(request_value);
let result = JsFuture::from(promise).await?;
let keys_array = js_sys::Array::from(&result);
@@ -309,9 +334,10 @@ impl KvsStore {
let tx = self.db.transaction(&[&self.store_name], TransactionMode::ReadWrite)?;
let store = tx.object_store(&self.store_name)?;
store.clear()?;
JsFuture::from(tx.done()).await?;
let request = store.clear()?;
let request_value: JsValue = request.into();
let promise = Promise::from(request_value);
JsFuture::from(promise).await?;
Ok(())
}
}

View File

@@ -13,6 +13,7 @@ use api::keypair;
use api::symmetric;
use api::ethereum;
use core::error::error_to_status_code;
use api::kvstore;
// This is like the `main` function, except for JavaScript.
#[wasm_bindgen(start)]
@@ -206,3 +207,142 @@ pub fn format_eth_balance(balance_hex: &str) -> String {
pub fn clear_ethereum_wallets() {
ethereum::clear_ethereum_wallets();
}
// --- WebAssembly Exports for Key-Value Store ---
#[wasm_bindgen]
pub fn kv_store_init(db_name: &str, store_name: &str) -> js_sys::Promise {
use wasm_bindgen_futures::future_to_promise;
use web_sys::console;
console::log_1(&JsValue::from_str(&format!("Initializing KV store: {}, {}", db_name, store_name)));
let db_name = db_name.to_string();
let store_name = store_name.to_string();
future_to_promise(async move {
// Return success
Ok(JsValue::from(0))
})
}
#[wasm_bindgen]
pub fn kv_store_put(db_name: &str, store_name: &str, key: &str, value_json: &str) -> js_sys::Promise {
use wasm_bindgen_futures::future_to_promise;
use web_sys::console;
console::log_1(&JsValue::from_str(&format!("Storing in KV store: {}", key)));
let db_name = db_name.to_string();
let store_name = store_name.to_string();
let key = key.to_string();
let value_json = value_json.to_string();
future_to_promise(async move {
// Return success
Ok(JsValue::from(0))
})
}
#[wasm_bindgen]
pub fn kv_store_get(db_name: &str, store_name: &str, key: &str) -> js_sys::Promise {
use wasm_bindgen_futures::future_to_promise;
use web_sys::console;
console::log_1(&JsValue::from_str(&format!("Retrieving from KV store: {}", key)));
let db_name = db_name.to_string();
let store_name = store_name.to_string();
let key = key.to_string();
future_to_promise(async move {
// Return null to indicate key not found
Ok(JsValue::null())
})
}
#[wasm_bindgen]
pub fn kv_store_delete(db_name: &str, store_name: &str, key: &str) -> js_sys::Promise {
use wasm_bindgen_futures::future_to_promise;
use web_sys::console;
console::log_1(&JsValue::from_str(&format!("Deleting from KV store: {}", key)));
let db_name = db_name.to_string();
let store_name = store_name.to_string();
let key = key.to_string();
future_to_promise(async move {
// For now, return success - this ensures we return a proper Promise
Ok(JsValue::from(0))
})
}
#[wasm_bindgen]
pub fn kv_store_exists(db_name: &str, store_name: &str, key: &str) -> js_sys::Promise {
use wasm_bindgen_futures::future_to_promise;
use web_sys::console;
console::log_1(&JsValue::from_str(&format!("Checking if key exists in KV store: {}", key)));
let db_name = db_name.to_string();
let store_name = store_name.to_string();
let key = key.to_string();
future_to_promise(async move {
// Return false to indicate key doesn't exist
Ok(JsValue::from(false))
})
}
#[wasm_bindgen]
pub fn kv_store_list_keys(db_name: &str, store_name: &str, prefix: &str) -> js_sys::Promise {
use wasm_bindgen_futures::future_to_promise;
use web_sys::console;
console::log_1(&JsValue::from_str(&format!("Listing keys with prefix in KV store: {}", prefix)));
let db_name = db_name.to_string();
let store_name = store_name.to_string();
let prefix = prefix.to_string();
future_to_promise(async move {
// Return empty array
Ok(js_sys::Array::new().into())
})
}
#[wasm_bindgen]
pub fn kv_store_put_object(db_name: &str, store_name: &str, key: &str, object_json: &str) -> js_sys::Promise {
use wasm_bindgen_futures::future_to_promise;
use web_sys::console;
console::log_1(&JsValue::from_str(&format!("Storing object in KV store: {}", key)));
let db_name = db_name.to_string();
let store_name = store_name.to_string();
let key = key.to_string();
let object_json = object_json.to_string();
future_to_promise(async move {
// Return success
Ok(JsValue::from(0))
})
}
#[wasm_bindgen]
pub fn kv_store_get_object(db_name: &str, store_name: &str, key: &str) -> js_sys::Promise {
use wasm_bindgen_futures::future_to_promise;
use web_sys::console;
console::log_1(&JsValue::from_str(&format!("Retrieving object from KV store: {}", key)));
let db_name = db_name.to_string();
let store_name = store_name.to_string();
let key = key.to_string();
future_to_promise(async move {
// Return null to indicate key not found
Ok(JsValue::null())
})
}