diff --git a/Cargo.toml b/Cargo.toml index 136933f..4f820bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,12 +21,12 @@ default = ["console_error_panic_hook"] [dependencies] console_error_panic_hook = { version = "0.1.7", optional = true } -js-sys = "0.3.56" +js-sys = "0.3.57" num-traits = { version = "0.2.14", default-features = false } thiserror = "1.0.30" tokio = { version = "1.17.0", default-features = false, features = ["sync"] } -wasm-bindgen = "0.2.79" -web-sys = { version = "0.3.56", features = [ +wasm-bindgen = "0.2.80" +web-sys = { version = "0.3.57", features = [ "console", "DomException", "DomStringList", @@ -54,7 +54,7 @@ wee_alloc = { version = "0.4.5", optional = true } serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.79" serde-wasm-bindgen = "0.4.2" -wasm-bindgen-test = "0.3.29" +wasm-bindgen-test = "0.3.30" [profile.release] # Tell `rustc` to optimize for small code size. diff --git a/src/error.rs b/src/error.rs index fb803b9..c154eb3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -7,6 +7,7 @@ pub type Result = std::result::Result; /// Error type for `rexie` crate #[derive(Debug, Error, Clone, PartialEq)] +#[non_exhaustive] pub enum Error { /// Error when receiving message from async channel #[error("error when receiving message from async channel")] @@ -52,6 +53,10 @@ pub enum Error { #[error("failed to execute indexed db request: {}", js_error_display(.0))] IndexedDbRequestError(JsValue), + /// Failed to execute indexed db upgrade + #[error("failed to execute indexed db upgrade: {}", js_error_display(.0))] + IndexedDbUpgradeFailed(JsValue), + /// Key range error #[error("key range error: {}", js_error_display(.0))] KeyRangeError(JsValue), @@ -64,6 +69,10 @@ pub enum Error { #[error("failed to open object store: {}", js_error_display(.0))] ObjectStoreOpenFailed(JsValue), + /// Failed to commit indexed db transaction + #[error("failed to commit indexed db transaction: {}", js_error_display(.0))] + TransactionCommitFailed(JsValue), + /// Failed to execute indexed db transaction #[error("failed to execute db transaction: {}", js_error_display(.0))] TransactionExecutionFailed(JsValue), @@ -99,9 +108,11 @@ impl From for JsValue { Error::IndexedDbNotFound => "IndexedDbNotFound".into(), Error::IndexedDbNotSupported(js_value) => js_value, Error::IndexedDbOpenFailed(js_value) => js_value, + Error::IndexedDbUpgradeFailed(js_value) => js_value, Error::KeyRangeError(js_value) => js_value, Error::ObjectStoreCreationFailed(js_value) => js_value, Error::ObjectStoreOpenFailed(js_value) => js_value, + Error::TransactionCommitFailed(js_value) => js_value, Error::TransactionExecutionFailed(js_value) => js_value, Error::TransactionOpenFailed(js_value) => js_value, Error::WindowNotFound => "WindowNotFound".into(), diff --git a/src/index.rs b/src/index.rs index c3ffe00..188a842 100644 --- a/src/index.rs +++ b/src/index.rs @@ -23,11 +23,11 @@ impl Index { } } - /// Creates a new index with given name and compound key path - pub fn new_compound(name: &str, key_path: impl IntoIterator) -> Self { + /// Creates a new index with given name and key path array + pub fn new_array<'a>(name: &str, key_path_array: impl IntoIterator) -> Self { Self { name: name.to_owned(), - key_path: key_path.into_iter().map(|s| s.to_string()).collect(), + key_path: key_path_array.into_iter().map(ToOwned::to_owned).collect(), unique: None, multi_entry: None, } diff --git a/src/object_store.rs b/src/object_store.rs index b614ff5..ea884c2 100644 --- a/src/object_store.rs +++ b/src/object_store.rs @@ -1,4 +1,4 @@ -use std::{collections::HashSet, string::ToString}; +use std::collections::HashSet; use js_sys::Array; use wasm_bindgen::prelude::*; @@ -31,12 +31,9 @@ impl ObjectStore { self } - /// Specify compound key path for the object store - pub fn key_path_array( - mut self, - key_path_array: impl IntoIterator, - ) -> Self { - self.key_path = key_path_array.into_iter().map(|s| s.to_string()).collect(); + /// Specify key path array for the object store + pub fn key_path_array<'a>(mut self, key_path_array: impl IntoIterator) -> Self { + self.key_path = key_path_array.into_iter().map(ToOwned::to_owned).collect(); self } @@ -107,7 +104,9 @@ impl ObjectStore { let mut indexes_to_remove = Vec::new(); for index in 0..db_index_names.length() { - let db_index_name = db_index_names.get(index).unwrap_throw(); + let db_index_name = db_index_names.get(index).ok_or_else(|| { + Error::ObjectStoreCreationFailed("unable to get index name".into()) + })?; if index_names.contains(&db_index_name) { index_names.remove(&db_index_name); @@ -117,7 +116,9 @@ impl ObjectStore { } for index_name in indexes_to_remove { - object_store.delete_index(&index_name).unwrap_throw(); + object_store + .delete_index(&index_name) + .map_err(Error::ObjectStoreCreationFailed)?; } Ok(()) diff --git a/src/request.rs b/src/request.rs index 4222c30..e89c126 100644 --- a/src/request.rs +++ b/src/request.rs @@ -30,12 +30,8 @@ pub async fn wait_request( pub async fn wait_transaction_abort(transaction: IdbTransaction) -> crate::Result<()> { let (sender, receiver) = oneshot::channel(); - let abort_closure: Closure = Closure::once(move |_| { - sender - .send(()) - .map_err(|_| Error::AsyncChannelError) - .unwrap_throw(); - }); + let abort_closure: Closure crate::Result<()>> = + Closure::once(move |_| sender.send(()).map_err(|_| Error::AsyncChannelError)); transaction.set_onabort(Some(get_callback(&abort_closure))); @@ -127,14 +123,27 @@ impl DbRequest for IdbTransaction { } } +impl DbRequest for &T +where + T: DbRequest, +{ + fn on_success(&self, callback: Option<&Function>) { + (**self).on_success(callback); + } + + fn on_error(&self, callback: Option<&Function>) { + (**self).on_error(callback); + } +} + #[must_use = "futures do nothing unless polled or spawned"] pub struct RequestFuture where R: DbRequest + Unpin, { _inner: R, - _success_closure: Closure, - _error_closure: Closure, + _success_closure: Closure crate::Result<()>>, + _error_closure: Closure crate::Result<()>>, receiver: UnboundedReceiver>, map_err: fn(JsValue) -> Error, } @@ -179,23 +188,24 @@ where fn get_success_closure( sender: UnboundedSender>, -) -> Closure { +) -> Closure crate::Result<()>> { Closure::once(move |event: Event| { - let target = event.target().unwrap_throw(); + let target = event.target().ok_or(Error::EventTargetNotFound)?; let request: &IdbRequest = AsRef::::as_ref(&target).unchecked_ref(); sender .send(request.result()) - .map_err(|_| Error::AsyncChannelError) - .unwrap_throw(); + .map_err(|_| Error::AsyncChannelError)?; + + Ok(()) }) } fn get_error_closure( sender: UnboundedSender>, -) -> Closure { +) -> Closure crate::Result<()>> { Closure::once(move |event: Event| { - let target = event.target().unwrap_throw(); + let target = event.target().ok_or(Error::EventTargetNotFound)?; let request: &IdbRequest = AsRef::::as_ref(&target).unchecked_ref(); let error: Result = match request.error() { @@ -204,10 +214,9 @@ fn get_error_closure( Err(error) => Err(error), }; - sender - .send(error) - .map_err(|_| Error::AsyncChannelError) - .unwrap_throw(); + sender.send(error).map_err(|_| Error::AsyncChannelError)?; + + Ok(()) }) } @@ -217,15 +226,14 @@ fn get_cursor_closure( advancing: Arc, limit: Option, offset: u32, -) -> Closure { +) -> Closure crate::Result<()>> { Closure::wrap(Box::new(move |event| { sender .send(cursor_closure_inner( event, &seen, &advancing, limit, offset, )) .map_err(|_| Error::AsyncChannelError) - .unwrap_throw(); - }) as Box) + }) as Box crate::Result<()>>) } fn cursor_closure_inner( @@ -281,6 +289,6 @@ fn cursor_closure_inner( } } -fn get_callback(closure: &Closure) -> &Function { +fn get_callback(closure: &Closure crate::Result<()>>) -> &Function { closure.as_ref().unchecked_ref() } diff --git a/src/rexie_builder.rs b/src/rexie_builder.rs index 55fc306..ee39e5e 100644 --- a/src/rexie_builder.rs +++ b/src/rexie_builder.rs @@ -86,10 +86,9 @@ fn get_idb_open_request(name: &str, version: Option) -> Result, -) -> Closure { - let upgrade_handler = Closure::once(move |event: Event| { - upgrade_handler(event, object_stores).unwrap_throw(); - }); +) -> Closure Result<()>> { + let upgrade_handler = + Closure::once(move |event: Event| -> Result<()> { upgrade_handler(event, object_stores) }); idb_open_request.set_onupgradeneeded(Some(upgrade_handler.as_ref().unchecked_ref())); @@ -117,7 +116,9 @@ fn upgrade_handler(event: Event, object_stores: Vec) -> Result<()> let mut stores_to_remove = Vec::new(); for index in 0..db_store_names.length() { - let db_store_name = db_store_names.get(index).unwrap_throw(); + let db_store_name = db_store_names + .get(index) + .ok_or_else(|| Error::IndexedDbUpgradeFailed("unable to get store name".into()))?; if store_names.contains(&db_store_name) { store_names.remove(&db_store_name); @@ -127,7 +128,8 @@ fn upgrade_handler(event: Event, object_stores: Vec) -> Result<()> } for store_name in stores_to_remove { - idb.delete_object_store(&store_name).unwrap_throw(); + idb.delete_object_store(&store_name) + .map_err(Error::IndexedDbUpgradeFailed)?; } Ok(()) diff --git a/src/transaction.rs b/src/transaction.rs index 5e7bf60..841e009 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -54,7 +54,10 @@ pub struct Transaction { impl Transaction { /// Returns mode of the transaction pub fn mode(&self) -> TransactionMode { - self.idb_transaction.mode().unwrap_throw().into() + self.idb_transaction + .mode() + .expect_throw("unable to get transaction mode") + .into() } /// Returns names of all stores in the transaction @@ -77,6 +80,24 @@ impl Transaction { wait_transaction_abort(self.idb_transaction).await } + /// Commits a transaction + /// + /// # Note + /// + /// Note that `commit()` doesn't normally have to be called — a transaction will automatically commit when all + /// outstanding requests have been satisfied and no new requests have been made. + /// + /// [Reference](https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction/commit) + pub async fn commit(self) -> Result<()> { + let done = wait_request(&self.idb_transaction, Error::TransactionExecutionFailed); + + self.idb_transaction + .commit() + .map_err(Error::TransactionCommitFailed)?; + + done.await.map(|_| ()) + } + /// Waits for a transaction to complete. pub async fn done(self) -> Result<()> { wait_request(self.idb_transaction, Error::TransactionExecutionFailed) diff --git a/tests/web.rs b/tests/web.rs index 28a0420..43a8cf7 100644 --- a/tests/web.rs +++ b/tests/web.rs @@ -59,7 +59,7 @@ async fn create_db() -> Rexie { .add_object_store( ObjectStore::new("invoices") .key_path_array(["id", "year"]) - .add_index(Index::new_compound("agent_customer", ["agent", "customer"])), + .add_index(Index::new_array("agent_customer", ["agent", "customer"])), ) .build() .await; @@ -100,7 +100,7 @@ async fn basic_test_db(rexie: &Rexie) { assert!(email_index.unique()); assert!(!email_index.multi_entry()); - assert!(transaction.done().await.is_ok()); + assert!(transaction.commit().await.is_ok()); } /// Closes and deletes the database @@ -122,7 +122,7 @@ async fn add_employee(rexie: &Rexie, name: &str, email: &str) -> Result { let employee = serde_wasm_bindgen::to_value(&employee).unwrap(); let employee_id = employees.add(&employee, None).await?; - transaction.done().await?; + transaction.commit().await?; Ok(num_traits::cast(employee_id.as_f64().unwrap()).unwrap()) } @@ -213,7 +213,7 @@ async fn add_invoice( let invoice = serde_wasm_bindgen::to_value(&invoice).unwrap(); invoices.add(&invoice, None).await?; - transaction.done().await?; + transaction.commit().await?; Ok(()) }