indexed_db/
cursor.rs

1use crate::{
2    transaction::transaction_request,
3    utils::{
4        make_key_range, map_cursor_advance_err, map_cursor_advance_until_err,
5        map_cursor_advance_until_primary_key_err, map_cursor_delete_err, map_cursor_update_err,
6        map_open_cursor_err,
7    },
8};
9use futures_util::future::Either;
10use std::{future::Future, marker::PhantomData, ops::RangeBounds};
11use web_sys::{
12    wasm_bindgen::{JsCast, JsValue},
13    IdbCursor, IdbCursorDirection, IdbCursorWithValue, IdbIndex, IdbObjectStore, IdbRequest,
14};
15
16#[cfg(doc)]
17use crate::{Index, ObjectStore};
18#[cfg(doc)]
19use web_sys::js_sys::Array;
20
21/// The direction for a cursor
22pub enum CursorDirection {
23    /// Advance one by one
24    Next,
25
26    /// Advance, skipping duplicate elements
27    NextUnique,
28
29    /// Go back, one by one
30    Prev,
31
32    /// Go back, skipping duplicate elements
33    PrevUnique,
34}
35
36impl CursorDirection {
37    pub(crate) fn to_sys(&self) -> IdbCursorDirection {
38        match self {
39            CursorDirection::Next => IdbCursorDirection::Next,
40            CursorDirection::NextUnique => IdbCursorDirection::Nextunique,
41            CursorDirection::Prev => IdbCursorDirection::Prev,
42            CursorDirection::PrevUnique => IdbCursorDirection::Prevunique,
43        }
44    }
45}
46
47/// Helper to build cursors over [`ObjectStore`]s
48pub struct CursorBuilder<Err> {
49    source: Either<IdbObjectStore, IdbIndex>,
50    query: JsValue,
51    direction: IdbCursorDirection,
52    _phantom: PhantomData<Err>,
53}
54
55impl<Err> CursorBuilder<Err> {
56    pub(crate) fn from_store(store: IdbObjectStore) -> CursorBuilder<Err> {
57        CursorBuilder {
58            source: Either::Left(store),
59            query: JsValue::UNDEFINED,
60            direction: IdbCursorDirection::Next,
61            _phantom: PhantomData,
62        }
63    }
64
65    pub(crate) fn from_index(index: IdbIndex) -> CursorBuilder<Err> {
66        CursorBuilder {
67            source: Either::Right(index),
68            query: JsValue::UNDEFINED,
69            direction: IdbCursorDirection::Next,
70            _phantom: PhantomData,
71        }
72    }
73
74    /// Open the cursor
75    ///
76    /// Internally, this uses [`IDBObjectStore::openCursor`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/openCursor).
77    pub fn open(self) -> impl Future<Output = crate::Result<Cursor<Err>, Err>> {
78        let req = match self.source {
79            Either::Left(store) => {
80                store.open_cursor_with_range_and_direction(&self.query, self.direction)
81            }
82            Either::Right(index) => {
83                index.open_cursor_with_range_and_direction(&self.query, self.direction)
84            }
85        };
86        match req {
87            Ok(open_req) => Either::Right(Cursor::from(open_req)),
88            Err(err) => Either::Left(std::future::ready(Err(map_open_cursor_err(err)))),
89        }
90    }
91
92    /// Open the cursor as a key-only cursor
93    ///
94    /// Internally, this uses [`IDBObjectStore::openKeyCursor`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/openKeyCursor).
95    pub fn open_key(self) -> impl Future<Output = crate::Result<Cursor<Err>, Err>> {
96        let req = match self.source {
97            Either::Left(store) => {
98                store.open_key_cursor_with_range_and_direction(&self.query, self.direction)
99            }
100            Either::Right(index) => {
101                index.open_key_cursor_with_range_and_direction(&self.query, self.direction)
102            }
103        };
104        match req {
105            Ok(open_req) => Either::Right(Cursor::from(open_req)),
106            Err(err) => Either::Left(std::future::ready(Err(map_open_cursor_err(err)))),
107        }
108    }
109
110    /// Limit the range of the cursor
111    ///
112    /// Internally, this sets [this property](https://developer.mozilla.org/en-US/docs/Web/API/IDBIndex/openCursor#range).
113    pub fn range(mut self, range: impl RangeBounds<JsValue>) -> crate::Result<Self, Err> {
114        self.query = make_key_range(range)?;
115        Ok(self)
116    }
117
118    /// Define the direction of the cursor
119    ///
120    /// Internally, this sets [this property](https://developer.mozilla.org/en-US/docs/Web/API/IDBIndex/openCursor#direction).
121    pub fn direction(mut self, direction: CursorDirection) -> Self {
122        self.direction = direction.to_sys();
123        self
124    }
125}
126
127/// Wrapper for [`IDBCursorWithValue`](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursorWithValue)
128pub struct Cursor<Err> {
129    sys: Option<IdbCursor>,
130    req: IdbRequest,
131    _phantom: PhantomData<Err>,
132}
133
134impl<Err> Cursor<Err> {
135    pub(crate) async fn from(req: IdbRequest) -> crate::Result<Cursor<Err>, Err> {
136        let res = transaction_request(req.clone())
137            .await
138            .map_err(map_open_cursor_err)?;
139        let is_already_over = res.is_null();
140        let sys = (!is_already_over).then(|| {
141            res.dyn_into::<IdbCursor>()
142                .expect("Cursor-returning request did not return an IDBCursor")
143        });
144        Ok(Cursor {
145            sys,
146            req,
147            _phantom: PhantomData,
148        })
149    }
150
151    /// Retrieve the value this [`Cursor`] is currently pointing at, or `None` if the cursor is completed
152    ///
153    /// If this cursor was opened as a key-only cursor, then trying to call this method will panic.
154    ///
155    /// Internally, this uses the [`IDBCursorWithValue::value`](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursorWithValue/value) property.
156    pub fn value(&self) -> Option<JsValue> {
157        self.sys.as_ref().map(|sys| {
158            sys.dyn_ref::<IdbCursorWithValue>()
159                .expect("Called Cursor::value on a key-only cursor")
160                .value()
161                .expect("Unable to retrieve value from known-good cursor")
162        })
163    }
164
165    /// Retrieve the key this [`Cursor`] is currently pointing at, or `None` if the cursor is completed
166    ///
167    /// Internally, this uses the [`IDBCursor::key`](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/key) property.
168    pub fn key(&self) -> Option<JsValue> {
169        self.sys.as_ref().map(|sys| {
170            sys.key()
171                .expect("Failed retrieving key from known-good cursor")
172        })
173    }
174
175    /// Retrieve the primary key this [`Cursor`] is currently pointing at, or `None` if the cursor is completed
176    ///
177    /// Internally, this uses the [`IDBCursor::primaryKey`](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/key) property.
178    pub fn primary_key(&self) -> Option<JsValue> {
179        self.sys.as_ref().map(|sys| {
180            sys.primary_key()
181                .expect("Failed retrieving primary key from known-good cursor")
182        })
183    }
184
185    /// Advance this [`Cursor`] by `count` elements
186    ///
187    /// Internally, this uses [`IDBCursor::advance`](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/advance).
188    pub async fn advance(&mut self, count: u32) -> crate::Result<(), Err> {
189        let Some(sys) = &self.sys else {
190            return Err(crate::Error::CursorCompleted);
191        };
192        sys.advance(count).map_err(map_cursor_advance_err)?;
193        if transaction_request(self.req.clone())
194            .await
195            .map_err(map_cursor_advance_err)?
196            .is_null()
197        {
198            self.sys = None;
199        }
200        Ok(())
201    }
202
203    /// Advance this [`Cursor`] until the provided key
204    ///
205    /// Internally, this uses [`IDBCursor::continue`](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/continue).
206    pub async fn advance_until(&mut self, key: &JsValue) -> crate::Result<(), Err> {
207        let Some(sys) = &self.sys else {
208            return Err(crate::Error::CursorCompleted);
209        };
210        sys.continue_with_key(key)
211            .map_err(map_cursor_advance_until_err)?;
212        if transaction_request(self.req.clone())
213            .await
214            .map_err(map_cursor_advance_until_err)?
215            .is_null()
216        {
217            self.sys = None;
218        }
219        Ok(())
220    }
221
222    /// Advance this [`Cursor`] until the provided primary key
223    ///
224    /// This is a helper function for cursors built on top of [`Index`]es. It allows for
225    /// quick resumption of index walking, faster than [`Cursor::advance_until`] if the
226    /// primary key for the wanted element is known.
227    ///
228    /// Note that this method does not work on cursors over object stores, nor on cursors
229    /// which are set with a direction of anything other than `Next` or `Prev`.
230    ///
231    /// Internally, this uses [`IDBCursor::continuePrimaryKey`](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/continuePrimaryKey).
232    pub async fn advance_until_primary_key(
233        &mut self,
234        index_key: &JsValue,
235        primary_key: &JsValue,
236    ) -> crate::Result<(), Err> {
237        let Some(sys) = &self.sys else {
238            return Err(crate::Error::CursorCompleted);
239        };
240        sys.continue_primary_key(&index_key, primary_key)
241            .map_err(map_cursor_advance_until_primary_key_err)?;
242        if transaction_request(self.req.clone())
243            .await
244            .map_err(map_cursor_advance_until_primary_key_err)?
245            .is_null()
246        {
247            self.sys = None;
248        }
249        Ok(())
250    }
251
252    /// Deletes the value currently pointed by this [`Cursor`]
253    ///
254    /// Note that this method does not work on key-only cursors over indexes.
255    ///
256    /// Internally, this uses [`IDBCursor::delete`](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/delete).
257    pub async fn delete(&self) -> crate::Result<(), Err> {
258        let Some(sys) = &self.sys else {
259            return Err(crate::Error::CursorCompleted);
260        };
261        let req = sys.delete().map_err(map_cursor_delete_err)?;
262        transaction_request(req)
263            .await
264            .map_err(map_cursor_delete_err)?;
265        Ok(())
266    }
267
268    /// Update the value currently pointed by this [`Cursor`] to `value`
269    ///
270    /// Note that this method does not work on key-only cursors over indexes.
271    ///
272    /// Internally, this uses [`IDBCursor::update`](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/update).
273    pub async fn update(&self, value: &JsValue) -> crate::Result<(), Err> {
274        let Some(sys) = &self.sys else {
275            return Err(crate::Error::CursorCompleted);
276        };
277        let req = sys.update(value).map_err(map_cursor_update_err)?;
278        transaction_request(req)
279            .await
280            .map_err(map_cursor_update_err)?;
281        Ok(())
282    }
283}