From 9b683b642fe0aee935663e5da2d2b78ae5be6e3b Mon Sep 17 00:00:00 2001 From: Devashish Dixit Date: Thu, 4 Jul 2024 15:42:42 +0800 Subject: [PATCH] Integrate with `idb` crate instead of custom handing using `web-sys` (#45) * Change from web-sys to idb * Fix bugs and bump crate version --- Cargo.toml | 45 ++---- README.md | 12 +- src/direction.rs | 21 --- src/error.rs | 123 ++-------------- src/index.rs | 49 +------ src/key_path.rs | 32 ----- src/key_range.rs | 58 -------- src/lib.rs | 18 ++- src/object_store.rs | 93 ++----------- src/request.rs | 294 --------------------------------------- src/rexie.rs | 43 ++---- src/rexie_builder.rs | 106 ++------------ src/transaction.rs | 99 ++++--------- src/transaction/index.rs | 156 ++++++++++++--------- src/transaction/store.rs | 227 ++++++++++++++---------------- tests/web.rs | 32 ++--- 16 files changed, 303 insertions(+), 1105 deletions(-) delete mode 100644 src/direction.rs delete mode 100644 src/key_path.rs delete mode 100644 src/key_range.rs delete mode 100644 src/request.rs diff --git a/Cargo.toml b/Cargo.toml index 1ee0c2e..7c970ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rexie" -version = "0.5.0" +version = "0.6.0" authors = ["Devashish Dixit "] license = "MIT/Apache-2.0" description = "Rexie is an easy-to-use, futures based wrapper around IndexedDB that compiles to webassembly" @@ -9,50 +9,31 @@ repository = "https://github.com/devashishdxt/rexie" categories = ["asynchronous", "database", "wasm", "web-programming"] keywords = ["wasm", "indexeddb", "futures", "idb", "indexed"] readme = "README.md" -include = ["Cargo.toml", "src/**/*.rs", "tests/**/*.rs", "README.md"] +include = [ + "Cargo.toml", + "src/**/*.rs", + "tests/**/*.rs", + "README.md", + "LICENSE_*", +] edition = "2021" [lib] path = "src/lib.rs" crate-type = ["cdylib", "rlib"] -[features] -default = ["js"] -js = ["wasm-bindgen-futures"] - [dependencies] -js-sys = "0.3.64" -num-traits = { version = "0.2.15", default-features = false } -thiserror = "1.0.40" -tokio = { version = "1.28.2", default-features = false, features = ["sync"] } -wasm-bindgen = "0.2.87" -wasm-bindgen-futures = { version = "0.4.37", optional = true } -web-sys = { version = "0.3.64", features = [ - "DomException", - "DomStringList", - "Event", - "IdbCursorWithValue", - "IdbCursorDirection", - "IdbDatabase", - "IdbFactory", - "IdbIndex", - "IdbIndexParameters", - "IdbKeyRange", - "IdbObjectStore", - "IdbObjectStoreParameters", - "IdbOpenDbRequest", - "IdbOpenDbOptions", - "IdbRequest", - "IdbTransaction", - "IdbTransactionMode", - "StorageType", -] } +idb = { version = "0.6.2", features = ["builder"] } +thiserror = "1.0.61" +wasm-bindgen = "0.2.92" [dev-dependencies] serde = { version = "1.0.164", features = ["derive"] } serde_json = "1.0.97" serde-wasm-bindgen = "0.5.0" wasm-bindgen-test = "0.3.37" +js-sys = "0.3.69" +num-traits = { version = "0.2.19", default-features = false } [profile.release] # Tell `rustc` to optimize for small code size. diff --git a/README.md b/README.md index 4cf3b2e..2e038a3 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,12 @@ To use Rexie, you need to add the following to your `Cargo.toml`: ```toml [dependencies] -rexie = "0.4" +rexie = "0.6" ``` ### Example -To create a new database, you can use the `Rexie::builder` method: +To create a new database, you can use the [`Rexie::builder`] method: ```rust use rexie::*; @@ -39,14 +39,14 @@ async fn build_database() -> Result { // Check basic details of the database assert_eq!(rexie.name(), "test"); - assert_eq!(rexie.version(), 1.0); + assert_eq!(rexie.version(), Ok(1)); assert_eq!(rexie.store_names(), vec!["employees"]); Ok(rexie) } ``` -To add an employee, you can use the `Store::add` method after creating a `Transaction`: +To add an employee, you can use the [`Store::add`] method after creating a [`Transaction`]: ```rust use rexie::*; @@ -77,7 +77,7 @@ async fn add_employee(rexie: &Rexie, name: &str, email: &str) -> Result { } ``` -To get an employee, you can use the `Store::get` method after creating a `Transaction`: +To get an employee, you can use the [`Store::get`] method after creating a [`Transaction`]: ```rust use rexie::*; @@ -90,7 +90,7 @@ async fn get_employee(rexie: &Rexie, id: u32) -> Result = serde_wasm_bindgen::from_value(employee).unwrap(); diff --git a/src/direction.rs b/src/direction.rs deleted file mode 100644 index 3d86db3..0000000 --- a/src/direction.rs +++ /dev/null @@ -1,21 +0,0 @@ -use web_sys::IdbCursorDirection; - -/// Direction in which the key-value paris are fetched from the store. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Direction { - Next, - NextUnique, - Prev, - PrevUnique, -} - -impl From for IdbCursorDirection { - fn from(direction: Direction) -> Self { - match direction { - Direction::Next => IdbCursorDirection::Next, - Direction::NextUnique => IdbCursorDirection::Nextunique, - Direction::Prev => IdbCursorDirection::Prev, - Direction::PrevUnique => IdbCursorDirection::Prevunique, - } - } -} diff --git a/src/error.rs b/src/error.rs index 41bfa06..5043226 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,122 +1,19 @@ -use js_sys::Error as JsError; use thiserror::Error; -use wasm_bindgen::prelude::*; /// Result with `rexie::Error` as error type. pub type Result = std::result::Result; /// Error type for `rexie` crate -#[derive(Debug, Error, Clone, PartialEq)] +#[derive(Debug, Error, PartialEq)] #[non_exhaustive] pub enum Error { - /// Error when receiving message from async channel - #[error("error when receiving message from async channel")] - AsyncChannelError, - - /// Error when fetching DOM exception - #[error("error when fetching DOM exception: {}", js_error_display(.0))] - DomExceptionError(JsValue), - - /// DOM Exception is none - #[error("dom exception is none")] - DomExceptionNotFound, - - /// Event target is none - #[error("event target is none")] - EventTargetNotFound, - - /// Index creation failed - #[error("index creation failed: {}", js_error_display(.0))] - IndexCreationFailed(JsValue), - - /// Index open failed - #[error("index open failed: {}", js_error_display(.0))] - IndexOpenFailed(JsValue), - - /// Failed to delete indexed db - #[error("failed to delete indexed db: {}", js_error_display(.0))] - IndexedDbDeleteFailed(JsValue), - - /// Indexed db not found - #[error("indexed db is none")] - IndexedDbNotFound(JsValue), - - /// Indexed db not supported - #[error("indexed db not supported: {}", js_error_display(.0))] - IndexedDbNotSupported(JsValue), - - /// Failed to open indexed db - #[error("failed to open indexed db: {}", js_error_display(.0))] - IndexedDbOpenFailed(JsValue), - - /// Failed to execute indexed db request - #[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), - - /// Object store creation failed - #[error("object store creation failed: {}", js_error_display(.0))] - ObjectStoreCreationFailed(JsValue), - - /// Failed to open object store - #[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), - - /// Transaction is none - #[error("transaction is none")] - TransactionNotFound, - - /// failed to open db transaction - #[error("failed to open db transaction: {}", js_error_display(.0))] - TransactionOpenFailed(JsValue), - - /// Unexpected JS type - #[error("unexpected js type")] - UnexpectedJsType, -} - -fn js_error_display(option: &JsValue) -> String { - ToString::to_string(&JsError::from(option.clone()).to_string()) -} - -impl From for JsValue { - fn from(error: Error) -> Self { - match error { - Error::AsyncChannelError => "AsyncChannelError".into(), - Error::EventTargetNotFound => "EventTargetNotFound".into(), - Error::IndexCreationFailed(js_value) => js_value, - Error::IndexOpenFailed(js_value) => js_value, - Error::IndexedDbNotFound(js_value) => js_value, - 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::DomExceptionError(js_value) => js_value, - Error::DomExceptionNotFound => "DomExceptionNotFound".into(), - Error::IndexedDbDeleteFailed(js_value) => js_value, - Error::TransactionNotFound => "TransactionNotFound".into(), - Error::IndexedDbRequestError(js_value) => js_value, - Error::UnexpectedJsType => "UnxpectedJsType".into(), - } - } + /// Indexed DB error + #[error("idb error")] + IdbError(#[from] idb::Error), + /// Couldn't abort a transaction + #[error("couldn't abort a transaction")] + TransactionAbortFailed, + /// Couldn't commit a transaction + #[error("couldn't commit a transaction")] + TransactioncommitFailed, } diff --git a/src/index.rs b/src/index.rs index e22f1a0..9086227 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1,71 +1,36 @@ -use web_sys::{IdbIndexParameters, IdbObjectStore}; +use idb::builder::IndexBuilder; -use crate::{key_path::KeyPath, Error, Result}; +use crate::KeyPath; /// An index builder. pub struct Index { - pub(crate) name: String, - pub(crate) key_path: KeyPath, - pub(crate) unique: Option, - pub(crate) multi_entry: Option, + pub(crate) builder: IndexBuilder, } impl Index { /// Creates a new index with given name and key path pub fn new(name: &str, key_path: &str) -> Self { Self { - name: name.to_owned(), - key_path: KeyPath::new_str(key_path), - unique: None, - multi_entry: None, + builder: IndexBuilder::new(name.to_owned(), KeyPath::new_single(key_path)), } } /// 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: KeyPath::new_array(key_path_array), - unique: None, - multi_entry: None, + builder: IndexBuilder::new(name.to_owned(), KeyPath::new_array(key_path_array)), } } /// Specify whether the index should be unique pub fn unique(mut self, unique: bool) -> Self { - self.unique = Some(unique); + self.builder = self.builder.unique(unique); self } /// Specify whether the index should be multi-entry, i.e., type of the value contained in key path is an array pub fn multi_entry(mut self, multi_entry: bool) -> Self { - self.multi_entry = Some(multi_entry); + self.builder = self.builder.multi_entry(multi_entry); self } } - -impl Index { - pub(crate) fn create(self, object_store: &IdbObjectStore) -> Result<()> { - if !object_store.index_names().contains(&self.name) { - let mut params = IdbIndexParameters::new(); - - if let Some(unique) = self.unique { - params.unique(unique); - } - - if let Some(multi_entry) = self.multi_entry { - params.multi_entry(multi_entry); - } - - object_store - .create_index_with_str_sequence_and_optional_parameters( - &self.name, - &self.key_path.into(), - ¶ms, - ) - .map_err(Error::IndexCreationFailed)?; - } - - Ok(()) - } -} diff --git a/src/key_path.rs b/src/key_path.rs deleted file mode 100644 index e4f0e34..0000000 --- a/src/key_path.rs +++ /dev/null @@ -1,32 +0,0 @@ -use js_sys::Array; -use wasm_bindgen::prelude::*; - -pub(crate) enum KeyPath { - String(String), - Array(Vec), -} - -impl KeyPath { - pub fn new_str(key_path: &str) -> Self { - Self::String(key_path.to_owned()) - } - - pub fn new_array<'a>(key_path_array: impl IntoIterator) -> Self { - Self::Array(key_path_array.into_iter().map(ToOwned::to_owned).collect()) - } -} - -impl From for JsValue { - fn from(key_path: KeyPath) -> Self { - match key_path { - KeyPath::String(key_path) => JsValue::from_str(&key_path), - KeyPath::Array(key_path_array) => { - let key_path = key_path_array - .iter() - .map(|s| JsValue::from_str(s)) - .collect::(); - key_path.into() - } - } - } -} diff --git a/src/key_range.rs b/src/key_range.rs deleted file mode 100644 index 58f5d49..0000000 --- a/src/key_range.rs +++ /dev/null @@ -1,58 +0,0 @@ -use wasm_bindgen::prelude::*; -use web_sys::IdbKeyRange; - -use crate::{Error, Result}; - -/// A key range. -pub struct KeyRange { - idb_key_range: IdbKeyRange, -} - -impl KeyRange { - /// Creates a new key range with given lower bound. The lower bound is inclusive if `lower_open` is false and - /// exclusive if `lower_open` is true. - pub fn lower_bound(lower_bound: &JsValue, lower_open: bool) -> Result { - let idb_key_range = IdbKeyRange::lower_bound_with_open(lower_bound, lower_open) - .map_err(Error::KeyRangeError)?; - - Ok(KeyRange { idb_key_range }) - } - - /// Creates a new key range with given upper bound. The upper bound is inclusive if `upper_open` is false and - /// exclusive if `upper_open` is true. - pub fn upper_bound(upper_bound: &JsValue, upper_open: bool) -> Result { - let idb_key_range = IdbKeyRange::upper_bound_with_open(upper_bound, upper_open) - .map_err(Error::KeyRangeError)?; - - Ok(KeyRange { idb_key_range }) - } - - /// Creates a new key range with given lower and upper bound. The lower bound is inclusive if `lower_open` is false - /// and exclusive if `lower_open` is true. The upper bound is inclusive if `upper_open` is false and exclusive if - /// `upper_open` is true. - pub fn bound( - lower: &JsValue, - upper: &JsValue, - lower_open: bool, - upper_open: bool, - ) -> Result { - let idb_key_range = - IdbKeyRange::bound_with_lower_open_and_upper_open(lower, upper, lower_open, upper_open) - .map_err(Error::KeyRangeError)?; - - Ok(Self { idb_key_range }) - } - - /// Creates a new key range that matches with only one value. - pub fn only(value: &JsValue) -> Result { - let idb_key_range = IdbKeyRange::only(value).map_err(Error::KeyRangeError)?; - - Ok(Self { idb_key_range }) - } -} - -impl AsRef for KeyRange { - fn as_ref(&self) -> &JsValue { - self.idb_key_range.as_ref() - } -} diff --git a/src/lib.rs b/src/lib.rs index bf244b9..36c49f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ //! //! ```toml //! [dependencies] -//! rexie = "0.4" +//! rexie = "0.6" //! ``` //! //! ## Example @@ -37,7 +37,7 @@ //! //! // Check basic details of the database //! assert_eq!(rexie.name(), "test"); -//! assert_eq!(rexie.version(), 1.0); +//! assert_eq!(rexie.version(), Ok(1)); //! assert_eq!(rexie.store_names(), vec!["employees"]); //! //! Ok(rexie) @@ -88,7 +88,7 @@ //! let employees = transaction.store("employees")?; //! //! // Get the employee -//! let employee = employees.get(&id.into()).await?.unwrap(); +//! let employee = employees.get(id.into()).await?.unwrap(); //! //! // Convert it to `serde_json::Value` from `JsValue` //! let employee: Option = serde_wasm_bindgen::from_value(employee).unwrap(); @@ -97,24 +97,22 @@ //! Ok(employee) //! } //! ``` -mod direction; mod error; mod index; -mod key_path; -mod key_range; mod object_store; -mod request; mod rexie; mod rexie_builder; mod transaction; +pub use idb::{ + CursorDirection as Direction, KeyPath, KeyRange, TransactionMode, TransactionResult, +}; + pub use self::{ - direction::Direction, error::{Error, Result}, index::Index, - key_range::KeyRange, object_store::ObjectStore, rexie::Rexie, rexie_builder::RexieBuilder, - transaction::{Store, StoreIndex, Transaction, TransactionMode}, + transaction::{Store, StoreIndex, Transaction}, }; diff --git a/src/object_store.rs b/src/object_store.rs index 7cf4bef..3b8cbe6 100644 --- a/src/object_store.rs +++ b/src/object_store.rs @@ -1,116 +1,43 @@ -use std::collections::HashSet; +use idb::builder::ObjectStoreBuilder; -use web_sys::{IdbDatabase, IdbObjectStoreParameters, IdbOpenDbRequest}; - -use crate::{key_path::KeyPath, Error, Index, Result}; +use crate::{Index, KeyPath}; /// An object store builder. pub struct ObjectStore { - pub(crate) name: String, - pub(crate) key_path: Option, - pub(crate) auto_increment: Option, - pub(crate) indexes: Vec, + pub(crate) builder: ObjectStoreBuilder, } impl ObjectStore { /// Creates a new object store with given name pub fn new(name: &str) -> Self { Self { - name: name.to_owned(), - key_path: None, - auto_increment: None, - indexes: Vec::new(), + builder: ObjectStoreBuilder::new(name), } } /// Specify key path for the object store pub fn key_path(mut self, key_path: &str) -> Self { - self.key_path = Some(KeyPath::new_str(key_path)); + self.builder = self.builder.key_path(Some(KeyPath::new_single(key_path))); self } /// 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 = Some(KeyPath::new_array(key_path_array)); + self.builder = self + .builder + .key_path(Some(KeyPath::new_array(key_path_array))); self } /// Specify whether the object store should auto increment keys pub fn auto_increment(mut self, auto_increment: bool) -> Self { - self.auto_increment = Some(auto_increment); + self.builder = self.builder.auto_increment(auto_increment); self } /// Add an index to the object store pub fn add_index(mut self, index: Index) -> Self { - self.indexes.push(index); + self.builder = self.builder.add_index(index.builder); self } } - -impl ObjectStore { - pub(crate) fn create( - self, - idb_open_request: &IdbOpenDbRequest, - idb: &IdbDatabase, - ) -> Result<()> { - let mut index_names = self.index_names(); - - let object_store = if idb.object_store_names().contains(&self.name) { - let transaction = idb_open_request - .transaction() - .ok_or(Error::TransactionNotFound)?; - - transaction - .object_store(&self.name) - .map_err(Error::ObjectStoreOpenFailed)? - } else { - let mut params = IdbObjectStoreParameters::new(); - - if let Some(auto_increment) = self.auto_increment { - params.auto_increment(auto_increment); - } - - if let Some(key_path) = self.key_path { - params.key_path(Some(&key_path.into())); - } - - idb.create_object_store_with_optional_parameters(&self.name, ¶ms) - .map_err(Error::ObjectStoreCreationFailed)? - }; - - for index in self.indexes { - index.create(&object_store)?; - } - - let db_index_names = object_store.index_names(); - let mut indexes_to_remove = Vec::new(); - - for index in 0..db_index_names.length() { - 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); - } else { - indexes_to_remove.push(db_index_name); - } - } - - for index_name in indexes_to_remove { - object_store - .delete_index(&index_name) - .map_err(Error::ObjectStoreCreationFailed)?; - } - - Ok(()) - } - - pub(crate) fn index_names(&self) -> HashSet { - self.indexes - .iter() - .map(|index| index.name.clone()) - .collect() - } -} diff --git a/src/request.rs b/src/request.rs deleted file mode 100644 index e89c126..0000000 --- a/src/request.rs +++ /dev/null @@ -1,294 +0,0 @@ -use std::{ - future::Future, - pin::Pin, - sync::{ - atomic::{AtomicBool, AtomicU32, Ordering}, - Arc, - }, - task::{Context, Poll}, -}; - -use js_sys::Function; -use tokio::sync::{ - mpsc::{self, UnboundedReceiver, UnboundedSender}, - oneshot, -}; -use wasm_bindgen::{prelude::*, JsCast}; -use web_sys::{Event, IdbCursorWithValue, IdbOpenDbRequest, IdbRequest, IdbTransaction}; - -use crate::Error; - -/// Waits for a request to finish. -pub async fn wait_request( - request: R, - map_err: fn(JsValue) -> Error, -) -> crate::Result { - RequestFuture::new(request, map_err).await -} - -/// Waits for transaction to abort. -pub async fn wait_transaction_abort(transaction: IdbTransaction) -> crate::Result<()> { - let (sender, receiver) = oneshot::channel(); - - let abort_closure: Closure crate::Result<()>> = - Closure::once(move |_| sender.send(()).map_err(|_| Error::AsyncChannelError)); - - transaction.set_onabort(Some(get_callback(&abort_closure))); - - transaction - .abort() - .map_err(Error::TransactionExecutionFailed)?; - - receiver.await.map_err(|_| Error::AsyncChannelError) -} - -/// Waits for cursor to finish. -pub async fn wait_cursor_request( - request: IdbRequest, - limit: Option, - offset: Option, - map_err: fn(JsValue) -> Error, -) -> crate::Result> { - let offset = offset.unwrap_or_default(); - - let advancing = if offset == 0 { - Arc::new(AtomicBool::new(false)) - } else { - Arc::new(AtomicBool::new(true)) - }; - - let seen = Arc::new(AtomicU32::new(0)); - - let (sender, mut receiver) = mpsc::unbounded_channel(); - - let cursor_closure = get_cursor_closure(sender, seen, advancing, limit, offset); - request.set_onsuccess(Some(get_callback(&cursor_closure))); - - let mut result = Vec::new(); - - while let Some(pair) = receiver.recv().await { - match pair.map_err(map_err)? { - CursorAction::Break => break, - CursorAction::BreakWithValue(key, value) => { - result.push((key, value)); - break; - } - CursorAction::Continue => continue, - CursorAction::ContinueWithValue(key, value) => result.push((key, value)), - } - } - - Ok(result) -} - -pub enum CursorAction { - Break, - BreakWithValue(JsValue, JsValue), - Continue, - ContinueWithValue(JsValue, JsValue), -} - -pub trait DbRequest { - fn on_success(&self, callback: Option<&Function>); - fn on_error(&self, callback: Option<&Function>); -} - -impl DbRequest for IdbRequest { - fn on_success(&self, callback: Option<&Function>) { - self.set_onsuccess(callback); - } - - fn on_error(&self, callback: Option<&Function>) { - self.set_onerror(callback); - } -} - -impl DbRequest for IdbOpenDbRequest { - fn on_success(&self, callback: Option<&Function>) { - self.set_onsuccess(callback); - } - - fn on_error(&self, callback: Option<&Function>) { - self.set_onerror(callback); - } -} - -impl DbRequest for IdbTransaction { - fn on_success(&self, callback: Option<&Function>) { - self.set_oncomplete(callback); - } - - fn on_error(&self, callback: Option<&Function>) { - self.set_onerror(callback); - } -} - -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 crate::Result<()>>, - _error_closure: Closure crate::Result<()>>, - receiver: UnboundedReceiver>, - map_err: fn(JsValue) -> Error, -} - -impl RequestFuture -where - R: DbRequest + Unpin, -{ - pub fn new(request: R, map_err: fn(JsValue) -> Error) -> Self { - let (sender, receiver) = mpsc::unbounded_channel(); - - let success_closure = get_success_closure(sender.clone()); - let error_closure = get_error_closure(sender); - - request.on_success(Some(get_callback(&success_closure))); - request.on_error(Some(get_callback(&error_closure))); - - Self { - _inner: request, - _success_closure: success_closure, - _error_closure: error_closure, - receiver, - map_err, - } - } -} - -impl Future for RequestFuture -where - R: DbRequest + Unpin, -{ - type Output = crate::Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { - self.receiver.poll_recv(cx).map(|option| match option { - None => Err(Error::AsyncChannelError), - Some(Ok(value)) => Ok(value), - Some(Err(err)) => Err((self.map_err)(err)), - }) - } -} - -fn get_success_closure( - sender: UnboundedSender>, -) -> Closure crate::Result<()>> { - Closure::once(move |event: Event| { - 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)?; - - Ok(()) - }) -} - -fn get_error_closure( - sender: UnboundedSender>, -) -> Closure crate::Result<()>> { - Closure::once(move |event: Event| { - let target = event.target().ok_or(Error::EventTargetNotFound)?; - let request: &IdbRequest = AsRef::::as_ref(&target).unchecked_ref(); - - let error: Result = match request.error() { - Ok(Some(exception)) => Err(exception.into()), - Ok(None) => Err(Error::DomExceptionNotFound.into()), - Err(error) => Err(error), - }; - - sender.send(error).map_err(|_| Error::AsyncChannelError)?; - - Ok(()) - }) -} - -fn get_cursor_closure( - sender: UnboundedSender>, - seen: Arc, - advancing: Arc, - limit: Option, - offset: u32, -) -> Closure crate::Result<()>> { - Closure::wrap(Box::new(move |event| { - sender - .send(cursor_closure_inner( - event, &seen, &advancing, limit, offset, - )) - .map_err(|_| Error::AsyncChannelError) - }) as Box crate::Result<()>>) -} - -fn cursor_closure_inner( - event: Event, - seen: &AtomicU32, - advancing: &AtomicBool, - limit: Option, - offset: u32, -) -> Result { - let target = event.target().ok_or(Error::EventTargetNotFound)?; - let request: &IdbRequest = AsRef::::as_ref(&target).unchecked_ref(); - - let result = request.result()?; - if result.is_falsy() { - return Ok(CursorAction::Break); - } - - let cursor = IdbCursorWithValue::from(result); - - if advancing.load(Ordering::Relaxed) { - cursor.advance(offset)?; - advancing.store(false, Ordering::Relaxed); - - Ok(CursorAction::Continue) - } else { - let key = cursor.key()?; - let value = cursor.value()?; - - if value.is_falsy() { - return Ok(CursorAction::Break); - } - - seen.fetch_add(1, Ordering::Relaxed); - - match limit { - None => { - cursor.continue_()?; - Ok(CursorAction::ContinueWithValue(key, value)) - } - Some(limit) => { - let current_seen = seen.load(Ordering::Relaxed); - - match current_seen.cmp(&limit) { - std::cmp::Ordering::Less => { - cursor.continue_()?; - Ok(CursorAction::ContinueWithValue(key, value)) - } - std::cmp::Ordering::Equal => Ok(CursorAction::BreakWithValue(key, value)), - std::cmp::Ordering::Greater => Ok(CursorAction::Break), - } - } - } - } -} - -fn get_callback(closure: &Closure crate::Result<()>>) -> &Function { - closure.as_ref().unchecked_ref() -} diff --git a/src/rexie.rs b/src/rexie.rs index 2c45718..5236f2a 100644 --- a/src/rexie.rs +++ b/src/rexie.rs @@ -1,13 +1,11 @@ -use js_sys::Array; -use wasm_bindgen::prelude::*; -use web_sys::IdbDatabase; +use idb::Database; -use crate::{Error, Result, RexieBuilder, Transaction, TransactionMode}; +use crate::{Result, RexieBuilder, Transaction, TransactionMode}; /// Rexie database (wrapper on top of indexed db) -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Debug)] pub struct Rexie { - pub(crate) db: IdbDatabase, + pub(crate) database: Database, } impl Rexie { @@ -18,27 +16,17 @@ impl Rexie { /// Returns name of the database pub fn name(&self) -> String { - self.db.name() + self.database.name() } /// Returns version of the database - pub fn version(&self) -> f64 { - self.db.version() + pub fn version(&self) -> Result { + self.database.version().map_err(Into::into) } /// Returns names of all stores in the database pub fn store_names(&self) -> Vec { - let list = self.db.object_store_names(); - - let mut result = Vec::new(); - - for index in 0..list.length() { - if let Some(s) = list.get(index) { - result.push(s); - } - } - - result + self.database.store_names() } /// Creates a new transaction on the database @@ -47,22 +35,13 @@ impl Rexie { store_names: &[T], mode: TransactionMode, ) -> Result { - let store_names: Array = store_names - .iter() - .map(|s| JsValue::from(s.as_ref())) - .collect(); - - let idb_transaction = self - .db - .transaction_with_str_sequence_and_mode(&store_names, mode.into()) - .map_err(Error::TransactionOpenFailed)?; - - Ok(Transaction { idb_transaction }) + let transaction = self.database.transaction(store_names, mode)?; + Ok(Transaction { transaction }) } /// Closes the database pub fn close(self) { - self.db.close(); + self.database.close(); } /// Deletes a database diff --git a/src/rexie_builder.rs b/src/rexie_builder.rs index 9f832e2..6d2cc78 100644 --- a/src/rexie_builder.rs +++ b/src/rexie_builder.rs @@ -1,16 +1,11 @@ -use std::collections::HashSet; +use idb::{builder::DatabaseBuilder, Factory}; -use js_sys::Reflect; -use wasm_bindgen::{prelude::*, JsCast}; -use web_sys::{Event, IdbDatabase, IdbFactory, IdbOpenDbRequest}; - -use crate::{request::wait_request, Error, ObjectStore, Result, Rexie}; +use crate::{ObjectStore, Result, Rexie}; /// Builder for creating a new database. pub struct RexieBuilder { name: String, - version: Option, - object_stores: Vec, + builder: DatabaseBuilder, } impl RexieBuilder { @@ -18,112 +13,31 @@ impl RexieBuilder { pub fn new(name: &str) -> Self { Self { name: name.to_owned(), - version: None, - object_stores: Vec::new(), + builder: DatabaseBuilder::new(name), } } /// Specify version of the database. pub fn version(mut self, version: u32) -> Self { - self.version = Some(version); + self.builder = self.builder.version(version); self } /// Add an object store to the database. pub fn add_object_store(mut self, object_store: ObjectStore) -> Self { - self.object_stores.push(object_store); + self.builder = self.builder.add_object_store(object_store.builder); self } /// Build the database. pub async fn build(self) -> Result { - let idb_open_request = get_idb_open_request(&self.name, self.version)?; - let _upgrade_handler = set_upgrade_handler(&idb_open_request, self.object_stores); - let db = wait_request(idb_open_request, Error::IndexedDbOpenFailed) - .await? - .unchecked_into(); - - Ok(Rexie { db }) + let database = self.builder.build().await?; + Ok(Rexie { database }) } /// Delete the database. pub async fn delete(self) -> Result<()> { - let factory = get_idb_factory()?; - let idb_open_request = factory - .delete_database(&self.name) - .map_err(Error::IndexedDbOpenFailed)?; - - wait_request(idb_open_request, Error::IndexedDbDeleteFailed) - .await - .map(|_| ()) - } -} - -fn get_idb_factory() -> Result { - Reflect::get(&js_sys::global(), &JsValue::from("indexedDB")) - .map_err(Error::IndexedDbNotFound)? - .dyn_into() - .map_err(Error::IndexedDbNotFound) -} - -fn get_idb_open_request(name: &str, version: Option) -> Result { - let idb_factory = get_idb_factory()?; - - match version { - Some(version) => idb_factory.open_with_u32(name, version), - None => idb_factory.open(name), + let factory = Factory::new()?; + factory.delete(&self.name)?.await.map_err(Into::into) } - .map_err(Error::IndexedDbOpenFailed) -} - -fn set_upgrade_handler( - idb_open_request: &IdbOpenDbRequest, - object_stores: Vec, -) -> 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())); - - upgrade_handler -} - -fn upgrade_handler(event: Event, object_stores: Vec) -> Result<()> { - let mut store_names: HashSet = object_stores.iter().map(|os| os.name.clone()).collect(); - - let idb_open_request: IdbOpenDbRequest = event - .target() - .ok_or(Error::EventTargetNotFound)? - .unchecked_into(); - - let idb: IdbDatabase = idb_open_request - .result() - .map_err(Error::IndexedDbNotSupported)? - .unchecked_into(); - - for object_store in object_stores { - object_store.create(&idb_open_request, &idb)?; - } - - let db_store_names = idb.object_store_names(); - let mut stores_to_remove = Vec::new(); - - for index in 0..db_store_names.length() { - 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); - } else { - stores_to_remove.push(db_store_name); - } - } - - for store_name in stores_to_remove { - idb.delete_object_store(&store_name) - .map_err(Error::IndexedDbUpgradeFailed)?; - } - - Ok(()) } diff --git a/src/transaction.rs b/src/transaction.rs index 841e009..3d6cdde 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -3,81 +3,35 @@ mod store; pub use self::{index::StoreIndex, store::Store}; -use wasm_bindgen::{prelude::*, throw_str}; -use web_sys::IdbTransaction; +use idb::Transaction as IdbTransaction; -use crate::{ - request::{wait_request, wait_transaction_abort}, - Error, Result, -}; - -/// Different transaction modes for indexed db -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum TransactionMode { - ReadOnly, - ReadWrite, - ReadWriteFlush, - Cleanup, - VersionChange, -} - -impl From for web_sys::IdbTransactionMode { - fn from(mode: TransactionMode) -> Self { - match mode { - TransactionMode::ReadOnly => Self::Readonly, - TransactionMode::ReadWrite => Self::Readwrite, - TransactionMode::ReadWriteFlush => Self::Readwriteflush, - TransactionMode::Cleanup => Self::Cleanup, - TransactionMode::VersionChange => Self::Versionchange, - } - } -} - -impl From for TransactionMode { - fn from(mode: web_sys::IdbTransactionMode) -> Self { - match mode { - web_sys::IdbTransactionMode::Readonly => Self::ReadOnly, - web_sys::IdbTransactionMode::Readwrite => Self::ReadWrite, - web_sys::IdbTransactionMode::Readwriteflush => Self::ReadWriteFlush, - web_sys::IdbTransactionMode::Cleanup => Self::Cleanup, - web_sys::IdbTransactionMode::Versionchange => Self::VersionChange, - _ => throw_str("invalid transaction mode"), - } - } -} +use crate::{Error, Result, TransactionMode, TransactionResult}; /// Transaction on the database pub struct Transaction { - pub(crate) idb_transaction: IdbTransaction, + pub(crate) transaction: IdbTransaction, } impl Transaction { /// Returns mode of the transaction - pub fn mode(&self) -> TransactionMode { - self.idb_transaction - .mode() - .expect_throw("unable to get transaction mode") - .into() + pub fn mode(&self) -> Result { + self.transaction.mode().map_err(Into::into) } /// Returns names of all stores in the transaction pub fn store_names(&self) -> Vec { - let list = self.idb_transaction.object_store_names(); - - let mut result = Vec::new(); - - for index in 0..list.length() { - if let Some(s) = list.get(index) { - result.push(s); - } - } - - result + self.transaction.store_names() } /// Aborts a transaction pub async fn abort(self) -> Result<()> { - wait_transaction_abort(self.idb_transaction).await + let result = self.transaction.abort()?.await?; + + if result.is_aborted() { + Ok(()) + } else { + Err(Error::TransactionAbortFailed) + } } /// Commits a transaction @@ -89,28 +43,25 @@ impl Transaction { /// /// [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); + let result = self.transaction.commit()?.await?; - self.idb_transaction - .commit() - .map_err(Error::TransactionCommitFailed)?; - - done.await.map(|_| ()) + if result.is_committed() { + Ok(()) + } else { + Err(Error::TransactioncommitFailed) + } } /// Waits for a transaction to complete. - pub async fn done(self) -> Result<()> { - wait_request(self.idb_transaction, Error::TransactionExecutionFailed) - .await - .map(|_| ()) + pub async fn done(self) -> Result { + self.transaction.await.map_err(Into::into) } /// Returns a store in the transaction pub fn store(&self, store_name: &str) -> Result { - Ok(Store::new( - self.idb_transaction - .object_store(store_name) - .map_err(Error::ObjectStoreOpenFailed)?, - )) + self.transaction + .object_store(store_name) + .map(|object_store| Store { object_store }) + .map_err(Into::into) } } diff --git a/src/transaction/index.rs b/src/transaction/index.rs index 84aaf67..d7b1d8e 100644 --- a/src/transaction/index.rs +++ b/src/transaction/index.rs @@ -1,110 +1,128 @@ +use idb::Index; use wasm_bindgen::JsValue; -use web_sys::IdbIndex; -use crate::{ - request::{wait_cursor_request, wait_request}, - Direction, Error, KeyRange, Result, -}; +use crate::{Direction, KeyRange, Result}; /// Index of an object store. pub struct StoreIndex { - pub(crate) idb_index: IdbIndex, + pub(crate) index: Index, } impl StoreIndex { - /// Creates a new instance of `StoreIndex`. - pub(crate) fn new(idb_index: IdbIndex) -> Self { - Self { idb_index } - } - /// Returns name of the index pub fn name(&self) -> String { - self.idb_index.name() + self.index.name() } /// Returns weather the index has unique enabled pub fn unique(&self) -> bool { - self.idb_index.unique() + self.index.unique() } /// Returns weather the index has multi entry enabled pub fn multi_entry(&self) -> bool { - self.idb_index.multi_entry() + self.index.multi_entry() } /// Gets a value from the store with given key - pub async fn get(&self, key: &JsValue) -> Result> { - let request = self - .idb_index - .get(key) - .map_err(Error::IndexedDbRequestError)?; - - let response = wait_request(request, Error::IndexedDbRequestError).await?; - if response.is_undefined() || response.is_null() { - Ok(None) - } else { - Ok(Some(response)) - } + pub async fn get(&self, key: JsValue) -> Result> { + self.index.get(key)?.await.map_err(Into::into) } - /// retrieves the primary keys of all objects inside the index + /// Retrieves the keys of all objects inside the index /// See: [MDN:IDBIndex/getAllKeys](https://developer.mozilla.org/en-US/docs/Web/API/IDBIndex/getAllKeys) pub async fn get_all_keys( &self, - key_range: Option<&KeyRange>, + key_range: Option, limit: Option, - ) -> Result { - let request = match (key_range, limit) { - (None, None) => self.idb_index.get_all_keys(), - (None, Some(limit)) => self - .idb_index - .get_all_keys_with_key_and_limit(&JsValue::UNDEFINED, limit), - (Some(key_range), None) => self.idb_index.get_all_keys_with_key(key_range.as_ref()), - (Some(key_range), Some(limit)) => self - .idb_index - .get_all_keys_with_key_and_limit(key_range.as_ref(), limit), - } - .map_err(Error::IndexedDbRequestError)?; - - wait_request(request, Error::IndexedDbRequestError).await + ) -> Result> { + self.index + .get_all_keys(key_range.map(Into::into), limit)? + .await + .map_err(Into::into) } - /// Gets all key-value pairs from the store with given key range, limit, offset and direction + /// Gets all values from the store with given key range and limit pub async fn get_all( &self, - key_range: Option<&KeyRange>, + key_range: Option, + limit: Option, + ) -> Result> { + self.index + .get_all(key_range.map(Into::into), limit)? + .await + .map_err(Into::into) + } + + /// Scans all key-value pairs from the store with given key range, limit, offset and direction + pub async fn scan( + &self, + key_range: Option, limit: Option, offset: Option, direction: Option, ) -> Result> { - let request = match (key_range, direction) { - (Some(key_range), Some(direction)) => self - .idb_index - .open_cursor_with_range_and_direction(key_range.as_ref(), direction.into()), - (Some(key_range), None) => self.idb_index.open_cursor_with_range(key_range.as_ref()), - (None, Some(direction)) => self - .idb_index - .open_cursor_with_range_and_direction(&JsValue::null(), direction.into()), - _ => self.idb_index.open_cursor(), - } - .map_err(Error::IndexedDbRequestError)?; + let cursor = self + .index + .open_cursor(key_range.map(Into::into), direction)? + .await?; - wait_cursor_request(request, limit, offset, Error::IndexedDbRequestError).await - } + match cursor { + None => Ok(Vec::new()), + Some(cursor) => { + let mut cursor = cursor.into_managed(); - /// Counts the number of key value pairs in the store - pub async fn count(&self, key_range: Option<&KeyRange>) -> Result { - let request = match key_range { - Some(key_range) => self.idb_index.count_with_key(key_range.as_ref()), - None => self.idb_index.count(), - } - .map_err(Error::IndexedDbRequestError)?; + let mut result = Vec::new(); + + match limit { + Some(limit) => { + if let Some(offset) = offset { + cursor.advance(offset).await?; + } + + for _ in 0..limit { + let key = cursor.key()?; + let value = cursor.value()?; + + match (key, value) { + (Some(key), Some(value)) => { + result.push((key, value)); + cursor.next(None).await?; + } + _ => break, + } + } + } + None => { + if let Some(offset) = offset { + cursor.advance(offset).await?; + } - let result = wait_request(request, Error::IndexedDbRequestError).await?; + loop { + let key = cursor.key()?; + let value = cursor.value()?; - result - .as_f64() - .and_then(num_traits::cast) - .ok_or(Error::UnexpectedJsType) + match (key, value) { + (Some(key), Some(value)) => { + result.push((key, value)); + cursor.next(None).await?; + } + _ => break, + } + } + } + } + + Ok(result) + } + } + } + + /// Counts the number of key value pairs in the store + pub async fn count(&self, key_range: Option) -> Result { + self.index + .count(key_range.map(Into::into))? + .await + .map_err(Into::into) } } diff --git a/src/transaction/store.rs b/src/transaction/store.rs index a504609..b100331 100644 --- a/src/transaction/store.rs +++ b/src/transaction/store.rs @@ -1,92 +1,59 @@ -use wasm_bindgen::prelude::*; -use web_sys::IdbObjectStore; +use idb::ObjectStore; +use wasm_bindgen::JsValue; -use crate::{ - request::{wait_cursor_request, wait_request}, - Direction, Error, KeyRange, Result, StoreIndex, -}; +use crate::{Direction, KeyPath, KeyRange, Result, StoreIndex}; /// An object store. pub struct Store { - pub(crate) idb_store: IdbObjectStore, + pub(crate) object_store: ObjectStore, } impl Store { - /// Creates a new instance of `Store`. - pub(crate) fn new(idb_store: IdbObjectStore) -> Self { - Self { idb_store } - } - /// Returns whether the store has auto increment enabled /// MDN Reference: [IDBObjectStore.autoIncrement](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/autoIncrement) pub fn auto_increment(&self) -> bool { - self.idb_store.auto_increment() + self.object_store.auto_increment() } /// Returns the name of the store /// MDN Reference: [IDBObjectStore.name](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/name) pub fn name(&self) -> String { - self.idb_store.name() + self.object_store.name() } /// Returns the key path of the store /// MDN Reference: [IDBObjectStore.keyPath](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/keyPath) - pub fn key_path(&self) -> Result> { - self.idb_store - .key_path() - .map(|js_value| js_value.as_string()) - .map_err(Error::IndexedDbRequestError) + pub fn key_path(&self) -> Result> { + self.object_store.key_path().map_err(Into::into) } /// Returns all the index names of the store /// MDN Reference: [IDBObjectStore/indexNames](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/indexNames) pub fn index_names(&self) -> Vec { - let list = self.idb_store.index_names(); - let list_len = list.length(); - let mut result = Vec::with_capacity(list_len as usize); - - for index in 0..list_len { - if let Some(s) = list.get(index) { - result.push(s); - } - } - - result + self.object_store.index_names() } /// Returns index of the store with given name /// MDN Reference: [IDBObjectStore/index](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/index) pub fn index(&self, name: &str) -> Result { - let idb_index = self.idb_store.index(name).map_err(Error::IndexOpenFailed)?; - - Ok(StoreIndex::new(idb_index)) + let index = self.object_store.index(name)?; + Ok(StoreIndex { index }) } /// Gets a value from the store with given key /// MDN Reference: [IDBObjectStore/get](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/get) - pub async fn get(&self, key: &JsValue) -> Result> { - let request = self - .idb_store - .get(key) - .map_err(Error::IndexedDbRequestError)?; - - let response = wait_request(request, Error::IndexedDbRequestError).await?; - if response.is_undefined() || response.is_null() { - Ok(None) - } else { - Ok(Some(response)) - } + pub async fn get(&self, key: JsValue) -> Result> { + self.object_store.get(key)?.await.map_err(Into::into) } /// Checks if a given key exists within the store /// MDN Reference: [IDBObjectStore/getKey](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/getKey) - pub async fn key_exists(&self, key: &JsValue) -> Result { - let request = self - .idb_store - .get_key(key) - .map_err(Error::IndexedDbRequestError)?; - let result = wait_request(request, Error::IndexedDbRequestError).await?; - Ok(result.as_bool().unwrap_or_default()) + pub async fn key_exists(&self, key: JsValue) -> Result { + self.object_store + .get_key(key)? + .await + .map(|key| key.is_some()) + .map_err(Into::into) } /// Retrieves record keys for all objects in the object store matching the specified @@ -94,106 +61,116 @@ impl Store { /// MDN Reference: [IDBStore/getAllKeys](https://developer.mozilla.org/en-US/docs/Web/API/IDBStore/getAllKeys) pub async fn get_all_keys( &self, - key_range: Option<&KeyRange>, + key_range: Option, limit: Option, - ) -> Result { - let request = match (key_range, limit) { - (None, None) => self.idb_store.get_all_keys(), - (None, Some(limit)) => self - .idb_store - .get_all_keys_with_key_and_limit(&JsValue::UNDEFINED, limit), - (Some(key_range), None) => self.idb_store.get_all_keys_with_key(key_range.as_ref()), - (Some(key_range), Some(limit)) => self - .idb_store - .get_all_keys_with_key_and_limit(key_range.as_ref(), limit), - } - .map_err(Error::IndexedDbRequestError)?; - - wait_request(request, Error::IndexedDbRequestError).await + ) -> Result> { + self.object_store + .get_all_keys(key_range.map(Into::into), limit)? + .await + .map_err(Into::into) } - /// Gets all key-value pairs from the store with given key range, limit, offset and direction + /// Gets all values from the store with given key range and limit pub async fn get_all( &self, - key_range: Option<&KeyRange>, + key_range: Option, + limit: Option, + ) -> Result> { + self.object_store + .get_all(key_range.map(Into::into), limit)? + .await + .map_err(Into::into) + } + + /// Scans all key-value pairs from the store with given key range, limit, offset and direction + pub async fn scan( + &self, + key_range: Option, limit: Option, offset: Option, direction: Option, ) -> Result> { - let request = match (key_range, direction) { - (Some(key_range), Some(direction)) => self - .idb_store - .open_cursor_with_range_and_direction(key_range.as_ref(), direction.into()), - (Some(key_range), None) => self.idb_store.open_cursor_with_range(key_range.as_ref()), - (None, Some(direction)) => self - .idb_store - .open_cursor_with_range_and_direction(&JsValue::null(), direction.into()), - _ => self.idb_store.open_cursor(), + let cursor = self + .object_store + .open_cursor(key_range.map(Into::into), direction)? + .await?; + + match cursor { + None => Ok(Vec::new()), + Some(cursor) => { + let mut cursor = cursor.into_managed(); + + let mut result = Vec::new(); + + match limit { + Some(limit) => { + if let Some(offset) = offset { + cursor.advance(offset).await?; + } + + for _ in 0..limit { + let key = cursor.key()?; + let value = cursor.value()?; + + match (key, value) { + (Some(key), Some(value)) => { + result.push((key, value)); + cursor.next(None).await?; + } + _ => break, + } + } + } + None => { + if let Some(offset) = offset { + cursor.advance(offset).await?; + } + + loop { + let key = cursor.key()?; + let value = cursor.value()?; + + match (key, value) { + (Some(key), Some(value)) => { + result.push((key, value)); + cursor.next(None).await?; + } + _ => break, + } + } + } + } + + Ok(result) + } } - .map_err(Error::IndexedDbRequestError)?; - - wait_cursor_request(request, limit, offset, Error::IndexedDbRequestError).await } /// Adds a key value pair in the store. Note that the key can be `None` if store has auto increment enabled. pub async fn add(&self, value: &JsValue, key: Option<&JsValue>) -> Result { - let request = match key { - Some(key) => self.idb_store.add_with_key(value, key), - None => self.idb_store.add(value), - } - .map_err(Error::IndexedDbRequestError)?; - - wait_request(request, Error::IndexedDbRequestError).await + self.object_store.add(value, key)?.await.map_err(Into::into) } /// Puts (adds or updates) a key value pair in the store. pub async fn put(&self, value: &JsValue, key: Option<&JsValue>) -> Result { - let request = match key { - Some(key) => self.idb_store.put_with_key(value, key), - None => self.idb_store.put(value), - } - .map_err(Error::IndexedDbRequestError)?; - - wait_request(request, Error::IndexedDbRequestError).await + self.object_store.put(value, key)?.await.map_err(Into::into) } /// Deletes a key value pair from the store - pub async fn delete(&self, key: &JsValue) -> Result<()> { - let request = self - .idb_store - .delete(key) - .map_err(Error::IndexedDbRequestError)?; - - wait_request(request, Error::IndexedDbRequestError).await?; - - Ok(()) + pub async fn delete(&self, key: JsValue) -> Result<()> { + self.object_store.delete(key)?.await.map_err(Into::into) } /// Counts the number of key value pairs in the store - pub async fn count(&self, key_range: Option<&KeyRange>) -> Result { - let request = match key_range { - Some(key_range) => self.idb_store.count_with_key(key_range.as_ref()), - None => self.idb_store.count(), - } - .map_err(Error::IndexedDbRequestError)?; - - let result = wait_request(request, Error::IndexedDbRequestError).await?; - - result - .as_f64() - .and_then(num_traits::cast) - .ok_or(Error::UnexpectedJsType) + pub async fn count(&self, key_range: Option) -> Result { + self.object_store + .count(key_range.map(Into::into))? + .await + .map_err(Into::into) } /// Deletes all key value pairs from the store pub async fn clear(&self) -> Result<()> { - let request = self - .idb_store - .clear() - .map_err(Error::IndexedDbRequestError)?; - - wait_request(request, Error::IndexedDbRequestError) - .await - .map(|_| ()) + self.object_store.clear()?.await.map_err(Into::into) } } diff --git a/tests/web.rs b/tests/web.rs index 6da56a8..4cb56f4 100644 --- a/tests/web.rs +++ b/tests/web.rs @@ -7,7 +7,7 @@ extern crate wasm_bindgen_test; use std::{assert, assert_eq, option::Option}; use js_sys::Array; -use rexie::{Direction, Index, KeyRange, ObjectStore, Result, Rexie, TransactionMode}; +use rexie::{Direction, Index, KeyPath, KeyRange, ObjectStore, Result, Rexie, TransactionMode}; use serde::{Deserialize, Serialize}; use wasm_bindgen::JsValue; use wasm_bindgen_test::*; @@ -70,7 +70,7 @@ async fn create_db() -> Rexie { /// Checks basic details of the database async fn basic_test_db(rexie: &Rexie) { assert_eq!(rexie.name(), "test"); - assert_eq!(rexie.version(), 1.0); + assert_eq!(rexie.version(), Ok(1)); assert_eq!( rexie.store_names(), vec!["departments", "employees", "invoices"] @@ -80,7 +80,7 @@ async fn basic_test_db(rexie: &Rexie) { assert!(transaction.is_ok()); let transaction = transaction.unwrap(); - assert_eq!(transaction.mode(), TransactionMode::ReadOnly); + assert_eq!(transaction.mode(), Ok(TransactionMode::ReadOnly)); assert_eq!(transaction.store_names(), vec!["employees"]); let employees = transaction.store("employees"); @@ -89,7 +89,7 @@ async fn basic_test_db(rexie: &Rexie) { assert_eq!(employees.name(), "employees"); assert!(employees.auto_increment()); - assert_eq!(employees.key_path(), Ok(Some("id".to_string()))); + assert_eq!(employees.key_path(), Ok(Some(KeyPath::new_single("id")))); assert_eq!(employees.index_names(), vec!["email"]); let email_index = employees.index("email"); @@ -136,7 +136,7 @@ async fn get_employee(rexie: &Rexie, id: u32) -> Result> { let employees = employees.unwrap(); Ok(employees - .get(&id.into()) + .get(id.into()) .await? .map(|value| serde_wasm_bindgen::from_value::(value).unwrap())) } @@ -151,7 +151,7 @@ async fn get_all_employees(rexie: &Rexie, direction: Option) -> Resul let employees = employees.unwrap(); let employees: Vec = employees - .get_all(None, None, None, direction) + .scan(None, None, None, direction) .await? .into_iter() .map(|pair| pair.1) @@ -165,7 +165,7 @@ async fn get_all_employees(rexie: &Rexie, direction: Option) -> Resul Ok(employees) } -async fn count_employees(rexie: &Rexie, key_range: Option<&KeyRange>) -> Result { +async fn count_employees(rexie: &Rexie, key_range: Option) -> Result { let transaction = rexie.transaction(&["employees"], TransactionMode::ReadOnly); assert!(transaction.is_ok()); let transaction = transaction.unwrap(); @@ -227,7 +227,7 @@ async fn get_invoice(rexie: &Rexie, id: usize, year: u16) -> Result