Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Test
on:
workflow_call: { }
workflow_call: {}
push:
branches:
- master
Expand Down Expand Up @@ -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
Expand All @@ -120,7 +120,7 @@ jobs:
fail-fast: false
matrix:
flags:
- ''
- ""
- --features cursors
- --features dates
- --features indices
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ name = "indexed_db_futures"
version = "0.6.4"
authors = ["Arturas Molcanovas <amolc@pm.me>"]
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"
Expand Down
35 changes: 3 additions & 32 deletions internal_macros/src/generic_bounds.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand All @@ -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<proc_macro2::Ident, ::syn::Token![,]>,)+)+
Expand Down Expand Up @@ -53,10 +51,8 @@ make_opts!(Opts => {
db_name|index_name|store_name|key_path => ::core::convert::AsRef<str>,
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]
Expand All @@ -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<Output = crate::Result<()>> + '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);
Expand Down
4 changes: 4 additions & 0 deletions src/error/unexpected_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions src/factory/req_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,9 @@ impl<N, V, B, U, Fa> OpenDbRequestBuilder<N, V, B, U, Fa> {

/// 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<U2, U2Fut>(
pub fn with_on_upgrade_needed_fut<U2>(
self,
on_upgrade_needed: U2,
) -> OpenDbRequestBuilder<N, V, B, OpenDbListener, Fa> {
Expand Down
36 changes: 27 additions & 9 deletions src/future/open_db/listener.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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)
}),
}
Expand Down Expand Up @@ -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<Fn, Fut>(callback: Fn) -> Self {
#[generic_bounds(upgrade_async_cb(Fn))]
pub(crate) fn new_upgrade_fut<Fn>(callback: Fn) -> Self {
let status = Status::new();
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
Self {
Expand All @@ -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);
Expand Down
56 changes: 54 additions & 2 deletions src/transaction.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -35,6 +36,27 @@ 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)]
pub enum OnTransactionDrop {
/// Abort the [`Transaction`] when it is dropped. This is the default
/// behavior of [`Transaction`].
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.
Expand Down Expand Up @@ -65,9 +87,35 @@ impl<'a> Transaction<'a> {
Self {
listeners: TxListeners::new(db, inner),
done: false,
on_drop: OnTransactionDrop::Abort,
}
}

/// 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<Self> {
let inner = match event.target() {
Some(target) => match target.dyn_ref::<web_sys::IdbOpenDbRequest>() {
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
Expand Down Expand Up @@ -115,7 +163,10 @@ impl Drop for Transaction<'_> {
self.listeners.free_listeners();

if !self.done {
let _ = self.as_sys().abort();
let _ = match self.on_drop {
OnTransactionDrop::Abort => self.as_sys().abort(),
OnTransactionDrop::Commit => self.as_sys().do_commit(),
};
}
}
}
Expand All @@ -126,6 +177,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()
}
}
Expand Down
6 changes: 4 additions & 2 deletions tests/tests/database/delete_obj_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
})
Expand All @@ -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()?
Expand Down
6 changes: 4 additions & 2 deletions tests/tests/database/obj_store_create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()?;
Expand All @@ -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())
Expand Down
Loading
Loading