@valon-technologies/gestalt 0.0.1-alpha.20 → 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/README.md +4 -6
- package/package.json +1 -4
- package/src/agent-conversions.ts +69 -2
- package/src/agent-manager.ts +26 -22
- package/src/agent.ts +33 -27
- package/src/api.ts +19 -0
- package/src/authorization.ts +0 -63
- package/src/index.ts +48 -60
- package/src/indexeddb.ts +450 -46
- package/src/internal/gen/v1/agent_pb.ts +103 -162
- package/src/internal/gen/v1/authentication_pb.ts +1 -1
- package/src/internal/gen/v1/authorization_pb.ts +1 -1
- package/src/internal/gen/v1/cache_pb.ts +1 -1
- package/src/internal/gen/v1/datastore_pb.ts +1 -1
- package/src/internal/gen/v1/external_credential_pb.ts +1 -1
- package/src/internal/gen/v1/plugin_pb.ts +94 -16
- package/src/internal/gen/v1/pluginruntime_pb.ts +1 -1
- package/src/internal/gen/v1/runtime_pb.ts +1 -1
- package/src/internal/gen/v1/s3_pb.ts +1 -1
- package/src/internal/gen/v1/secrets_pb.ts +1 -1
- package/src/internal/gen/v1/workflow_pb.ts +170 -74
- package/src/plugin.ts +4 -4
- package/src/runtime.ts +17 -4
- package/src/workflow-manager.ts +53 -57
- package/src/workflow.ts +231 -163
- package/src/protocol/v1.ts +0 -19
- package/src/test-agent-contract.ts +0 -43
package/src/indexeddb.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { connect } from "node:net";
|
|
2
2
|
|
|
3
|
-
import {
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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: {
|
|
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 & {
|
|
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 = !!
|
|
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: {
|
|
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: {
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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({
|
|
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(
|
|
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(
|
|
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)
|
|
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: {
|
|
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: {
|
|
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: {
|
|
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(
|
|
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(() =>
|
|
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(() =>
|
|
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(
|
|
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(
|
|
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(
|
|
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")
|
|
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 {
|
|
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)
|
|
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)
|
|
1282
|
-
|
|
1283
|
-
if (v instanceof
|
|
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)
|
|
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")
|
|
1329
|
-
|
|
1330
|
-
if (
|
|
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(
|
|
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) =>
|
|
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(
|
|
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(
|
|
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(
|
|
1789
|
+
throw new TransactionError(
|
|
1790
|
+
`unsupported transaction mode: ${String(mode)}`,
|
|
1791
|
+
);
|
|
1397
1792
|
}
|
|
1398
1793
|
}
|
|
1399
1794
|
|
|
1400
|
-
function toProtoTransactionDurabilityHint(
|
|
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(
|
|
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 (
|
|
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)
|
|
1829
|
+
if (err?.code === 3 || err?.code === 9)
|
|
1830
|
+
throw new TransactionError(err.message);
|
|
1427
1831
|
throw err;
|
|
1428
1832
|
}
|