indexed_db_futures/
transaction.rs

1//! An [`IDBTransaction`](https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction) implementation.
2
3use crate::database::Database;
4use crate::error::Error;
5use crate::internal_utils::{StructName, SystemRepr};
6pub use base::TransactionRef;
7use listeners::TxListeners;
8pub(crate) use options::TransactionOptionsSys;
9pub use options::{TransactionDurability, TransactionOptions};
10use std::fmt::{Debug, Formatter};
11use std::ops::Deref;
12pub(crate) use tx_sys::TransactionSys;
13pub use web_sys::IdbTransactionMode as TransactionMode;
14
15mod base;
16mod listeners;
17mod options;
18mod tx_sys;
19
20iffeat! {
21    #[cfg(feature = "tx-done")]
22    mod on_done;
23    pub use on_done::{TransactionDone, TransactionFinishKind};
24}
25
26/// An [`IDBTransaction`](https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction) implementation.
27///
28/// Unlike JS transactions, **this defaults to aborting the transaction instead of committing it** -
29/// the opposite of the default behaviour in JS. Dropping the transaction without calling
30/// [`commit`](Transaction::commit) will act the same as calling
31/// [`abort`](Transaction::abort) - see browser compatibility note on the `abort` fn for caveats.
32#[derive(StructName)]
33#[must_use]
34pub struct Transaction<'a> {
35    listeners: TxListeners<'a>,
36
37    done: bool,
38}
39
40/// A [transaction's](Transaction) result.
41#[derive(Debug, PartialEq, derive_more::From)]
42enum TransactionResult {
43    /// Transaction committed successfully.
44    Ok,
45
46    /// Transaction errored.
47    Err(Error),
48
49    /// Transaction aborted.
50    Abort,
51}
52
53macro_rules! map_result {
54    ($expr: expr, ok: $ok: ident, unexpected: $unexpect: ident => $err: ident) => {
55        match $expr {
56            TransactionResult::$ok => Ok(()),
57            TransactionResult::Err(e) => Err(e),
58            TransactionResult::$unexpect => Err(crate::error::UnexpectedDataError::$err.into()),
59        }
60    };
61}
62
63impl<'a> Transaction<'a> {
64    pub(crate) fn new(db: &'a Database, inner: web_sys::IdbTransaction) -> Self {
65        Self {
66            listeners: TxListeners::new(db, inner),
67            done: false,
68        }
69    }
70
71    /// Rolls back all the changes to objects in the database associated with this transaction.
72    ///
73    /// # Browser compatibility note
74    ///
75    /// Note that, depending on the browser, the this function may or may not roll back requests that have already been
76    /// `await`ed. Chrome & Firefox, for example, appear to roll back `await`ed requests, while Safari only rolls back
77    /// requests that have been built ([primitive](crate::BuildPrimitive) | [serde](crate::BuildSerde)), but not
78    /// `await`ed.
79    #[allow(clippy::missing_errors_doc)]
80    pub async fn abort(mut self) -> crate::Result<()> {
81        self.done = true;
82        self.as_sys().abort()?;
83
84        map_result!(self.listeners.recv().await, ok: Abort, unexpected: Ok => TransactionCommitted)
85    }
86
87    /// Commits all the changes made to objects in the database associated with this transaction.
88    #[allow(clippy::missing_errors_doc)]
89    pub async fn commit(mut self) -> crate::Result<()> {
90        self.done = true;
91        self.as_sys().do_commit()?;
92
93        map_result!(self.listeners.recv().await, ok: Ok, unexpected: Abort => TransactionAborted)
94    }
95}
96
97#[::sealed::sealed]
98#[allow(unused_qualifications)]
99impl crate::internal_utils::SystemRepr for Transaction<'_> {
100    type Repr = TransactionSys;
101
102    #[inline]
103    fn as_sys(&self) -> &Self::Repr {
104        self.transaction()
105    }
106
107    #[inline]
108    fn into_sys(self) -> Self::Repr {
109        self.as_sys().clone()
110    }
111}
112
113impl Drop for Transaction<'_> {
114    fn drop(&mut self) {
115        self.listeners.free_listeners();
116
117        if !self.done {
118            let _ = self.as_sys().abort();
119        }
120    }
121}
122
123impl Debug for Transaction<'_> {
124    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
125        f.debug_struct(Self::TYPE_NAME)
126            .field("transaction", self.as_sys())
127            .field("db", self.db())
128            .field("done", &self.done)
129            .finish()
130    }
131}
132
133impl<'a> Deref for Transaction<'a> {
134    type Target = TransactionRef<'a>;
135
136    #[inline]
137    fn deref(&self) -> &Self::Target {
138        self.listeners.tx_ref()
139    }
140}