diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 404cba9..4ce6dca 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,6 @@ name: Test on: - workflow_call: { } + workflow_call: {} push: branches: - master @@ -41,7 +41,7 @@ jobs: uses: alorel-actions/cargo/init@v2 id: toolchain with: - toolchain: nightly-2024-10-18 + toolchain: nightly-2025-05-05 cache-prefix: doc local: true @@ -104,7 +104,7 @@ jobs: include: - toolchain: nightly-2025-05-05 os: ubuntu-latest - - toolchain: 1.75.0 + - toolchain: 1.85.0 os: ubuntu-latest - toolchain: stable os: ubuntu-latest @@ -120,7 +120,7 @@ jobs: fail-fast: false matrix: flags: - - '' + - "" - --features cursors - --features dates - --features indices diff --git a/Cargo.toml b/Cargo.toml index 33f2ed4..a71db38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ name = "indexed_db_futures" version = "0.6.4" authors = ["Arturas Molcanovas "] edition = "2021" -rust-version = "1.75.0" +rust-version = "1.85.0" license = "MIT" description = "Future bindings for IndexedDB via web_sys" repository = "https://github.com/Alorel/rust-indexed-db" diff --git a/internal_macros/src/generic_bounds.rs b/internal_macros/src/generic_bounds.rs index cf2d0e3..a2e646d 100644 --- a/internal_macros/src/generic_bounds.rs +++ b/internal_macros/src/generic_bounds.rs @@ -1,7 +1,5 @@ use crate::commons::FnTarget; use crate::TokenStream1; -use macroific::prelude::*; -use proc_macro2::Ident; use quote::ToTokens; use syn::{parse_quote, Error, WherePredicate}; @@ -22,7 +20,7 @@ macro_rules! make_opts { /// /// | Option | Type | /// |--------|-----------| - $($(#[doc = concat!(" | `", stringify!($extra_opt), "` | `", stringify!($extra_ty), "` |")])+)+ + $($(#[doc = concat!(" | `", stringify!($extra_opt), "` | `", stringify!($extra_ty), "` |")])+)* #[derive(::macroific::attr_parse::AttributeOptions)] pub(super) struct $struct_name { $($($opt: ::syn::punctuated::Punctuated,)+)+ @@ -53,10 +51,8 @@ make_opts!(Opts => { db_name|index_name|store_name|key_path => ::core::convert::AsRef, db_version => crate::factory::DBVersion, blocked_cb => ::core::ops::FnOnce(crate::database::VersionChangeEvent) -> crate::Result<()> + 'static, - upgrade_cb => ::core::ops::FnOnce(crate::database::VersionChangeEvent, crate::database::Database) -> crate::Result<()> + 'static, - [custom] => { - upgrade_async_cb => UpgradeAsyncCb, - }, + upgrade_cb => ::core::ops::FnOnce(crate::database::VersionChangeEvent, &crate::transaction::Transaction<'_>) -> crate::Result<()> + 'static, + upgrade_async_cb => ::core::ops::AsyncFnOnce(crate::database::VersionChangeEvent, &crate::transaction::Transaction<'_>) -> crate::Result<()> + 'static, }); #[inline] @@ -73,31 +69,6 @@ pub(super) fn exec(spec: TokenStream1, target: TokenStream1) -> TokenStream1 { } } -#[derive(ParseOption)] -struct UpgradeAsyncCb { - #[attr_opts(default = false)] - fun: Ident, - - #[attr_opts(default = false)] - fut: Ident, -} - -impl UpgradeAsyncCb { - fn extend_target(self, target: &mut FnTarget) { - let Self { fun, fut } = self; - let wheres = [ - parse_quote!(#fun: ::core::ops::FnOnce(crate::database::VersionChangeEvent, crate::database::Database) -> #fut + 'static), - parse_quote!(#fut: ::core::future::Future> + 'static), - ]; - - target - .generics_mut() - .make_where_clause() - .predicates - .extend::<[WherePredicate; 2]>(wheres); - } -} - fn on_err(mut target: TokenStream1, e: Error) -> TokenStream1 { let e: TokenStream1 = e.into_compile_error().into(); target.extend(e); diff --git a/src/cursor.rs b/src/cursor.rs index 4787132..c096db3 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -64,7 +64,7 @@ impl<'a, Qs> Cursor<'a, Qs> { /// followed by [`value`](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursorWithValue/value) in JS. #[errdoc(Cursor(TransactionInactiveError, InvalidStateError))] #[inline] - pub fn next_record(&mut self) -> CursorNextRequest + pub fn next_record(&mut self) -> CursorNextRequest<'_, T> where T: TryFromJs, { @@ -73,7 +73,7 @@ impl<'a, Qs> Cursor<'a, Qs> { /// Mirror of [`Self::next_record`] for `serde`-deserialisable values. #[cfg(feature = "serde")] - pub fn next_record_ser(&mut self) -> CursorNextRequest + pub fn next_record_ser(&mut self) -> CursorNextRequest<'_, T> where T: crate::serde::DeserialiseFromJs, { @@ -106,7 +106,7 @@ impl<'a, Qs> Cursor<'a, Qs> { DataCloneError, ))] #[inline] - pub fn update(&self, value: V) -> Update { + pub fn update(&self, value: V) -> Update<'_, V> { Update::new(self, value) } diff --git a/src/cursor/key_cursor.rs b/src/cursor/key_cursor.rs index a4a57a1..310f843 100644 --- a/src/cursor/key_cursor.rs +++ b/src/cursor/key_cursor.rs @@ -34,7 +34,7 @@ impl<'a, Qs> KeyCursor<'a, Qs> { /// followed by [`key`](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/key) in JS. #[inline] #[errdoc(Cursor(TransactionInactiveError, InvalidStateError))] - pub fn next_key(&mut self) -> CursorNextRequest + pub fn next_key(&mut self) -> CursorNextRequest<'_, T> where T: TryFromJs, { @@ -44,7 +44,7 @@ impl<'a, Qs> KeyCursor<'a, Qs> { /// Mirror of [`Self::next_key`] for `serde`-deserialisable keys. #[inline] #[cfg(feature = "serde")] - pub fn next_key_ser(&mut self) -> CursorNextRequest + pub fn next_key_ser(&mut self) -> CursorNextRequest<'_, T> where T: crate::serde::DeserialiseFromJs, { diff --git a/src/cursor/stream.rs b/src/cursor/stream.rs index 54490ec..ed3e020 100644 --- a/src/cursor/stream.rs +++ b/src/cursor/stream.rs @@ -90,7 +90,7 @@ impl<'a, Qs, T> Stream, T> { DataErrorUpdate, DataCloneError ))] - pub fn update(&self, value: V) -> super::Update { + pub fn update(&self, value: V) -> super::Update<'_, V> { self.cursor.update(value) } diff --git a/src/database.rs b/src/database.rs index c0105e9..c9823f0 100644 --- a/src/database.rs +++ b/src/database.rs @@ -52,7 +52,7 @@ impl Database { /// Create an object store with the given name. #[generic_bounds(store_name(N))] #[inline] - pub fn create_object_store(&self, name: N) -> StoreBuilder { + pub fn create_object_store(&self, name: N) -> StoreBuilder<'_, N> { StoreBuilder::new(self, name) } @@ -99,7 +99,7 @@ impl Database { /// List the names of the object stores within this database. #[inline] - pub fn object_store_names(&self) -> DomStringIter { + pub fn object_store_names(&self) -> DomStringIter<'_> { DomStringIter::new(self.as_sys().object_store_names()) } @@ -107,7 +107,7 @@ impl Database { /// [`Build::build`](crate::Build::build). #[errdoc(Database(NotFoundErrorTx, InvalidAccessErrorTx))] #[inline] - pub fn transaction(&self, store_names: S) -> TransactionBuilder { + pub fn transaction(&self, store_names: S) -> TransactionBuilder<'_, S> { TransactionBuilder::new(self, store_names) } diff --git a/src/error/unexpected_data.rs b/src/error/unexpected_data.rs index 49da5e3..16fb24b 100644 --- a/src/error/unexpected_data.rs +++ b/src/error/unexpected_data.rs @@ -22,6 +22,10 @@ pub enum UnexpectedDataError { #[error("`Future` polled unexpectedly.")] PollState, + /// Expected a Transaction to exist, but it was not found. + #[error("Expected the Transaction to exist, but it was not found.")] + TransactionNotFound, + /// Expected a Transaction to be aborted, but it was committed. #[error("Expected the Transaction to be aborted, but it was committed.")] TransactionCommitted, diff --git a/src/factory/req_builder.rs b/src/factory/req_builder.rs index 57f5343..eedf7fc 100644 --- a/src/factory/req_builder.rs +++ b/src/factory/req_builder.rs @@ -102,9 +102,9 @@ impl OpenDbRequestBuilder { /// Set the [upgradeneeded](https://developer.mozilla.org/en-US/docs/Web/API/IDBOpenDBRequest/upgradeneeded_event) /// event handler that returns a `Future`. - #[generic_bounds(upgrade_async_cb(fun(U2), fut(U2Fut)))] + #[generic_bounds(upgrade_async_cb(U2))] #[cfg(feature = "async-upgrade")] - pub fn with_on_upgrade_needed_fut( + pub fn with_on_upgrade_needed_fut( self, on_upgrade_needed: U2, ) -> OpenDbRequestBuilder { diff --git a/src/future/open_db/listener.rs b/src/future/open_db/listener.rs index a5ea5cb..f0de35a 100644 --- a/src/future/open_db/listener.rs +++ b/src/future/open_db/listener.rs @@ -1,5 +1,6 @@ use crate::database::{Database, VersionChangeEvent}; use crate::error::{Error, UnexpectedDataError}; +use crate::transaction::{OnTransactionDrop, Transaction}; use internal_macros::generic_bounds; use std::fmt::{Debug, Display, Formatter}; use std::mem; @@ -56,9 +57,16 @@ impl OpenDbListener { #[cfg(feature = "async-upgrade")] async_notify: Self::fake_rx(), listener: Closure::once(move |evt: web_sys::IdbVersionChangeEvent| { - let res = Database::from_event(&evt) - .and_then(move |db| callback(VersionChangeEvent::new(evt), db)); - + let res = Database::from_event(&evt).and_then(|db| { + Transaction::from_raw_version_change_event(&db, &evt).and_then(|mut tx| { + callback(VersionChangeEvent::new(evt), &tx).inspect(|()| { + // If the callback succeeded, we want to ensure that + // the transaction is committed when dropped and not + // aborted. + tx.on_drop(OnTransactionDrop::Commit); + }) + }) + }); Self::handle_result(LBL_UPGRADE, &status, res) }), } @@ -154,8 +162,8 @@ const _: () = { tokio::sync::mpsc::unbounded_channel().1 } - #[generic_bounds(upgrade_async_cb(fun(Fn), fut(Fut)))] - pub(crate) fn new_upgrade_fut(callback: Fn) -> Self { + #[generic_bounds(upgrade_async_cb(Fn))] + pub(crate) fn new_upgrade_fut(callback: Fn) -> Self { let status = Status::new(); let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); Self { @@ -168,11 +176,21 @@ const _: () = { }; Self::set_status(&status, Status::Pending, LBL_UPGRADE)?; - let fut = callback(VersionChangeEvent::new(evt), db); - wasm_bindgen_futures::spawn_local(async move { - let result = match fut.await { - Ok(()) => Status::Ok, + let db = db; + let result = match Transaction::from_raw_version_change_event(&db, &evt) { + Ok(mut transaction) => { + match callback(VersionChangeEvent::new(evt), &transaction).await { + Ok(()) => { + // If the callback succeeded, we want to ensure that + // the transaction is committed when dropped and not + // aborted. + transaction.on_drop(OnTransactionDrop::Commit); + Status::Ok + } + Err(e) => Status::Err(e), + } + } Err(e) => Status::Err(e), }; let _ = Self::set_status(&status, result, LBL_UPGRADE); diff --git a/src/index/object_store_ext.rs b/src/index/object_store_ext.rs index 3735621..d197cef 100644 --- a/src/index/object_store_ext.rs +++ b/src/index/object_store_ext.rs @@ -19,7 +19,7 @@ impl ObjectStore<'_> { ))] #[generic_bounds(index_name(N), key_path(KP))] #[inline] - pub fn create_index(&self, name: N, key_path: KeyPath) -> IndexBuilder { + pub fn create_index(&self, name: N, key_path: KeyPath) -> IndexBuilder<'_, N, KP> { IndexBuilder::new(self, name, key_path) } @@ -38,7 +38,7 @@ impl ObjectStore<'_> { /// Open an index with the given name #[errdoc(Index(InvalidStateErrorIndex, NotFoundError))] #[allow(clippy::missing_errors_doc)] - pub fn index(&self, name: &str) -> crate::Result { + pub fn index(&self, name: &str) -> crate::Result> { match self.as_sys().index(name) { Ok(sys) => Ok(Index::new(self, sys)), Err(e) => Err(e.into()), @@ -46,7 +46,7 @@ impl ObjectStore<'_> { } /// Return the names of the indices on this object store. - pub fn index_names(&self) -> DomStringIter { + pub fn index_names(&self) -> DomStringIter<'_> { DomStringIter::new(self.as_sys().index_names()) } } diff --git a/src/key_path.rs b/src/key_path.rs index ec2ca97..50aa38f 100644 --- a/src/key_path.rs +++ b/src/key_path.rs @@ -22,7 +22,7 @@ pub enum KeyPath { #[generic_bounds(key_path(T))] impl KeyPath { - /// Convert the key path to a JsValue. + /// Convert the key path to a `JsValue`. pub fn to_js(&self) -> JsValue { match self { Self::One(v) => JsValue::from_str(v.as_ref()), diff --git a/src/lib.rs b/src/lib.rs index cea5db1..2ff81b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,19 +80,21 @@ //! ## Opening a database & making some schema changes //! //! ``` -//! use indexed_db_futures::database::Database; +//! use indexed_db_futures::database::{Database, VersionChangeEvent}; //! use indexed_db_futures::prelude::*; -//! use indexed_db_futures::transaction::TransactionMode; +//! use indexed_db_futures::transaction::{Transaction, TransactionMode}; //! //! # async fn example() -> indexed_db_futures::OpenDbResult<()> { //! # #[allow(dead_code)] +//! # #[cfg(all(feature = "async-upgrade", feature = "tx-done"))] //! let db = Database::open("my_db") //! .with_version(2u8) //! .with_on_blocked(|event| { //! log::debug!("DB upgrade blocked: {:?}", event); //! Ok(()) //! }) -//! .with_on_upgrade_needed_fut(|event, db| async move { +//! .with_on_upgrade_needed_fut(async |event: VersionChangeEvent, tx: &Transaction<'_>| { +//! let db = tx.db(); //! // Convert versions from floats to integers to allow using them in match expressions //! let old_version = event.old_version() as u64; //! let new_version = event.new_version().map(|v| v as u64); @@ -141,6 +143,8 @@ //! ## Reading/writing with `serde` //! //! ``` +//! # #[cfg(feature = "serde")] +//! # mod wrapper { //! # use indexed_db_futures::object_store::ObjectStore; //! # use indexed_db_futures::prelude::*; //! # use serde::{Deserialize, Serialize}; @@ -157,6 +161,7 @@ //! let user: Option = object_store.get(1u32).serde()?.await?; //! # Ok(()) //! # } +//! # } //! ``` //! //! # Iterating a cursor @@ -166,6 +171,7 @@ //! # use indexed_db_futures::prelude::*; //! # //! # #[allow(dead_code)] +//! # #[cfg(feature = "cursors")] //! # async fn example(object_store: ObjectStore<'_>) -> indexed_db_futures::Result<()> { //! let Some(mut cursor) = object_store.open_cursor().await? else { //! log::debug!("Cursor empty"); @@ -181,6 +187,8 @@ //! # Iterating an index as a stream //! //! ``` +//! # #[cfg(all(feature = "serde", feature = "indices", feature = "cursors", feature = "streams"))] +//! # mod wrapper { //! # use indexed_db_futures::object_store::ObjectStore; //! # use indexed_db_futures::prelude::*; //! # use serde::{Deserialize, Serialize}; @@ -203,6 +211,7 @@ //! let records = stream.try_collect::>().await?; //! # Ok(()) //! # } +//! # } //! ``` //! diff --git a/src/object_store.rs b/src/object_store.rs index 4be2c87..626ee14 100644 --- a/src/object_store.rs +++ b/src/object_store.rs @@ -56,7 +56,7 @@ impl<'a> ObjectStore<'a> { ConstraintError, ))] #[inline] - pub fn add(&self, value: V) -> Add { + pub fn add(&self, value: V) -> Add<'_, V> { Add::new(self, value) } @@ -80,7 +80,7 @@ impl<'a> ObjectStore<'a> { ConstraintError, ))] #[inline] - pub fn put(&self, value: V) -> Put { + pub fn put(&self, value: V) -> Put<'_, V> { Put::new(self, value) } @@ -117,7 +117,7 @@ impl<'a> ObjectStore<'a> { InvalidStateError, DataErrorDelete, ))] - pub fn delete(&self, key_range: I) -> Delete + pub fn delete(&self, key_range: I) -> Delete<'_, K> where I: Into>, { diff --git a/src/query_source.rs b/src/query_source.rs index c128b1e..e78a8c4 100644 --- a/src/query_source.rs +++ b/src/query_source.rs @@ -30,34 +30,34 @@ mod get_key; pub trait QuerySource { /// Count the number of documents in the index/object store. #[errdoc(QuerySource(InvalidStateError, TransactionInactiveError, DataError))] - fn count(&self) -> Count + fn count(&self) -> Count<'_, Self> where Self: Sized; /// Get one record from the object store or index. Returns the first match if a non-[only](KeyRange::Only) key is /// provided and multiple records match. #[errdoc(QuerySource(InvalidStateError, TransactionInactiveError, DataError))] - fn get(&self, key: I) -> Get + fn get(&self, key: I) -> Get<'_, Self, K, V> where Self: Sized, I: Into>; /// Return the first matching key selected by the specified query. #[errdoc(QuerySource(TransactionInactiveError, InvalidStateError, DataError))] - fn get_key(&self, key_range: I) -> GetKey + fn get_key(&self, key_range: I) -> GetKey<'_, Self, K> where Self: Sized, I: Into>; /// Get all records in the object store or index. #[errdoc(QuerySource(InvalidStateError, TransactionInactiveError, DataError))] - fn get_all(&self) -> GetAllRecords + fn get_all(&self) -> GetAllRecords<'_, Self, V> where Self: Sized; /// Get all keys in the object store or index. #[errdoc(QuerySource(InvalidStateError, TransactionInactiveError, DataError))] - fn get_all_keys(&self) -> GetAllKeys + fn get_all_keys(&self) -> GetAllKeys<'_, Self, K> where Self: Sized; @@ -77,12 +77,12 @@ pub trait QuerySource { /// Open a cursor that iterates over the records in the index or object store. /// Resolves to `None` if the cursor is empty. #[errdoc(Cursor(TransactionInactiveError, DataErrorOpen, InvalidStateErrorOpen))] - fn open_cursor(&self) -> CursorBuilder where Self: Sized; + fn open_cursor(&self) -> CursorBuilder<'_, Self> where Self: Sized; /// Open a cursor that iterates over the keys in the index or object store. /// Resolves to `None` if the cursor is empty. #[errdoc(Cursor(TransactionInactiveError, DataErrorOpen, InvalidStateErrorOpen))] - fn open_key_cursor(&self) -> KeyCursorBuilder where Self: Sized; + fn open_key_cursor(&self) -> KeyCursorBuilder<'_, Self> where Self: Sized; } } @@ -99,7 +99,7 @@ impl, R: QuerySourceInternal> QuerySource for T { } #[inline] - fn count(&self) -> Count { + fn count(&self) -> Count<'_, Self> { Count::new(self) } @@ -110,14 +110,14 @@ impl, R: QuerySourceInternal> QuerySource for T { } } - fn get(&self, key: I) -> Get + fn get(&self, key: I) -> Get<'_, Self, K, V> where I: Into>, { Get::new(self, key.into()) } - fn get_key(&self, key_range: I) -> GetKey + fn get_key(&self, key_range: I) -> GetKey<'_, Self, K> where I: Into>, { @@ -125,24 +125,24 @@ impl, R: QuerySourceInternal> QuerySource for T { } #[inline] - fn get_all(&self) -> GetAllRecords { + fn get_all(&self) -> GetAllRecords<'_, Self, V> { GetAllRecords::new(self) } #[inline] - fn get_all_keys(&self) -> GetAllKeys { + fn get_all_keys(&self) -> GetAllKeys<'_, Self, K> { GetAllKeys::new(self) } iffeat! { #[cfg(feature = "cursors")] #[inline] - fn open_cursor(&self) -> CursorBuilder { + fn open_cursor(&self) -> CursorBuilder<'_, Self> { CursorBuilder::new(self) } #[inline] - fn open_key_cursor(&self) -> KeyCursorBuilder { + fn open_key_cursor(&self) -> KeyCursorBuilder<'_, Self> { KeyCursorBuilder::new(self) } } diff --git a/src/transaction.rs b/src/transaction.rs index 3ce7ae8..144a08a 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -1,7 +1,7 @@ //! An [`IDBTransaction`](https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction) implementation. use crate::database::Database; -use crate::error::Error; +use crate::error::{Error, SimpleValueError, UnexpectedDataError}; use crate::internal_utils::{StructName, SystemRepr}; pub use base::TransactionRef; use listeners::TxListeners; @@ -10,6 +10,7 @@ pub use options::{TransactionDurability, TransactionOptions}; use std::fmt::{Debug, Formatter}; use std::ops::Deref; pub(crate) use tx_sys::TransactionSys; +use wasm_bindgen::JsCast; pub use web_sys::IdbTransactionMode as TransactionMode; mod base; @@ -35,6 +36,28 @@ pub struct Transaction<'a> { listeners: TxListeners<'a>, done: bool, + on_drop: OnTransactionDrop, +} + +/// An enum representing the possible behavior which a [`Transaction`] may exhibit +/// when it is dropped. +/// +/// Note that unlike JavaScript's [`IDBTransaction`][1], this crate's [`Transaction`] +/// defaults to aborting - i.e., [`OnTransactionDrop::Abort`] - instead of +/// committing - i.e., [`OnTransactionDrop::Commit`] - the transaction! +/// +/// [1]: https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction +#[derive(Debug, Copy, Clone, Default)] +pub enum OnTransactionDrop { + /// Abort the [`Transaction`] when it is dropped. This is the default + /// behavior of [`Transaction`]. + #[default] + Abort, + /// Commit the [`Transaction`] when it is dropped. This is the default + /// behavior of an [`IDBTransaction`][1] in JavaScript. + /// + /// [1]: https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction + Commit, } /// A [transaction's](Transaction) result. @@ -65,9 +88,35 @@ impl<'a> Transaction<'a> { Self { listeners: TxListeners::new(db, inner), done: false, + on_drop: OnTransactionDrop::default(), } } + /// Create a [`Transaction`] from an [`web_sys::IdbVersionChangeEvent`]. + /// + /// This is useful for extracting the transaction being used to upgrade + /// the database. + pub(crate) fn from_raw_version_change_event( + db: &'a Database, + event: &web_sys::IdbVersionChangeEvent, + ) -> crate::Result { + let inner = match event.target() { + Some(target) => match target.dyn_ref::() { + Some(req) => req + .transaction() + .ok_or(Error::from(UnexpectedDataError::TransactionNotFound)), + None => Err(SimpleValueError::DynCast(target.unchecked_into()).into()), + }, + None => Err(UnexpectedDataError::NoEventTarget.into()), + }?; + Ok(Self::new(db, inner)) + } + + /// Set the behavior for when the [`Transaction`] is dropped + pub fn on_drop(&mut self, on_drop: OnTransactionDrop) { + self.on_drop = on_drop; + } + /// Rolls back all the changes to objects in the database associated with this transaction. /// /// # Browser compatibility note @@ -114,7 +163,18 @@ impl Drop for Transaction<'_> { fn drop(&mut self) { self.listeners.free_listeners(); - if !self.done { + // Given that the default behavior in JavaScript is to commit the + // transaction when it is dropped, we only need to perform an action + // when we want to abort the transaction. + // + // Typically, it would make sense to explicitly commit the transaction + // with `TransactionSys::do_commit` when encountering `OnTransactionDrop::Commit`. + // However, for some reason, explicitly committing the transaction causes + // tests in a headless Chrome browser to hang, even though they pass in + // all other contexts, including a non-headless Chrome browser. So, until + // this is resolved, it is best to let `OnTransactionDrop::Commit` be + // handled implicitly by the JavaScript runtime. + if !self.done & matches!(self.on_drop, OnTransactionDrop::Abort) { let _ = self.as_sys().abort(); } } @@ -126,6 +186,7 @@ impl Debug for Transaction<'_> { .field("transaction", self.as_sys()) .field("db", self.db()) .field("done", &self.done) + .field("on_drop", &self.on_drop) .finish() } } diff --git a/src/transaction/base.rs b/src/transaction/base.rs index b6f0038..4f2c730 100644 --- a/src/transaction/base.rs +++ b/src/transaction/base.rs @@ -32,7 +32,7 @@ impl<'a> TransactionRef<'a> { /// Get an object store that's part of the transaction. #[errdoc(Transaction(NotFoundError, InvalidStateError))] #[allow(clippy::missing_errors_doc)] - pub fn object_store(&self, name: &str) -> crate::Result { + pub fn object_store(&self, name: &str) -> crate::Result> { match self.as_sys().object_store(name) { Ok(store) => Ok(ObjectStore::new(store, self)), Err(e) => Err(e.into()), @@ -41,7 +41,7 @@ impl<'a> TransactionRef<'a> { /// Get an iterator of the names of [`IdbObjectStore`](ObjectStore) objects /// associated with the transaction. - pub fn object_store_names(&self) -> DomStringIter { + pub fn object_store_names(&self) -> DomStringIter<'_> { DomStringIter::new(self.as_sys().object_store_names()) } diff --git a/src/typed_array.rs b/src/typed_array.rs index 499db0a..9a25100 100644 --- a/src/typed_array.rs +++ b/src/typed_array.rs @@ -36,7 +36,7 @@ pub struct TypedArraySlice<'a, T>(#[new(name(source))] &'a [T]); impl TypedArray { /// Convert this [`TypedArray`] into a [`TypedArraySlice`]. #[must_use] - pub fn as_slice(&self) -> TypedArraySlice { + pub fn as_slice(&self) -> TypedArraySlice<'_, T> { TypedArraySlice::new(&self.0) } } diff --git a/tests/tests/database/delete_obj_store.rs b/tests/tests/database/delete_obj_store.rs index 8660ba1..7eb2a79 100644 --- a/tests/tests/database/delete_obj_store.rs +++ b/tests/tests/database/delete_obj_store.rs @@ -12,7 +12,8 @@ pub async fn invalid_state_error() { #[wasm_bindgen_test] pub async fn not_found_error() { let err = Database::open(random_str()) - .with_on_upgrade_needed(move |_, db| { + .with_on_upgrade_needed(move |_, tx| { + let db = tx.db(); db.delete_object_store(&db.name())?; Ok(()) }) @@ -28,7 +29,8 @@ pub async fn happy_path() { let n1_clone = n1.clone(); let db = Database::open(&n1) - .with_on_upgrade_needed(move |_, db| { + .with_on_upgrade_needed(move |_, tx| { + let db = tx.db(); db.create_object_store(&n1_clone).build()?; db.create_object_store(&random_str()) .build()? diff --git a/tests/tests/database/obj_store_create.rs b/tests/tests/database/obj_store_create.rs index 7667bfe..f090ecf 100644 --- a/tests/tests/database/obj_store_create.rs +++ b/tests/tests/database/obj_store_create.rs @@ -12,7 +12,8 @@ pub async fn happy_path() { #[wasm_bindgen_test] pub async fn constraint_error() { let err = Database::open(random_str()) - .with_on_upgrade_needed(move |_, db| { + .with_on_upgrade_needed(move |_, tx| { + let db = tx.db(); let name = random_str(); db.create_object_store(&name).build()?; db.create_object_store(&name).build()?; @@ -27,7 +28,8 @@ pub async fn constraint_error() { #[wasm_bindgen_test] pub async fn invalid_access_error() { let err = Database::open(random_str()) - .with_on_upgrade_needed(move |_, db| { + .with_on_upgrade_needed(move |_, tx| { + let db = tx.db(); db.create_object_store(&db.name()) .with_auto_increment(true) .with_key_path("".into()) diff --git a/tests/tests/example_reproductions.rs b/tests/tests/example_reproductions.rs index 1d45b9f..9488fff 100644 --- a/tests/tests/example_reproductions.rs +++ b/tests/tests/example_reproductions.rs @@ -28,7 +28,8 @@ pub async fn multi_threaded_executor() { } let db = Database::open("my_db_multi_threaded_executor") - .with_on_upgrade_needed(|_, db| { + .with_on_upgrade_needed(|_, tx| { + let db = tx.db(); db.create_object_store("my_store") .with_auto_increment(true) .build()?; @@ -50,47 +51,46 @@ pub async fn opening_a_database_and_making_some_schema_changes() { let _ = Database::open("opening_a_database_and_making_some_schema_changes") .with_version(2u8) .with_on_blocked(|_| Ok(())) - .with_on_upgrade_needed_fut(|event, db| { + .with_on_upgrade_needed_fut(async |event, tx| { + let db = tx.db(); // Convert versions from floats to integers to allow using them in match expressions let old_version = event.old_version() as u64; let new_version = event.new_version().map(|v| v as u64); - async move { - match (old_version, new_version) { - (0, Some(1)) => { - db.create_object_store("my_store") - .with_auto_increment(true) - .build()?; + match (old_version, new_version) { + (0, Some(1)) => { + db.create_object_store("my_store") + .with_auto_increment(true) + .build()?; + } + (prev, Some(2)) => { + if prev == 1 { + db.delete_object_store("my_store")?; } - (prev, Some(2)) => { - if prev == 1 { - db.delete_object_store("my_store")?; - } - // Create an object store and await its transaction before inserting data. - db.create_object_store("my_other_store") - .with_auto_increment(true) - .build()? - .transaction() - .on_done()? - .await - .into_result()?; - - //- Start a new transaction & add some data - let tx = db - .transaction("my_other_store") - .with_mode(TransactionMode::Readwrite) - .build()?; - let store = tx.object_store("my_other_store")?; - store.add("foo").await?; - store.add("bar").await?; - tx.commit().await?; - } - _ => {} + // Create an object store and await its transaction before inserting data. + db.create_object_store("my_other_store") + .with_auto_increment(true) + .build()? + .transaction() + .on_done()? + .await + .into_result()?; + + //- Start a new transaction & add some data + let tx = db + .transaction("my_other_store") + .with_mode(TransactionMode::Readwrite) + .build()?; + let store = tx.object_store("my_other_store")?; + store.add("foo").await?; + store.add("bar").await?; + tx.commit().await?; } - - Ok(()) + _ => {} } + + Ok(()) }) .await .expect("Error opening DB"); @@ -111,7 +111,8 @@ pub async fn rw_serde() { } let db = Database::open("example_rw_serde") - .with_on_upgrade_needed(|_, db| { + .with_on_upgrade_needed(|_, tx| { + let db = tx.db(); db.create_object_store("users") .with_key_path("id".into()) .build()?; @@ -159,7 +160,8 @@ pub async fn readme_example() { async fn main() -> indexed_db_futures::OpenDbResult<()> { let db = Database::open("my_db_readme_example") .with_version(2u8) - .with_on_upgrade_needed(|event, db| { + .with_on_upgrade_needed(|event, tx| { + let db = tx.db(); // Convert versions from floats to integers to allow using them in match expressions let old_version = event.old_version() as u64; let new_version = event.new_version().map(|v| v as u64); @@ -262,7 +264,8 @@ pub async fn iterating_a_cursor() { let db = Database::open("example_iterating_a_cursor") .with_version(2u8) - .with_on_upgrade_needed(|_, db| { + .with_on_upgrade_needed(|_, tx| { + let db = tx.db(); db.create_object_store("my_store").build()?; Ok(()) }) @@ -329,7 +332,8 @@ pub async fn iterating_index_as_a_stream() { } let db = Database::open("example_iterating_index_as_a_stream") - .with_on_upgrade_needed(|_, db| { + .with_on_upgrade_needed(|_, tx| { + let db = tx.db(); let store = db .create_object_store("my_store") .with_key_path("id".into()) diff --git a/tests/tests/index/create.rs b/tests/tests/index/create.rs index 4690a48..6818f1c 100644 --- a/tests/tests/index/create.rs +++ b/tests/tests/index/create.rs @@ -4,7 +4,8 @@ use idb_fut::database::Database; #[wasm_bindgen_test] pub async fn constraint_error() { let err = Database::open(random_str()) - .with_on_upgrade_needed(move |_, db| { + .with_on_upgrade_needed(move |_, tx| { + let db = tx.db(); let name = db.name(); let store = db .create_object_store(&name) diff --git a/tests/tests/object_store/add_put.rs b/tests/tests/object_store/add_put.rs index 1c26e43..7e43947 100644 --- a/tests/tests/object_store/add_put.rs +++ b/tests/tests/object_store/add_put.rs @@ -11,7 +11,8 @@ macro_rules! common_tests { #[wasm_bindgen_test] pub async fn data_error_inline_key() { - let db = random_db_with_init(move |_, db| { + let db = random_db_with_init(move |_, tx| { + let db = tx.db(); db.create_object_store(&db.name()) .with_key_path("foo".into()) .build()?; @@ -64,7 +65,8 @@ macro_rules! common_tests { #[cfg(feature = "serde")] #[wasm_bindgen_test] pub async fn serde_object_nesting() { - let db = random_db_with_init(move |_, db| { + let db = random_db_with_init(move |_, tx| { + let db = tx.db(); db.create_object_store(&db.name()) .with_key_path("foo".into()) .build()?; diff --git a/tests/tests/object_store/query_source/key_path.rs b/tests/tests/object_store/query_source/key_path.rs index 9522485..12a699d 100644 --- a/tests/tests/object_store/query_source/key_path.rs +++ b/tests/tests/object_store/query_source/key_path.rs @@ -5,7 +5,8 @@ use idb_fut::KeyPath; #[wasm_bindgen_test] pub async fn auto_incremented() { let db = Database::open(random_str()) - .with_on_upgrade_needed(move |_, db| { + .with_on_upgrade_needed(move |_, tx| { + let db = tx.db(); db.create_object_store(&db.name()) .with_auto_increment(true) .build()?; @@ -21,7 +22,8 @@ pub async fn auto_incremented() { #[wasm_bindgen_test] pub async fn none() { let db = Database::open(random_str()) - .with_on_upgrade_needed(move |_, db| { + .with_on_upgrade_needed(move |_, tx| { + let db = tx.db(); db.create_object_store(&db.name()).build()?; Ok(()) }) @@ -35,7 +37,8 @@ pub async fn none() { #[wasm_bindgen_test] pub async fn explicit() { let db = Database::open(random_str()) - .with_on_upgrade_needed(move |_, db| { + .with_on_upgrade_needed(move |_, tx| { + let db = tx.db(); db.create_object_store(&db.name()) .with_key_path(Key::KEY_PATH) .build()?; diff --git a/tests/tests/transaction/mod.rs b/tests/tests/transaction/mod.rs index 54afa41..6e1868e 100644 --- a/tests/tests/transaction/mod.rs +++ b/tests/tests/transaction/mod.rs @@ -7,7 +7,8 @@ pub mod on_done; #[wasm_bindgen_test] pub async fn multi_store() { - let db = random_db_with_init(move |_, db| { + let db = random_db_with_init(move |_, tx| { + let db = tx.db(); db.create_object_store("s1").build()?; db.create_object_store("s2").build()?; Ok(()) diff --git a/tests/tests/transaction/on_done.rs b/tests/tests/transaction/on_done.rs index febb2e8..b6aefc0 100644 --- a/tests/tests/transaction/on_done.rs +++ b/tests/tests/transaction/on_done.rs @@ -40,7 +40,8 @@ pub mod async_upgrade { let err = Database::open(random_str()) .with_version(2u8) - .with_on_upgrade_needed_fut(move |_, db| async move { + .with_on_upgrade_needed_fut(async |_, tx| { + let db = tx.db(); // Create an object store and await its transaction db.create_object_store(STORE_NAME) .with_auto_increment(true) @@ -74,7 +75,8 @@ pub mod async_upgrade { .with_version(2u8) .with_on_upgrade_needed_fut({ let events = Arc::clone(&events); - move |_, db| async move { + async move |_, tx| { + let db = tx.db(); events.lock().unwrap().push(Event::CallbackStart); // Create an object store and await its transaction diff --git a/tests/tests/utils/init.rs b/tests/tests/utils/init.rs index 4601248..849c9ec 100644 --- a/tests/tests/utils/init.rs +++ b/tests/tests/utils/init.rs @@ -1,9 +1,10 @@ use crate::prelude::*; use idb_fut::database::{Database, VersionChangeEvent}; +use indexed_db_futures::transaction::Transaction; pub async fn random_db_with_init(on_upgrade_needed: F) -> Database where - F: Fn(VersionChangeEvent, Database) -> idb_fut::Result<()> + 'static, + F: Fn(VersionChangeEvent, &Transaction<'_>) -> idb_fut::Result<()> + 'static, { Database::open(random_str()) .with_on_upgrade_needed(on_upgrade_needed) @@ -13,7 +14,8 @@ where /// Crate a DB with and an object store with a matching name and default params. pub async fn random_db_with_store() -> Database { - random_db_with_init(move |_, db| { + random_db_with_init(move |_, tx| { + let db = tx.db(); db.create_object_store(&db.name()).build()?; Ok(()) }) @@ -22,7 +24,8 @@ pub async fn random_db_with_store() -> Database { /// Create a random DB and a store with a matching name that expect [`KeyVal`] inputs. pub async fn random_db_keyval() -> Database { - random_db_with_init(move |_, db| { + random_db_with_init(move |_, tx| { + let db = tx.db(); db.create_object_store(&db.name()) .with_auto_increment(false) .with_key_path(Key::KEY_PATH) @@ -35,7 +38,8 @@ pub async fn random_db_keyval() -> Database { /// [`random_db_keyval`] + an index with default params. #[cfg(feature = "indices")] pub async fn random_db_idx_keyval() -> Database { - random_db_with_init(move |_, db| { + random_db_with_init(move |_, tx| { + let db = tx.db(); let name = db.name(); let store = db .create_object_store(&name)