Skip to main content

gestalt/
indexeddb_provider.rs

1use std::cmp::Ordering;
2use std::collections::BTreeMap;
3
4use hyper_util::rt::TokioIo;
5use tokio::sync::mpsc;
6use tokio_stream::wrappers::ReceiverStream;
7use tonic::Request;
8use tonic::codegen::async_trait;
9use tonic::metadata::MetadataValue;
10use tonic::service::Interceptor;
11use tonic::service::interceptor::InterceptedService;
12use tonic::transport::{Channel, ClientTlsConfig, Endpoint, Uri};
13use tower::service_fn;
14
15use crate::env::{
16    ENV_HOST_SERVICE_SOCKET, ENV_HOST_SERVICE_TOKEN, HOST_SERVICE_BINDING_HEADER,
17    host_service_configured,
18};
19use crate::generated::v1::{self as pb, indexed_db_client::IndexedDbClient};
20
21type IndexedDbTransport = InterceptedService<Channel, RelayTokenInterceptor>;
22
23const INDEXEDDB_RELAY_TOKEN_HEADER: &str = "x-gestalt-host-service-relay-token";
24const CURSOR_CHANNEL_BUFFER: usize = 1;
25const TRANSACTION_CHANNEL_BUFFER: usize = 1;
26
27#[derive(Debug, thiserror::Error)]
28/// Errors returned by the IndexedDB transport client.
29pub enum IndexedDBError {
30    /// The requested record, object store, index, or cursor entry was missing.
31    #[error("not found")]
32    NotFound,
33    /// A create operation conflicted with an existing value.
34    #[error("already exists")]
35    AlreadyExists,
36    /// A cursor was opened in key-only mode and a value was requested.
37    #[error("cursor is keys-only; value not available")]
38    KeysOnly,
39    /// A provider-side helper received invalid input.
40    #[error("{0}")]
41    InvalidArgument(String),
42    /// An explicit transaction failed or was already closed.
43    #[error("{0}")]
44    Transaction(String),
45    /// The host-service transport could not be created.
46    #[error("{0}")]
47    Transport(#[from] tonic::transport::Error),
48    /// The host-service RPC returned a gRPC status.
49    #[error("{0}")]
50    Status(#[from] tonic::Status),
51    /// Required environment or target configuration was invalid.
52    #[error("{0}")]
53    Env(String),
54}
55
56/// JSON-like value stored in an object store row.
57pub type Record = BTreeMap<String, serde_json::Value>;
58
59/// One IndexedDB key: number, date, string, binary, or array of keys.
60#[derive(Debug, Clone, PartialEq)]
61pub enum Key {
62    /// Integer number key.
63    Int(i64),
64    /// Floating-point number key.
65    Float(f64),
66    /// String key.
67    Str(String),
68    /// Date key.
69    Date(std::time::SystemTime),
70    /// Binary key.
71    Bytes(Vec<u8>),
72    /// Composite array key.
73    Array(Vec<Key>),
74}
75
76impl From<&str> for Key {
77    fn from(value: &str) -> Self {
78        Self::Str(value.to_string())
79    }
80}
81
82impl From<String> for Key {
83    fn from(value: String) -> Self {
84        Self::Str(value)
85    }
86}
87
88impl From<i64> for Key {
89    fn from(value: i64) -> Self {
90        Self::Int(value)
91    }
92}
93
94impl From<i32> for Key {
95    fn from(value: i32) -> Self {
96        Self::Int(i64::from(value))
97    }
98}
99
100impl From<u64> for Key {
101    fn from(value: u64) -> Self {
102        Self::Int(value as i64)
103    }
104}
105
106impl From<f64> for Key {
107    fn from(value: f64) -> Self {
108        Self::Float(value)
109    }
110}
111
112impl From<bool> for Key {
113    fn from(value: bool) -> Self {
114        Self::Int(i64::from(value))
115    }
116}
117
118impl From<std::time::SystemTime> for Key {
119    fn from(value: std::time::SystemTime) -> Self {
120        Self::Date(value)
121    }
122}
123
124impl From<Vec<u8>> for Key {
125    fn from(value: Vec<u8>) -> Self {
126        Self::Bytes(value)
127    }
128}
129
130impl From<&[u8]> for Key {
131    fn from(value: &[u8]) -> Self {
132        Self::Bytes(value.to_vec())
133    }
134}
135
136impl<T: Into<Key>> From<Vec<T>> for Key {
137    fn from(values: Vec<T>) -> Self {
138        if values.len() == 1 {
139            values.into_iter().next().expect("one value").into()
140        } else {
141            Self::Array(values.into_iter().map(Into::into).collect())
142        }
143    }
144}
145
146/// Constrains a query or cursor by lower and upper bounds.
147#[derive(Debug, Clone, PartialEq)]
148pub struct KeyRange {
149    /// Lower bound, inclusive unless `lower_open` is true.
150    pub lower: Option<Key>,
151    /// Upper bound, inclusive unless `upper_open` is true.
152    pub upper: Option<Key>,
153    /// Whether the lower bound is exclusive.
154    pub lower_open: bool,
155    /// Whether the upper bound is exclusive.
156    pub upper_open: bool,
157}
158
159impl KeyRange {
160    /// Returns a range containing only `key`.
161    pub fn only(key: impl Into<Key>) -> Self {
162        let key = key.into();
163        Self {
164            lower: Some(key.clone()),
165            upper: Some(key),
166            lower_open: false,
167            upper_open: false,
168        }
169    }
170
171    /// Returns a range between optional lower and upper bounds.
172    pub fn bound(
173        lower: impl Into<Key>,
174        upper: impl Into<Key>,
175        lower_open: bool,
176        upper_open: bool,
177    ) -> Self {
178        Self {
179            lower: Some(lower.into()),
180            upper: Some(upper.into()),
181            lower_open,
182            upper_open,
183        }
184    }
185
186    /// Returns a range with only a lower bound.
187    pub fn lower_bound(key: impl Into<Key>, open: bool) -> Self {
188        Self {
189            lower: Some(key.into()),
190            upper: None,
191            lower_open: open,
192            upper_open: false,
193        }
194    }
195
196    /// Returns a range with only an upper bound.
197    pub fn upper_bound(key: impl Into<Key>, open: bool) -> Self {
198        Self {
199            lower: None,
200            upper: Some(key.into()),
201            lower_open: false,
202            upper_open: open,
203        }
204    }
205}
206
207/// Describes one IndexedDB query: all records, one exact key, or a key range.
208#[derive(Debug, Clone, PartialEq)]
209pub enum Query {
210    /// Every record in scope (absent wire query).
211    All,
212    /// One exact key (scalar or composite array).
213    Key(Key),
214    /// Records whose keys fall within bounds.
215    Range(KeyRange),
216}
217
218impl Query {
219    /// Query that matches every record in scope.
220    pub fn all() -> Self {
221        Self::All
222    }
223
224    fn to_proto(&self) -> Option<pb::IndexedDbQuery> {
225        use crate::indexeddb_query_codec::{QueryKind, query_to_proto};
226        query_to_proto(match self {
227            Self::All => QueryKind::All,
228            Self::Key(key) => QueryKind::Key(key),
229            Self::Range(range) => QueryKind::Range {
230                lower: range.lower.as_ref(),
231                upper: range.upper.as_ref(),
232                lower_open: range.lower_open,
233                upper_open: range.upper_open,
234            },
235        })
236    }
237}
238
239impl From<KeyRange> for Query {
240    fn from(range: KeyRange) -> Self {
241        Self::Range(range)
242    }
243}
244
245impl<T: Into<Key>> From<T> for Query {
246    fn from(value: T) -> Self {
247        Self::Key(value.into())
248    }
249}
250
251impl From<Option<KeyRange>> for Query {
252    fn from(range: Option<KeyRange>) -> Self {
253        match range {
254            None => Self::All,
255            Some(range) => Self::Range(range),
256        }
257    }
258}
259
260/// Describes one secondary index on an object store.
261#[derive(Debug, Clone, PartialEq)]
262pub struct IndexSchema {
263    /// Index name.
264    pub name: String,
265    /// Record path used as the index key.
266    pub key_path: Vec<String>,
267    /// Whether the index enforces uniqueness.
268    pub unique: bool,
269}
270
271/// Describes the indexes attached to an object store.
272#[derive(Debug, Clone, PartialEq)]
273pub struct ObjectStoreSchema {
274    /// Secondary indexes to create with the object store.
275    pub indexes: Vec<IndexSchema>,
276}
277
278#[derive(Debug, Clone, Copy, PartialEq, Eq)]
279/// Controls cursor traversal order.
280pub enum CursorDirection {
281    /// Iterate in ascending key order.
282    Next,
283    /// Iterate in ascending key order while collapsing duplicate index keys.
284    NextUnique,
285    /// Iterate in descending key order.
286    Prev,
287    /// Iterate in descending key order while collapsing duplicate index keys.
288    PrevUnique,
289}
290
291impl CursorDirection {
292    fn to_proto(self) -> i32 {
293        match self {
294            Self::Next => pb::CursorDirection::CursorNext as i32,
295            Self::NextUnique => pb::CursorDirection::CursorNextUnique as i32,
296            Self::Prev => pb::CursorDirection::CursorPrev as i32,
297            Self::PrevUnique => pb::CursorDirection::CursorPrevUnique as i32,
298        }
299    }
300}
301
302#[derive(Debug, Clone, Copy, PartialEq, Eq)]
303/// Controls whether an explicit transaction may mutate scoped stores.
304pub enum TransactionMode {
305    /// Transaction may only read from scoped object stores.
306    Readonly,
307    /// Transaction may read and write scoped object stores.
308    Readwrite,
309}
310
311impl TransactionMode {
312    fn to_proto(self) -> i32 {
313        match self {
314            Self::Readonly => pb::TransactionMode::TransactionReadonly as i32,
315            Self::Readwrite => pb::TransactionMode::TransactionReadwrite as i32,
316        }
317    }
318}
319
320#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
321/// Provider durability hint for explicit transactions.
322pub enum TransactionDurabilityHint {
323    /// Let the host choose its default durability behavior.
324    #[default]
325    Default,
326    /// Prefer stricter durability.
327    Strict,
328    /// Prefer relaxed durability.
329    Relaxed,
330}
331
332impl TransactionDurabilityHint {
333    fn to_proto(self) -> i32 {
334        match self {
335            Self::Default => pb::TransactionDurabilityHint::TransactionDurabilityDefault as i32,
336            Self::Strict => pb::TransactionDurabilityHint::TransactionDurabilityStrict as i32,
337            Self::Relaxed => pb::TransactionDurabilityHint::TransactionDurabilityRelaxed as i32,
338        }
339    }
340}
341
342#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
343/// Options for an explicit transaction.
344pub struct TransactionOptions {
345    /// Durability hint for explicit transactions.
346    pub durability_hint: TransactionDurabilityHint,
347}
348
349#[async_trait]
350/// Fakeable client contract for IndexedDB-compatible storage.
351pub trait IndexedDBApi: Send {
352    /// The store handle this scope yields.
353    type ObjectStore: ObjectStoreApi;
354    /// The transaction handle this database yields.
355    type Transaction: TransactionApi;
356
357    /// Creates a named object store and returns a typed handle for it.
358    async fn create_object_store(
359        &mut self,
360        name: &str,
361        schema: ObjectStoreSchema,
362    ) -> Result<Self::ObjectStore, IndexedDBError>;
363
364    /// Deletes a named object store.
365    async fn delete_object_store(&mut self, name: &str) -> Result<(), IndexedDBError>;
366
367    /// Returns a typed handle for one object store.
368    fn object_store(&self, name: &str) -> Self::ObjectStore;
369
370    /// Opens an explicit transaction over a fixed object-store scope.
371    async fn transaction(
372        &self,
373        stores: &[&str],
374        mode: TransactionMode,
375        options: TransactionOptions,
376    ) -> Result<Self::Transaction, IndexedDBError>;
377}
378
379#[async_trait]
380/// Fakeable IndexedDB object-store contract.
381pub trait ObjectStoreApi: Send {
382    /// The index handle this store yields.
383    type Index: IndexApi;
384    /// The cursor handle this scope yields.
385    type Cursor: CursorApi;
386
387    /// Loads one record by primary key.
388    async fn get(&mut self, id: &str) -> Result<Record, IndexedDBError>;
389
390    /// Resolves the primary key for id.
391    async fn get_key(&mut self, id: &str) -> Result<String, IndexedDBError>;
392
393    /// Inserts a new row and fails if the key already exists.
394    async fn add(&mut self, record: Record) -> Result<(), IndexedDBError>;
395
396    /// Upserts a row by primary key.
397    async fn put(&mut self, record: Record) -> Result<(), IndexedDBError>;
398
399    /// Deletes one row by primary key.
400    async fn delete(&mut self, id: &str) -> Result<(), IndexedDBError>;
401
402    /// Deletes every row in the object store.
403    async fn clear(&mut self) -> Result<(), IndexedDBError>;
404
405    /// Loads every row that matches query.
406    async fn get_all(&mut self, query: Query) -> Result<Vec<Record>, IndexedDBError>;
407
408    /// Loads every primary key that matches query.
409    async fn get_all_keys(&mut self, query: Query) -> Result<Vec<String>, IndexedDBError>;
410
411    /// Counts rows that match query.
412    async fn count(&mut self, query: Query) -> Result<i64, IndexedDBError>;
413
414    /// Deletes rows that match query and returns the delete count.
415    async fn delete_range(&mut self, query: Query) -> Result<i64, IndexedDBError>;
416
417    /// Returns a typed handle for one secondary index.
418    fn index(&self, name: &str) -> Self::Index;
419
420    /// Opens a full-value cursor over the object store.
421    async fn open_cursor(
422        &mut self,
423        query: Query,
424        direction: CursorDirection,
425    ) -> Result<Self::Cursor, IndexedDBError>;
426
427    /// Opens a key-only cursor over the object store.
428    async fn open_key_cursor(
429        &mut self,
430        query: Query,
431        direction: CursorDirection,
432    ) -> Result<Self::Cursor, IndexedDBError>;
433}
434
435#[async_trait]
436/// Fakeable IndexedDB secondary-index contract.
437pub trait IndexApi: Send {
438    /// The cursor handle this scope yields.
439    type Cursor: CursorApi;
440
441    /// Loads the first row that matches query.
442    async fn get(&mut self, query: Query) -> Result<Record, IndexedDBError>;
443
444    /// Resolves the primary key for the first row that matches query.
445    async fn get_key(&mut self, query: Query) -> Result<String, IndexedDBError>;
446
447    /// Loads every row that matches query.
448    async fn get_all(&mut self, query: Query) -> Result<Vec<Record>, IndexedDBError>;
449
450    /// Loads every primary key that matches query.
451    async fn get_all_keys(&mut self, query: Query) -> Result<Vec<String>, IndexedDBError>;
452
453    /// Counts rows that match query.
454    async fn count(&mut self, query: Query) -> Result<i64, IndexedDBError>;
455
456    /// Deletes rows that match query and returns the delete count.
457    async fn delete(&mut self, query: Query) -> Result<i64, IndexedDBError>;
458
459    /// Opens a full-value cursor over the secondary index.
460    async fn open_cursor(
461        &mut self,
462        query: Query,
463        direction: CursorDirection,
464    ) -> Result<Self::Cursor, IndexedDBError>;
465
466    /// Opens a key-only cursor over the secondary index.
467    async fn open_key_cursor(
468        &mut self,
469        query: Query,
470        direction: CursorDirection,
471    ) -> Result<Self::Cursor, IndexedDBError>;
472}
473
474#[async_trait]
475/// Fakeable explicit IndexedDB transaction contract.
476pub trait TransactionApi: Send {
477    /// The transaction-scoped store handle.
478    type ObjectStore<'a>: TransactionObjectStoreApi + 'a
479    where
480        Self: 'a;
481
482    /// Returns a transaction-scoped object store.
483    fn object_store<'a>(&'a mut self, name: &str) -> Self::ObjectStore<'a>;
484
485    /// Commits the transaction.
486    async fn commit(&mut self) -> Result<(), IndexedDBError>;
487
488    /// Aborts the transaction.
489    async fn abort(&mut self, reason: &str) -> Result<(), IndexedDBError>;
490}
491
492#[async_trait]
493/// Fakeable transaction-scoped object-store contract.
494pub trait TransactionObjectStoreApi: Send {
495    /// The transaction-scoped index handle.
496    type Index<'a>: TransactionIndexApi + 'a
497    where
498        Self: 'a;
499
500    /// Loads one record by primary key inside the transaction.
501    async fn get(&mut self, id: &str) -> Result<Record, IndexedDBError>;
502
503    /// Resolves the primary key for id inside the transaction.
504    async fn get_key(&mut self, id: &str) -> Result<String, IndexedDBError>;
505
506    /// Inserts a new row inside the transaction.
507    async fn add(&mut self, record: Record) -> Result<(), IndexedDBError>;
508
509    /// Upserts a row inside the transaction.
510    async fn put(&mut self, record: Record) -> Result<(), IndexedDBError>;
511
512    /// Deletes one row inside the transaction.
513    async fn delete(&mut self, id: &str) -> Result<(), IndexedDBError>;
514
515    /// Deletes every row in the object store inside the transaction.
516    async fn clear(&mut self) -> Result<(), IndexedDBError>;
517
518    /// Loads every row that matches query inside the transaction.
519    async fn get_all(&mut self, query: Query) -> Result<Vec<Record>, IndexedDBError>;
520
521    /// Loads every primary key that matches query inside the transaction.
522    async fn get_all_keys(&mut self, query: Query) -> Result<Vec<String>, IndexedDBError>;
523
524    /// Counts rows that match query inside the transaction.
525    async fn count(&mut self, query: Query) -> Result<i64, IndexedDBError>;
526
527    /// Deletes rows that match query inside the transaction.
528    async fn delete_range(&mut self, query: Query) -> Result<i64, IndexedDBError>;
529
530    /// Returns a transaction-scoped secondary index.
531    fn index<'a>(&'a mut self, name: &str) -> Self::Index<'a>;
532}
533
534#[async_trait]
535/// Fakeable transaction-scoped secondary-index contract.
536pub trait TransactionIndexApi: Send {
537    /// Loads the first row that matches query inside the transaction.
538    async fn get(&mut self, query: Query) -> Result<Record, IndexedDBError>;
539
540    /// Resolves the primary key for the first matching row inside the transaction.
541    async fn get_key(&mut self, query: Query) -> Result<String, IndexedDBError>;
542
543    /// Loads every row that matches query inside the transaction.
544    async fn get_all(&mut self, query: Query) -> Result<Vec<Record>, IndexedDBError>;
545
546    /// Loads every primary key that matches query inside the transaction.
547    async fn get_all_keys(&mut self, query: Query) -> Result<Vec<String>, IndexedDBError>;
548
549    /// Counts rows that match query inside the transaction.
550    async fn count(&mut self, query: Query) -> Result<i64, IndexedDBError>;
551
552    /// Deletes rows that match query inside the transaction.
553    async fn delete(&mut self, query: Query) -> Result<i64, IndexedDBError>;
554}
555
556#[async_trait]
557/// Fakeable IndexedDB cursor contract.
558pub trait CursorApi: Send {
559    /// Returns the current cursor key.
560    fn key(&self) -> Option<Key>;
561
562    /// Returns the current row's primary key.
563    fn primary_key(&self) -> &str;
564
565    /// Returns the current row value.
566    fn value(&self) -> Result<Record, IndexedDBError>;
567
568    /// Advances the cursor by one row.
569    async fn continue_next(&mut self) -> Result<bool, IndexedDBError>;
570
571    /// Advances the cursor to key, or exhausts it if key does not exist.
572    async fn continue_to_key(&mut self, key: impl Into<Key> + Send)
573    -> Result<bool, IndexedDBError>;
574
575    /// Skips count rows ahead.
576    async fn advance(&mut self, count: i32) -> Result<bool, IndexedDBError>;
577
578    /// Deletes the current row and keeps the cursor open.
579    async fn delete(&mut self) -> Result<(), IndexedDBError>;
580
581    /// Replaces the current row and keeps the cursor open.
582    async fn update(&mut self, value: Record) -> Result<(), IndexedDBError>;
583
584    /// Closes the cursor stream and releases its transport resources.
585    async fn close(self) -> Result<(), IndexedDBError>
586    where
587        Self: Sized;
588}
589
590/// Native open-cursor request used by provider-side cursor helpers.
591#[derive(Debug, Clone)]
592pub struct IndexedDBOpenCursorRequest {
593    /// Object store to open.
594    pub store: String,
595    /// Optional key range to apply.
596    pub range: Option<KeyRange>,
597    /// Cursor traversal direction.
598    pub direction: CursorDirection,
599    /// Whether returned cursor entries omit records.
600    pub keys_only: bool,
601    /// Secondary index name. Empty means object-store cursor.
602    pub index: String,
603    /// Index values supplied by an index query.
604    pub values: Vec<serde_json::Value>,
605}
606
607impl Default for IndexedDBOpenCursorRequest {
608    fn default() -> Self {
609        Self {
610            store: String::new(),
611            range: None,
612            direction: CursorDirection::Next,
613            keys_only: false,
614            index: String::new(),
615            values: Vec::new(),
616        }
617    }
618}
619
620/// One provider-side cursor row.
621#[derive(Debug, Clone, PartialEq)]
622pub struct IndexedDBCursorSnapshotEntry {
623    /// Object-store key, or secondary-index key for index cursors.
624    pub key: Key,
625    /// Canonical primary key for the object-store row.
626    pub primary_key: String,
627    /// Native primary-key value used as a stable tie-breaker for duplicate index keys.
628    pub primary_key_value: Key,
629    /// Row value returned by full-value cursors.
630    pub record: Record,
631}
632
633/// Provider-side IndexedDB cursor snapshot.
634///
635/// The snapshot sorts rows, applies IndexedDB range bounds, and implements
636/// movement semantics for native Rust providers without exposing wire message
637/// types.
638#[derive(Debug, Clone)]
639pub struct IndexedDBCursorSnapshot {
640    /// Whether entry keys contain secondary-index values.
641    pub index_cursor: bool,
642    /// Whether returned cursor entries should omit records.
643    pub keys_only: bool,
644    /// Whether entries are ordered from greatest to least key.
645    pub reverse: bool,
646    /// Whether duplicate index keys are collapsed while iterating.
647    pub unique: bool,
648    /// Sorted and range-filtered entries used by cursor movement.
649    pub entries: Vec<IndexedDBCursorSnapshotEntry>,
650    /// Current cursor position, or -1 when unpositioned.
651    pub pos: isize,
652}
653
654impl IndexedDBCursorSnapshot {
655    /// Creates an empty provider-side cursor snapshot from a native request.
656    pub fn new(req: &IndexedDBOpenCursorRequest) -> Self {
657        Self {
658            index_cursor: !req.index.is_empty(),
659            keys_only: req.keys_only,
660            reverse: matches!(
661                req.direction,
662                CursorDirection::Prev | CursorDirection::PrevUnique
663            ),
664            unique: matches!(
665                req.direction,
666                CursorDirection::NextUnique | CursorDirection::PrevUnique
667            ),
668            entries: Vec::new(),
669            pos: -1,
670        }
671    }
672
673    /// Sorts entries, applies the supplied key range, and stores the snapshot.
674    pub fn load(
675        &mut self,
676        mut entries: Vec<IndexedDBCursorSnapshotEntry>,
677        range: Option<&KeyRange>,
678    ) -> Result<(), IndexedDBError> {
679        entries.sort_by(|left, right| {
680            let mut cmp = compare_indexeddb_values(&left.key, &right.key);
681            if cmp == Ordering::Equal {
682                cmp = compare_indexeddb_values(&left.primary_key_value, &right.primary_key_value);
683            }
684            if self.reverse { cmp.reverse() } else { cmp }
685        });
686        self.entries = self.apply_range(entries, range)?;
687        self.pos = -1;
688        Ok(())
689    }
690
691    /// Returns entries that satisfy the supplied key range without mutating state.
692    pub fn apply_range(
693        &self,
694        entries: Vec<IndexedDBCursorSnapshotEntry>,
695        range: Option<&KeyRange>,
696    ) -> Result<Vec<IndexedDBCursorSnapshotEntry>, IndexedDBError> {
697        let Some(range) = range else {
698            return Ok(entries);
699        };
700        let (lower, upper) = indexeddb_range_bounds(Some(range), self.index_cursor);
701        let mut filtered = Vec::with_capacity(entries.len());
702        for entry in entries {
703            let key = normalize_indexeddb_bound(&entry.key, self.index_cursor);
704            if let Some(lower) = &lower {
705                let cmp = compare_indexeddb_values(&key, lower);
706                if range.lower_open && cmp != Ordering::Greater {
707                    continue;
708                }
709                if !range.lower_open && cmp == Ordering::Less {
710                    continue;
711                }
712            }
713            if let Some(upper) = &upper {
714                let cmp = compare_indexeddb_values(&key, upper);
715                if range.upper_open && cmp != Ordering::Less {
716                    continue;
717                }
718                if !range.upper_open && cmp == Ordering::Greater {
719                    continue;
720                }
721            }
722            filtered.push(entry);
723        }
724        Ok(filtered)
725    }
726
727    /// Advances to the next entry, or returns `None` when exhausted.
728    #[allow(clippy::should_implement_trait)]
729    pub fn next(&mut self) -> Result<Option<&IndexedDBCursorSnapshotEntry>, IndexedDBError> {
730        if self.unique
731            && self.index_cursor
732            && self.pos >= 0
733            && (self.pos as usize) < self.entries.len()
734        {
735            let previous = self.entries[self.pos as usize].key.clone();
736            self.pos += 1;
737            while (self.pos as usize) < self.entries.len() {
738                if compare_indexeddb_values(&self.entries[self.pos as usize].key, &previous)
739                    != Ordering::Equal
740                {
741                    return Ok(Some(self.current()?));
742                }
743                self.pos += 1;
744            }
745            return Ok(None);
746        }
747
748        self.pos += 1;
749        if (self.pos as usize) >= self.entries.len() {
750            return Ok(None);
751        }
752        Ok(Some(self.current()?))
753    }
754
755    /// Advances to `target` or the next entry past it for this direction.
756    pub fn continue_to_key(
757        &mut self,
758        target: &Key,
759    ) -> Result<Option<&IndexedDBCursorSnapshotEntry>, IndexedDBError> {
760        let previous = if self.unique
761            && self.index_cursor
762            && self.pos >= 0
763            && (self.pos as usize) < self.entries.len()
764        {
765            Some(self.entries[self.pos as usize].key.clone())
766        } else {
767            None
768        };
769        self.pos += 1;
770        while (self.pos as usize) < self.entries.len() {
771            let current = &self.entries[self.pos as usize].key;
772            if let Some(previous) = &previous {
773                if self.unique
774                    && self.index_cursor
775                    && compare_indexeddb_values(current, previous) == Ordering::Equal
776                {
777                    self.pos += 1;
778                    continue;
779                }
780            }
781            let cmp = compare_indexeddb_values(current, target);
782            if self.reverse {
783                if cmp != Ordering::Greater {
784                    return Ok(Some(self.current()?));
785                }
786            } else if cmp != Ordering::Less {
787                return Ok(Some(self.current()?));
788            }
789            self.pos += 1;
790        }
791        Ok(None)
792    }
793
794    /// Skips `count` entries and returns the new current entry.
795    pub fn advance(
796        &mut self,
797        count: i32,
798    ) -> Result<Option<&IndexedDBCursorSnapshotEntry>, IndexedDBError> {
799        if count <= 0 {
800            return Err(IndexedDBError::InvalidArgument(
801                "advance count must be positive".to_string(),
802            ));
803        }
804        for i in 0..count {
805            if self.next()?.is_none() {
806                return Ok(None);
807            }
808            if i == count - 1 {
809                return Ok(Some(self.current()?));
810            }
811        }
812        Ok(None)
813    }
814
815    /// Returns the currently positioned entry.
816    pub fn current(&self) -> Result<&IndexedDBCursorSnapshotEntry, IndexedDBError> {
817        if self.pos < 0 || (self.pos as usize) >= self.entries.len() {
818            return Err(IndexedDBError::NotFound);
819        }
820        Ok(&self.entries[self.pos as usize])
821    }
822}
823
824/// Creates an empty provider-side cursor snapshot from a native request.
825pub fn new_indexeddb_cursor_snapshot(req: &IndexedDBOpenCursorRequest) -> IndexedDBCursorSnapshot {
826    IndexedDBCursorSnapshot::new(req)
827}
828
829/// Normalizes object-store or index cursor range bounds.
830///
831/// Scalar index bounds are compared as one-part composite keys so providers can
832/// share the same comparison path for scalar and compound indexes.
833pub fn indexeddb_range_bounds(
834    range: Option<&KeyRange>,
835    index_cursor: bool,
836) -> (Option<Key>, Option<Key>) {
837    let Some(range) = range else {
838        return (None, None);
839    };
840    let lower = range
841        .lower
842        .as_ref()
843        .map(|value| normalize_indexeddb_bound(value, index_cursor));
844    let upper = range
845        .upper
846        .as_ref()
847        .map(|value| normalize_indexeddb_bound(value, index_cursor));
848    (lower, upper)
849}
850
851/// Compares native IndexedDB key values.
852pub fn compare_indexeddb_values(left: &Key, right: &Key) -> Ordering {
853    compare_keys(left, right)
854}
855
856#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
857enum KeyKind {
858    Number,
859    Date,
860    String,
861    Binary,
862    Array,
863}
864
865fn key_kind(key: &Key) -> KeyKind {
866    match key {
867        Key::Int(_) | Key::Float(_) => KeyKind::Number,
868        Key::Date(_) => KeyKind::Date,
869        Key::Str(_) => KeyKind::String,
870        Key::Bytes(_) => KeyKind::Binary,
871        Key::Array(_) => KeyKind::Array,
872    }
873}
874
875/// Compares native IndexedDB key values using W3C ordering.
876pub fn compare_keys(left: &Key, right: &Key) -> Ordering {
877    let left_kind = key_kind(left);
878    let right_kind = key_kind(right);
879    if left_kind != right_kind {
880        return left_kind.cmp(&right_kind);
881    }
882    match (left, right) {
883        (Key::Int(left), Key::Int(right)) => left.cmp(right),
884        (Key::Float(left), Key::Float(right)) => compare_float_keys(*left, *right),
885        (Key::Int(left), Key::Float(right)) => compare_int_float_keys(*left, *right),
886        (Key::Float(left), Key::Int(right)) => compare_int_float_keys(*right, *left).reverse(),
887        (Key::Date(left), Key::Date(right)) => left.cmp(right),
888        (Key::Str(left), Key::Str(right)) => compare_utf16_strings(left, right),
889        (Key::Bytes(left), Key::Bytes(right)) => left.cmp(right),
890        (Key::Array(left), Key::Array(right)) => {
891            for (left_value, right_value) in left.iter().zip(right.iter()) {
892                let cmp = compare_keys(left_value, right_value);
893                if cmp != Ordering::Equal {
894                    return cmp;
895                }
896            }
897            left.len().cmp(&right.len())
898        }
899        _ => Ordering::Equal,
900    }
901}
902
903/// Reports whether `key` satisfies `range`. A range with no bounds matches every key.
904pub fn key_in_range(key: &Key, range: &KeyRange) -> bool {
905    if let Some(lower) = &range.lower {
906        let cmp = compare_keys(key, lower);
907        if range.lower_open {
908            if cmp != Ordering::Greater {
909                return false;
910            }
911        } else if cmp == Ordering::Less {
912            return false;
913        }
914    }
915    if let Some(upper) = &range.upper {
916        let cmp = compare_keys(key, upper);
917        if range.upper_open {
918            if cmp != Ordering::Less {
919                return false;
920            }
921        } else if cmp == Ordering::Greater {
922            return false;
923        }
924    }
925    true
926}
927
928/// Reports whether `key` satisfies `query`.
929pub fn match_query(key: &Key, query: &Query) -> bool {
930    match query {
931        Query::All => true,
932        Query::Key(target) => compare_keys(key, target) == Ordering::Equal,
933        Query::Range(range) => key_in_range(key, range),
934    }
935}
936
937fn normalize_indexeddb_bound(value: &Key, index_cursor: bool) -> Key {
938    if !index_cursor {
939        return value.clone();
940    }
941    if matches!(value, Key::Array(_)) {
942        return value.clone();
943    }
944    Key::Array(vec![value.clone()])
945}
946
947fn compare_utf16_strings(left: &str, right: &str) -> Ordering {
948    let left: Vec<u16> = left.encode_utf16().collect();
949    let right: Vec<u16> = right.encode_utf16().collect();
950    for (left_unit, right_unit) in left.iter().zip(right.iter()) {
951        match left_unit.cmp(right_unit) {
952            Ordering::Equal => {}
953            other => return other,
954        }
955    }
956    left.len().cmp(&right.len())
957}
958
959fn compare_int_float_keys(left: i64, right: f64) -> Ordering {
960    if right.is_nan() {
961        return Ordering::Equal;
962    }
963    match (left as f64).partial_cmp(&right) {
964        Some(ordering) => ordering,
965        None => left.cmp(&(right as i64)),
966    }
967}
968
969fn compare_float_keys(left: f64, right: f64) -> Ordering {
970    left.partial_cmp(&right).unwrap_or(Ordering::Equal)
971}
972
973#[cfg(test)]
974mod tests {
975    use super::*;
976    use std::time::{Duration, UNIX_EPOCH};
977
978    fn entry(key: Key, primary_key: &str, primary_key_value: Key) -> IndexedDBCursorSnapshotEntry {
979        IndexedDBCursorSnapshotEntry {
980            key,
981            primary_key: primary_key.to_string(),
982            primary_key_value,
983            record: Record::new(),
984        }
985    }
986
987    #[test]
988    fn cursor_snapshot_sorts_ranges_and_skips_duplicate_unique_index_keys() {
989        let mut snapshot = new_indexeddb_cursor_snapshot(&IndexedDBOpenCursorRequest {
990            direction: CursorDirection::NextUnique,
991            index: "by_status".to_string(),
992            ..Default::default()
993        });
994
995        snapshot
996            .load(
997                vec![
998                    entry(
999                        Key::Array(vec![Key::from("todo")]),
1000                        "issue-2",
1001                        Key::from("issue-2"),
1002                    ),
1003                    entry(
1004                        Key::Array(vec![Key::from("done")]),
1005                        "issue-3",
1006                        Key::from("issue-3"),
1007                    ),
1008                    entry(
1009                        Key::Array(vec![Key::from("todo")]),
1010                        "issue-1",
1011                        Key::from("issue-1"),
1012                    ),
1013                ],
1014                Some(&KeyRange {
1015                    lower: Some(Key::Array(vec![Key::from("done")])),
1016                    upper: Some(Key::Array(vec![Key::from("todo")])),
1017                    lower_open: false,
1018                    upper_open: false,
1019                }),
1020            )
1021            .expect("load");
1022
1023        assert_eq!(
1024            snapshot.next().expect("first").unwrap().primary_key,
1025            "issue-3"
1026        );
1027        assert_eq!(
1028            snapshot.next().expect("second").unwrap().primary_key,
1029            "issue-1"
1030        );
1031        assert!(snapshot.next().expect("exhausted").is_none());
1032    }
1033
1034    #[test]
1035    fn cursor_snapshot_advance_moves_exactly_count_entries_from_current_position() {
1036        let mut snapshot = new_indexeddb_cursor_snapshot(&IndexedDBOpenCursorRequest::default());
1037        snapshot
1038            .load(
1039                vec![
1040                    entry(Key::from("a"), "a", Key::from("a")),
1041                    entry(Key::from("b"), "b", Key::from("b")),
1042                    entry(Key::from("c"), "c", Key::from("c")),
1043                ],
1044                None,
1045            )
1046            .expect("load");
1047
1048        assert_eq!(snapshot.next().expect("first").unwrap().primary_key, "a");
1049        assert_eq!(
1050            snapshot.advance(1).expect("second").unwrap().primary_key,
1051            "b"
1052        );
1053        assert_eq!(
1054            snapshot.advance(1).expect("third").unwrap().primary_key,
1055            "c"
1056        );
1057    }
1058
1059    #[test]
1060    fn cursor_snapshot_index_range_accepts_scalar_entry_keys() {
1061        let mut snapshot = new_indexeddb_cursor_snapshot(&IndexedDBOpenCursorRequest {
1062            index: "by_status".to_string(),
1063            ..Default::default()
1064        });
1065        snapshot
1066            .load(
1067                vec![
1068                    entry(Key::from("done"), "issue-2", Key::from("issue-2")),
1069                    entry(Key::from("active"), "issue-1", Key::from("issue-1")),
1070                ],
1071                Some(&KeyRange {
1072                    lower: Some(Key::from("active")),
1073                    upper: Some(Key::from("active")),
1074                    lower_open: false,
1075                    upper_open: false,
1076                }),
1077            )
1078            .expect("load");
1079
1080        let first = snapshot.next().expect("first").unwrap();
1081        assert_eq!(first.primary_key, "issue-1");
1082        assert_eq!(first.key, Key::from("active"));
1083        assert!(snapshot.next().expect("exhausted").is_none());
1084    }
1085
1086    #[test]
1087    fn range_bounds_normalize_scalar_index_bounds() {
1088        let (lower, upper) = indexeddb_range_bounds(
1089            Some(&KeyRange {
1090                lower: Some(Key::from("active")),
1091                upper: Some(Key::Array(vec![Key::from("done")])),
1092                lower_open: false,
1093                upper_open: false,
1094            }),
1095            true,
1096        );
1097
1098        assert_eq!(lower, Some(Key::Array(vec![Key::from("active")])));
1099        assert_eq!(upper, Some(Key::Array(vec![Key::from("done")])));
1100    }
1101
1102    #[test]
1103    fn compare_values_orders_composite_keys() {
1104        assert_eq!(
1105            compare_indexeddb_values(
1106                &Key::Array(vec![Key::from("active"), Key::Int(1)]),
1107                &Key::Array(vec![Key::from("active"), Key::Int(2)])
1108            ),
1109            Ordering::Less
1110        );
1111        assert_eq!(
1112            compare_indexeddb_values(
1113                &Key::Array(vec![Key::from("active"), Key::Int(2)]),
1114                &Key::Array(vec![Key::from("active"), Key::Int(2)])
1115            ),
1116            Ordering::Equal
1117        );
1118        assert_eq!(
1119            compare_indexeddb_values(
1120                &Key::Array(vec![Key::from("active"), Key::Int(3)]),
1121                &Key::Array(vec![Key::from("active"), Key::Int(2)])
1122            ),
1123            Ordering::Greater
1124        );
1125    }
1126
1127    #[test]
1128    fn compare_values_orders_large_integer_keys_exactly() {
1129        assert_eq!(
1130            compare_indexeddb_values(
1131                &Key::Int(9_007_199_254_740_993),
1132                &Key::Int(9_007_199_254_740_992)
1133            ),
1134            Ordering::Greater
1135        );
1136        assert_eq!(
1137            compare_indexeddb_values(&Key::Int(i64::MAX), &Key::Float(u64::MAX as f64)),
1138            Ordering::Less
1139        );
1140    }
1141
1142    #[test]
1143    fn compare_keys_orders_date_before_string_and_bytes_after_string() {
1144        let date = Key::Date(UNIX_EPOCH + Duration::from_secs(1_700_000_000));
1145        assert_eq!(compare_keys(&Key::Int(1), &date), Ordering::Less);
1146        assert_eq!(compare_keys(&date, &Key::from("a")), Ordering::Less);
1147        assert_eq!(
1148            compare_keys(&Key::from("a"), &Key::Bytes(vec![0x00])),
1149            Ordering::Less
1150        );
1151    }
1152
1153    #[test]
1154    fn date_and_bytes_keys_match_exact_queries() {
1155        let date = Key::Date(UNIX_EPOCH + Duration::from_secs(1_700_000_000));
1156        let bytes = Key::Bytes(vec![0x01, 0x02]);
1157        assert!(match_query(&date, &Query::Key(date.clone())));
1158        assert!(match_query(&bytes, &Query::Key(bytes.clone())));
1159        assert!(key_in_range(
1160            &date,
1161            &KeyRange {
1162                lower: Some(date.clone()),
1163                upper: Some(date.clone()),
1164                lower_open: false,
1165                upper_open: false,
1166            }
1167        ));
1168    }
1169}
1170
1171/// Streaming cursor over object store or secondary index rows.
1172pub struct Cursor {
1173    tx: mpsc::Sender<pb::CursorClientMessage>,
1174    stream: tonic::Streaming<pb::CursorResponse>,
1175    keys_only: bool,
1176    entry: Option<pb::CursorEntry>,
1177    done: bool,
1178}
1179
1180impl Cursor {
1181    /// Returns the current cursor key.
1182    pub fn key(&self) -> Option<Key> {
1183        let entry = self.entry.as_ref()?;
1184        entry
1185            .key
1186            .as_ref()
1187            .map(crate::indexeddb_query_codec::key_from_wire_key_value)
1188    }
1189
1190    /// Returns the current row's primary key.
1191    pub fn primary_key(&self) -> &str {
1192        self.entry
1193            .as_ref()
1194            .map(|e| e.primary_key.as_str())
1195            .unwrap_or("")
1196    }
1197
1198    /// Returns the current row value.
1199    pub fn value(&self) -> Result<Record, IndexedDBError> {
1200        if self.keys_only {
1201            return Err(IndexedDBError::KeysOnly);
1202        }
1203        let entry = self.entry.as_ref().ok_or(IndexedDBError::NotFound)?;
1204        Ok(entry
1205            .record
1206            .as_ref()
1207            .map(pb_record_to_record)
1208            .unwrap_or_default())
1209    }
1210
1211    /// Advances the cursor by one row.
1212    pub async fn continue_next(&mut self) -> Result<bool, IndexedDBError> {
1213        let cmd = pb::cursor_command::Command::Next(true);
1214        self.send_and_recv(cmd).await
1215    }
1216
1217    /// Advances the cursor to key, or exhausts it if key does not exist.
1218    pub async fn continue_to_key(&mut self, key: impl Into<Key>) -> Result<bool, IndexedDBError> {
1219        let key = key.into();
1220        let cmd = pb::cursor_command::Command::ContinueToKey(pb::CursorKeyTarget {
1221            key: Some(crate::indexeddb_query_codec::cursor_key_to_proto(&key)),
1222        });
1223        self.send_and_recv(cmd).await
1224    }
1225
1226    /// Skips count rows ahead.
1227    pub async fn advance(&mut self, count: i32) -> Result<bool, IndexedDBError> {
1228        let cmd = pb::cursor_command::Command::Advance(count);
1229        self.send_and_recv(cmd).await
1230    }
1231
1232    /// Deletes the current row and keeps the cursor open.
1233    pub async fn delete(&mut self) -> Result<(), IndexedDBError> {
1234        if self.done {
1235            return Err(IndexedDBError::NotFound);
1236        }
1237        let cmd = pb::cursor_command::Command::Delete(true);
1238        self.send_mutation(cmd).await
1239    }
1240
1241    /// Replaces the current row and keeps the cursor open.
1242    pub async fn update(&mut self, value: Record) -> Result<(), IndexedDBError> {
1243        if self.done {
1244            return Err(IndexedDBError::NotFound);
1245        }
1246        let cmd = pb::cursor_command::Command::Update(record_to_pb_record(value));
1247        self.send_mutation(cmd).await
1248    }
1249
1250    /// Closes the cursor stream and releases its transport resources.
1251    pub async fn close(self) -> Result<(), IndexedDBError> {
1252        let msg = pb::CursorClientMessage {
1253            msg: Some(pb::cursor_client_message::Msg::Command(pb::CursorCommand {
1254                command: Some(pb::cursor_command::Command::Close(true)),
1255            })),
1256        };
1257        self.tx
1258            .send(msg)
1259            .await
1260            .map_err(|e| IndexedDBError::Status(tonic::Status::internal(e.to_string())))?;
1261        Ok(())
1262    }
1263
1264    async fn send_mutation(
1265        &mut self,
1266        cmd: pb::cursor_command::Command,
1267    ) -> Result<(), IndexedDBError> {
1268        let msg = pb::CursorClientMessage {
1269            msg: Some(pb::cursor_client_message::Msg::Command(pb::CursorCommand {
1270                command: Some(cmd),
1271            })),
1272        };
1273        self.tx
1274            .send(msg)
1275            .await
1276            .map_err(|e| IndexedDBError::Status(tonic::Status::internal(e.to_string())))?;
1277        // Read ack -- if it contains an entry, update cursor state.
1278        let resp = self
1279            .stream
1280            .message()
1281            .await
1282            .map_err(map_status)?
1283            .ok_or_else(|| {
1284                IndexedDBError::Status(tonic::Status::internal(
1285                    "cursor stream ended during mutation",
1286                ))
1287            })?;
1288        match resp.result {
1289            Some(pb::cursor_response::Result::Entry(entry)) => {
1290                self.entry = Some(entry);
1291            }
1292            Some(pb::cursor_response::Result::Done(_)) => {}
1293            None => {
1294                return Err(IndexedDBError::Status(tonic::Status::internal(
1295                    "unexpected cursor mutation ack",
1296                )));
1297            }
1298        }
1299        Ok(())
1300    }
1301
1302    async fn send_and_recv(
1303        &mut self,
1304        cmd: pb::cursor_command::Command,
1305    ) -> Result<bool, IndexedDBError> {
1306        if self.done {
1307            return Ok(false);
1308        }
1309        let msg = pb::CursorClientMessage {
1310            msg: Some(pb::cursor_client_message::Msg::Command(pb::CursorCommand {
1311                command: Some(cmd),
1312            })),
1313        };
1314        self.tx
1315            .send(msg)
1316            .await
1317            .map_err(|e| IndexedDBError::Status(tonic::Status::internal(e.to_string())))?;
1318
1319        let resp = self
1320            .stream
1321            .message()
1322            .await
1323            .map_err(map_status)?
1324            .ok_or_else(|| {
1325                IndexedDBError::Status(tonic::Status::internal("cursor stream ended"))
1326            })?;
1327
1328        match resp.result {
1329            Some(pb::cursor_response::Result::Entry(entry)) => {
1330                self.entry = Some(entry);
1331                self.done = false;
1332                Ok(true)
1333            }
1334            Some(pb::cursor_response::Result::Done(exhausted)) => {
1335                if exhausted {
1336                    self.done = true;
1337                }
1338                self.entry = None;
1339                Ok(false)
1340            }
1341            None => {
1342                self.entry = None;
1343                self.done = true;
1344                Ok(false)
1345            }
1346        }
1347    }
1348}
1349
1350async fn open_cursor_inner(
1351    client: &mut IndexedDbClient<IndexedDbTransport>,
1352    req: pb::OpenCursorRequest,
1353) -> Result<Cursor, IndexedDBError> {
1354    let keys_only = req.keys_only;
1355    let (tx, rx) = mpsc::channel::<pb::CursorClientMessage>(CURSOR_CHANNEL_BUFFER);
1356
1357    let open_msg = pb::CursorClientMessage {
1358        msg: Some(pb::cursor_client_message::Msg::Open(req)),
1359    };
1360    tx.send(open_msg)
1361        .await
1362        .map_err(|e| IndexedDBError::Status(tonic::Status::internal(e.to_string())))?;
1363
1364    let receiver_stream = ReceiverStream::new(rx);
1365    let mut stream = client
1366        .open_cursor(receiver_stream)
1367        .await
1368        .map_err(map_status)?
1369        .into_inner();
1370
1371    // Read the open ack to surface creation errors synchronously.
1372    let ack = stream.message().await.map_err(map_status)?.ok_or_else(|| {
1373        IndexedDBError::Status(tonic::Status::internal("cursor stream ended during open"))
1374    })?;
1375    match ack.result {
1376        Some(pb::cursor_response::Result::Done(false)) => {}
1377        Some(pb::cursor_response::Result::Done(true)) => {
1378            return Err(IndexedDBError::Status(tonic::Status::internal(
1379                "unexpected exhausted cursor open ack",
1380            )));
1381        }
1382        _ => {
1383            return Err(IndexedDBError::Status(tonic::Status::internal(
1384                "unexpected cursor open ack",
1385            )));
1386        }
1387    }
1388
1389    Ok(Cursor {
1390        tx,
1391        stream,
1392        keys_only,
1393        entry: None,
1394        done: false,
1395    })
1396}
1397
1398/// Client for a running IndexedDB provider.
1399pub struct IndexedDB {
1400    client: IndexedDbClient<IndexedDbTransport>,
1401}
1402
1403impl IndexedDB {
1404    /// Connects to the default IndexedDB transport socket.
1405    pub async fn connect() -> Result<Self, IndexedDBError> {
1406        Self::connect_named("").await
1407    }
1408
1409    /// Connects to a named IndexedDB transport socket.
1410    pub async fn connect_named(name: &str) -> Result<Self, IndexedDBError> {
1411        host_service_configured("indexeddb").map_err(IndexedDBError::Env)?;
1412        let target = std::env::var(ENV_HOST_SERVICE_SOCKET)
1413            .map_err(|_| IndexedDBError::Env(format!("{ENV_HOST_SERVICE_SOCKET} is not set")))?;
1414        let token = std::env::var(ENV_HOST_SERVICE_TOKEN).unwrap_or_default();
1415        let channel = match parse_indexeddb_target(&target)? {
1416            IndexedDBTarget::Unix(path) => {
1417                Endpoint::try_from("http://[::]:50051")?
1418                    .connect_with_connector(service_fn(move |_: Uri| {
1419                        let path = path.clone();
1420                        async move {
1421                            tokio::net::UnixStream::connect(path)
1422                                .await
1423                                .map(TokioIo::new)
1424                        }
1425                    }))
1426                    .await?
1427            }
1428            IndexedDBTarget::Tcp(address) => {
1429                Endpoint::from_shared(format!("http://{address}"))?
1430                    .connect()
1431                    .await?
1432            }
1433            IndexedDBTarget::Tls(address) => {
1434                Endpoint::from_shared(format!("https://{address}"))?
1435                    .tls_config(ClientTlsConfig::new().with_native_roots())?
1436                    .connect()
1437                    .await?
1438            }
1439        };
1440
1441        let client = IndexedDbClient::with_interceptor(
1442            channel,
1443            relay_token_interceptor(token.trim(), name)?,
1444        );
1445
1446        Ok(Self { client })
1447    }
1448
1449    /// Creates a named object store.
1450    pub async fn create_object_store(
1451        &mut self,
1452        name: &str,
1453        schema: ObjectStoreSchema,
1454    ) -> Result<ObjectStore, IndexedDBError> {
1455        let indexes = schema
1456            .indexes
1457            .into_iter()
1458            .map(|idx| pb::IndexSchema {
1459                name: idx.name,
1460                key_path: idx.key_path,
1461                unique: idx.unique,
1462            })
1463            .collect();
1464        self.client
1465            .create_object_store(pb::CreateObjectStoreRequest {
1466                name: name.to_string(),
1467                schema: Some(pb::ObjectStoreSchema {
1468                    indexes,
1469                    columns: vec![],
1470                }),
1471            })
1472            .await
1473            .map_err(map_status)?;
1474        Ok(self.object_store(name))
1475    }
1476
1477    /// Deletes a named object store.
1478    pub async fn delete_object_store(&mut self, name: &str) -> Result<(), IndexedDBError> {
1479        self.client
1480            .delete_object_store(pb::DeleteObjectStoreRequest {
1481                name: name.to_string(),
1482            })
1483            .await
1484            .map_err(map_status)?;
1485        Ok(())
1486    }
1487
1488    /// Returns a typed handle for one object store.
1489    pub fn object_store(&self, name: &str) -> ObjectStore {
1490        ObjectStore {
1491            client: self.client.clone(),
1492            store: name.to_string(),
1493        }
1494    }
1495
1496    /// Opens an explicit transaction over a fixed object-store scope.
1497    pub async fn transaction(
1498        &self,
1499        stores: &[&str],
1500        mode: TransactionMode,
1501        options: TransactionOptions,
1502    ) -> Result<Transaction, IndexedDBError> {
1503        let (tx, rx) = mpsc::channel::<pb::TransactionClientMessage>(TRANSACTION_CHANNEL_BUFFER);
1504        tx.send(pb::TransactionClientMessage {
1505            msg: Some(pb::transaction_client_message::Msg::Begin(
1506                pb::BeginTransactionRequest {
1507                    stores: stores.iter().map(|store| store.to_string()).collect(),
1508                    mode: mode.to_proto(),
1509                    durability_hint: options.durability_hint.to_proto(),
1510                },
1511            )),
1512        })
1513        .await
1514        .map_err(|e| IndexedDBError::Status(tonic::Status::internal(e.to_string())))?;
1515
1516        let receiver_stream = ReceiverStream::new(rx);
1517        let mut client = self.client.clone();
1518        let mut stream = client
1519            .transaction(receiver_stream)
1520            .await
1521            .map_err(map_status)?
1522            .into_inner();
1523
1524        let ack = stream.message().await.map_err(map_status)?.ok_or_else(|| {
1525            IndexedDBError::Transaction("transaction stream ended during begin".to_string())
1526        })?;
1527        match ack.msg {
1528            Some(pb::transaction_server_message::Msg::Begin(_)) => {}
1529            _ => {
1530                return Err(IndexedDBError::Transaction(
1531                    "expected transaction begin response".to_string(),
1532                ));
1533            }
1534        }
1535
1536        Ok(Transaction {
1537            tx: Some(tx),
1538            stream,
1539            request_id: 0,
1540            closed: false,
1541        })
1542    }
1543}
1544
1545/// Explicit transaction over one or more object stores.
1546pub struct Transaction {
1547    tx: Option<mpsc::Sender<pb::TransactionClientMessage>>,
1548    stream: tonic::Streaming<pb::TransactionServerMessage>,
1549    request_id: u64,
1550    closed: bool,
1551}
1552
1553impl Transaction {
1554    /// Returns a transaction-scoped object store.
1555    pub fn object_store<'a>(&'a mut self, name: &str) -> TransactionObjectStore<'a> {
1556        TransactionObjectStore {
1557            tx: self,
1558            store: name.to_string(),
1559        }
1560    }
1561
1562    /// Commits the transaction.
1563    pub async fn commit(&mut self) -> Result<(), IndexedDBError> {
1564        self.ensure_open()?;
1565        let tx = self.tx.as_ref().ok_or_else(|| {
1566            IndexedDBError::Transaction("transaction is already finished".to_string())
1567        })?;
1568        tx.send(pb::TransactionClientMessage {
1569            msg: Some(pb::transaction_client_message::Msg::Commit(
1570                pb::TransactionCommitRequest {},
1571            )),
1572        })
1573        .await
1574        .map_err(|e| IndexedDBError::Status(tonic::Status::internal(e.to_string())))?;
1575        self.closed = true;
1576        self.tx.take();
1577
1578        let resp = self
1579            .stream
1580            .message()
1581            .await
1582            .map_err(map_status)?
1583            .ok_or_else(|| {
1584                IndexedDBError::Transaction("transaction stream ended during commit".to_string())
1585            })?;
1586        match resp.msg {
1587            Some(pb::transaction_server_message::Msg::Commit(commit)) => {
1588                map_rpc_status(commit.error)
1589            }
1590            _ => Err(IndexedDBError::Transaction(
1591                "expected transaction commit response".to_string(),
1592            )),
1593        }
1594    }
1595
1596    /// Aborts the transaction. Aborting an already finished transaction is a no-op.
1597    pub async fn abort(&mut self, reason: &str) -> Result<(), IndexedDBError> {
1598        if self.closed {
1599            return Ok(());
1600        }
1601        let tx = self.tx.as_ref().ok_or_else(|| {
1602            IndexedDBError::Transaction("transaction is already finished".to_string())
1603        })?;
1604        tx.send(pb::TransactionClientMessage {
1605            msg: Some(pb::transaction_client_message::Msg::Abort(
1606                pb::TransactionAbortRequest {
1607                    reason: reason.to_string(),
1608                },
1609            )),
1610        })
1611        .await
1612        .map_err(|e| IndexedDBError::Status(tonic::Status::internal(e.to_string())))?;
1613        self.closed = true;
1614        self.tx.take();
1615
1616        let resp = self
1617            .stream
1618            .message()
1619            .await
1620            .map_err(map_status)?
1621            .ok_or_else(|| {
1622                IndexedDBError::Transaction("transaction stream ended during abort".to_string())
1623            })?;
1624        match resp.msg {
1625            Some(pb::transaction_server_message::Msg::Abort(abort)) => map_rpc_status(abort.error),
1626            _ => Err(IndexedDBError::Transaction(
1627                "expected transaction abort response".to_string(),
1628            )),
1629        }
1630    }
1631
1632    async fn send_operation(
1633        &mut self,
1634        operation: pb::transaction_operation::Operation,
1635    ) -> Result<pb::TransactionOperationResponse, IndexedDBError> {
1636        self.ensure_open()?;
1637        self.request_id += 1;
1638        let request_id = self.request_id;
1639        let tx = self.tx.as_ref().ok_or_else(|| {
1640            IndexedDBError::Transaction("transaction is already finished".to_string())
1641        })?;
1642        tx.send(pb::TransactionClientMessage {
1643            msg: Some(pb::transaction_client_message::Msg::Operation(
1644                pb::TransactionOperation {
1645                    request_id,
1646                    operation: Some(operation),
1647                },
1648            )),
1649        })
1650        .await
1651        .map_err(|e| IndexedDBError::Status(tonic::Status::internal(e.to_string())))?;
1652
1653        let resp = self
1654            .stream
1655            .message()
1656            .await
1657            .map_err(map_status)?
1658            .ok_or_else(|| {
1659                IndexedDBError::Transaction("transaction stream ended during operation".to_string())
1660            })?;
1661        let op = match resp.msg {
1662            Some(pb::transaction_server_message::Msg::Operation(op)) => op,
1663            _ => {
1664                self.close_locally();
1665                return Err(IndexedDBError::Transaction(
1666                    "expected transaction operation response".to_string(),
1667                ));
1668            }
1669        };
1670        if op.request_id != request_id {
1671            self.close_locally();
1672            return Err(IndexedDBError::Transaction(
1673                "transaction response request id mismatch".to_string(),
1674            ));
1675        }
1676        if let Err(err) = map_rpc_status(op.error.clone()) {
1677            self.close_locally();
1678            return Err(err);
1679        }
1680        Ok(op)
1681    }
1682
1683    fn ensure_open(&self) -> Result<(), IndexedDBError> {
1684        if self.closed {
1685            return Err(IndexedDBError::Transaction(
1686                "transaction is already finished".to_string(),
1687            ));
1688        }
1689        Ok(())
1690    }
1691
1692    fn close_locally(&mut self) {
1693        self.closed = true;
1694        self.tx.take();
1695    }
1696}
1697
1698/// Object-store operations scoped to an explicit transaction.
1699pub struct TransactionObjectStore<'a> {
1700    tx: &'a mut Transaction,
1701    store: String,
1702}
1703
1704impl TransactionObjectStore<'_> {
1705    /// Loads one record by primary key inside the transaction.
1706    pub async fn get(&mut self, id: &str) -> Result<Record, IndexedDBError> {
1707        let resp = self
1708            .tx
1709            .send_operation(pb::transaction_operation::Operation::Get(
1710                pb::ObjectStoreRequest {
1711                    store: self.store.clone(),
1712                    id: id.to_string(),
1713                },
1714            ))
1715            .await?;
1716        match resp.result {
1717            Some(pb::transaction_operation_response::Result::Record(record)) => Ok(record
1718                .record
1719                .as_ref()
1720                .map(pb_record_to_record)
1721                .unwrap_or_default()),
1722            _ => Err(unexpected_transaction_result()),
1723        }
1724    }
1725
1726    /// Resolves the primary key for id inside the transaction.
1727    pub async fn get_key(&mut self, id: &str) -> Result<String, IndexedDBError> {
1728        let resp = self
1729            .tx
1730            .send_operation(pb::transaction_operation::Operation::GetKey(
1731                pb::ObjectStoreRequest {
1732                    store: self.store.clone(),
1733                    id: id.to_string(),
1734                },
1735            ))
1736            .await?;
1737        match resp.result {
1738            Some(pb::transaction_operation_response::Result::Key(key)) => Ok(key.key),
1739            _ => Err(unexpected_transaction_result()),
1740        }
1741    }
1742
1743    /// Inserts a new row inside the transaction.
1744    pub async fn add(&mut self, record: Record) -> Result<(), IndexedDBError> {
1745        self.tx
1746            .send_operation(pb::transaction_operation::Operation::Add(
1747                pb::RecordRequest {
1748                    store: self.store.clone(),
1749                    record: Some(record_to_pb_record(record)),
1750                },
1751            ))
1752            .await?;
1753        Ok(())
1754    }
1755
1756    /// Upserts a row inside the transaction.
1757    pub async fn put(&mut self, record: Record) -> Result<(), IndexedDBError> {
1758        self.tx
1759            .send_operation(pb::transaction_operation::Operation::Put(
1760                pb::RecordRequest {
1761                    store: self.store.clone(),
1762                    record: Some(record_to_pb_record(record)),
1763                },
1764            ))
1765            .await?;
1766        Ok(())
1767    }
1768
1769    /// Deletes one row inside the transaction.
1770    pub async fn delete(&mut self, id: &str) -> Result<(), IndexedDBError> {
1771        self.tx
1772            .send_operation(pb::transaction_operation::Operation::Delete(
1773                pb::ObjectStoreRequest {
1774                    store: self.store.clone(),
1775                    id: id.to_string(),
1776                },
1777            ))
1778            .await?;
1779        Ok(())
1780    }
1781
1782    /// Deletes every row in the object store inside the transaction.
1783    pub async fn clear(&mut self) -> Result<(), IndexedDBError> {
1784        self.tx
1785            .send_operation(pb::transaction_operation::Operation::Clear(
1786                pb::ObjectStoreNameRequest {
1787                    store: self.store.clone(),
1788                },
1789            ))
1790            .await?;
1791        Ok(())
1792    }
1793
1794    /// Loads every row that matches query inside the transaction.
1795    pub async fn get_all(
1796        &mut self,
1797        query: impl Into<Query>,
1798        count: Option<u32>,
1799    ) -> Result<Vec<Record>, IndexedDBError> {
1800        let query = query.into();
1801        let resp = self
1802            .tx
1803            .send_operation(pb::transaction_operation::Operation::GetAll(
1804                pb::ObjectStoreRangeRequest {
1805                    store: self.store.clone(),
1806                    query: query.to_proto(),
1807                    count,
1808                },
1809            ))
1810            .await?;
1811        match resp.result {
1812            Some(pb::transaction_operation_response::Result::Records(records)) => {
1813                Ok(records.records.iter().map(pb_record_to_record).collect())
1814            }
1815            _ => Err(unexpected_transaction_result()),
1816        }
1817    }
1818
1819    /// Loads every primary key that matches query inside the transaction.
1820    pub async fn get_all_keys(
1821        &mut self,
1822        query: impl Into<Query>,
1823        count: Option<u32>,
1824    ) -> Result<Vec<String>, IndexedDBError> {
1825        let query = query.into();
1826        let resp = self
1827            .tx
1828            .send_operation(pb::transaction_operation::Operation::GetAllKeys(
1829                pb::ObjectStoreRangeRequest {
1830                    store: self.store.clone(),
1831                    query: query.to_proto(),
1832                    count,
1833                },
1834            ))
1835            .await?;
1836        match resp.result {
1837            Some(pb::transaction_operation_response::Result::Keys(keys)) => Ok(keys.keys),
1838            _ => Err(unexpected_transaction_result()),
1839        }
1840    }
1841
1842    /// Counts rows that match query inside the transaction.
1843    pub async fn count(&mut self, query: impl Into<Query>) -> Result<i64, IndexedDBError> {
1844        let query = query.into();
1845        let resp = self
1846            .tx
1847            .send_operation(pb::transaction_operation::Operation::Count(
1848                pb::ObjectStoreRangeRequest {
1849                    store: self.store.clone(),
1850                    query: query.to_proto(),
1851                    count: None,
1852                },
1853            ))
1854            .await?;
1855        match resp.result {
1856            Some(pb::transaction_operation_response::Result::Count(count)) => Ok(count.count),
1857            _ => Err(unexpected_transaction_result()),
1858        }
1859    }
1860
1861    /// Deletes rows that match query inside the transaction.
1862    pub async fn delete_range(&mut self, query: impl Into<Query>) -> Result<i64, IndexedDBError> {
1863        let query = query.into();
1864        let resp = self
1865            .tx
1866            .send_operation(pb::transaction_operation::Operation::DeleteRange(
1867                pb::ObjectStoreRangeRequest {
1868                    store: self.store.clone(),
1869                    query: query.to_proto(),
1870                    count: None,
1871                },
1872            ))
1873            .await?;
1874        match resp.result {
1875            Some(pb::transaction_operation_response::Result::Delete(deleted)) => {
1876                Ok(deleted.deleted)
1877            }
1878            _ => Err(unexpected_transaction_result()),
1879        }
1880    }
1881
1882    /// Returns a transaction-scoped secondary index.
1883    pub fn index<'a>(&'a mut self, name: &str) -> TransactionIndex<'a> {
1884        TransactionIndex {
1885            tx: &mut *self.tx,
1886            store: self.store.clone(),
1887            index: name.to_string(),
1888        }
1889    }
1890}
1891
1892/// Secondary-index operations scoped to an explicit transaction.
1893pub struct TransactionIndex<'a> {
1894    tx: &'a mut Transaction,
1895    store: String,
1896    index: String,
1897}
1898
1899impl TransactionIndex<'_> {
1900    /// Loads the first row that matches query inside the transaction.
1901    pub async fn get(&mut self, query: impl Into<Query>) -> Result<Record, IndexedDBError> {
1902        let resp = self
1903            .tx
1904            .send_operation(pb::transaction_operation::Operation::IndexGet(
1905                self.index_request(query.into(), None),
1906            ))
1907            .await?;
1908        match resp.result {
1909            Some(pb::transaction_operation_response::Result::Record(record)) => Ok(record
1910                .record
1911                .as_ref()
1912                .map(pb_record_to_record)
1913                .unwrap_or_default()),
1914            _ => Err(unexpected_transaction_result()),
1915        }
1916    }
1917
1918    /// Resolves the primary key for the first matching row inside the transaction.
1919    pub async fn get_key(&mut self, query: impl Into<Query>) -> Result<String, IndexedDBError> {
1920        let resp = self
1921            .tx
1922            .send_operation(pb::transaction_operation::Operation::IndexGetKey(
1923                self.index_request(query.into(), None),
1924            ))
1925            .await?;
1926        match resp.result {
1927            Some(pb::transaction_operation_response::Result::Key(key)) => Ok(key.key),
1928            _ => Err(unexpected_transaction_result()),
1929        }
1930    }
1931
1932    /// Loads every row that matches query inside the transaction.
1933    pub async fn get_all(
1934        &mut self,
1935        query: impl Into<Query>,
1936        count: Option<u32>,
1937    ) -> Result<Vec<Record>, IndexedDBError> {
1938        let resp = self
1939            .tx
1940            .send_operation(pb::transaction_operation::Operation::IndexGetAll(
1941                self.index_request(query.into(), count),
1942            ))
1943            .await?;
1944        match resp.result {
1945            Some(pb::transaction_operation_response::Result::Records(records)) => {
1946                Ok(records.records.iter().map(pb_record_to_record).collect())
1947            }
1948            _ => Err(unexpected_transaction_result()),
1949        }
1950    }
1951
1952    /// Loads every primary key that matches query inside the transaction.
1953    pub async fn get_all_keys(
1954        &mut self,
1955        query: impl Into<Query>,
1956        count: Option<u32>,
1957    ) -> Result<Vec<String>, IndexedDBError> {
1958        let resp = self
1959            .tx
1960            .send_operation(pb::transaction_operation::Operation::IndexGetAllKeys(
1961                self.index_request(query.into(), count),
1962            ))
1963            .await?;
1964        match resp.result {
1965            Some(pb::transaction_operation_response::Result::Keys(keys)) => Ok(keys.keys),
1966            _ => Err(unexpected_transaction_result()),
1967        }
1968    }
1969
1970    /// Counts rows that match query inside the transaction.
1971    pub async fn count(&mut self, query: impl Into<Query>) -> Result<i64, IndexedDBError> {
1972        let resp = self
1973            .tx
1974            .send_operation(pb::transaction_operation::Operation::IndexCount(
1975                self.index_request(query.into(), None),
1976            ))
1977            .await?;
1978        match resp.result {
1979            Some(pb::transaction_operation_response::Result::Count(count)) => Ok(count.count),
1980            _ => Err(unexpected_transaction_result()),
1981        }
1982    }
1983
1984    /// Deletes rows that match query inside the transaction.
1985    pub async fn delete(&mut self, query: impl Into<Query>) -> Result<i64, IndexedDBError> {
1986        let resp = self
1987            .tx
1988            .send_operation(pb::transaction_operation::Operation::IndexDelete(
1989                self.index_request(query.into(), None),
1990            ))
1991            .await?;
1992        match resp.result {
1993            Some(pb::transaction_operation_response::Result::Delete(deleted)) => {
1994                Ok(deleted.deleted)
1995            }
1996            _ => Err(unexpected_transaction_result()),
1997        }
1998    }
1999
2000    fn index_request(&self, query: Query, count: Option<u32>) -> pb::IndexQueryRequest {
2001        pb::IndexQueryRequest {
2002            store: self.store.clone(),
2003            index: self.index.clone(),
2004            query: query.to_proto(),
2005            count,
2006        }
2007    }
2008}
2009
2010enum IndexedDBTarget {
2011    Unix(String),
2012    Tcp(String),
2013    Tls(String),
2014}
2015
2016fn parse_indexeddb_target(raw_target: &str) -> Result<IndexedDBTarget, IndexedDBError> {
2017    let target = raw_target.trim();
2018    if target.is_empty() {
2019        return Err(IndexedDBError::Env(
2020            "IndexedDB transport target is required".to_string(),
2021        ));
2022    }
2023    if let Some(address) = target.strip_prefix("tcp://") {
2024        let address = address.trim();
2025        if address.is_empty() {
2026            return Err(IndexedDBError::Env(format!(
2027                "IndexedDB tcp target {raw_target:?} is missing host:port"
2028            )));
2029        }
2030        return Ok(IndexedDBTarget::Tcp(address.to_string()));
2031    }
2032    if let Some(address) = target.strip_prefix("tls://") {
2033        let address = address.trim();
2034        if address.is_empty() {
2035            return Err(IndexedDBError::Env(format!(
2036                "IndexedDB tls target {raw_target:?} is missing host:port"
2037            )));
2038        }
2039        return Ok(IndexedDBTarget::Tls(address.to_string()));
2040    }
2041    if let Some(path) = target.strip_prefix("unix://") {
2042        let path = path.trim();
2043        if path.is_empty() {
2044            return Err(IndexedDBError::Env(format!(
2045                "IndexedDB unix target {raw_target:?} is missing a socket path"
2046            )));
2047        }
2048        return Ok(IndexedDBTarget::Unix(path.to_string()));
2049    }
2050    if target.contains("://") {
2051        let scheme = target.split("://").next().unwrap_or_default();
2052        return Err(IndexedDBError::Env(format!(
2053            "unsupported IndexedDB target scheme {scheme:?}"
2054        )));
2055    }
2056    Ok(IndexedDBTarget::Unix(target.to_string()))
2057}
2058
2059/// CRUD, range-query, and cursor access for one object store.
2060pub struct ObjectStore {
2061    client: IndexedDbClient<IndexedDbTransport>,
2062    store: String,
2063}
2064
2065impl ObjectStore {
2066    /// Loads one record by primary key.
2067    pub async fn get(&mut self, id: &str) -> Result<Record, IndexedDBError> {
2068        let resp = self
2069            .client
2070            .get(pb::ObjectStoreRequest {
2071                store: self.store.clone(),
2072                id: id.to_string(),
2073            })
2074            .await
2075            .map_err(map_status)?;
2076        Ok(resp
2077            .into_inner()
2078            .record
2079            .as_ref()
2080            .map(pb_record_to_record)
2081            .unwrap_or_default())
2082    }
2083
2084    /// Resolves the primary key for id.
2085    pub async fn get_key(&mut self, id: &str) -> Result<String, IndexedDBError> {
2086        let resp = self
2087            .client
2088            .get_key(pb::ObjectStoreRequest {
2089                store: self.store.clone(),
2090                id: id.to_string(),
2091            })
2092            .await
2093            .map_err(map_status)?;
2094        Ok(resp.into_inner().key)
2095    }
2096
2097    /// Inserts a new row and fails if the key already exists.
2098    pub async fn add(&mut self, record: Record) -> Result<(), IndexedDBError> {
2099        self.client
2100            .add(pb::RecordRequest {
2101                store: self.store.clone(),
2102                record: Some(record_to_pb_record(record)),
2103            })
2104            .await
2105            .map_err(map_status)?;
2106        Ok(())
2107    }
2108
2109    /// Upserts a row by primary key.
2110    pub async fn put(&mut self, record: Record) -> Result<(), IndexedDBError> {
2111        self.client
2112            .put(pb::RecordRequest {
2113                store: self.store.clone(),
2114                record: Some(record_to_pb_record(record)),
2115            })
2116            .await
2117            .map_err(map_status)?;
2118        Ok(())
2119    }
2120
2121    /// Deletes one row by primary key.
2122    pub async fn delete(&mut self, id: &str) -> Result<(), IndexedDBError> {
2123        self.client
2124            .delete(pb::ObjectStoreRequest {
2125                store: self.store.clone(),
2126                id: id.to_string(),
2127            })
2128            .await
2129            .map_err(map_status)?;
2130        Ok(())
2131    }
2132
2133    /// Deletes every row in the object store.
2134    pub async fn clear(&mut self) -> Result<(), IndexedDBError> {
2135        self.client
2136            .clear(pb::ObjectStoreNameRequest {
2137                store: self.store.clone(),
2138            })
2139            .await
2140            .map_err(map_status)?;
2141        Ok(())
2142    }
2143
2144    /// Loads every row that matches query.
2145    pub async fn get_all(
2146        &mut self,
2147        query: impl Into<Query>,
2148        count: Option<u32>,
2149    ) -> Result<Vec<Record>, IndexedDBError> {
2150        let query = query.into();
2151        let resp = self
2152            .client
2153            .get_all(pb::ObjectStoreRangeRequest {
2154                store: self.store.clone(),
2155                query: query.to_proto(),
2156                count,
2157            })
2158            .await
2159            .map_err(map_status)?;
2160        Ok(resp
2161            .into_inner()
2162            .records
2163            .iter()
2164            .map(pb_record_to_record)
2165            .collect())
2166    }
2167
2168    /// Loads every primary key that matches query.
2169    pub async fn get_all_keys(
2170        &mut self,
2171        query: impl Into<Query>,
2172        count: Option<u32>,
2173    ) -> Result<Vec<String>, IndexedDBError> {
2174        let query = query.into();
2175        let resp = self
2176            .client
2177            .get_all_keys(pb::ObjectStoreRangeRequest {
2178                store: self.store.clone(),
2179                query: query.to_proto(),
2180                count,
2181            })
2182            .await
2183            .map_err(map_status)?;
2184        Ok(resp.into_inner().keys)
2185    }
2186
2187    /// Counts rows that match query.
2188    pub async fn count(&mut self, query: impl Into<Query>) -> Result<i64, IndexedDBError> {
2189        let query = query.into();
2190        let resp = self
2191            .client
2192            .count(pb::ObjectStoreRangeRequest {
2193                store: self.store.clone(),
2194                query: query.to_proto(),
2195                count: None,
2196            })
2197            .await
2198            .map_err(map_status)?;
2199        Ok(resp.into_inner().count)
2200    }
2201
2202    /// Deletes rows that match query and returns the delete count.
2203    pub async fn delete_range(&mut self, query: impl Into<Query>) -> Result<i64, IndexedDBError> {
2204        let query = query.into();
2205        let resp = self
2206            .client
2207            .delete_range(pb::ObjectStoreRangeRequest {
2208                store: self.store.clone(),
2209                query: query.to_proto(),
2210                count: None,
2211            })
2212            .await
2213            .map_err(map_status)?;
2214        Ok(resp.into_inner().deleted)
2215    }
2216
2217    /// Returns a typed handle for one secondary index.
2218    pub fn index(&self, name: &str) -> Index {
2219        Index {
2220            client: self.client.clone(),
2221            store: self.store.clone(),
2222            index: name.to_string(),
2223        }
2224    }
2225
2226    /// Opens a full-value cursor over the object store.
2227    pub async fn open_cursor(
2228        &mut self,
2229        query: impl Into<Query>,
2230        direction: CursorDirection,
2231    ) -> Result<Cursor, IndexedDBError> {
2232        let query = query.into();
2233        let req = pb::OpenCursorRequest {
2234            store: self.store.clone(),
2235            index: String::new(),
2236            query: query.to_proto(),
2237            direction: direction.to_proto(),
2238            keys_only: false,
2239        };
2240        open_cursor_inner(&mut self.client, req).await
2241    }
2242
2243    /// Opens a key-only cursor over the object store.
2244    pub async fn open_key_cursor(
2245        &mut self,
2246        query: impl Into<Query>,
2247        direction: CursorDirection,
2248    ) -> Result<Cursor, IndexedDBError> {
2249        let query = query.into();
2250        let req = pb::OpenCursorRequest {
2251            store: self.store.clone(),
2252            index: String::new(),
2253            query: query.to_proto(),
2254            direction: direction.to_proto(),
2255            keys_only: true,
2256        };
2257        open_cursor_inner(&mut self.client, req).await
2258    }
2259}
2260
2261/// Lookup and cursor access through one secondary index.
2262pub struct Index {
2263    client: IndexedDbClient<IndexedDbTransport>,
2264    store: String,
2265    index: String,
2266}
2267
2268impl Index {
2269    fn index_request(&self, query: Query, count: Option<u32>) -> pb::IndexQueryRequest {
2270        pb::IndexQueryRequest {
2271            store: self.store.clone(),
2272            index: self.index.clone(),
2273            query: query.to_proto(),
2274            count,
2275        }
2276    }
2277
2278    /// Loads the first row that matches query.
2279    pub async fn get(&mut self, query: impl Into<Query>) -> Result<Record, IndexedDBError> {
2280        let resp = self
2281            .client
2282            .index_get(self.index_request(query.into(), None))
2283            .await
2284            .map_err(map_status)?;
2285        Ok(resp
2286            .into_inner()
2287            .record
2288            .as_ref()
2289            .map(pb_record_to_record)
2290            .unwrap_or_default())
2291    }
2292
2293    /// Resolves the primary key for the first row that matches query.
2294    pub async fn get_key(&mut self, query: impl Into<Query>) -> Result<String, IndexedDBError> {
2295        let resp = self
2296            .client
2297            .index_get_key(self.index_request(query.into(), None))
2298            .await
2299            .map_err(map_status)?;
2300        Ok(resp.into_inner().key)
2301    }
2302
2303    /// Loads every row that matches query.
2304    pub async fn get_all(
2305        &mut self,
2306        query: impl Into<Query>,
2307        count: Option<u32>,
2308    ) -> Result<Vec<Record>, IndexedDBError> {
2309        let resp = self
2310            .client
2311            .index_get_all(self.index_request(query.into(), count))
2312            .await
2313            .map_err(map_status)?;
2314        Ok(resp
2315            .into_inner()
2316            .records
2317            .iter()
2318            .map(pb_record_to_record)
2319            .collect())
2320    }
2321
2322    /// Loads every primary key that matches query.
2323    pub async fn get_all_keys(
2324        &mut self,
2325        query: impl Into<Query>,
2326        count: Option<u32>,
2327    ) -> Result<Vec<String>, IndexedDBError> {
2328        let resp = self
2329            .client
2330            .index_get_all_keys(self.index_request(query.into(), count))
2331            .await
2332            .map_err(map_status)?;
2333        Ok(resp.into_inner().keys)
2334    }
2335
2336    /// Counts rows that match query.
2337    pub async fn count(&mut self, query: impl Into<Query>) -> Result<i64, IndexedDBError> {
2338        let resp = self
2339            .client
2340            .index_count(self.index_request(query.into(), None))
2341            .await
2342            .map_err(map_status)?;
2343        Ok(resp.into_inner().count)
2344    }
2345
2346    /// Deletes rows that match query and returns the delete count.
2347    pub async fn delete(&mut self, query: impl Into<Query>) -> Result<i64, IndexedDBError> {
2348        let resp = self
2349            .client
2350            .index_delete(self.index_request(query.into(), None))
2351            .await
2352            .map_err(map_status)?;
2353        Ok(resp.into_inner().deleted)
2354    }
2355
2356    /// Opens a full-value cursor over the secondary index.
2357    pub async fn open_cursor(
2358        &mut self,
2359        query: impl Into<Query>,
2360        direction: CursorDirection,
2361    ) -> Result<Cursor, IndexedDBError> {
2362        let query = query.into();
2363        let req = pb::OpenCursorRequest {
2364            store: self.store.clone(),
2365            index: self.index.clone(),
2366            query: query.to_proto(),
2367            direction: direction.to_proto(),
2368            keys_only: false,
2369        };
2370        open_cursor_inner(&mut self.client, req).await
2371    }
2372
2373    /// Opens a key-only cursor over the secondary index.
2374    pub async fn open_key_cursor(
2375        &mut self,
2376        query: impl Into<Query>,
2377        direction: CursorDirection,
2378    ) -> Result<Cursor, IndexedDBError> {
2379        let query = query.into();
2380        let req = pb::OpenCursorRequest {
2381            store: self.store.clone(),
2382            index: self.index.clone(),
2383            query: query.to_proto(),
2384            direction: direction.to_proto(),
2385            keys_only: true,
2386        };
2387        open_cursor_inner(&mut self.client, req).await
2388    }
2389}
2390
2391#[async_trait]
2392impl IndexedDBApi for IndexedDB {
2393    type ObjectStore = ObjectStore;
2394    type Transaction = Transaction;
2395
2396    async fn create_object_store(
2397        &mut self,
2398        name: &str,
2399        schema: ObjectStoreSchema,
2400    ) -> Result<ObjectStore, IndexedDBError> {
2401        IndexedDB::create_object_store(self, name, schema).await
2402    }
2403
2404    async fn delete_object_store(&mut self, name: &str) -> Result<(), IndexedDBError> {
2405        IndexedDB::delete_object_store(self, name).await
2406    }
2407
2408    fn object_store(&self, name: &str) -> ObjectStore {
2409        IndexedDB::object_store(self, name)
2410    }
2411
2412    async fn transaction(
2413        &self,
2414        stores: &[&str],
2415        mode: TransactionMode,
2416        options: TransactionOptions,
2417    ) -> Result<Transaction, IndexedDBError> {
2418        IndexedDB::transaction(self, stores, mode, options).await
2419    }
2420}
2421
2422#[async_trait]
2423impl ObjectStoreApi for ObjectStore {
2424    type Index = Index;
2425    type Cursor = Cursor;
2426
2427    async fn get(&mut self, id: &str) -> Result<Record, IndexedDBError> {
2428        ObjectStore::get(self, id).await
2429    }
2430
2431    async fn get_key(&mut self, id: &str) -> Result<String, IndexedDBError> {
2432        ObjectStore::get_key(self, id).await
2433    }
2434
2435    async fn add(&mut self, record: Record) -> Result<(), IndexedDBError> {
2436        ObjectStore::add(self, record).await
2437    }
2438
2439    async fn put(&mut self, record: Record) -> Result<(), IndexedDBError> {
2440        ObjectStore::put(self, record).await
2441    }
2442
2443    async fn delete(&mut self, id: &str) -> Result<(), IndexedDBError> {
2444        ObjectStore::delete(self, id).await
2445    }
2446
2447    async fn clear(&mut self) -> Result<(), IndexedDBError> {
2448        ObjectStore::clear(self).await
2449    }
2450
2451    async fn get_all(&mut self, query: Query) -> Result<Vec<Record>, IndexedDBError> {
2452        ObjectStore::get_all(self, query, None).await
2453    }
2454
2455    async fn get_all_keys(&mut self, query: Query) -> Result<Vec<String>, IndexedDBError> {
2456        ObjectStore::get_all_keys(self, query, None).await
2457    }
2458
2459    async fn count(&mut self, query: Query) -> Result<i64, IndexedDBError> {
2460        ObjectStore::count(self, query).await
2461    }
2462
2463    async fn delete_range(&mut self, query: Query) -> Result<i64, IndexedDBError> {
2464        ObjectStore::delete_range(self, query).await
2465    }
2466
2467    fn index(&self, name: &str) -> Index {
2468        ObjectStore::index(self, name)
2469    }
2470
2471    async fn open_cursor(
2472        &mut self,
2473        query: Query,
2474        direction: CursorDirection,
2475    ) -> Result<Cursor, IndexedDBError> {
2476        ObjectStore::open_cursor(self, query, direction).await
2477    }
2478
2479    async fn open_key_cursor(
2480        &mut self,
2481        query: Query,
2482        direction: CursorDirection,
2483    ) -> Result<Cursor, IndexedDBError> {
2484        ObjectStore::open_key_cursor(self, query, direction).await
2485    }
2486}
2487
2488#[async_trait]
2489impl IndexApi for Index {
2490    type Cursor = Cursor;
2491
2492    async fn get(&mut self, query: Query) -> Result<Record, IndexedDBError> {
2493        Index::get(self, query).await
2494    }
2495
2496    async fn get_key(&mut self, query: Query) -> Result<String, IndexedDBError> {
2497        Index::get_key(self, query).await
2498    }
2499
2500    async fn get_all(&mut self, query: Query) -> Result<Vec<Record>, IndexedDBError> {
2501        Index::get_all(self, query, None).await
2502    }
2503
2504    async fn get_all_keys(&mut self, query: Query) -> Result<Vec<String>, IndexedDBError> {
2505        Index::get_all_keys(self, query, None).await
2506    }
2507
2508    async fn count(&mut self, query: Query) -> Result<i64, IndexedDBError> {
2509        Index::count(self, query).await
2510    }
2511
2512    async fn delete(&mut self, query: Query) -> Result<i64, IndexedDBError> {
2513        Index::delete(self, query).await
2514    }
2515
2516    async fn open_cursor(
2517        &mut self,
2518        query: Query,
2519        direction: CursorDirection,
2520    ) -> Result<Cursor, IndexedDBError> {
2521        Index::open_cursor(self, query, direction).await
2522    }
2523
2524    async fn open_key_cursor(
2525        &mut self,
2526        query: Query,
2527        direction: CursorDirection,
2528    ) -> Result<Cursor, IndexedDBError> {
2529        Index::open_key_cursor(self, query, direction).await
2530    }
2531}
2532
2533#[async_trait]
2534impl TransactionApi for Transaction {
2535    type ObjectStore<'a> = TransactionObjectStore<'a>;
2536
2537    fn object_store<'a>(&'a mut self, name: &str) -> TransactionObjectStore<'a> {
2538        Transaction::object_store(self, name)
2539    }
2540
2541    async fn commit(&mut self) -> Result<(), IndexedDBError> {
2542        Transaction::commit(self).await
2543    }
2544
2545    async fn abort(&mut self, reason: &str) -> Result<(), IndexedDBError> {
2546        Transaction::abort(self, reason).await
2547    }
2548}
2549
2550#[async_trait]
2551impl<'tx> TransactionObjectStoreApi for TransactionObjectStore<'tx> {
2552    type Index<'a>
2553        = TransactionIndex<'a>
2554    where
2555        Self: 'a;
2556
2557    async fn get(&mut self, id: &str) -> Result<Record, IndexedDBError> {
2558        TransactionObjectStore::get(self, id).await
2559    }
2560
2561    async fn get_key(&mut self, id: &str) -> Result<String, IndexedDBError> {
2562        TransactionObjectStore::get_key(self, id).await
2563    }
2564
2565    async fn add(&mut self, record: Record) -> Result<(), IndexedDBError> {
2566        TransactionObjectStore::add(self, record).await
2567    }
2568
2569    async fn put(&mut self, record: Record) -> Result<(), IndexedDBError> {
2570        TransactionObjectStore::put(self, record).await
2571    }
2572
2573    async fn delete(&mut self, id: &str) -> Result<(), IndexedDBError> {
2574        TransactionObjectStore::delete(self, id).await
2575    }
2576
2577    async fn clear(&mut self) -> Result<(), IndexedDBError> {
2578        TransactionObjectStore::clear(self).await
2579    }
2580
2581    async fn get_all(&mut self, query: Query) -> Result<Vec<Record>, IndexedDBError> {
2582        TransactionObjectStore::get_all(self, query, None).await
2583    }
2584
2585    async fn get_all_keys(&mut self, query: Query) -> Result<Vec<String>, IndexedDBError> {
2586        TransactionObjectStore::get_all_keys(self, query, None).await
2587    }
2588
2589    async fn count(&mut self, query: Query) -> Result<i64, IndexedDBError> {
2590        TransactionObjectStore::count(self, query).await
2591    }
2592
2593    async fn delete_range(&mut self, query: Query) -> Result<i64, IndexedDBError> {
2594        TransactionObjectStore::delete_range(self, query).await
2595    }
2596
2597    fn index<'a>(&'a mut self, name: &str) -> TransactionIndex<'a> {
2598        TransactionObjectStore::index(self, name)
2599    }
2600}
2601
2602#[async_trait]
2603impl TransactionIndexApi for TransactionIndex<'_> {
2604    async fn get(&mut self, query: Query) -> Result<Record, IndexedDBError> {
2605        TransactionIndex::get(self, query).await
2606    }
2607
2608    async fn get_key(&mut self, query: Query) -> Result<String, IndexedDBError> {
2609        TransactionIndex::get_key(self, query).await
2610    }
2611
2612    async fn get_all(&mut self, query: Query) -> Result<Vec<Record>, IndexedDBError> {
2613        TransactionIndex::get_all(self, query, None).await
2614    }
2615
2616    async fn get_all_keys(&mut self, query: Query) -> Result<Vec<String>, IndexedDBError> {
2617        TransactionIndex::get_all_keys(self, query, None).await
2618    }
2619
2620    async fn count(&mut self, query: Query) -> Result<i64, IndexedDBError> {
2621        TransactionIndex::count(self, query).await
2622    }
2623
2624    async fn delete(&mut self, query: Query) -> Result<i64, IndexedDBError> {
2625        TransactionIndex::delete(self, query).await
2626    }
2627}
2628
2629#[async_trait]
2630impl CursorApi for Cursor {
2631    fn key(&self) -> Option<Key> {
2632        Cursor::key(self)
2633    }
2634
2635    fn primary_key(&self) -> &str {
2636        Cursor::primary_key(self)
2637    }
2638
2639    fn value(&self) -> Result<Record, IndexedDBError> {
2640        Cursor::value(self)
2641    }
2642
2643    async fn continue_next(&mut self) -> Result<bool, IndexedDBError> {
2644        Cursor::continue_next(self).await
2645    }
2646
2647    async fn continue_to_key(
2648        &mut self,
2649        key: impl Into<Key> + Send,
2650    ) -> Result<bool, IndexedDBError> {
2651        Cursor::continue_to_key(self, key).await
2652    }
2653
2654    async fn advance(&mut self, count: i32) -> Result<bool, IndexedDBError> {
2655        Cursor::advance(self, count).await
2656    }
2657
2658    async fn delete(&mut self) -> Result<(), IndexedDBError> {
2659        Cursor::delete(self).await
2660    }
2661
2662    async fn update(&mut self, value: Record) -> Result<(), IndexedDBError> {
2663        Cursor::update(self, value).await
2664    }
2665
2666    async fn close(self) -> Result<(), IndexedDBError> {
2667        Cursor::close(self).await
2668    }
2669}
2670
2671fn map_status(err: tonic::Status) -> IndexedDBError {
2672    match err.code() {
2673        tonic::Code::NotFound => IndexedDBError::NotFound,
2674        tonic::Code::AlreadyExists => IndexedDBError::AlreadyExists,
2675        tonic::Code::InvalidArgument => IndexedDBError::InvalidArgument(err.message().to_string()),
2676        tonic::Code::FailedPrecondition => IndexedDBError::Transaction(err.message().to_string()),
2677        _ => IndexedDBError::Status(err),
2678    }
2679}
2680
2681fn map_rpc_status(
2682    status: Option<crate::generated::google::rpc::Status>,
2683) -> Result<(), IndexedDBError> {
2684    let Some(status) = status else {
2685        return Ok(());
2686    };
2687    match status.code {
2688        0 => Ok(()),
2689        5 => Err(IndexedDBError::NotFound),
2690        6 => Err(IndexedDBError::AlreadyExists),
2691        3 => Err(IndexedDBError::InvalidArgument(status.message)),
2692        9 => Err(IndexedDBError::Transaction(status.message)),
2693        _ => Err(IndexedDBError::Transaction(status.message)),
2694    }
2695}
2696
2697fn unexpected_transaction_result() -> IndexedDBError {
2698    IndexedDBError::Transaction("unexpected transaction operation result".to_string())
2699}
2700
2701fn record_to_pb_record(record: Record) -> pb::Record {
2702    pb::Record {
2703        fields: record
2704            .into_iter()
2705            .map(|(k, v)| (k, json_to_typed_value(&v)))
2706            .collect(),
2707    }
2708}
2709
2710fn pb_record_to_record(r: &pb::Record) -> Record {
2711    r.fields
2712        .iter()
2713        .map(|(k, v)| (k.clone(), typed_value_to_json(v)))
2714        .collect()
2715}
2716
2717fn json_to_typed_value(v: &serde_json::Value) -> pb::TypedValue {
2718    use pb::typed_value::Kind;
2719    let kind = match v {
2720        serde_json::Value::Null => Kind::NullValue(0),
2721        serde_json::Value::Bool(b) => Kind::BoolValue(*b),
2722        serde_json::Value::Number(n) => {
2723            if let Some(i) = n.as_i64() {
2724                Kind::IntValue(i)
2725            } else {
2726                Kind::FloatValue(n.as_f64().unwrap_or(0.0))
2727            }
2728        }
2729        serde_json::Value::String(s) => Kind::StringValue(s.clone()),
2730        serde_json::Value::Array(arr) => {
2731            let values = arr.iter().map(json_to_prost_value).collect();
2732            Kind::JsonValue(prost_types::Value {
2733                kind: Some(prost_types::value::Kind::ListValue(
2734                    prost_types::ListValue { values },
2735                )),
2736            })
2737        }
2738        serde_json::Value::Object(obj) => {
2739            let fields = obj
2740                .iter()
2741                .map(|(k, v)| (k.clone(), json_to_prost_value(v)))
2742                .collect();
2743            Kind::JsonValue(prost_types::Value {
2744                kind: Some(prost_types::value::Kind::StructValue(prost_types::Struct {
2745                    fields,
2746                })),
2747            })
2748        }
2749    };
2750    pb::TypedValue { kind: Some(kind) }
2751}
2752
2753fn prost_value_to_json(v: &prost_types::Value) -> serde_json::Value {
2754    use prost_types::value::Kind;
2755    match &v.kind {
2756        Some(Kind::NullValue(_)) => serde_json::Value::Null,
2757        Some(Kind::BoolValue(b)) => serde_json::Value::Bool(*b),
2758        Some(Kind::NumberValue(n)) => serde_json::json!(*n),
2759        Some(Kind::StringValue(s)) => serde_json::Value::String(s.clone()),
2760        Some(Kind::ListValue(list)) => {
2761            serde_json::Value::Array(list.values.iter().map(prost_value_to_json).collect())
2762        }
2763        Some(Kind::StructValue(st)) => {
2764            let obj: serde_json::Map<String, serde_json::Value> = st
2765                .fields
2766                .iter()
2767                .map(|(k, v)| (k.clone(), prost_value_to_json(v)))
2768                .collect();
2769            serde_json::Value::Object(obj)
2770        }
2771        None => serde_json::Value::Null,
2772    }
2773}
2774
2775fn json_to_prost_value(v: &serde_json::Value) -> prost_types::Value {
2776    use prost_types::value::Kind;
2777    let kind = match v {
2778        serde_json::Value::Null => Kind::NullValue(0),
2779        serde_json::Value::Bool(b) => Kind::BoolValue(*b),
2780        serde_json::Value::Number(n) => Kind::NumberValue(n.as_f64().unwrap_or(0.0)),
2781        serde_json::Value::String(s) => Kind::StringValue(s.clone()),
2782        serde_json::Value::Array(arr) => {
2783            let values = arr.iter().map(json_to_prost_value).collect();
2784            Kind::ListValue(prost_types::ListValue { values })
2785        }
2786        serde_json::Value::Object(obj) => {
2787            let fields = obj
2788                .iter()
2789                .map(|(k, v)| (k.clone(), json_to_prost_value(v)))
2790                .collect();
2791            Kind::StructValue(prost_types::Struct { fields })
2792        }
2793    };
2794    prost_types::Value { kind: Some(kind) }
2795}
2796
2797fn typed_value_to_json(v: &pb::TypedValue) -> serde_json::Value {
2798    use pb::typed_value::Kind;
2799    match &v.kind {
2800        Some(Kind::NullValue(_)) => serde_json::Value::Null,
2801        Some(Kind::BoolValue(b)) => serde_json::Value::Bool(*b),
2802        Some(Kind::IntValue(i)) => serde_json::json!(*i),
2803        Some(Kind::FloatValue(f)) => serde_json::json!(*f),
2804        Some(Kind::StringValue(s)) => serde_json::Value::String(s.clone()),
2805        Some(Kind::BytesValue(b)) => serde_json::json!(b),
2806        Some(Kind::JsonValue(pv)) => prost_value_to_json(pv),
2807        Some(Kind::TimeValue(ts)) => {
2808            serde_json::Value::String(format!("{}.{}", ts.seconds, ts.nanos))
2809        }
2810        None => serde_json::Value::Null,
2811    }
2812}
2813
2814fn relay_token_interceptor(
2815    token: &str,
2816    binding: &str,
2817) -> Result<RelayTokenInterceptor, IndexedDBError> {
2818    let relay_token = if token.trim().is_empty() {
2819        None
2820    } else {
2821        Some(MetadataValue::try_from(token.to_string()).map_err(|err| {
2822            IndexedDBError::Env(format!("invalid IndexedDB relay token metadata: {err}"))
2823        })?)
2824    };
2825    let binding = if binding.trim().is_empty() {
2826        None
2827    } else {
2828        Some(
2829            MetadataValue::try_from(binding.trim().to_string()).map_err(|err| {
2830                IndexedDBError::Env(format!("invalid IndexedDB binding metadata: {err}"))
2831            })?,
2832        )
2833    };
2834    Ok(RelayTokenInterceptor {
2835        relay_token,
2836        binding,
2837    })
2838}
2839
2840#[derive(Clone)]
2841struct RelayTokenInterceptor {
2842    relay_token: Option<MetadataValue<tonic::metadata::Ascii>>,
2843    binding: Option<MetadataValue<tonic::metadata::Ascii>>,
2844}
2845
2846impl Interceptor for RelayTokenInterceptor {
2847    fn call(&mut self, mut request: Request<()>) -> Result<Request<()>, tonic::Status> {
2848        if let Some(header) = self.relay_token.clone() {
2849            request
2850                .metadata_mut()
2851                .insert(INDEXEDDB_RELAY_TOKEN_HEADER, header);
2852        }
2853        if let Some(header) = self.binding.clone() {
2854            request
2855                .metadata_mut()
2856                .insert(HOST_SERVICE_BINDING_HEADER, header);
2857        }
2858        Ok(request)
2859    }
2860}