indexed_db/
transaction.rs1use crate::{
2 utils::{err_from_event, str_slice_to_array},
3 ObjectStore,
4};
5use futures_channel::oneshot;
6use futures_util::future::{self, Either};
7use std::{future::Future, marker::PhantomData};
8use web_sys::{
9 wasm_bindgen::{JsCast, JsValue},
10 IdbDatabase, IdbRequest, IdbTransaction, IdbTransactionMode,
11};
12
13pub(crate) mod unsafe_jar;
14
15#[derive(Debug)]
17pub struct Transaction<Err> {
18 sys: IdbTransaction,
19 _phantom: PhantomData<Err>,
20}
21
22impl<Err> Transaction<Err> {
23 pub(crate) fn from_sys(sys: IdbTransaction) -> Transaction<Err> {
24 Transaction {
25 sys,
26 _phantom: PhantomData,
27 }
28 }
29
30 pub(crate) fn as_sys(&self) -> &IdbTransaction {
31 &self.sys
32 }
33
34 pub fn object_store(&self, name: &str) -> crate::Result<ObjectStore<Err>, Err> {
38 Ok(ObjectStore::from_sys(self.sys.object_store(name).map_err(
39 |err| match error_name!(&err) {
40 Some("NotFoundError") => crate::Error::DoesNotExist,
41 _ => crate::Error::from_js_value(err),
42 },
43 )?))
44 }
45}
46
47pub struct TransactionBuilder<Err> {
49 db: IdbDatabase,
50 stores: JsValue,
51 mode: IdbTransactionMode,
52 _phantom: PhantomData<Err>,
53 }
55
56impl<Err> TransactionBuilder<Err> {
57 pub(crate) fn from_names(db: IdbDatabase, names: &[&str]) -> TransactionBuilder<Err> {
58 TransactionBuilder {
59 db,
60 stores: str_slice_to_array(names).into(),
61 mode: IdbTransactionMode::Readonly,
62 _phantom: PhantomData,
63 }
64 }
65
66 pub fn rw(mut self) -> Self {
71 self.mode = IdbTransactionMode::Readwrite;
72 self
73 }
74
75 pub async fn run<Fun, RetFut, Ret>(self, transaction: Fun) -> crate::Result<Ret, Err>
102 where
103 Fun: 'static + FnOnce(Transaction<Err>) -> RetFut,
104 RetFut: 'static + Future<Output = crate::Result<Ret, Err>>,
105 Ret: 'static,
106 Err: 'static,
107 {
108 let t = self
109 .db
110 .transaction_with_str_sequence_and_mode(&self.stores, self.mode)
111 .map_err(|err| match error_name!(&err) {
112 Some("InvalidStateError") => crate::Error::DatabaseIsClosed,
113 Some("NotFoundError") => crate::Error::DoesNotExist,
114 Some("InvalidAccessError") => crate::Error::InvalidArgument,
115 _ => crate::Error::from_js_value(err),
116 })?;
117 let (tx, rx) = futures_channel::oneshot::channel();
118 let fut = {
119 let t = t.clone();
120 async move {
121 let res = transaction(Transaction::from_sys(t.clone())).await;
122 let return_value = match &res {
123 Ok(_) => Ok(()),
124 Err(_) => Err(()),
125 };
126 if let Err(_) = tx.send(res) {
127 let _ = t.abort();
129 }
130 return_value
131 }
132 };
133 unsafe_jar::run(t, fut);
134 let res = rx.await;
135 if unsafe_jar::POLLED_FORBIDDEN_THING.get() {
136 panic!("Transaction blocked without any request under way");
137 }
138 res.expect("Transaction never completed")
139 }
140}
141
142pub(crate) async fn transaction_request(req: IdbRequest) -> Result<JsValue, JsValue> {
143 let (success_tx, success_rx) = oneshot::channel();
147 let (error_tx, error_rx) = oneshot::channel();
148
149 let _callbacks = unsafe_jar::add_request(req, success_tx, error_tx);
151
152 let res = match future::select(success_rx, error_rx).await {
153 Either::Left((res, _)) => Ok(res.unwrap()),
154 Either::Right((res, _)) => Err(res.unwrap()),
155 };
156
157 res.map_err(|evt| err_from_event(evt).into()).map(|evt| {
158 evt.target()
159 .expect("Trying to parse indexed_db::Error from an event that has no target")
160 .dyn_into::<web_sys::IdbRequest>()
161 .expect(
162 "Trying to parse indexed_db::Error from an event that is not from an IDBRequest",
163 )
164 .result()
165 .expect("Failed retrieving the result of successful IDBRequest")
166 })
167}