indexed_db/
object_store.rs

1use crate::{
2    transaction::transaction_request,
3    utils::{
4        array_to_vec, make_key_range, map_add_err, map_clear_err, map_count_err, map_count_res,
5        map_delete_err, map_get_err, none_if_undefined, str_slice_to_array,
6    },
7    CursorBuilder, Index,
8};
9use futures_util::future::{Either, FutureExt};
10use std::{future::Future, marker::PhantomData, ops::RangeBounds};
11use web_sys::{js_sys::JsString, wasm_bindgen::JsValue, IdbIndexParameters, IdbObjectStore};
12
13#[cfg(doc)]
14use crate::Cursor;
15
16/// Wrapper for [`IDBObjectStore`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore),
17/// for use in transactions
18#[derive(Debug)]
19pub struct ObjectStore<Err> {
20    sys: IdbObjectStore,
21    _phantom: PhantomData<Err>,
22}
23
24impl<Err> ObjectStore<Err> {
25    pub(crate) fn from_sys(sys: IdbObjectStore) -> ObjectStore<Err> {
26        ObjectStore {
27            sys,
28            _phantom: PhantomData,
29        }
30    }
31
32    /// Build an index over this object store
33    ///
34    /// Note that this method can only be called from within an `on_upgrade_needed` callback. It returns
35    /// a builder, and calling the `create` method on this builder will perform the actual creation.
36    ///
37    /// If you want to make an index that searches multiple columns, please use [`ObjectStore::build_compound_index`].
38    ///
39    /// Internally, this uses [`IDBObjectStore::createIndex`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/createIndex).
40    pub fn build_index<'a>(&self, name: &'a str, key_path: &str) -> IndexBuilder<'a, Err> {
41        IndexBuilder {
42            store: self.sys.clone(),
43            name,
44            key_path: JsString::from(key_path).into(),
45            options: IdbIndexParameters::new(),
46            _phantom: PhantomData,
47        }
48    }
49
50    /// Build a compound index over this object store
51    ///
52    /// Note that this method can only be called from within an `on_upgrade_needed` callback. It returns
53    /// a builder, and calling the `create` method on this builder will perform the actual creation.
54    ///
55    /// Interesting points about indices:
56    /// - It is not possible to index `bool` in IndexedDB.
57    /// - If your index uses a column that does not exist, then the object will not be recorded in the index.
58    ///   This is useful for unique compound indices, usually when you would have conditionally indexed a `bool` column otherwise.
59    /// - You cannot build a compound multi-entry index, it needs to be a regular index.
60    ///
61    /// Internally, this uses [`IDBObjectStore::createIndex`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/createIndex).
62    pub fn build_compound_index<'a>(
63        &self,
64        name: &'a str,
65        key_paths: &[&str],
66    ) -> IndexBuilder<'a, Err> {
67        IndexBuilder {
68            store: self.sys.clone(),
69            name,
70            key_path: str_slice_to_array(key_paths).into(),
71            options: IdbIndexParameters::new(),
72            _phantom: PhantomData,
73        }
74    }
75
76    /// Delete an index from this object store
77    ///
78    /// Note that this method can only be called from within an `on_upgrade_needed` callback.
79    ///
80    /// Internally, this uses [`IDBObjectStore::deleteIndex`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/deleteIndex).
81    pub fn delete_index(&self, name: &str) -> crate::Result<(), Err> {
82        self.sys
83            .delete_index(name)
84            .map_err(|err| match error_name!(&err) {
85                Some("InvalidStateError") => crate::Error::ObjectStoreWasRemoved,
86                Some("NotFoundError") => crate::Error::DoesNotExist,
87                _ => crate::Error::from_js_value(err),
88            })
89    }
90
91    /// Add the value `value` to this object store, and return its auto-computed key
92    ///
93    /// This will error if the key already existed.
94    ///
95    /// Internally, this uses [`IDBObjectStore::add`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/add).
96    pub fn add(&self, value: &JsValue) -> impl Future<Output = crate::Result<JsValue, Err>> {
97        match self.sys.add(value) {
98            Ok(add_req) => {
99                Either::Left(transaction_request(add_req).map(|res| res.map_err(map_add_err)))
100            }
101            Err(e) => Either::Right(std::future::ready(Err(map_add_err(e)))),
102        }
103    }
104
105    /// Add the value `value` to this object store, with key `key`
106    ///
107    /// This will error if the key already existed.
108    ///
109    /// Internally, this uses [`IDBObjectStore::add`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/add).
110    pub fn add_kv(
111        &self,
112        key: &JsValue,
113        value: &JsValue,
114    ) -> impl Future<Output = crate::Result<(), Err>> {
115        match self.sys.add_with_key(value, key) {
116            Ok(add_req) => Either::Left(
117                transaction_request(add_req).map(|res| res.map_err(map_add_err).map(|_| ())),
118            ),
119            Err(e) => Either::Right(std::future::ready(Err(map_add_err(e)))),
120        }
121    }
122
123    /// Add the value `value` to this object store, and return its auto-computed key
124    ///
125    /// This will overwrite the previous value if the key already existed.
126    ///
127    /// Internally, this uses [`IDBObjectStore::add`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/add).
128    pub fn put(&self, value: &JsValue) -> impl Future<Output = crate::Result<JsValue, Err>> {
129        match self.sys.put(value) {
130            Ok(add_req) => {
131                Either::Left(transaction_request(add_req).map(|res| res.map_err(map_add_err)))
132            }
133            Err(e) => Either::Right(std::future::ready(Err(map_add_err(e)))),
134        }
135    }
136
137    /// Add the value `value` to this object store, with key `key`
138    ///
139    /// This will overwrite the previous value if the key already existed.
140    ///
141    /// Internally, this uses [`IDBObjectStore::add`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/add).
142    pub fn put_kv(
143        &self,
144        key: &JsValue,
145        value: &JsValue,
146    ) -> impl Future<Output = crate::Result<(), Err>> {
147        match self.sys.put_with_key(value, key) {
148            Ok(add_req) => Either::Left(
149                transaction_request(add_req).map(|res| res.map_err(map_add_err).map(|_| ())),
150            ),
151            Err(e) => Either::Right(std::future::ready(Err(map_add_err(e)))),
152        }
153    }
154
155    /// Clear this object store
156    ///
157    /// Internally, this uses [`IDBObjectStore::clear`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/clear).
158    pub fn clear(&self) -> impl Future<Output = crate::Result<(), Err>> {
159        match self.sys.clear() {
160            Ok(clear_req) => Either::Left(
161                transaction_request(clear_req).map(|res| res.map_err(map_clear_err).map(|_| ())),
162            ),
163            Err(err) => Either::Right(std::future::ready(Err(map_clear_err(err)))),
164        }
165    }
166
167    /// Count the number of objects in this store
168    ///
169    /// Internally, this uses [`IDBObjectStore::count`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/count).
170    pub fn count(&self) -> impl Future<Output = crate::Result<usize, Err>> {
171        match self.sys.count() {
172            Ok(count_req) => Either::Left(
173                transaction_request(count_req)
174                    .map(|res| res.map_err(map_count_err).map(map_count_res)),
175            ),
176            Err(e) => Either::Right(std::future::ready(Err(map_count_err(e)))),
177        }
178    }
179
180    /// Checks whether the provided key exists in this object store
181    ///
182    /// Internally, this uses [`IDBObjectStore::count`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/count).
183    pub fn contains(&self, key: &JsValue) -> impl Future<Output = crate::Result<bool, Err>> {
184        match self.sys.count_with_key(key) {
185            Ok(count_req) => Either::Left(
186                transaction_request(count_req)
187                    .map(|res| res.map_err(map_count_err).map(|n| map_count_res(n) != 0)),
188            ),
189            Err(e) => Either::Right(std::future::ready(Err(map_count_err(e)))),
190        }
191    }
192
193    /// Counts the number of objects with a key in `range`
194    ///
195    /// Note that the unbounded range is not a valid range for IndexedDB.
196    ///
197    /// Internally, this uses [`IDBObjectStore::count`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/count).
198    pub fn count_in(
199        &self,
200        range: impl RangeBounds<JsValue>,
201    ) -> impl Future<Output = crate::Result<usize, Err>> {
202        let range = match make_key_range(range) {
203            Ok(range) => range,
204            Err(e) => return Either::Left(std::future::ready(Err(e))),
205        };
206        match self.sys.count_with_key(&range) {
207            Ok(count_req) => Either::Right(
208                transaction_request(count_req)
209                    .map(|res| res.map_err(map_count_err).map(map_count_res)),
210            ),
211            Err(e) => Either::Left(std::future::ready(Err(map_count_err(e)))),
212        }
213    }
214
215    /// Delete the object with key `key`
216    ///
217    /// Unfortunately, the IndexedDb API does not indicate whether an object was actually deleted.
218    ///
219    /// Internally, this uses [`IDBObjectStore::delete`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/delete).
220    pub fn delete(&self, key: &JsValue) -> impl Future<Output = crate::Result<(), Err>> {
221        match self.sys.delete(key) {
222            Ok(delete_req) => Either::Left(
223                transaction_request(delete_req).map(|res| res.map_err(map_delete_err).map(|_| ())),
224            ),
225            Err(e) => Either::Right(std::future::ready(Err(map_delete_err(e)))),
226        }
227    }
228
229    /// Delete all the objects with a key in `range`
230    ///
231    /// Note that the unbounded range is not a valid range for IndexedDB.
232    /// Unfortunately, the IndexedDb API does not indicate whether an object was actually deleted.
233    ///
234    /// Internally, this uses [`IDBObjectStore::delete`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/delete).
235    pub fn delete_range(
236        &self,
237        range: impl RangeBounds<JsValue>,
238    ) -> impl Future<Output = crate::Result<(), Err>> {
239        let range = match make_key_range(range) {
240            Ok(range) => range,
241            Err(e) => return Either::Left(std::future::ready(Err(e))),
242        };
243        match self.sys.delete(&range) {
244            Ok(delete_req) => Either::Right(
245                transaction_request(delete_req).map(|res| res.map_err(map_delete_err).map(|_| ())),
246            ),
247            Err(e) => Either::Left(std::future::ready(Err(map_delete_err(e)))),
248        }
249    }
250
251    /// Get the object with key `key`
252    ///
253    /// Internally, this uses [`IDBObjectStore::get`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/get).
254    pub fn get(&self, key: &JsValue) -> impl Future<Output = crate::Result<Option<JsValue>, Err>> {
255        match self.sys.get(key) {
256            Ok(get_req) => Either::Right(
257                transaction_request(get_req)
258                    .map(|res| res.map_err(map_get_err).map(none_if_undefined)),
259            ),
260            Err(err) => Either::Left(std::future::ready(Err(map_get_err(err)))),
261        }
262    }
263
264    /// Get the first value with a key in `range`, ordered by key
265    ///
266    /// Note that the unbounded range is not a valid range for IndexedDB.
267    ///
268    /// Internally, this uses [`IDBObjectStore::get`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/get).
269    pub fn get_first_in(
270        &self,
271        range: impl RangeBounds<JsValue>,
272    ) -> impl Future<Output = crate::Result<Option<JsValue>, Err>> {
273        let range = match make_key_range(range) {
274            Ok(range) => range,
275            Err(e) => return Either::Left(std::future::ready(Err(e))),
276        };
277        match self.sys.get(&range) {
278            Ok(get_req) => Either::Right(
279                transaction_request(get_req)
280                    .map(|res| res.map_err(map_get_err).map(none_if_undefined)),
281            ),
282            Err(e) => Either::Left(std::future::ready(Err(map_get_err(e)))),
283        }
284    }
285
286    /// Get all the objects in the store, with a maximum number of results of `limit`
287    ///
288    /// Internally, this uses [`IDBObjectStore::getAll`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/getAll).
289    pub fn get_all(
290        &self,
291        limit: Option<u32>,
292    ) -> impl Future<Output = crate::Result<Vec<JsValue>, Err>> {
293        let get_req = match limit {
294            None => self.sys.get_all(),
295            Some(limit) => self
296                .sys
297                .get_all_with_key_and_limit(&JsValue::UNDEFINED, limit),
298        };
299        match get_req {
300            Ok(get_req) => Either::Right(
301                transaction_request(get_req).map(|res| res.map_err(map_get_err).map(array_to_vec)),
302            ),
303            Err(err) => Either::Left(std::future::ready(Err(map_get_err(err)))),
304        }
305    }
306
307    /// Get all the objects with a key in the provided range, with a maximum number of results of `limit`
308    ///
309    /// Internally, this uses [`IDBObjectStore::getAll`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/getAll).
310    pub fn get_all_in(
311        &self,
312        range: impl RangeBounds<JsValue>,
313        limit: Option<u32>,
314    ) -> impl Future<Output = crate::Result<Vec<JsValue>, Err>> {
315        let range = match make_key_range(range) {
316            Ok(range) => range,
317            Err(e) => return Either::Left(std::future::ready(Err(e))),
318        };
319        let get_req = match limit {
320            None => self.sys.get_all_with_key(&range),
321            Some(limit) => self.sys.get_all_with_key_and_limit(&range, limit),
322        };
323        match get_req {
324            Ok(get_req) => Either::Right(
325                transaction_request(get_req).map(|res| res.map_err(map_get_err).map(array_to_vec)),
326            ),
327            Err(err) => Either::Left(std::future::ready(Err(map_get_err(err)))),
328        }
329    }
330
331    /// Get the first existing key in the provided range
332    ///
333    /// Internally, this uses [`IDBObjectStore::getKey`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/getKey).
334    pub fn get_first_key_in(
335        &self,
336        range: impl RangeBounds<JsValue>,
337    ) -> impl Future<Output = crate::Result<Option<JsValue>, Err>> {
338        let range = match make_key_range(range) {
339            Ok(range) => range,
340            Err(e) => return Either::Left(std::future::ready(Err(e))),
341        };
342        match self.sys.get_key(&range) {
343            Ok(get_req) => Either::Right(
344                transaction_request(get_req)
345                    .map(|res| res.map_err(map_get_err).map(none_if_undefined)),
346            ),
347            Err(err) => Either::Left(std::future::ready(Err(map_get_err(err)))),
348        }
349    }
350
351    /// List all the keys in the object store, with a maximum number of results of `limit`
352    ///
353    /// Internally, this uses [`IDBObjectStore::getAllKeys`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/getAllKeys).
354    pub fn get_all_keys(
355        &self,
356        limit: Option<u32>,
357    ) -> impl Future<Output = crate::Result<Vec<JsValue>, Err>> {
358        let get_req = match limit {
359            None => self.sys.get_all_keys(),
360            Some(limit) => self
361                .sys
362                .get_all_keys_with_key_and_limit(&JsValue::UNDEFINED, limit),
363        };
364        match get_req {
365            Ok(get_req) => Either::Right(
366                transaction_request(get_req).map(|res| res.map_err(map_get_err).map(array_to_vec)),
367            ),
368            Err(err) => Either::Left(std::future::ready(Err(map_get_err(err)))),
369        }
370    }
371
372    /// List all the keys in the provided range, with a maximum number of results of `limit`
373    ///
374    /// Internally, this uses [`IDBObjectStore::getAllKeys`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/getAllKeys).
375    pub fn get_all_keys_in(
376        &self,
377        range: impl RangeBounds<JsValue>,
378        limit: Option<u32>,
379    ) -> impl Future<Output = crate::Result<Vec<JsValue>, Err>> {
380        let range = match make_key_range(range) {
381            Ok(range) => range,
382            Err(e) => return Either::Left(std::future::ready(Err(e))),
383        };
384        let get_req = match limit {
385            None => self.sys.get_all_keys_with_key(&range),
386            Some(limit) => self.sys.get_all_keys_with_key_and_limit(&range, limit),
387        };
388        match get_req {
389            Ok(get_req) => Either::Right(
390                transaction_request(get_req).map(|res| res.map_err(map_get_err).map(array_to_vec)),
391            ),
392            Err(err) => Either::Left(std::future::ready(Err(map_get_err(err)))),
393        }
394    }
395
396    /// Get the [`Index`] with the provided name
397    ///
398    /// Internally, this uses [`IDBObjectStore::index`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/index).
399    pub fn index(&self, name: &str) -> crate::Result<Index<Err>, Err> {
400        Ok(Index::from_sys(self.sys.index(name).map_err(
401            |err| match error_name!(&err) {
402                Some("InvalidStateError") => crate::Error::ObjectStoreWasRemoved,
403                Some("NotFoundError") => crate::Error::DoesNotExist,
404                _ => crate::Error::from_js_value(err),
405            },
406        )?))
407    }
408
409    /// Open a [`Cursor`] on this object store
410    pub fn cursor(&self) -> CursorBuilder<Err> {
411        CursorBuilder::from_store(self.sys.clone())
412    }
413}
414
415/// Helper to build indexes over an [`ObjectStore`]
416pub struct IndexBuilder<'a, Err> {
417    store: IdbObjectStore,
418    name: &'a str,
419    key_path: JsValue,
420    options: IdbIndexParameters,
421    _phantom: PhantomData<Err>,
422}
423
424impl<'a, Err> IndexBuilder<'a, Err> {
425    /// Create the index
426    ///
427    /// Internally, this uses [`IDBObjectStore::createIndex`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/createIndex).
428    pub fn create(self) -> crate::Result<(), Err> {
429        self.store
430            .create_index_with_str_sequence_and_optional_parameters(
431                self.name,
432                &self.key_path,
433                &self.options,
434            )
435            .map_err(|err| match error_name!(&err) {
436                Some("ConstraintError") => crate::Error::AlreadyExists,
437                Some("InvalidAccessError") => crate::Error::InvalidArgument,
438                Some("InvalidStateError") => crate::Error::ObjectStoreWasRemoved,
439                Some("SyntaxError") => crate::Error::InvalidKey,
440                _ => crate::Error::from_js_value(err),
441            })
442            .map(|_| ())
443    }
444
445    /// Mark this index as unique
446    ///
447    /// Internally, this sets [this property](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/createIndex#unique).
448    pub fn unique(self) -> Self {
449        self.options.set_unique(true);
450        self
451    }
452
453    /// Mark this index as multi-entry
454    ///
455    /// Internally, this sets [this property](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/createIndex#multientry).
456    pub fn multi_entry(self) -> Self {
457        self.options.set_multi_entry(true);
458        self
459    }
460}