1use crate::{transaction::unsafe_jar, utils::generic_request, Database, Transaction};
2use futures_channel::oneshot;
3use futures_util::{
4 future::{self, Either},
5 pin_mut, FutureExt,
6};
7use std::{future::Future, marker::PhantomData};
8use web_sys::{
9 js_sys::{self, Function},
10 wasm_bindgen::{closure::Closure, JsCast, JsValue},
11 IdbDatabase, IdbFactory, IdbOpenDbRequest, IdbVersionChangeEvent, WorkerGlobalScope,
12};
13
14#[derive(Debug)]
21pub struct Factory<Err> {
22 sys: IdbFactory,
23 _phantom: PhantomData<Err>,
24}
25
26impl<Err: 'static> Factory<Err> {
27 pub fn get() -> crate::Result<Factory<Err>, Err> {
31 let indexed_db = if let Some(window) = web_sys::window() {
32 window.indexed_db()
33 } else if let Ok(worker_scope) = js_sys::global().dyn_into::<WorkerGlobalScope>() {
34 worker_scope.indexed_db()
35 } else {
36 return Err(crate::Error::NotInBrowser);
37 };
38
39 let sys = indexed_db
40 .map_err(|_| crate::Error::IndexedDbDisabled)?
41 .ok_or(crate::Error::IndexedDbDisabled)?;
42
43 Ok(Factory {
44 sys,
45 _phantom: PhantomData,
46 })
47 }
48
49 pub fn cmp(&self, lhs: &JsValue, rhs: &JsValue) -> crate::Result<std::cmp::Ordering, Err> {
55 use std::cmp::Ordering::*;
56 self.sys
57 .cmp(lhs, rhs)
58 .map(|v| match v {
59 -1 => Less,
60 0 => Equal,
61 1 => Greater,
62 v => panic!("Unexpected result of IDBFactory::cmp: {v}"),
63 })
64 .map_err(|e| match error_name!(&e) {
65 Some("DataError") => crate::Error::InvalidKey,
66 _ => crate::Error::from_js_value(e),
67 })
68 }
69
70 pub async fn delete_database(&self, name: &str) -> crate::Result<(), Err> {
79 generic_request(
80 self.sys
81 .delete_database(name)
82 .map_err(crate::Error::from_js_value)?
83 .into(),
84 )
85 .await
86 .map(|_| ())
87 .map_err(crate::Error::from_js_event)
88 }
89
90 pub async fn open<Fun, RetFut>(
101 &self,
102 name: &str,
103 version: u32,
104 on_upgrade_needed: Fun,
105 ) -> crate::Result<Database<Err>, Err>
106 where
107 Fun: 'static + FnOnce(VersionChangeEvent<Err>) -> RetFut,
108 RetFut: 'static + Future<Output = crate::Result<(), Err>>,
109 {
110 if version == 0 {
111 return Err(crate::Error::VersionMustNotBeZero);
112 }
113
114 let open_req = self
115 .sys
116 .open_with_u32(name, version)
117 .map_err(crate::Error::from_js_value)?;
118
119 let (upgrade_tx, upgrade_rx) = oneshot::channel();
120 let on_upgrade_needed = Closure::once(|evt: IdbVersionChangeEvent| {
121 let evt = VersionChangeEvent::from_sys(evt);
122 let transaction = evt.transaction().as_sys().clone();
123 let fut = {
124 let transaction = transaction.clone();
125 async move {
126 let res = on_upgrade_needed(evt).await;
127 let return_value = match &res {
128 Ok(_) => Ok(()),
129 Err(_) => Err(()),
130 };
131 if let Err(_) = upgrade_tx.send(res) {
132 let _ = transaction.abort();
134 }
135 return_value
136 }
137 };
138 unsafe_jar::run(transaction, fut);
139 });
140 open_req.set_onupgradeneeded(Some(
141 on_upgrade_needed.as_ref().dyn_ref::<Function>().unwrap(),
142 ));
143
144 let completion_fut = generic_request(open_req.clone().into());
145 pin_mut!(completion_fut);
146
147 let res = future::select(upgrade_rx, completion_fut).await;
148 if unsafe_jar::POLLED_FORBIDDEN_THING.get() {
149 panic!("Transaction blocked without any request under way");
150 }
151
152 match res {
153 Either::Right((completion, _)) => {
154 completion.map_err(crate::Error::from_js_event)?;
155 }
156 Either::Left((upgrade_res, completion_fut)) => {
157 let upgrade_res = upgrade_res.expect("Closure dropped before its end of scope");
158 upgrade_res?;
159 completion_fut.await.map_err(crate::Error::from_js_event)?;
160 }
161 }
162
163 let db = open_req
164 .result()
165 .map_err(crate::Error::from_js_value)?
166 .dyn_into::<IdbDatabase>()
167 .expect("Result of successful IDBOpenDBRequest is not an IDBDatabase");
168
169 Ok(Database::from_sys(db))
170 }
171
172 pub async fn open_latest_version(&self, name: &str) -> crate::Result<Database<Err>, Err> {
180 let open_req = self.sys.open(name).map_err(crate::Error::from_js_value)?;
181
182 let completion_fut = generic_request(open_req.clone().into())
183 .map(|res| res.map_err(crate::Error::from_js_event));
184 pin_mut!(completion_fut);
185
186 completion_fut.await?;
187
188 let db = open_req
189 .result()
190 .map_err(crate::Error::from_js_value)?
191 .dyn_into::<IdbDatabase>()
192 .expect("Result of successful IDBOpenDBRequest is not an IDBDatabase");
193
194 Ok(Database::from_sys(db))
195 }
196}
197
198#[derive(Debug)]
200pub struct VersionChangeEvent<Err> {
201 sys: IdbVersionChangeEvent,
202 db: Database<Err>,
203 transaction: Transaction<Err>,
204}
205
206impl<Err> VersionChangeEvent<Err> {
207 fn from_sys(sys: IdbVersionChangeEvent) -> VersionChangeEvent<Err> {
208 let db_req = sys
209 .target()
210 .expect("IDBVersionChangeEvent had no target")
211 .dyn_into::<IdbOpenDbRequest>()
212 .expect("IDBVersionChangeEvent target was not an IDBOpenDBRequest");
213 let db_sys = db_req
214 .result()
215 .expect("IDBOpenDBRequest had no result in its on_upgrade_needed handler")
216 .dyn_into::<IdbDatabase>()
217 .expect("IDBOpenDBRequest result was not an IDBDatabase");
218 let transaction_sys = db_req
219 .transaction()
220 .expect("IDBOpenDBRequest had no associated transaction");
221 let db = Database::from_sys(db_sys);
222 let transaction = Transaction::from_sys(transaction_sys);
223 VersionChangeEvent {
224 sys,
225 db,
226 transaction,
227 }
228 }
229
230 pub fn old_version(&self) -> u32 {
234 self.sys.old_version() as u32
235 }
236
237 pub fn new_version(&self) -> u32 {
241 self.sys
242 .new_version()
243 .expect("IDBVersionChangeEvent did not provide a new version") as u32
244 }
245
246 pub fn database(&self) -> &Database<Err> {
248 &self.db
249 }
250
251 pub fn transaction(&self) -> &Transaction<Err> {
255 &self.transaction
256 }
257}