@valon-technologies/gestalt 0.0.1-alpha.19 → 0.0.1-alpha.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/indexeddb.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  import { connect } from "node:net";
2
2
 
3
- import { createClient, type Client, type Interceptor } from "@connectrpc/connect";
3
+ import {
4
+ createClient,
5
+ type Client,
6
+ type Interceptor,
7
+ } from "@connectrpc/connect";
4
8
  import { createGrpcTransport } from "@connectrpc/connect-node";
5
9
  import {
6
10
  IndexedDB as IndexedDBService,
@@ -41,27 +45,35 @@ function indexedDBTransportOptions(rawTarget: string): {
41
45
  if (target.startsWith("tcp://")) {
42
46
  const address = target.slice("tcp://".length).trim();
43
47
  if (!address) {
44
- throw new Error(`IndexedDB tcp target ${JSON.stringify(rawTarget)} is missing host:port`);
48
+ throw new Error(
49
+ `IndexedDB tcp target ${JSON.stringify(rawTarget)} is missing host:port`,
50
+ );
45
51
  }
46
52
  return { baseUrl: `http://${address}` };
47
53
  }
48
54
  if (target.startsWith("tls://")) {
49
55
  const address = target.slice("tls://".length).trim();
50
56
  if (!address) {
51
- throw new Error(`IndexedDB tls target ${JSON.stringify(rawTarget)} is missing host:port`);
57
+ throw new Error(
58
+ `IndexedDB tls target ${JSON.stringify(rawTarget)} is missing host:port`,
59
+ );
52
60
  }
53
61
  return { baseUrl: `https://${address}` };
54
62
  }
55
63
  if (target.startsWith("unix://")) {
56
64
  const socketPath = target.slice("unix://".length).trim();
57
65
  if (!socketPath) {
58
- throw new Error(`IndexedDB unix target ${JSON.stringify(rawTarget)} is missing a socket path`);
66
+ throw new Error(
67
+ `IndexedDB unix target ${JSON.stringify(rawTarget)} is missing a socket path`,
68
+ );
59
69
  }
60
70
  return { baseUrl: "http://localhost", nodeOptions: { path: socketPath } };
61
71
  }
62
72
  if (target.includes("://")) {
63
73
  const parsed = new URL(target);
64
- throw new Error(`Unsupported IndexedDB target scheme ${JSON.stringify(parsed.protocol.replace(/:$/, ""))}`);
74
+ throw new Error(
75
+ `Unsupported IndexedDB target scheme ${JSON.stringify(parsed.protocol.replace(/:$/, ""))}`,
76
+ );
65
77
  }
66
78
  return { baseUrl: "http://localhost", nodeOptions: { path: target } };
67
79
  }
@@ -127,7 +139,9 @@ export enum CursorDirection {
127
139
  PrevUnique = 3,
128
140
  }
129
141
 
130
- const CURSOR_DIRECTION_TO_PROTO: { [K in CursorDirection]: ProtoCursorDirection } = {
142
+ const CURSOR_DIRECTION_TO_PROTO: {
143
+ [K in CursorDirection]: ProtoCursorDirection;
144
+ } = {
131
145
  [CursorDirection.Next]: ProtoCursorDirection.CURSOR_NEXT,
132
146
  [CursorDirection.NextUnique]: ProtoCursorDirection.CURSOR_NEXT_UNIQUE,
133
147
  [CursorDirection.Prev]: ProtoCursorDirection.CURSOR_PREV,
@@ -173,7 +187,11 @@ export class Cursor {
173
187
  static async open(
174
188
  client: Client<typeof IndexedDBService>,
175
189
  store: string,
176
- options?: OpenCursorOptions & { keysOnly?: boolean; index?: string; indexValues?: unknown[] },
190
+ options?: OpenCursorOptions & {
191
+ keysOnly?: boolean;
192
+ index?: string;
193
+ indexValues?: unknown[];
194
+ },
177
195
  ): Promise<Cursor | null> {
178
196
  const sendQueue = new AsyncQueue<any>();
179
197
  const direction = options?.direction ?? CursorDirection.Next;
@@ -195,7 +213,7 @@ export class Cursor {
195
213
  const responses = client.openCursor(sendQueue);
196
214
  const responseIterator = responses[Symbol.asyncIterator]();
197
215
 
198
- const isIndex = !!(options?.index);
216
+ const isIndex = !!options?.index;
199
217
  const cursor = new Cursor(sendQueue, responseIterator, isIndex);
200
218
  // Read the open ack to surface creation errors synchronously.
201
219
  await cursor.recvOpenAck();
@@ -235,7 +253,10 @@ export class Cursor {
235
253
  */
236
254
  async continue(): Promise<boolean> {
237
255
  this.sendQueue.push({
238
- msg: { case: "command" as const, value: { command: { case: "next" as const, value: true } } },
256
+ msg: {
257
+ case: "command" as const,
258
+ value: { command: { case: "next" as const, value: true } },
259
+ },
239
260
  });
240
261
  return this.pull();
241
262
  }
@@ -293,7 +314,9 @@ export class Cursor {
293
314
  this.sendQueue.push({
294
315
  msg: {
295
316
  case: "command" as const,
296
- value: { command: { case: "update" as const, value: toProtoRecord(record) } },
317
+ value: {
318
+ command: { case: "update" as const, value: toProtoRecord(record) },
319
+ },
297
320
  },
298
321
  });
299
322
  await this.recvMutationAck();
@@ -510,6 +533,311 @@ export interface ObjectStoreSchema {
510
533
  columns?: ColumnSchema[];
511
534
  }
512
535
 
536
+ /**
537
+ * Native open-cursor request used by provider-side cursor helpers.
538
+ */
539
+ export interface IndexedDBOpenCursorRequest {
540
+ store?: string;
541
+ range?: KeyRange;
542
+ direction?: CursorDirection;
543
+ keysOnly?: boolean;
544
+ index?: string;
545
+ values?: unknown[];
546
+ }
547
+
548
+ /**
549
+ * One provider-side cursor row.
550
+ */
551
+ export interface IndexedDBCursorSnapshotEntry {
552
+ /** Object-store key, or secondary-index key for index cursors. */
553
+ key: unknown;
554
+ /** Canonical primary key for the object-store row. */
555
+ primaryKey: string;
556
+ /** Native primary-key value used as a stable tie-breaker for duplicate index keys. */
557
+ primaryKeyValue?: unknown;
558
+ /** Row value returned by full-value cursors. */
559
+ record?: Record;
560
+ }
561
+
562
+ /**
563
+ * Provider-side IndexedDB cursor snapshot.
564
+ *
565
+ * The snapshot sorts rows, applies IndexedDB range bounds, and implements
566
+ * movement semantics for native TypeScript providers without exposing wire
567
+ * message types.
568
+ */
569
+ export class IndexedDBCursorSnapshot {
570
+ /** Whether entry keys contain secondary-index values. */
571
+ indexCursor: boolean;
572
+ /** Whether returned cursor entries should omit records. */
573
+ keysOnly: boolean;
574
+ /** Whether entries are ordered from greatest to least key. */
575
+ reverse: boolean;
576
+ /** Whether duplicate index keys are collapsed while iterating. */
577
+ unique: boolean;
578
+ /** Sorted and range-filtered entries used by cursor movement. */
579
+ entries: IndexedDBCursorSnapshotEntry[] = [];
580
+ /** Current cursor position, or -1 when unpositioned. */
581
+ pos = -1;
582
+
583
+ constructor(request: IndexedDBOpenCursorRequest = {}) {
584
+ const direction = request.direction ?? CursorDirection.Next;
585
+ this.indexCursor = !!request.index;
586
+ this.keysOnly = request.keysOnly ?? false;
587
+ this.reverse =
588
+ direction === CursorDirection.Prev ||
589
+ direction === CursorDirection.PrevUnique;
590
+ this.unique =
591
+ direction === CursorDirection.NextUnique ||
592
+ direction === CursorDirection.PrevUnique;
593
+ }
594
+
595
+ /**
596
+ * Sorts entries, applies the supplied key range, and stores the snapshot.
597
+ */
598
+ load(entries: IndexedDBCursorSnapshotEntry[], range?: KeyRange): void {
599
+ const ordered = [...entries].sort((left, right) =>
600
+ this.compareEntries(left, right),
601
+ );
602
+ if (this.reverse) ordered.reverse();
603
+ this.entries = this.applyRange(ordered, range);
604
+ this.pos = -1;
605
+ }
606
+
607
+ /**
608
+ * Returns entries that satisfy the supplied key range without mutating state.
609
+ */
610
+ applyRange(
611
+ entries: IndexedDBCursorSnapshotEntry[],
612
+ range?: KeyRange,
613
+ ): IndexedDBCursorSnapshotEntry[] {
614
+ if (!range) return entries;
615
+ const [lower, upper] = indexedDBRangeBounds(range, this.indexCursor);
616
+ const filtered: IndexedDBCursorSnapshotEntry[] = [];
617
+ for (const entry of entries) {
618
+ const key = normalizeIndexedDBBound(entry.key, this.indexCursor);
619
+ if (lower !== undefined) {
620
+ const cmp = compareIndexedDBValues(key, lower);
621
+ if (range.lowerOpen && cmp <= 0) continue;
622
+ if (!range.lowerOpen && cmp < 0) continue;
623
+ }
624
+ if (upper !== undefined) {
625
+ const cmp = compareIndexedDBValues(key, upper);
626
+ if (range.upperOpen && cmp >= 0) continue;
627
+ if (!range.upperOpen && cmp > 0) continue;
628
+ }
629
+ filtered.push(entry);
630
+ }
631
+ return filtered;
632
+ }
633
+
634
+ /**
635
+ * Advances to the next entry, or returns undefined when exhausted.
636
+ */
637
+ next(): IndexedDBCursorSnapshotEntry | undefined {
638
+ if (
639
+ this.unique &&
640
+ this.indexCursor &&
641
+ this.pos >= 0 &&
642
+ this.pos < this.entries.length
643
+ ) {
644
+ const previous = this.entries[this.pos]!.key;
645
+ for (this.pos += 1; this.pos < this.entries.length; this.pos += 1) {
646
+ if (
647
+ compareIndexedDBValues(this.entries[this.pos]!.key, previous) !== 0
648
+ ) {
649
+ return this.current();
650
+ }
651
+ }
652
+ return undefined;
653
+ }
654
+
655
+ this.pos += 1;
656
+ if (this.pos >= this.entries.length) return undefined;
657
+ return this.current();
658
+ }
659
+
660
+ /**
661
+ * Advances to target or the next entry past target for this direction.
662
+ */
663
+ continueToKey(target: unknown): IndexedDBCursorSnapshotEntry | undefined {
664
+ const previous =
665
+ this.unique &&
666
+ this.indexCursor &&
667
+ this.pos >= 0 &&
668
+ this.pos < this.entries.length
669
+ ? this.entries[this.pos]!.key
670
+ : undefined;
671
+ for (this.pos += 1; this.pos < this.entries.length; this.pos += 1) {
672
+ const current = this.entries[this.pos]!.key;
673
+ if (
674
+ previous !== undefined &&
675
+ this.unique &&
676
+ this.indexCursor &&
677
+ compareIndexedDBValues(current, previous) === 0
678
+ ) {
679
+ continue;
680
+ }
681
+ const cmp = compareIndexedDBValues(current, target);
682
+ if (this.reverse) {
683
+ if (cmp <= 0) return this.current();
684
+ } else if (cmp >= 0) {
685
+ return this.current();
686
+ }
687
+ }
688
+ return undefined;
689
+ }
690
+
691
+ /**
692
+ * Skips count entries and returns the new current entry.
693
+ */
694
+ advance(count: number): IndexedDBCursorSnapshotEntry | undefined {
695
+ if (count <= 0) throw new RangeError("advance count must be positive");
696
+ let entry: IndexedDBCursorSnapshotEntry | undefined;
697
+ for (let i = 0; i < count; i += 1) {
698
+ entry = this.next();
699
+ if (!entry) return undefined;
700
+ }
701
+ return entry;
702
+ }
703
+
704
+ /**
705
+ * Returns the currently positioned entry.
706
+ */
707
+ current(): IndexedDBCursorSnapshotEntry {
708
+ if (this.pos < 0 || this.pos >= this.entries.length) {
709
+ throw new NotFoundError("cursor is not positioned");
710
+ }
711
+ return this.entries[this.pos]!;
712
+ }
713
+
714
+ private compareEntries(
715
+ left: IndexedDBCursorSnapshotEntry,
716
+ right: IndexedDBCursorSnapshotEntry,
717
+ ): number {
718
+ let cmp = compareIndexedDBValues(left.key, right.key);
719
+ if (cmp === 0) {
720
+ cmp = compareIndexedDBValues(
721
+ left.primaryKeyValue ?? left.primaryKey,
722
+ right.primaryKeyValue ?? right.primaryKey,
723
+ );
724
+ }
725
+ return cmp;
726
+ }
727
+ }
728
+
729
+ /**
730
+ * Creates an empty provider-side cursor snapshot from a native request.
731
+ */
732
+ export function newIndexedDBCursorSnapshot(
733
+ request: IndexedDBOpenCursorRequest,
734
+ ): IndexedDBCursorSnapshot {
735
+ return new IndexedDBCursorSnapshot(request);
736
+ }
737
+
738
+ /**
739
+ * Normalizes object-store or index cursor range bounds.
740
+ *
741
+ * Scalar index bounds are compared as one-part composite keys so providers can
742
+ * share the same comparison path for scalar and compound indexes.
743
+ */
744
+ export function indexedDBRangeBounds(
745
+ range: KeyRange | undefined,
746
+ indexCursor: boolean,
747
+ ): [unknown, unknown] {
748
+ if (!range) return [undefined, undefined];
749
+ const lower =
750
+ range.lower !== undefined
751
+ ? normalizeIndexedDBBound(range.lower, indexCursor)
752
+ : undefined;
753
+ const upper =
754
+ range.upper !== undefined
755
+ ? normalizeIndexedDBBound(range.upper, indexCursor)
756
+ : undefined;
757
+ return [lower, upper];
758
+ }
759
+
760
+ /**
761
+ * Compares native IndexedDB key values.
762
+ */
763
+ export function compareIndexedDBValues(left: unknown, right: unknown): number {
764
+ if (Array.isArray(left) && Array.isArray(right)) {
765
+ for (let i = 0; i < left.length; i += 1) {
766
+ if (i >= right.length) return 1;
767
+ const cmp = compareIndexedDBValues(left[i], right[i]);
768
+ if (cmp !== 0) return cmp;
769
+ }
770
+ if (left.length < right.length) return -1;
771
+ return 0;
772
+ }
773
+ if (typeof left === "string" && typeof right === "string")
774
+ return compareScalars(left, right);
775
+ if (left instanceof Date && right instanceof Date)
776
+ return compareScalars(left.getTime(), right.getTime());
777
+ if (isBytes(left) && isBytes(right))
778
+ return compareBytes(byteView(left), byteView(right));
779
+ if (typeof left === "boolean" && typeof right === "boolean")
780
+ return compareScalars(Number(left), Number(right));
781
+ if (isNumeric(left) && isNumeric(right)) return compareNumeric(left, right);
782
+ return compareScalars(String(left), String(right));
783
+ }
784
+
785
+ function normalizeIndexedDBBound(
786
+ value: unknown,
787
+ indexCursor: boolean,
788
+ ): unknown {
789
+ if (!indexCursor) return value;
790
+ if (Array.isArray(value)) return [...value];
791
+ return [value];
792
+ }
793
+
794
+ function compareScalars<T extends bigint | number | string>(
795
+ left: T,
796
+ right: T,
797
+ ): number {
798
+ if (left < right) return -1;
799
+ if (left > right) return 1;
800
+ return 0;
801
+ }
802
+
803
+ function compareNumeric(left: number | bigint, right: number | bigint): number {
804
+ if (typeof left === "number" && typeof right === "number")
805
+ return compareScalars(left, right);
806
+ if (typeof left === "bigint" && typeof right === "bigint")
807
+ return compareScalars(left, right);
808
+ return compareMixedNumeric(left, right);
809
+ }
810
+
811
+ function compareMixedNumeric(
812
+ left: number | bigint,
813
+ right: number | bigint,
814
+ ): number {
815
+ if (left < right) return -1;
816
+ if (left > right) return 1;
817
+ return 0;
818
+ }
819
+
820
+ function isNumeric(value: unknown): value is number | bigint {
821
+ return typeof value === "number" || typeof value === "bigint";
822
+ }
823
+
824
+ function isBytes(value: unknown): value is ArrayBuffer | Uint8Array {
825
+ return value instanceof ArrayBuffer || value instanceof Uint8Array;
826
+ }
827
+
828
+ function byteView(value: ArrayBuffer | Uint8Array): Uint8Array {
829
+ return value instanceof Uint8Array ? value : new Uint8Array(value);
830
+ }
831
+
832
+ function compareBytes(left: Uint8Array, right: Uint8Array): number {
833
+ const length = Math.min(left.length, right.length);
834
+ for (let i = 0; i < length; i += 1) {
835
+ const cmp = compareScalars(left[i]!, right[i]!);
836
+ if (cmp !== 0) return cmp;
837
+ }
838
+ return compareScalars(left.length, right.length);
839
+ }
840
+
513
841
  /**
514
842
  * Client for invoking a host-provided IndexedDB service over the Gestalt transport.
515
843
  *
@@ -524,7 +852,7 @@ export interface ObjectStoreSchema {
524
852
  export class IndexedDB {
525
853
  private client: Client<typeof IndexedDBService>;
526
854
 
527
- constructor(name?: string) {
855
+ constructor(name?: string) {
528
856
  const envName = indexedDBSocketEnv(name);
529
857
  const target = process.env[envName];
530
858
  if (!target) {
@@ -550,7 +878,10 @@ export class IndexedDB {
550
878
  /**
551
879
  * Creates an object store.
552
880
  */
553
- async createObjectStore(name: string, schema?: ObjectStoreSchema): Promise<void> {
881
+ async createObjectStore(
882
+ name: string,
883
+ schema?: ObjectStoreSchema,
884
+ ): Promise<void> {
554
885
  await this.client.createObjectStore({
555
886
  name,
556
887
  schema: {
@@ -613,7 +944,10 @@ export class Transaction {
613
944
  private requestId = 0n;
614
945
  private streamChain: Promise<void> = Promise.resolve();
615
946
 
616
- private constructor(sendQueue: AsyncQueue<any>, responseIterator: AsyncIterator<any>) {
947
+ private constructor(
948
+ sendQueue: AsyncQueue<any>,
949
+ responseIterator: AsyncIterator<any>,
950
+ ) {
617
951
  this.sendQueue = sendQueue;
618
952
  this.responseIterator = responseIterator;
619
953
  }
@@ -634,7 +968,9 @@ export class Transaction {
634
968
  value: {
635
969
  stores,
636
970
  mode: toProtoTransactionMode(mode),
637
- durabilityHint: toProtoTransactionDurabilityHint(options?.durabilityHint ?? "default"),
971
+ durabilityHint: toProtoTransactionDurabilityHint(
972
+ options?.durabilityHint ?? "default",
973
+ ),
638
974
  },
639
975
  },
640
976
  });
@@ -697,7 +1033,9 @@ export class Transaction {
697
1033
  return this.withStreamLock(async () => {
698
1034
  if (this.closed) return;
699
1035
  this.closed = true;
700
- this.sendQueue.push({ msg: { case: "abort" as const, value: { reason } } });
1036
+ this.sendQueue.push({
1037
+ msg: { case: "abort" as const, value: { reason } },
1038
+ });
701
1039
  try {
702
1040
  const { value: resp, done } = await this.responseIterator.next();
703
1041
  this.sendQueue.end();
@@ -733,7 +1071,9 @@ export class Transaction {
733
1071
  const { value: resp, done } = await this.responseIterator.next();
734
1072
  if (done || !resp) {
735
1073
  this.closeLocally();
736
- throw new TransactionError("transaction stream ended during operation");
1074
+ throw new TransactionError(
1075
+ "transaction stream ended during operation",
1076
+ );
737
1077
  }
738
1078
  if (resp.msg?.case !== "operation") {
739
1079
  this.closeLocally();
@@ -742,7 +1082,9 @@ export class Transaction {
742
1082
  const opResp = resp.msg.value;
743
1083
  if (opResp.requestId !== requestId) {
744
1084
  this.closeLocally();
745
- throw new TransactionError("transaction response request id mismatch");
1085
+ throw new TransactionError(
1086
+ "transaction response request id mismatch",
1087
+ );
746
1088
  }
747
1089
  raiseStatus(opResp.error);
748
1090
  return opResp;
@@ -768,7 +1110,8 @@ export class Transaction {
768
1110
  }
769
1111
 
770
1112
  private ensureOpen(): void {
771
- if (this.closed) throw new TransactionError("transaction is already finished");
1113
+ if (this.closed)
1114
+ throw new TransactionError("transaction is already finished");
772
1115
  }
773
1116
 
774
1117
  private closeLocally(): void {
@@ -857,7 +1200,10 @@ export class TransactionObjectStore {
857
1200
  async getAll(keyRange?: KeyRange): Promise<Record[]> {
858
1201
  const resp = await this.tx.sendOperation({
859
1202
  case: "getAll" as const,
860
- value: { store: this.store, range: keyRange ? toProtoKeyRange(keyRange) : undefined },
1203
+ value: {
1204
+ store: this.store,
1205
+ range: keyRange ? toProtoKeyRange(keyRange) : undefined,
1206
+ },
861
1207
  });
862
1208
  return resp.result.value.records.map((r: any) => fromProtoRecord(r));
863
1209
  }
@@ -868,7 +1214,10 @@ export class TransactionObjectStore {
868
1214
  async getAllKeys(keyRange?: KeyRange): Promise<string[]> {
869
1215
  const resp = await this.tx.sendOperation({
870
1216
  case: "getAllKeys" as const,
871
- value: { store: this.store, range: keyRange ? toProtoKeyRange(keyRange) : undefined },
1217
+ value: {
1218
+ store: this.store,
1219
+ range: keyRange ? toProtoKeyRange(keyRange) : undefined,
1220
+ },
872
1221
  });
873
1222
  return resp.result.value.keys;
874
1223
  }
@@ -879,7 +1228,10 @@ export class TransactionObjectStore {
879
1228
  async count(keyRange?: KeyRange): Promise<number> {
880
1229
  const resp = await this.tx.sendOperation({
881
1230
  case: "count" as const,
882
- value: { store: this.store, range: keyRange ? toProtoKeyRange(keyRange) : undefined },
1231
+ value: {
1232
+ store: this.store,
1233
+ range: keyRange ? toProtoKeyRange(keyRange) : undefined,
1234
+ },
883
1235
  });
884
1236
  return Number(resp.result.value.count);
885
1237
  }
@@ -952,7 +1304,10 @@ export class TransactionIndex {
952
1304
  /**
953
1305
  * Reads all indexed primary keys inside the transaction.
954
1306
  */
955
- async getAllKeys(keyRange?: KeyRange, ...values: unknown[]): Promise<string[]> {
1307
+ async getAllKeys(
1308
+ keyRange?: KeyRange,
1309
+ ...values: unknown[]
1310
+ ): Promise<string[]> {
956
1311
  const resp = await this.tx.sendOperation({
957
1312
  case: "indexGetAllKeys" as const,
958
1313
  value: this.indexRequest(values, keyRange),
@@ -1024,14 +1379,18 @@ export class ObjectStore {
1024
1379
  * Inserts a new record and fails if it already exists.
1025
1380
  */
1026
1381
  async add(record: Record): Promise<void> {
1027
- await rpc(() => this.client.add({ store: this.store, record: toProtoRecord(record) }));
1382
+ await rpc(() =>
1383
+ this.client.add({ store: this.store, record: toProtoRecord(record) }),
1384
+ );
1028
1385
  }
1029
1386
 
1030
1387
  /**
1031
1388
  * Inserts or replaces a record.
1032
1389
  */
1033
1390
  async put(record: Record): Promise<void> {
1034
- await rpc(() => this.client.put({ store: this.store, record: toProtoRecord(record) }));
1391
+ await rpc(() =>
1392
+ this.client.put({ store: this.store, record: toProtoRecord(record) }),
1393
+ );
1035
1394
  }
1036
1395
 
1037
1396
  /**
@@ -1171,7 +1530,10 @@ export class Index {
1171
1530
  /**
1172
1531
  * Reads all primary keys matching the supplied index values and optional range.
1173
1532
  */
1174
- async getAllKeys(keyRange?: KeyRange, ...values: unknown[]): Promise<string[]> {
1533
+ async getAllKeys(
1534
+ keyRange?: KeyRange,
1535
+ ...values: unknown[]
1536
+ ): Promise<string[]> {
1175
1537
  const resp = await this.client.indexGetAllKeys({
1176
1538
  store: this.store,
1177
1539
  index: this.indexName,
@@ -1209,7 +1571,10 @@ export class Index {
1209
1571
  /**
1210
1572
  * Opens a cursor over the index.
1211
1573
  */
1212
- async openCursor(options?: OpenCursorOptions, ...values: unknown[]): Promise<Cursor | null> {
1574
+ async openCursor(
1575
+ options?: OpenCursorOptions,
1576
+ ...values: unknown[]
1577
+ ): Promise<Cursor | null> {
1213
1578
  return Cursor.open(this.client, this.store, {
1214
1579
  ...options,
1215
1580
  index: this.indexName,
@@ -1220,7 +1585,10 @@ export class Index {
1220
1585
  /**
1221
1586
  * Opens a key-only cursor over the index.
1222
1587
  */
1223
- async openKeyCursor(options?: OpenCursorOptions, ...values: unknown[]): Promise<Cursor | null> {
1588
+ async openKeyCursor(
1589
+ options?: OpenCursorOptions,
1590
+ ...values: unknown[]
1591
+ ): Promise<Cursor | null> {
1224
1592
  return Cursor.open(this.client, this.store, {
1225
1593
  ...options,
1226
1594
  keysOnly: true,
@@ -1232,13 +1600,19 @@ export class Index {
1232
1600
 
1233
1601
  function fromProtoKeyValue(kv: any): unknown {
1234
1602
  if (kv.kind?.case === "scalar") return fromProtoTypedValue(kv.kind.value);
1235
- if (kv.kind?.case === "array") return kv.kind.value.elements.map(fromProtoKeyValue);
1603
+ if (kv.kind?.case === "array")
1604
+ return kv.kind.value.elements.map(fromProtoKeyValue);
1236
1605
  return undefined;
1237
1606
  }
1238
1607
 
1239
1608
  function toProtoKeyValue(v: unknown): any {
1240
1609
  if (Array.isArray(v)) {
1241
- return { kind: { case: "array" as const, value: { elements: v.map(toProtoKeyValue) } } };
1610
+ return {
1611
+ kind: {
1612
+ case: "array" as const,
1613
+ value: { elements: v.map(toProtoKeyValue) },
1614
+ },
1615
+ };
1242
1616
  }
1243
1617
  return { kind: { case: "scalar" as const, value: toProtoTypedValue(v) } };
1244
1618
  }
@@ -1268,7 +1642,8 @@ function fromProtoRecord(record: any): Record {
1268
1642
  }
1269
1643
 
1270
1644
  function toProtoTypedValue(v: unknown): any {
1271
- if (v === null || v === undefined) return { kind: { case: "nullValue", value: 0 } };
1645
+ if (v === null || v === undefined)
1646
+ return { kind: { case: "nullValue", value: 0 } };
1272
1647
  if (typeof v === "boolean") return { kind: { case: "boolValue", value: v } };
1273
1648
  if (typeof v === "bigint") return { kind: { case: "intValue", value: v } };
1274
1649
  if (typeof v === "number") {
@@ -1278,9 +1653,12 @@ function toProtoTypedValue(v: unknown): any {
1278
1653
  return { kind: { case: "floatValue", value: v } };
1279
1654
  }
1280
1655
  if (typeof v === "string") return { kind: { case: "stringValue", value: v } };
1281
- if (v instanceof Date) return { kind: { case: "timeValue", value: timestampFromDate(v) } };
1282
- if (v instanceof Uint8Array) return { kind: { case: "bytesValue", value: v } };
1283
- if (v instanceof ArrayBuffer) return { kind: { case: "bytesValue", value: new Uint8Array(v) } };
1656
+ if (v instanceof Date)
1657
+ return { kind: { case: "timeValue", value: timestampFromDate(v) } };
1658
+ if (v instanceof Uint8Array)
1659
+ return { kind: { case: "bytesValue", value: v } };
1660
+ if (v instanceof ArrayBuffer)
1661
+ return { kind: { case: "bytesValue", value: new Uint8Array(v) } };
1284
1662
  return { kind: { case: "jsonValue", value: toProtoJsonValue(v) } };
1285
1663
  }
1286
1664
 
@@ -1323,11 +1701,18 @@ function toJsInt(value: bigint): number | bigint {
1323
1701
  }
1324
1702
 
1325
1703
  function toProtoJsonValue(value: unknown): any {
1326
- if (value === null || value === undefined) return { kind: { case: "nullValue", value: 0 } };
1704
+ if (value === null || value === undefined)
1705
+ return { kind: { case: "nullValue", value: 0 } };
1327
1706
  if (typeof value === "boolean") return { kind: { case: "boolValue", value } };
1328
- if (typeof value === "number") return { kind: { case: "numberValue", value } };
1329
- if (typeof value === "string") return { kind: { case: "stringValue", value } };
1330
- if (value instanceof Date || value instanceof Uint8Array || value instanceof ArrayBuffer) {
1707
+ if (typeof value === "number")
1708
+ return { kind: { case: "numberValue", value } };
1709
+ if (typeof value === "string")
1710
+ return { kind: { case: "stringValue", value } };
1711
+ if (
1712
+ value instanceof Date ||
1713
+ value instanceof Uint8Array ||
1714
+ value instanceof ArrayBuffer
1715
+ ) {
1331
1716
  throw new Error(`unsupported JSON value type: ${value.constructor.name}`);
1332
1717
  }
1333
1718
  if (Array.isArray(value)) {
@@ -1340,7 +1725,9 @@ function toProtoJsonValue(value: unknown): any {
1340
1725
  }
1341
1726
  if (typeof value === "object") {
1342
1727
  const fields: { [key: string]: unknown } = {};
1343
- for (const [key, inner] of Object.entries(value as { [key: string]: unknown })) {
1728
+ for (const [key, inner] of Object.entries(
1729
+ value as { [key: string]: unknown },
1730
+ )) {
1344
1731
  fields[key] = toProtoJsonValue(inner);
1345
1732
  }
1346
1733
  return {
@@ -1363,16 +1750,22 @@ function fromProtoJsonValue(value: any): unknown {
1363
1750
  case "boolValue":
1364
1751
  return value.kind.value;
1365
1752
  case "listValue":
1366
- return (value.kind.value?.values ?? []).map((item: unknown) => fromProtoJsonValue(item));
1753
+ return (value.kind.value?.values ?? []).map((item: unknown) =>
1754
+ fromProtoJsonValue(item),
1755
+ );
1367
1756
  case "structValue": {
1368
1757
  const out: Record = {};
1369
- for (const [key, inner] of Object.entries(value.kind.value?.fields ?? {})) {
1758
+ for (const [key, inner] of Object.entries(
1759
+ value.kind.value?.fields ?? {},
1760
+ )) {
1370
1761
  out[key] = fromProtoJsonValue(inner);
1371
1762
  }
1372
1763
  return out;
1373
1764
  }
1374
1765
  default:
1375
- throw new Error(`unsupported JSON value kind: ${String(value?.kind?.case)}`);
1766
+ throw new Error(
1767
+ `unsupported JSON value kind: ${String(value?.kind?.case)}`,
1768
+ );
1376
1769
  }
1377
1770
  }
1378
1771
 
@@ -1393,11 +1786,15 @@ function toProtoTransactionMode(mode: TransactionMode): ProtoTransactionMode {
1393
1786
  case "readwrite":
1394
1787
  return ProtoTransactionMode.TRANSACTION_READWRITE;
1395
1788
  default:
1396
- throw new TransactionError(`unsupported transaction mode: ${String(mode)}`);
1789
+ throw new TransactionError(
1790
+ `unsupported transaction mode: ${String(mode)}`,
1791
+ );
1397
1792
  }
1398
1793
  }
1399
1794
 
1400
- function toProtoTransactionDurabilityHint(hint: TransactionDurabilityHint): ProtoTransactionDurabilityHint {
1795
+ function toProtoTransactionDurabilityHint(
1796
+ hint: TransactionDurabilityHint,
1797
+ ): ProtoTransactionDurabilityHint {
1401
1798
  switch (hint) {
1402
1799
  case "default":
1403
1800
  return ProtoTransactionDurabilityHint.TRANSACTION_DURABILITY_DEFAULT;
@@ -1406,7 +1803,9 @@ function toProtoTransactionDurabilityHint(hint: TransactionDurabilityHint): Prot
1406
1803
  case "relaxed":
1407
1804
  return ProtoTransactionDurabilityHint.TRANSACTION_DURABILITY_RELAXED;
1408
1805
  default:
1409
- throw new TransactionError(`unsupported transaction durability hint: ${String(hint)}`);
1806
+ throw new TransactionError(
1807
+ `unsupported transaction durability hint: ${String(hint)}`,
1808
+ );
1410
1809
  }
1411
1810
  }
1412
1811
 
@@ -1418,11 +1817,16 @@ function raiseStatus(status: any): void {
1418
1817
  }
1419
1818
 
1420
1819
  function mapTransactionTransportError(err: any): never {
1421
- if (err instanceof NotFoundError || err instanceof AlreadyExistsError || err instanceof TransactionError) {
1820
+ if (
1821
+ err instanceof NotFoundError ||
1822
+ err instanceof AlreadyExistsError ||
1823
+ err instanceof TransactionError
1824
+ ) {
1422
1825
  throw err;
1423
1826
  }
1424
1827
  if (err?.code === 5) throw new NotFoundError(err.message);
1425
1828
  if (err?.code === 6) throw new AlreadyExistsError(err.message);
1426
- if (err?.code === 3 || err?.code === 9) throw new TransactionError(err.message);
1829
+ if (err?.code === 3 || err?.code === 9)
1830
+ throw new TransactionError(err.message);
1427
1831
  throw err;
1428
1832
  }