@peerbit/indexer-tests 0.0.1-cccc078

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/tests.ts ADDED
@@ -0,0 +1,3651 @@
1
+ import {
2
+ deserialize,
3
+ field,
4
+ fixedArray,
5
+ option,
6
+ serialize,
7
+ variant,
8
+ vec,
9
+ } from "@dao-xyz/borsh";
10
+ import { randomBytes } from "@peerbit/crypto";
11
+ import {
12
+ And,
13
+ BoolQuery,
14
+ ByteMatchQuery,
15
+ Compare,
16
+ CountRequest,
17
+ DeleteRequest,
18
+ type Index,
19
+ type IndexEngineInitProperties,
20
+ type Indices,
21
+ IntegerCompare,
22
+ IsNull,
23
+ Nested,
24
+ Not,
25
+ Or,
26
+ Query,
27
+ SearchRequest,
28
+ type Shape,
29
+ Sort,
30
+ SortDirection,
31
+ StringMatch,
32
+ StringMatchMethod,
33
+ SumRequest,
34
+ extractFieldValue,
35
+ getIdProperty,
36
+ id,
37
+ iterate,
38
+ toId,
39
+ } from "@peerbit/indexer-interface";
40
+ import {
41
+ /* delay, */
42
+ delay,
43
+ waitForResolved,
44
+ } from "@peerbit/time";
45
+ import { expect } from "chai";
46
+ import sodium from "libsodium-wrappers";
47
+ import { equals } from "uint8arrays";
48
+ import { v4 as uuid } from "uuid";
49
+
50
+ @variant("nested_object")
51
+ class NestedValue {
52
+ @field({ type: "u32" })
53
+ number: number;
54
+
55
+ constructor(properties: { number: number }) {
56
+ this.number = properties.number;
57
+ }
58
+ }
59
+
60
+ abstract class Base {}
61
+
62
+ @variant(0)
63
+ class Document extends Base {
64
+ @field({ type: "string" })
65
+ id: string;
66
+
67
+ @field({ type: option("string") })
68
+ name?: string;
69
+
70
+ @field({ type: option("u64") })
71
+ number?: bigint;
72
+
73
+ @field({ type: option("bool") })
74
+ bool?: boolean;
75
+
76
+ @field({ type: option(Uint8Array) })
77
+ data?: Uint8Array;
78
+
79
+ @field({ type: option(fixedArray("u8", 32)) })
80
+ fixedData?: Uint8Array;
81
+
82
+ @field({ type: option(NestedValue) })
83
+ nested?: NestedValue;
84
+
85
+ @field({ type: vec("string") })
86
+ tags: string[];
87
+
88
+ @field({ type: vec(NestedValue) })
89
+ nestedVec: NestedValue[];
90
+
91
+ constructor(opts: Partial<Document>) {
92
+ super();
93
+ this.id = opts.id;
94
+ this.name = opts.name;
95
+ this.number = opts.number;
96
+ this.tags = opts.tags || [];
97
+ this.bool = opts.bool;
98
+ this.data = opts.data;
99
+ this.fixedData = opts.fixedData;
100
+ this.nested = opts.nested;
101
+ this.nestedVec = opts.nestedVec || [];
102
+ }
103
+ }
104
+
105
+ const bigIntSort = <T extends number | bigint>(a: T, b: T): number =>
106
+ a > b ? 1 : 0 || -(a < b);
107
+
108
+ const search = <T>(
109
+ index: Index<T, any>,
110
+ query: SearchRequest,
111
+ options?: { shape: Shape },
112
+ ) => {
113
+ // fetch max u32
114
+ query.fetch = 0xffffffff;
115
+ return index.query(query, options);
116
+ };
117
+
118
+ export const tests = (
119
+ createIndicies: (directory?: string) => Indices | Promise<Indices>,
120
+ type: "transient" | "persist" = "transient",
121
+ shapingSupported: boolean,
122
+ ) => {
123
+ return describe("index", () => {
124
+ let store: Index<any, any>;
125
+ let indices: Indices;
126
+ let defaultDocs: Document[] = [];
127
+
128
+ const setupDefault = async () => {
129
+ // Create store
130
+ const result = await setup({ schema: Base });
131
+
132
+ const doc = new Document({
133
+ id: "1",
134
+ name: "hello",
135
+ number: 1n,
136
+ tags: [],
137
+ });
138
+
139
+ const docEdit = new Document({
140
+ id: "1",
141
+ name: "hello world",
142
+ number: 1n,
143
+ bool: true,
144
+ data: new Uint8Array([1]),
145
+ fixedData: new Uint8Array(32).fill(1),
146
+ tags: [],
147
+ });
148
+
149
+ const doc2 = new Document({
150
+ id: "2",
151
+ name: "hello world",
152
+ number: 4n,
153
+ tags: [],
154
+ });
155
+
156
+ const doc2Edit = new Document({
157
+ id: "2",
158
+ name: "Hello World",
159
+ number: 2n,
160
+ data: new Uint8Array([2]),
161
+ fixedData: new Uint8Array(32).fill(2),
162
+ tags: ["Hello", "World"],
163
+ });
164
+
165
+ const doc3 = new Document({
166
+ id: "3",
167
+ name: "foo",
168
+ number: 3n,
169
+ data: new Uint8Array([3]),
170
+ fixedData: new Uint8Array(32).fill(3),
171
+ tags: ["Hello"],
172
+ });
173
+
174
+ const doc4 = new Document({
175
+ id: "4",
176
+ name: undefined,
177
+ number: undefined,
178
+ tags: [],
179
+ });
180
+
181
+ await store.put(doc);
182
+ await waitForResolved(async () =>
183
+ expect(await store.getSize()).equals(1),
184
+ );
185
+ await store.put(docEdit);
186
+ await store.put(doc2);
187
+ await waitForResolved(async () =>
188
+ expect(await store.getSize()).equals(2),
189
+ );
190
+
191
+ await store.put(doc2Edit);
192
+ await store.put(doc3);
193
+ await store.put(doc4);
194
+ await waitForResolved(async () => expect(await store.getSize()).equal(4));
195
+
196
+ defaultDocs = [docEdit, doc2Edit, doc3, doc4];
197
+ return result;
198
+ };
199
+
200
+ const checkDocument = (document: any, ...matchAny: any[]) => {
201
+ const match = matchAny.find((x) =>
202
+ x.id instanceof Uint8Array
203
+ ? equals(x.id, document.id)
204
+ : x.id === document.id,
205
+ );
206
+
207
+ expect(match).to.exist;
208
+
209
+ const keysMatch = Object.keys(match);
210
+ const keysDocument = Object.keys(document);
211
+
212
+ expect(keysMatch).to.have.members(keysDocument);
213
+ expect(keysDocument).to.have.members(keysMatch);
214
+ for (const key of keysMatch) {
215
+ const value = document[key];
216
+ const matchValue = match[key];
217
+ if (value instanceof Uint8Array) {
218
+ expect(equals(value, matchValue)).to.be.true;
219
+ } else {
220
+ expect(value).to.deep.equal(matchValue);
221
+ }
222
+ }
223
+
224
+ // expect(document).to.deep.equal(match);
225
+ expect(document).to.be.instanceOf(matchAny[0].constructor);
226
+ };
227
+
228
+ const setup = async <T>(
229
+ properties: Partial<IndexEngineInitProperties<T, any>> & { schema: any },
230
+ directory?: string,
231
+ ): Promise<{
232
+ indices: Indices;
233
+ store: Index<T, any>;
234
+ directory: string;
235
+ }> => {
236
+ // store && await store.stop()
237
+ indices && (await indices.stop());
238
+
239
+ await sodium.ready;
240
+ directory = directory
241
+ ? directory
242
+ : type === "persist"
243
+ ? "./tmp/document-index/" + uuid()
244
+ : undefined;
245
+ indices = await createIndicies(directory); // TODO add directory testsc
246
+ await indices.start();
247
+ const indexProps: IndexEngineInitProperties<T, any> = {
248
+ ...{
249
+ indexBy: getIdProperty(properties.schema) || ["id"],
250
+ iterator: { batch: { maxSize: 5e6, sizeProperty: ["__size"] } },
251
+ /* nested: {
252
+ match: (obj: any): obj is IndexWrapper => obj instanceof IndexWrapper,
253
+ query: (nested: any, query: any) => nested.search(query)
254
+ } */
255
+ },
256
+ ...properties,
257
+ };
258
+ store = await indices.init(indexProps); // TODO add directory tests
259
+ return { indices, store, directory };
260
+ /* return new IndexWrapper(index, indexProps.indexBy, directory); */
261
+ };
262
+
263
+ afterEach(async () => {
264
+ defaultDocs = [];
265
+ await indices?.stop?.();
266
+ });
267
+
268
+ describe("indexBy", () => {
269
+ const testIndex = async (
270
+ store: Index<any, any>,
271
+ doc: any,
272
+ idProperty: string[] = ["id"],
273
+ ) => {
274
+ await store.put(doc);
275
+ let docId = extractFieldValue<any>(doc, idProperty);
276
+ let result = await store.get(toId(docId));
277
+ expect(result).to.exist;
278
+ checkDocument(result.value, doc);
279
+ let deleteQueryObject = {};
280
+ let current = deleteQueryObject;
281
+ for (const [i, path] of idProperty.entries()) {
282
+ if (i < idProperty.length - 1) {
283
+ current[path] = {};
284
+ current = current[path];
285
+ } else {
286
+ current[path] = docId;
287
+ }
288
+ }
289
+
290
+ await store.del(
291
+ new DeleteRequest({
292
+ query: deleteQueryObject,
293
+ }),
294
+ );
295
+ expect(await store.getSize()).equal(0);
296
+ result = await store.get(toId(docId));
297
+
298
+ expect(result).equal(undefined);
299
+ };
300
+
301
+ describe("string", () => {
302
+ class SimpleDocument {
303
+ @field({ type: "string" })
304
+ id: string;
305
+
306
+ @field({ type: "string" })
307
+ value: string;
308
+
309
+ constructor(properties: { id: string; value: string }) {
310
+ this.id = properties.id;
311
+ this.value = properties.value;
312
+ }
313
+ }
314
+
315
+ it("will throw error if indexBy does not exist in document", async () => {
316
+ let store: any;
317
+ try {
318
+ const out = await setup({
319
+ indexBy: ["__missing__"],
320
+ schema: SimpleDocument,
321
+ });
322
+ store = out.store;
323
+ } catch (error: any) {
324
+ // some impl might want to throw here, since the schema is known in advance and the indexBy will be missing
325
+ expect(error["message"]).equal(
326
+ "Primary key __missing__ not found in schema",
327
+ );
328
+ return;
329
+ }
330
+ const doc = new SimpleDocument({
331
+ id: "abc 123",
332
+ value: "Hello world",
333
+ });
334
+
335
+ // else throw when putting the doc
336
+ try {
337
+ await store.put(doc);
338
+ } catch (error) {
339
+ expect(error).to.haveOwnProperty(
340
+ "message",
341
+ "Unexpected index key: undefined, expected: string, number, bigint or Uint8Array",
342
+ );
343
+ }
344
+ });
345
+
346
+ it("index by another property", async () => {
347
+ const { store } = await setup({
348
+ indexBy: ["value"],
349
+ schema: SimpleDocument,
350
+ });
351
+
352
+ const helloWorld = "Hello world";
353
+ const doc = new SimpleDocument({
354
+ id: "abc 123",
355
+ value: helloWorld,
356
+ });
357
+
358
+ // put doc
359
+ await store.put(doc);
360
+
361
+ expect((await store.get(toId(helloWorld)))?.value.value).equal(
362
+ helloWorld,
363
+ );
364
+ });
365
+
366
+ it("can StringQuery index", async () => {
367
+ const { store } = await setup({
368
+ indexBy: ["value"],
369
+ schema: SimpleDocument,
370
+ });
371
+
372
+ const doc = new SimpleDocument({
373
+ id: "abc 123",
374
+ value: "Hello world",
375
+ });
376
+
377
+ await store.put(doc);
378
+
379
+ const results = await search(
380
+ store,
381
+ new SearchRequest({
382
+ query: [
383
+ new StringMatch({
384
+ key: "id",
385
+ value: "123",
386
+ caseInsensitive: false,
387
+ method: StringMatchMethod.contains,
388
+ }),
389
+ ],
390
+ }),
391
+ );
392
+ expect(results.results).to.have.length(1);
393
+ });
394
+ });
395
+
396
+ describe("bytes", () => {
397
+ class DocumentUint8arrayId {
398
+ @field({ type: Uint8Array })
399
+ id: Uint8Array;
400
+
401
+ @field({ type: "string" })
402
+ value: string;
403
+
404
+ constructor(properties: { id: Uint8Array; value: string }) {
405
+ this.id = properties.id;
406
+ this.value = properties.value;
407
+ }
408
+ }
409
+
410
+ it("index as Uint8array", async () => {
411
+ const { store } = await setup({ schema: DocumentUint8arrayId });
412
+
413
+ const id = new Uint8Array([1, 2, 3]);
414
+ const doc = new DocumentUint8arrayId({
415
+ id,
416
+ value: "Hello world",
417
+ });
418
+ await testIndex(store, doc);
419
+ });
420
+
421
+ class DocumentFixedUint8arrayId {
422
+ @field({ type: fixedArray("u8", 32) })
423
+ id: Uint8Array;
424
+
425
+ @field({ type: "string" })
426
+ value: string;
427
+
428
+ constructor(properties: { id: Uint8Array; value: string }) {
429
+ this.id = properties.id;
430
+ this.value = properties.value;
431
+ }
432
+ }
433
+
434
+ it("index as fixed Uint8array", async () => {
435
+ const { store } = await setup({ schema: DocumentFixedUint8arrayId });
436
+
437
+ const id = new Uint8Array(32).fill(1);
438
+ const doc = new DocumentFixedUint8arrayId({
439
+ id,
440
+ value: "Hello world",
441
+ });
442
+ await testIndex(store, doc);
443
+ });
444
+ });
445
+
446
+ describe("number", () => {
447
+ class DocumentNumberId {
448
+ @field({ type: "u32" })
449
+ id: number;
450
+
451
+ @field({ type: "string" })
452
+ value: string;
453
+
454
+ constructor(properties: { id: number; value: string }) {
455
+ this.id = properties.id;
456
+ this.value = properties.value;
457
+ }
458
+ }
459
+
460
+ it("index as number", async () => {
461
+ const { store } = await setup({ schema: DocumentNumberId });
462
+
463
+ const id = 123456789;
464
+ const doc = new DocumentNumberId({
465
+ id,
466
+ value: "Hello world",
467
+ });
468
+
469
+ await testIndex(store, doc);
470
+ });
471
+ });
472
+
473
+ describe("bigint", () => {
474
+ class DocumentBigintId {
475
+ @field({ type: "u64" })
476
+ id: bigint;
477
+
478
+ @field({ type: "string" })
479
+ value: string;
480
+
481
+ constructor(properties: { id: bigint; value: string }) {
482
+ this.id = properties.id;
483
+ this.value = properties.value;
484
+ }
485
+ }
486
+
487
+ it("index as bigint", async () => {
488
+ const { store } = await setup({ schema: DocumentBigintId });
489
+
490
+ // make the id less than 2^53, but greater than u32 max
491
+ const id = BigInt(2 ** 53 - 1);
492
+ const doc = new DocumentBigintId({
493
+ id,
494
+ value: "Hello world",
495
+ });
496
+ await testIndex(store, doc);
497
+ });
498
+ });
499
+
500
+ describe("by decorator", () => {
501
+ class DocumentWithDecoratedId {
502
+ @id({ type: "string" })
503
+ xyz: string;
504
+
505
+ constructor(properties: { xyz: string }) {
506
+ this.xyz = properties.xyz;
507
+ }
508
+ }
509
+
510
+ it("can index by decorated id", async () => {
511
+ const { store } = await setup({ schema: DocumentWithDecoratedId });
512
+ const doc = new DocumentWithDecoratedId({
513
+ xyz: "abc",
514
+ });
515
+
516
+ await testIndex(store, doc, getIdProperty(DocumentWithDecoratedId));
517
+ });
518
+ });
519
+
520
+ describe("nested by decorator ", () => {
521
+ class Nested {
522
+ @id({ type: "string" })
523
+ id: string;
524
+
525
+ constructor(id: string) {
526
+ this.id = id;
527
+ }
528
+ }
529
+
530
+ class NestedDocument {
531
+ @field({ type: Nested })
532
+ nested: Nested;
533
+
534
+ constructor(nested: Nested) {
535
+ this.nested = nested;
536
+ }
537
+ }
538
+
539
+ it("can index by nested decorated id", async () => {
540
+ const { store } = await setup({ schema: NestedDocument });
541
+ const doc = new NestedDocument(new Nested("abc"));
542
+ await testIndex(store, doc, getIdProperty(NestedDocument));
543
+ });
544
+ });
545
+ });
546
+
547
+ describe("search", () => {
548
+ describe("fields", () => {
549
+ it("no-args", async () => {
550
+ await setupDefault();
551
+
552
+ const results = await search(store, new SearchRequest({ query: [] }));
553
+ expect(results.results).to.have.length(4);
554
+ for (const result of results.results) {
555
+ checkDocument(result.value, ...defaultDocs);
556
+ }
557
+ });
558
+
559
+ describe("string", () => {
560
+ beforeEach(async () => {
561
+ await setupDefault();
562
+ });
563
+ it("exact", async () => {
564
+ const responses = await search(
565
+ store,
566
+ new SearchRequest({
567
+ query: [
568
+ new StringMatch({
569
+ key: "name",
570
+ value: "hello world",
571
+ caseInsensitive: true,
572
+ }),
573
+ ],
574
+ }),
575
+ );
576
+ expect(
577
+ responses.results.map((x) => x.id.primitive),
578
+ ).to.have.members(["1", "2"]);
579
+ });
580
+
581
+ it("exact-case-insensitive", async () => {
582
+ const responses = await search(
583
+ store,
584
+ new SearchRequest({
585
+ query: [
586
+ new StringMatch({
587
+ key: "name",
588
+ value: "Hello World",
589
+ caseInsensitive: true,
590
+ }),
591
+ ],
592
+ }),
593
+ );
594
+ expect(responses.results).to.have.length(2);
595
+ expect(
596
+ responses.results.map((x) => x.id.primitive),
597
+ ).to.have.members(["1", "2"]);
598
+ });
599
+
600
+ it("exact case sensitive", async () => {
601
+ let responses = await search(
602
+ store,
603
+ new SearchRequest({
604
+ query: [
605
+ new StringMatch({
606
+ key: "name",
607
+ value: "Hello World",
608
+ caseInsensitive: false,
609
+ }),
610
+ ],
611
+ }),
612
+ );
613
+ expect(responses.results).to.have.length(1);
614
+ expect(
615
+ responses.results.map((x) => x.id.primitive),
616
+ ).to.have.members(["2"]);
617
+ responses = await search(
618
+ store,
619
+ new SearchRequest({
620
+ query: [
621
+ new StringMatch({
622
+ key: "name",
623
+ value: "hello world",
624
+ caseInsensitive: false,
625
+ }),
626
+ ],
627
+ }),
628
+ );
629
+ expect(
630
+ responses.results.map((x) => x.id.primitive),
631
+ ).to.have.members(["1"]);
632
+ });
633
+ it("prefix", async () => {
634
+ const responses = await search(
635
+ store,
636
+ new SearchRequest({
637
+ query: [
638
+ new StringMatch({
639
+ key: "name",
640
+ value: "hel",
641
+ method: StringMatchMethod.prefix,
642
+ caseInsensitive: true,
643
+ }),
644
+ ],
645
+ }),
646
+ );
647
+ expect(responses.results).to.have.length(2);
648
+ expect(
649
+ responses.results.map((x) => x.id.primitive),
650
+ ).to.have.members(["1", "2"]);
651
+ });
652
+
653
+ it("contains", async () => {
654
+ const responses = await search(
655
+ store,
656
+ new SearchRequest({
657
+ query: [
658
+ new StringMatch({
659
+ key: "name",
660
+ value: "ello",
661
+ method: StringMatchMethod.contains,
662
+ caseInsensitive: true,
663
+ }),
664
+ ],
665
+ }),
666
+ );
667
+ expect(responses.results).to.have.length(2);
668
+ expect(
669
+ responses.results.map((x) => x.id.primitive),
670
+ ).to.have.members(["1", "2"]);
671
+ });
672
+
673
+ describe("arr", () => {
674
+ it("arr", async () => {
675
+ const responses = await search(
676
+ store,
677
+ new SearchRequest({
678
+ query: [
679
+ new StringMatch({
680
+ key: "tags",
681
+ value: "world",
682
+ method: StringMatchMethod.contains,
683
+ caseInsensitive: true,
684
+ }),
685
+ ],
686
+ }),
687
+ );
688
+ expect(responses.results).to.have.length(1);
689
+ expect(
690
+ responses.results.map((x) => x.id.primitive),
691
+ ).to.have.members(["2"]);
692
+
693
+ checkDocument(responses.results[0].value, ...defaultDocs);
694
+ });
695
+ });
696
+ });
697
+
698
+ it("missing", async () => {
699
+ await setupDefault();
700
+
701
+ const responses = await search(
702
+ store,
703
+ new SearchRequest({
704
+ query: [
705
+ new IsNull({
706
+ key: "name",
707
+ }),
708
+ ],
709
+ }),
710
+ );
711
+
712
+ expect(responses.results).to.have.length(1);
713
+ expect(responses.results.map((x) => x.id.primitive)).to.deep.equal([
714
+ "4",
715
+ ]);
716
+ });
717
+
718
+ describe("uint8arrays", () => {
719
+ describe("dynamic", () => {
720
+ describe("bytematch", () => {
721
+ it("matches", async () => {
722
+ await setupDefault();
723
+
724
+ const responses = await search(
725
+ store,
726
+ new SearchRequest({
727
+ query: [
728
+ new ByteMatchQuery({
729
+ key: "data",
730
+ value: new Uint8Array([1]),
731
+ }),
732
+ ],
733
+ }),
734
+ );
735
+ expect(responses.results).to.have.length(1);
736
+ expect(
737
+ responses.results.map((x) => x.id.primitive),
738
+ ).to.deep.equal(["1"]);
739
+ });
740
+ it("un-matches", async () => {
741
+ await setupDefault();
742
+
743
+ const responses = await search(
744
+ store,
745
+ new SearchRequest({
746
+ query: [
747
+ new ByteMatchQuery({
748
+ key: "data",
749
+ value: new Uint8Array([199]),
750
+ }),
751
+ ],
752
+ }),
753
+ );
754
+ expect(responses.results).to.be.empty;
755
+ });
756
+ });
757
+ describe("integer", () => {
758
+ it("exists", async () => {
759
+ await setupDefault();
760
+
761
+ const responses = await search(
762
+ store,
763
+ new SearchRequest({
764
+ query: [
765
+ new IntegerCompare({
766
+ key: "data",
767
+ compare: Compare.Equal,
768
+ value: 1,
769
+ }),
770
+ ],
771
+ }),
772
+ );
773
+ expect(responses.results).to.have.length(1);
774
+ expect(
775
+ responses.results.map((x) => x.id.primitive),
776
+ ).to.deep.equal(["1"]);
777
+ });
778
+
779
+ it("does not exist", async () => {
780
+ await setupDefault();
781
+
782
+ const responses = await search(
783
+ store,
784
+ new SearchRequest({
785
+ query: [
786
+ new IntegerCompare({
787
+ key: "data",
788
+ compare: Compare.Equal,
789
+ value: 199,
790
+ }),
791
+ ],
792
+ }),
793
+ );
794
+ expect(responses.results).to.be.empty;
795
+ });
796
+ });
797
+ });
798
+
799
+ describe("fixed", () => {
800
+ describe("bytematch", () => {
801
+ it("matches", async () => {
802
+ await setupDefault();
803
+
804
+ const responses = await search(
805
+ store,
806
+ new SearchRequest({
807
+ query: [
808
+ new ByteMatchQuery({
809
+ key: "fixedData",
810
+ value: new Uint8Array(32).fill(1),
811
+ }),
812
+ ],
813
+ }),
814
+ );
815
+ expect(responses.results).to.have.length(1);
816
+ expect(
817
+ responses.results.map((x) => x.id.primitive),
818
+ ).to.deep.equal(["1"]);
819
+ });
820
+ it("un-matches", async () => {
821
+ await setupDefault();
822
+
823
+ const responses = await search(
824
+ store,
825
+ new SearchRequest({
826
+ query: [
827
+ new ByteMatchQuery({
828
+ key: "data",
829
+ value: new Uint8Array(32).fill(99),
830
+ }),
831
+ ],
832
+ }),
833
+ );
834
+ expect(responses.results).to.be.empty;
835
+ });
836
+ });
837
+ describe("integer", () => {
838
+ it("exists", async () => {
839
+ await setupDefault();
840
+
841
+ const responses = await search(
842
+ store,
843
+ new SearchRequest({
844
+ query: [
845
+ new IntegerCompare({
846
+ key: "data",
847
+ compare: Compare.Equal,
848
+ value: 1,
849
+ }),
850
+ ],
851
+ }),
852
+ );
853
+ expect(responses.results).to.have.length(1);
854
+ expect(
855
+ responses.results.map((x) => x.id.primitive),
856
+ ).to.deep.equal(["1"]);
857
+ });
858
+
859
+ it("does not exist", async () => {
860
+ await setupDefault();
861
+
862
+ const responses = await search(
863
+ store,
864
+ new SearchRequest({
865
+ query: [
866
+ new IntegerCompare({
867
+ key: "data",
868
+ compare: Compare.Equal,
869
+ value: 199,
870
+ }),
871
+ ],
872
+ }),
873
+ );
874
+ expect(responses.results).to.be.empty;
875
+ });
876
+ });
877
+ });
878
+ });
879
+ +it("bool", async () => {
880
+ await setupDefault();
881
+
882
+ const responses = await search(
883
+ store,
884
+ new SearchRequest({
885
+ query: [
886
+ new BoolQuery({
887
+ key: "bool",
888
+ value: true,
889
+ }),
890
+ ],
891
+ }),
892
+ );
893
+ expect(responses.results).to.have.length(1);
894
+ expect(responses.results.map((x) => x.id.primitive)).to.deep.equal([
895
+ "1",
896
+ ]);
897
+ });
898
+
899
+ describe("array", () => {
900
+ describe("uint8arrays", () => {
901
+ class Uint8arraysVec {
902
+ @field({ type: Uint8Array })
903
+ id: Uint8Array;
904
+
905
+ @field({ type: vec(Uint8Array) })
906
+ bytesArrays: Uint8Array[];
907
+
908
+ constructor(properties?: { bytesArrays: Uint8Array[] }) {
909
+ this.id = randomBytes(32);
910
+ this.bytesArrays = properties?.bytesArrays || [];
911
+ }
912
+ }
913
+
914
+ it("uint8array[]", async () => {
915
+ const out = await setup({ schema: Uint8arraysVec });
916
+ store = out.store;
917
+ const d1 = new Uint8arraysVec({
918
+ bytesArrays: [new Uint8Array([1]), new Uint8Array([2])],
919
+ });
920
+ await store.put(d1);
921
+ await store.put(
922
+ new Uint8arraysVec({
923
+ bytesArrays: [new Uint8Array([3])],
924
+ }),
925
+ );
926
+
927
+ const results = await search(
928
+ store,
929
+ new SearchRequest({
930
+ query: [
931
+ new ByteMatchQuery({
932
+ key: "bytesArrays",
933
+ value: new Uint8Array([2]),
934
+ }),
935
+ ],
936
+ }),
937
+ );
938
+ expect(results.results.map((x) => x.value.id)).to.deep.equal([
939
+ d1.id,
940
+ ]);
941
+ });
942
+ });
943
+
944
+ describe("documents", () => {
945
+ class DocumentsVec {
946
+ @field({ type: Uint8Array })
947
+ id: Uint8Array;
948
+
949
+ @field({ type: vec(Document) })
950
+ documents: Document[];
951
+
952
+ constructor(properties?: { documents: Document[] }) {
953
+ this.id = randomBytes(32);
954
+ this.documents = properties?.documents || [];
955
+ }
956
+ }
957
+
958
+ it("can search", async () => {
959
+ const out = await setup({ schema: DocumentsVec });
960
+ store = out.store;
961
+
962
+ const d1 = new DocumentsVec({
963
+ documents: [
964
+ new Document({ id: uuid(), number: 123n, tags: [] }),
965
+ ],
966
+ });
967
+ await store.put(d1);
968
+ await store.put(
969
+ new DocumentsVec({
970
+ documents: [
971
+ new Document({ id: uuid(), number: 124n, tags: [] }),
972
+ ],
973
+ }),
974
+ );
975
+
976
+ const results = await search(
977
+ store,
978
+ new SearchRequest({
979
+ query: new IntegerCompare({
980
+ key: ["documents", "number"],
981
+ compare: Compare.Equal,
982
+ value: d1.documents[0]!.number,
983
+ }),
984
+ }),
985
+ );
986
+ expect(results.results.map((x) => x.value.id)).to.deep.equal([
987
+ d1.id,
988
+ ]);
989
+ });
990
+
991
+ it("update array", async () => {
992
+ const out = await setup({ schema: DocumentsVec });
993
+ store = out.store;
994
+
995
+ const d1 = new DocumentsVec({
996
+ documents: [
997
+ new Document({ id: uuid(), number: 123n, tags: [] }),
998
+ ],
999
+ });
1000
+ await store.put(d1);
1001
+
1002
+ d1.documents = [
1003
+ new Document({ id: uuid(), number: 124n, tags: [] }),
1004
+ ];
1005
+
1006
+ await store.put(d1);
1007
+
1008
+ // should have update results
1009
+ expect(
1010
+ (
1011
+ await search(
1012
+ store,
1013
+ new SearchRequest({
1014
+ query: new IntegerCompare({
1015
+ key: ["documents", "number"],
1016
+ compare: Compare.Equal,
1017
+ value: 123n,
1018
+ }),
1019
+ }),
1020
+ )
1021
+ ).results.length,
1022
+ ).to.equal(0);
1023
+
1024
+ expect(
1025
+ (
1026
+ await search(
1027
+ store,
1028
+ new SearchRequest({
1029
+ query: new IntegerCompare({
1030
+ key: ["documents", "number"],
1031
+ compare: Compare.Equal,
1032
+ value: 124n,
1033
+ }),
1034
+ }),
1035
+ )
1036
+ ).results.map((x) => x.value.id),
1037
+ ).to.deep.equal([d1.id]);
1038
+ });
1039
+
1040
+ it("put delete put", async () => {
1041
+ const { store } = await setup({ schema: DocumentsVec });
1042
+
1043
+ const d1 = new DocumentsVec({
1044
+ documents: [
1045
+ new Document({ id: uuid(), number: 123n, tags: [] }),
1046
+ ],
1047
+ });
1048
+
1049
+ await store.put(d1);
1050
+ const [deleted] = await store.del(
1051
+ new DeleteRequest({
1052
+ query: {
1053
+ id: d1.id,
1054
+ },
1055
+ }),
1056
+ );
1057
+
1058
+ expect(deleted.key).to.deep.equal(d1.id);
1059
+
1060
+ expect(
1061
+ (
1062
+ await search(
1063
+ store,
1064
+ new SearchRequest({
1065
+ query: new IntegerCompare({
1066
+ key: ["documents", "number"],
1067
+ compare: Compare.Equal,
1068
+ value: 123n,
1069
+ }),
1070
+ }),
1071
+ )
1072
+ ).results.length,
1073
+ ).to.equal(0);
1074
+
1075
+ d1.documents = [
1076
+ new Document({ id: uuid(), number: 124n, tags: [] }),
1077
+ ];
1078
+ await store.put(d1);
1079
+
1080
+ expect(
1081
+ (
1082
+ await search(
1083
+ store,
1084
+ new SearchRequest({
1085
+ query: new IntegerCompare({
1086
+ key: ["documents", "number"],
1087
+ compare: Compare.Equal,
1088
+ value: 124n,
1089
+ }),
1090
+ }),
1091
+ )
1092
+ ).results.map((x) => x.value.id),
1093
+ ).to.deep.equal([d1.id]);
1094
+
1095
+ expect(
1096
+ (
1097
+ await search(
1098
+ store,
1099
+ new SearchRequest({
1100
+ query: new IntegerCompare({
1101
+ key: ["documents", "number"],
1102
+ compare: Compare.Equal,
1103
+ value: 123n,
1104
+ }),
1105
+ }),
1106
+ )
1107
+ ).results.length,
1108
+ ).to.equal(0);
1109
+ });
1110
+ });
1111
+ });
1112
+
1113
+ describe("logical", () => {
1114
+ beforeEach(async () => {
1115
+ await setupDefault();
1116
+ });
1117
+
1118
+ it("and", async () => {
1119
+ const responses = await search(
1120
+ store,
1121
+ new SearchRequest({
1122
+ query: [
1123
+ new And([
1124
+ new StringMatch({
1125
+ key: "name",
1126
+ value: "hello",
1127
+ caseInsensitive: true,
1128
+ method: StringMatchMethod.contains,
1129
+ }),
1130
+ new StringMatch({
1131
+ key: "name",
1132
+ value: "world",
1133
+ caseInsensitive: true,
1134
+ method: StringMatchMethod.contains,
1135
+ }),
1136
+ ]),
1137
+ ],
1138
+ }),
1139
+ );
1140
+ expect(responses.results).to.have.length(2);
1141
+ expect(
1142
+ responses.results.map((x) => x.id.primitive),
1143
+ ).to.have.members(["1", "2"]);
1144
+ });
1145
+
1146
+ it("or", async () => {
1147
+ const responses = await search(
1148
+ store,
1149
+ new SearchRequest({
1150
+ query: [
1151
+ new Or([
1152
+ new StringMatch({
1153
+ key: "id",
1154
+ value: "1",
1155
+ }),
1156
+ new StringMatch({
1157
+ key: "id",
1158
+ value: "2",
1159
+ }),
1160
+ ]),
1161
+ ],
1162
+ }),
1163
+ );
1164
+ expect(responses.results).to.have.length(2);
1165
+ expect(
1166
+ responses.results.map((x) => x.id.primitive),
1167
+ ).to.have.members(["1", "2"]);
1168
+ });
1169
+
1170
+ it("not", async () => {
1171
+ const responses = await search(
1172
+ store,
1173
+ new SearchRequest({
1174
+ query: [
1175
+ new And([
1176
+ new Not(
1177
+ new IntegerCompare({
1178
+ key: "number",
1179
+ compare: Compare.Greater,
1180
+ value: 1n,
1181
+ }),
1182
+ ),
1183
+ ]),
1184
+ ],
1185
+ }),
1186
+ );
1187
+ expect(responses.results).to.have.length(1);
1188
+ expect(
1189
+ responses.results.map((x) => x.id.primitive),
1190
+ ).to.have.members(["1"]);
1191
+ });
1192
+ });
1193
+
1194
+ describe("number", () => {
1195
+ beforeEach(async () => {
1196
+ await setupDefault();
1197
+ });
1198
+ it("equal", async () => {
1199
+ const response = await search(
1200
+ store,
1201
+ new SearchRequest({
1202
+ query: [
1203
+ new IntegerCompare({
1204
+ key: "number",
1205
+ compare: Compare.Equal,
1206
+ value: 2n,
1207
+ }),
1208
+ ],
1209
+ }),
1210
+ );
1211
+ expect(response.results).to.have.length(1);
1212
+ expect(response.results[0].value.number).to.be.oneOf([2n, 2]);
1213
+ });
1214
+
1215
+ it("gt", async () => {
1216
+ const response = await search(
1217
+ store,
1218
+ new SearchRequest({
1219
+ query: [
1220
+ new IntegerCompare({
1221
+ key: "number",
1222
+ compare: Compare.Greater,
1223
+ value: 2n,
1224
+ }),
1225
+ ],
1226
+ }),
1227
+ );
1228
+ expect(response.results).to.have.length(1);
1229
+ expect(response.results[0].value.number).to.be.oneOf([3n, 3]);
1230
+ });
1231
+
1232
+ it("gte", async () => {
1233
+ const response = await search(
1234
+ store,
1235
+ new SearchRequest({
1236
+ query: [
1237
+ new IntegerCompare({
1238
+ key: "number",
1239
+ compare: Compare.GreaterOrEqual,
1240
+ value: 2n,
1241
+ }),
1242
+ ],
1243
+ }),
1244
+ );
1245
+ response.results.sort((a, b) =>
1246
+ bigIntSort(a.value.number as bigint, b.value.number as bigint),
1247
+ );
1248
+ expect(response.results).to.have.length(2);
1249
+ expect(response.results[0].value.number).to.be.oneOf([2n, 2]);
1250
+ expect(response.results[1].value.number).to.be.oneOf([3n, 3]);
1251
+ });
1252
+
1253
+ it("lt", async () => {
1254
+ const response = await search(
1255
+ store,
1256
+ new SearchRequest({
1257
+ query: [
1258
+ new IntegerCompare({
1259
+ key: "number",
1260
+ compare: Compare.Less,
1261
+ value: 2n,
1262
+ }),
1263
+ ],
1264
+ }),
1265
+ );
1266
+ expect(response.results).to.have.length(1);
1267
+ expect(response.results[0].value.number).to.be.oneOf([1n, 1]);
1268
+ });
1269
+
1270
+ it("lte", async () => {
1271
+ const response = await search(
1272
+ store,
1273
+ new SearchRequest({
1274
+ query: [
1275
+ new IntegerCompare({
1276
+ key: "number",
1277
+ compare: Compare.LessOrEqual,
1278
+ value: 2n,
1279
+ }),
1280
+ ],
1281
+ }),
1282
+ );
1283
+ response.results.sort((a, b) =>
1284
+ bigIntSort(a.value.number as bigint, b.value.number as bigint),
1285
+ );
1286
+ expect(response.results).to.have.length(2);
1287
+ expect(response.results[0].value.number).to.be.oneOf([1n, 1]);
1288
+ expect(response.results[1].value.number).to.be.oneOf([2n, 2]);
1289
+ });
1290
+ });
1291
+
1292
+ describe("bigint", () => {
1293
+ @variant(0)
1294
+ class BigInt {
1295
+ @id({ type: "string" })
1296
+ id: string;
1297
+
1298
+ @field({ type: "u64" })
1299
+ bigint: bigint;
1300
+
1301
+ constructor(id: string, bigint: bigint) {
1302
+ this.id = id;
1303
+ this.bigint = bigint;
1304
+ }
1305
+ }
1306
+ let first = 1720600661484958580n;
1307
+ let second = first + 1n;
1308
+ let third = first + 2n;
1309
+ beforeEach(async () => {
1310
+ await setup({ schema: BigInt });
1311
+ await store.put(new BigInt("0", first));
1312
+ await store.put(new BigInt("1", second));
1313
+ await store.put(new BigInt("2", third));
1314
+
1315
+ const ser = deserialize(
1316
+ serialize(new BigInt("0", first)),
1317
+ BigInt,
1318
+ ).bigint;
1319
+ expect(ser).to.equal(first);
1320
+ });
1321
+
1322
+ it("equal", async () => {
1323
+ const response = await search(
1324
+ store,
1325
+ new SearchRequest({
1326
+ query: [
1327
+ new IntegerCompare({
1328
+ key: "bigint",
1329
+ compare: Compare.Equal,
1330
+ value: first,
1331
+ }),
1332
+ ],
1333
+ }),
1334
+ );
1335
+ expect(response.results).to.have.length(1);
1336
+ expect(response.results[0].value.bigint).to.equal(first);
1337
+ });
1338
+
1339
+ it("gt", async () => {
1340
+ const response = await search(
1341
+ store,
1342
+ new SearchRequest({
1343
+ query: [
1344
+ new IntegerCompare({
1345
+ key: "bigint",
1346
+ compare: Compare.Greater,
1347
+ value: second,
1348
+ }),
1349
+ ],
1350
+ }),
1351
+ );
1352
+ expect(response.results).to.have.length(1);
1353
+ expect(response.results[0].value.bigint).to.equal(third);
1354
+ });
1355
+
1356
+ it("gte", async () => {
1357
+ const response = await search(
1358
+ store,
1359
+ new SearchRequest({
1360
+ query: [
1361
+ new IntegerCompare({
1362
+ key: "bigint",
1363
+ compare: Compare.GreaterOrEqual,
1364
+ value: second,
1365
+ }),
1366
+ ],
1367
+ }),
1368
+ );
1369
+ response.results.sort((a, b) =>
1370
+ bigIntSort(a.value.bigint as bigint, b.value.bigint as bigint),
1371
+ );
1372
+ expect(response.results).to.have.length(2);
1373
+ expect(response.results[0].value.bigint).to.equal(second);
1374
+ expect(response.results[1].value.bigint).to.equal(third);
1375
+ });
1376
+
1377
+ it("lt", async () => {
1378
+ const response = await search(
1379
+ store,
1380
+ new SearchRequest({
1381
+ query: [
1382
+ new IntegerCompare({
1383
+ key: "bigint",
1384
+ compare: Compare.Less,
1385
+ value: second,
1386
+ }),
1387
+ ],
1388
+ }),
1389
+ );
1390
+ expect(response.results).to.have.length(1);
1391
+ expect(response.results[0].value.bigint).to.equal(first);
1392
+ });
1393
+
1394
+ it("lte", async () => {
1395
+ const response = await search(
1396
+ store,
1397
+ new SearchRequest({
1398
+ query: [
1399
+ new IntegerCompare({
1400
+ key: "bigint",
1401
+ compare: Compare.LessOrEqual,
1402
+ value: second,
1403
+ }),
1404
+ ],
1405
+ }),
1406
+ );
1407
+ response.results.sort((a, b) =>
1408
+ bigIntSort(a.value.number as bigint, b.value.number as bigint),
1409
+ );
1410
+
1411
+ expect(response.results).to.have.length(2);
1412
+ expect(response.results[0].value.bigint).to.equal(first);
1413
+ expect(response.results[1].value.bigint).to.equal(second);
1414
+ });
1415
+ });
1416
+
1417
+ describe("nested", () => {
1418
+ describe("one level", () => {
1419
+ class Nested {
1420
+ @field({ type: "u64" })
1421
+ number: bigint;
1422
+
1423
+ @field({ type: "bool" })
1424
+ bool: boolean;
1425
+
1426
+ constructor(opts: Nested) {
1427
+ this.number = opts.number;
1428
+ this.bool = opts.bool;
1429
+ }
1430
+ }
1431
+
1432
+ @variant(0)
1433
+ class DocumentWithNesting {
1434
+ @id({ type: "string" })
1435
+ id: string;
1436
+
1437
+ @field({ type: option(Nested) })
1438
+ nested?: Nested;
1439
+
1440
+ constructor(opts: DocumentWithNesting) {
1441
+ this.id = opts.id;
1442
+ this.nested = opts.nested;
1443
+ }
1444
+ }
1445
+
1446
+ beforeEach(async () => {
1447
+ await setup({ schema: DocumentWithNesting });
1448
+ });
1449
+
1450
+ it("number", async () => {
1451
+ await store.put(
1452
+ new DocumentWithNesting({
1453
+ id: "1",
1454
+ nested: new Nested({ number: 1n, bool: false }),
1455
+ }),
1456
+ );
1457
+ const doc2 = new DocumentWithNesting({
1458
+ id: "2",
1459
+ nested: new Nested({ number: 2n, bool: true }),
1460
+ });
1461
+ await store.put(doc2);
1462
+
1463
+ const response = await search(
1464
+ store,
1465
+ new SearchRequest({
1466
+ query: [
1467
+ new IntegerCompare({
1468
+ key: ["nested", "number"],
1469
+ compare: Compare.GreaterOrEqual,
1470
+ value: 2n,
1471
+ }),
1472
+ ],
1473
+ }),
1474
+ );
1475
+ expect(response.results).to.have.length(1);
1476
+ expect(response.results[0].value.id).to.equal("2");
1477
+
1478
+ checkDocument(response.results[0].value, doc2);
1479
+ });
1480
+
1481
+ it("bool", async () => {
1482
+ const doc1 = new DocumentWithNesting({
1483
+ id: "1",
1484
+ nested: new Nested({ number: 1n, bool: false }),
1485
+ });
1486
+
1487
+ await store.put(doc1);
1488
+ const doc2 = new DocumentWithNesting({
1489
+ id: "2",
1490
+ nested: new Nested({ number: 2n, bool: true }),
1491
+ });
1492
+
1493
+ await store.put(doc2);
1494
+ let response = await search(
1495
+ store,
1496
+ new SearchRequest({
1497
+ query: [
1498
+ new BoolQuery({
1499
+ key: ["nested", "bool"],
1500
+ value: true,
1501
+ }),
1502
+ ],
1503
+ }),
1504
+ );
1505
+ expect(response.results).to.have.length(1);
1506
+ expect(response.results[0].value.id).to.equal("2");
1507
+ checkDocument(response.results[0].value, doc2);
1508
+
1509
+ response = await search(
1510
+ store,
1511
+ new SearchRequest({
1512
+ query: [
1513
+ new BoolQuery({
1514
+ key: ["nested", "bool"],
1515
+ value: false,
1516
+ }),
1517
+ ],
1518
+ }),
1519
+ );
1520
+ expect(response.results).to.have.length(1);
1521
+ expect(response.results[0].value.id).to.equal("1");
1522
+ checkDocument(response.results[0].value, doc1);
1523
+ });
1524
+ });
1525
+
1526
+ describe("one level flat constructor", () => {
1527
+ class Nested {
1528
+ @field({ type: "bool" })
1529
+ bool: boolean;
1530
+
1531
+ constructor(bool: boolean) {
1532
+ this.bool = bool;
1533
+ }
1534
+ }
1535
+
1536
+ @variant(0)
1537
+ class DocumentWithNesting {
1538
+ @id({ type: "string" })
1539
+ id: string;
1540
+
1541
+ @field({ type: option(Nested) })
1542
+ nested?: Nested;
1543
+
1544
+ constructor(opts: DocumentWithNesting) {
1545
+ this.id = opts.id;
1546
+ this.nested = opts.nested;
1547
+ }
1548
+ }
1549
+
1550
+ beforeEach(async () => {
1551
+ await setup({ schema: DocumentWithNesting });
1552
+ });
1553
+
1554
+ it("bool", async () => {
1555
+ const doc1 = new DocumentWithNesting({
1556
+ id: "1",
1557
+ nested: new Nested(false),
1558
+ });
1559
+
1560
+ await store.put(doc1);
1561
+ const doc2 = new DocumentWithNesting({
1562
+ id: "2",
1563
+ nested: new Nested(true),
1564
+ });
1565
+
1566
+ await store.put(doc2);
1567
+ let response = await search(
1568
+ store,
1569
+ new SearchRequest({
1570
+ query: [
1571
+ new BoolQuery({
1572
+ key: ["nested", "bool"],
1573
+ value: true,
1574
+ }),
1575
+ ],
1576
+ }),
1577
+ );
1578
+ expect(response.results).to.have.length(1);
1579
+ expect(response.results[0].value.id).to.equal("2");
1580
+ checkDocument(response.results[0].value, doc2);
1581
+
1582
+ response = await search(
1583
+ store,
1584
+ new SearchRequest({
1585
+ query: [
1586
+ new BoolQuery({
1587
+ key: ["nested", "bool"],
1588
+ value: false,
1589
+ }),
1590
+ ],
1591
+ }),
1592
+ );
1593
+ expect(response.results).to.have.length(1);
1594
+ expect(response.results[0].value.id).to.equal("1");
1595
+ checkDocument(response.results[0].value, doc1);
1596
+ });
1597
+ });
1598
+
1599
+ describe("2-level-variants", () => {
1600
+ class L1 {
1601
+ @field({ type: option("u64") })
1602
+ number?: bigint;
1603
+
1604
+ constructor(opts: L1) {
1605
+ this.number = opts.number;
1606
+ }
1607
+ }
1608
+
1609
+ class L0 {
1610
+ @field({ type: option(L1) })
1611
+ nestedAgain?: L1;
1612
+
1613
+ constructor(opts: L0) {
1614
+ this.nestedAgain = opts.nestedAgain;
1615
+ }
1616
+ }
1617
+
1618
+ @variant("DocumentWithNestedNesting")
1619
+ class DocumentWithNestedNesting {
1620
+ @id({ type: "string" })
1621
+ id: string;
1622
+
1623
+ @field({ type: option(L0) })
1624
+ nested?: L0;
1625
+
1626
+ constructor(opts: DocumentWithNestedNesting) {
1627
+ this.id = opts.id;
1628
+ this.nested = opts.nested;
1629
+ }
1630
+ }
1631
+
1632
+ beforeEach(async () => {
1633
+ await setup({ schema: DocumentWithNestedNesting });
1634
+ });
1635
+
1636
+ it("nested", async () => {
1637
+ await store.put(
1638
+ new DocumentWithNestedNesting({
1639
+ id: "1",
1640
+ nested: new L0({
1641
+ nestedAgain: new L1({ number: 1n }),
1642
+ }),
1643
+ }),
1644
+ );
1645
+ const doc2 = new DocumentWithNestedNesting({
1646
+ id: "2",
1647
+ nested: new L0({
1648
+ nestedAgain: new L1({ number: 2n }),
1649
+ }),
1650
+ });
1651
+ await store.put(doc2);
1652
+
1653
+ const response = await search(
1654
+ store,
1655
+ new SearchRequest({
1656
+ query: [
1657
+ new IntegerCompare({
1658
+ key: ["nested", "nestedAgain", "number"],
1659
+ compare: Compare.GreaterOrEqual,
1660
+ value: 2n,
1661
+ }),
1662
+ ],
1663
+ }),
1664
+ );
1665
+ expect(response.results).to.have.length(1);
1666
+ expect(response.results[0].value.id).to.equal("2");
1667
+
1668
+ checkDocument(response.results[0].value, doc2);
1669
+ });
1670
+ });
1671
+
1672
+ describe("3-level-variants", () => {
1673
+ class L2 {
1674
+ @field({ type: option("u64") })
1675
+ number?: bigint;
1676
+
1677
+ constructor(opts: L2) {
1678
+ this.number = opts.number;
1679
+ }
1680
+ }
1681
+
1682
+ class L1 {
1683
+ @field({ type: option(L2) })
1684
+ nestedAgainAgain?: L2;
1685
+
1686
+ constructor(opts: L1) {
1687
+ this.nestedAgainAgain = opts.nestedAgainAgain;
1688
+ }
1689
+ }
1690
+
1691
+ class L0 {
1692
+ @field({ type: option(L1) })
1693
+ nestedAgain?: L1;
1694
+
1695
+ constructor(opts: L0) {
1696
+ this.nestedAgain = opts.nestedAgain;
1697
+ }
1698
+ }
1699
+
1700
+ @variant("DocumentWithNestedNesting")
1701
+ class DocumentWithNestedNesting {
1702
+ @id({ type: "string" })
1703
+ id: string;
1704
+
1705
+ @field({ type: option(L0) })
1706
+ nested?: L0;
1707
+
1708
+ constructor(opts: DocumentWithNestedNesting) {
1709
+ this.id = opts.id;
1710
+ this.nested = opts.nested;
1711
+ }
1712
+ }
1713
+
1714
+ beforeEach(async () => {
1715
+ await setup({ schema: DocumentWithNestedNesting });
1716
+ });
1717
+
1718
+ it("nested", async () => {
1719
+ await store.put(
1720
+ new DocumentWithNestedNesting({
1721
+ id: "1",
1722
+ nested: new L0({
1723
+ nestedAgain: new L1({
1724
+ nestedAgainAgain: new L2({ number: 1n }),
1725
+ }),
1726
+ }),
1727
+ }),
1728
+ );
1729
+ const doc2 = new DocumentWithNestedNesting({
1730
+ id: "2",
1731
+ nested: new L0({
1732
+ nestedAgain: new L1({
1733
+ nestedAgainAgain: new L2({ number: 2n }),
1734
+ }),
1735
+ }),
1736
+ });
1737
+ await store.put(doc2);
1738
+
1739
+ const response = await search(
1740
+ store,
1741
+ new SearchRequest({
1742
+ query: [
1743
+ new IntegerCompare({
1744
+ key: [
1745
+ "nested",
1746
+ "nestedAgain",
1747
+ "nestedAgainAgain",
1748
+ "number",
1749
+ ],
1750
+ compare: Compare.GreaterOrEqual,
1751
+ value: 2n,
1752
+ }),
1753
+ ],
1754
+ }),
1755
+ );
1756
+
1757
+ expect(response.results).to.have.length(1);
1758
+ expect(response.results[0].value.id).to.equal("2");
1759
+ checkDocument(response.results[0].value, doc2);
1760
+ });
1761
+ });
1762
+
1763
+ describe("poly-morphism", () => {
1764
+ describe("non-array", () => {
1765
+ abstract class Base {}
1766
+
1767
+ @variant("v0")
1768
+ class V0 extends Base {
1769
+ @field({ type: option("u64") })
1770
+ number?: bigint;
1771
+
1772
+ constructor(opts: V0) {
1773
+ super();
1774
+ this.number = opts.number;
1775
+ }
1776
+ }
1777
+
1778
+ @variant("v1")
1779
+ class V1 extends Base {
1780
+ @field({ type: option("string") })
1781
+ string?: string;
1782
+
1783
+ constructor(opts: V1) {
1784
+ super();
1785
+ this.string = opts.string;
1786
+ }
1787
+ }
1788
+
1789
+ @variant("PolymorphDocument")
1790
+ class PolymorphDocument {
1791
+ @id({ type: "string" })
1792
+ id: string;
1793
+
1794
+ @field({ type: option(Base) })
1795
+ nested?: Base;
1796
+
1797
+ constructor(opts: PolymorphDocument) {
1798
+ this.id = opts.id;
1799
+ this.nested = opts.nested;
1800
+ }
1801
+ }
1802
+
1803
+ beforeEach(async () => {
1804
+ await setup({ schema: PolymorphDocument });
1805
+ });
1806
+
1807
+ it("can query multiple versions at once", async () => {
1808
+ await store.put(
1809
+ new PolymorphDocument({
1810
+ id: "1",
1811
+ nested: new V0({
1812
+ number: 1n,
1813
+ }),
1814
+ }),
1815
+ );
1816
+ const doc2 = new PolymorphDocument({
1817
+ id: "2",
1818
+ nested: new V1({
1819
+ string: "hello",
1820
+ }),
1821
+ });
1822
+ await store.put(doc2);
1823
+
1824
+ const response = await search(
1825
+ store,
1826
+ new SearchRequest({
1827
+ query: [
1828
+ new StringMatch({
1829
+ key: ["nested", "string"],
1830
+ value: "hello",
1831
+ }),
1832
+ ],
1833
+ }),
1834
+ );
1835
+
1836
+ expect(response.results).to.have.length(1);
1837
+ expect(response.results[0].value.id).to.equal("2");
1838
+
1839
+ checkDocument(response.results[0].value, doc2);
1840
+ });
1841
+ });
1842
+
1843
+ describe("non-array-nested", () => {
1844
+ abstract class Base {}
1845
+
1846
+ @variant("v0")
1847
+ class V0 extends Base {
1848
+ @field({ type: option("u64") })
1849
+ number?: bigint;
1850
+
1851
+ constructor(opts: V0) {
1852
+ super();
1853
+ this.number = opts.number;
1854
+ }
1855
+ }
1856
+
1857
+ @variant("v1")
1858
+ class V1 extends Base {
1859
+ @field({ type: option("string") })
1860
+ string?: string;
1861
+
1862
+ constructor(opts: V1) {
1863
+ super();
1864
+ this.string = opts.string;
1865
+ }
1866
+ }
1867
+
1868
+ class Nested {
1869
+ @field({ type: option(Base) })
1870
+ nestedAgain?: Base;
1871
+
1872
+ constructor(opts: Nested) {
1873
+ this.nestedAgain = opts.nestedAgain;
1874
+ }
1875
+ }
1876
+
1877
+ @variant("PolymorphDocument")
1878
+ class PolymorphDocument {
1879
+ @id({ type: "string" })
1880
+ id: string;
1881
+
1882
+ @field({ type: Nested })
1883
+ nested?: Nested;
1884
+
1885
+ constructor(opts: PolymorphDocument) {
1886
+ this.id = opts.id;
1887
+ this.nested = opts.nested;
1888
+ }
1889
+ }
1890
+
1891
+ beforeEach(async () => {
1892
+ await setup({ schema: PolymorphDocument });
1893
+ });
1894
+
1895
+ it("can query multiple versions at once", async () => {
1896
+ await store.put(
1897
+ new PolymorphDocument({
1898
+ id: "1",
1899
+ nested: new Nested({
1900
+ nestedAgain: new V0({
1901
+ number: 1n,
1902
+ }),
1903
+ }),
1904
+ }),
1905
+ );
1906
+
1907
+ const doc2 = new PolymorphDocument({
1908
+ id: "2",
1909
+ nested: new Nested({
1910
+ nestedAgain: new V1({
1911
+ string: "hello",
1912
+ }),
1913
+ }),
1914
+ });
1915
+ await store.put(doc2);
1916
+
1917
+ const response = await search(
1918
+ store,
1919
+ new SearchRequest({
1920
+ query: [
1921
+ new StringMatch({
1922
+ key: ["nested", "nestedAgain", "string"],
1923
+ value: "hello",
1924
+ }),
1925
+ ],
1926
+ }),
1927
+ );
1928
+
1929
+ expect(response.results).to.have.length(1);
1930
+ expect(response.results[0].value.id).to.equal("2");
1931
+
1932
+ checkDocument(response.results[0].value, doc2);
1933
+ });
1934
+ });
1935
+
1936
+ describe("array", () => {
1937
+ describe("polymorphism-simple-base", () => {
1938
+ abstract class Base {}
1939
+
1940
+ @variant("av0")
1941
+ class AV0 extends Base {
1942
+ @field({ type: option("u64") })
1943
+ number?: bigint;
1944
+
1945
+ constructor(opts: AV0) {
1946
+ super();
1947
+ this.number = opts.number;
1948
+ }
1949
+ }
1950
+
1951
+ @variant("av1")
1952
+ class AV1 extends Base {
1953
+ @field({ type: option("string") })
1954
+ string?: string;
1955
+
1956
+ constructor(opts: AV1) {
1957
+ super();
1958
+ this.string = opts.string;
1959
+ }
1960
+ }
1961
+
1962
+ @variant("PolymorpArrayDocument")
1963
+ class PolymorpArrayDocument {
1964
+ @id({ type: "string" })
1965
+ id: string;
1966
+
1967
+ @field({ type: vec(Base) })
1968
+ array: Base[];
1969
+
1970
+ constructor(opts: PolymorpArrayDocument) {
1971
+ this.id = opts.id;
1972
+ this.array = opts.array;
1973
+ }
1974
+ }
1975
+
1976
+ beforeEach(async () => {
1977
+ await setup({ schema: PolymorpArrayDocument });
1978
+ });
1979
+
1980
+ it("can query multiple versions at once", async () => {
1981
+ await store.put(
1982
+ new PolymorpArrayDocument({
1983
+ id: "1",
1984
+ array: [
1985
+ new AV0({
1986
+ number: 0n,
1987
+ }),
1988
+ new AV1({
1989
+ string: "hello",
1990
+ }),
1991
+ ],
1992
+ }),
1993
+ );
1994
+
1995
+ const doc2 = new PolymorpArrayDocument({
1996
+ id: "2",
1997
+ array: [
1998
+ new AV1({
1999
+ string: "world",
2000
+ }),
2001
+ new AV0({
2002
+ number: 2n,
2003
+ }),
2004
+ ],
2005
+ });
2006
+
2007
+ await store.put(doc2);
2008
+
2009
+ const response = await search(
2010
+ store,
2011
+ new SearchRequest({
2012
+ query: [
2013
+ new StringMatch({
2014
+ key: ["array", "string"],
2015
+ value: "world",
2016
+ }),
2017
+ ],
2018
+ }),
2019
+ );
2020
+
2021
+ expect(response.results).to.have.length(1);
2022
+ expect(response.results[0].value.id).to.equal("2");
2023
+ checkDocument(response.results[0].value, doc2);
2024
+ });
2025
+ });
2026
+
2027
+ describe("polymorphism-variant-base", () => {
2028
+ @variant(0)
2029
+ abstract class Base {}
2030
+
2031
+ @variant("bv0")
2032
+ class V0 extends Base {
2033
+ @field({ type: option("u64") })
2034
+ number?: bigint;
2035
+
2036
+ constructor(opts: V0) {
2037
+ super();
2038
+ this.number = opts.number;
2039
+ }
2040
+ }
2041
+
2042
+ @variant("bv1")
2043
+ class V1 extends Base {
2044
+ @field({ type: option("string") })
2045
+ string?: string;
2046
+
2047
+ constructor(opts: V1) {
2048
+ super();
2049
+ this.string = opts.string;
2050
+ }
2051
+ }
2052
+
2053
+ @variant("PolymorpArrayDocument")
2054
+ class PolymorpDocument {
2055
+ @id({ type: "string" })
2056
+ id: string;
2057
+
2058
+ @field({ type: Base })
2059
+ base: Base;
2060
+
2061
+ constructor(opts: PolymorpDocument) {
2062
+ this.id = opts.id;
2063
+ this.base = opts.base;
2064
+ }
2065
+ }
2066
+
2067
+ beforeEach(async () => {
2068
+ await setup({ schema: PolymorpDocument });
2069
+ });
2070
+
2071
+ it("can query multiple versions at once", async () => {
2072
+ await store.put(
2073
+ new PolymorpDocument({
2074
+ id: "1",
2075
+ base: new V0({
2076
+ number: 0n,
2077
+ }),
2078
+ }),
2079
+ );
2080
+
2081
+ const doc2 = new PolymorpDocument({
2082
+ id: "2",
2083
+ base: new V1({
2084
+ string: "world",
2085
+ }),
2086
+ });
2087
+
2088
+ await store.put(doc2);
2089
+
2090
+ const response = await search(
2091
+ store,
2092
+ new SearchRequest({
2093
+ query: [
2094
+ new StringMatch({
2095
+ key: ["base", "string"],
2096
+ value: "world",
2097
+ }),
2098
+ ],
2099
+ }),
2100
+ );
2101
+
2102
+ expect(response.results).to.have.length(1);
2103
+ expect(response.results[0].value.id).to.equal("2");
2104
+ checkDocument(response.results[0].value, doc2);
2105
+ });
2106
+ });
2107
+
2108
+ describe("nested-string-array", () => {
2109
+ class Nested {
2110
+ @field({ type: vec("string") })
2111
+ arr: string[];
2112
+
2113
+ constructor(opts: Nested) {
2114
+ this.arr = opts.arr;
2115
+ }
2116
+ }
2117
+
2118
+ @variant("NestedArrayDocument")
2119
+ class NestedArrayDocument {
2120
+ @id({ type: "string" })
2121
+ id: string;
2122
+
2123
+ @field({ type: Nested })
2124
+ nested: Nested;
2125
+
2126
+ constructor(opts: NestedArrayDocument) {
2127
+ this.id = opts.id;
2128
+ this.nested = opts.nested;
2129
+ }
2130
+ }
2131
+
2132
+ beforeEach(async () => {
2133
+ await setup({ schema: NestedArrayDocument });
2134
+ });
2135
+
2136
+ it("can query nested array", async () => {
2137
+ await store.put(
2138
+ new NestedArrayDocument({
2139
+ id: "1",
2140
+ nested: new Nested({
2141
+ arr: ["hello", "world"],
2142
+ }),
2143
+ }),
2144
+ );
2145
+
2146
+ const doc2 = new NestedArrayDocument({
2147
+ id: "2",
2148
+ nested: new Nested({
2149
+ arr: ["hello", "värld"],
2150
+ }),
2151
+ });
2152
+ await store.put(doc2);
2153
+
2154
+ const response = await search(
2155
+ store,
2156
+ new SearchRequest({
2157
+ query: [
2158
+ new StringMatch({
2159
+ key: ["nested", "arr"],
2160
+ value: "värld",
2161
+ }),
2162
+ ],
2163
+ }),
2164
+ );
2165
+
2166
+ expect(response.results).to.have.length(1);
2167
+ expect(
2168
+ response.results.map((x) => x.value.id),
2169
+ ).to.have.members(["2"]);
2170
+ checkDocument(response.results[0].value, doc2);
2171
+ });
2172
+ });
2173
+
2174
+ describe("nested multiple fields", () => {
2175
+ class NestedMultipleFieldsDocument {
2176
+ @field({ type: "string" })
2177
+ a: string;
2178
+
2179
+ @field({ type: "string" })
2180
+ b: string;
2181
+
2182
+ constructor(opts: NestedMultipleFieldsDocument) {
2183
+ this.a = opts.a;
2184
+ this.b = opts.b;
2185
+ }
2186
+ }
2187
+
2188
+ @variant("NestedMultipleFieldsArrayDocument")
2189
+ class NestedMultipleFieldsArrayDocument {
2190
+ @id({ type: "string" })
2191
+ id: string;
2192
+
2193
+ @field({ type: vec(NestedMultipleFieldsDocument) })
2194
+ array: NestedMultipleFieldsDocument[];
2195
+
2196
+ constructor(opts: NestedMultipleFieldsArrayDocument) {
2197
+ this.id = opts.id;
2198
+ this.array = opts.array;
2199
+ }
2200
+ }
2201
+
2202
+ beforeEach(async () => {
2203
+ await setup({ schema: NestedMultipleFieldsArrayDocument });
2204
+ });
2205
+
2206
+ it("combined query", async () => {
2207
+ const doc1 = new NestedMultipleFieldsArrayDocument({
2208
+ id: "1",
2209
+ array: [
2210
+ new NestedMultipleFieldsDocument({
2211
+ a: "hello",
2212
+ b: "world",
2213
+ }),
2214
+ ],
2215
+ });
2216
+ await store.put(doc1);
2217
+ await store.put(
2218
+ new NestedMultipleFieldsArrayDocument({
2219
+ id: "2",
2220
+ array: [
2221
+ new NestedMultipleFieldsDocument({
2222
+ a: "hello",
2223
+ b: "värld",
2224
+ }),
2225
+ new NestedMultipleFieldsDocument({
2226
+ a: "hej",
2227
+ b: "world",
2228
+ }),
2229
+ ],
2230
+ }),
2231
+ );
2232
+
2233
+ const response = await search(
2234
+ store,
2235
+ new SearchRequest({
2236
+ query: [
2237
+ new Nested({
2238
+ path: "array",
2239
+ query: new And([
2240
+ new StringMatch({
2241
+ key: "a",
2242
+ value: "hello",
2243
+ }),
2244
+ new StringMatch({
2245
+ key: "b",
2246
+ value: "world",
2247
+ }),
2248
+ ]),
2249
+ }),
2250
+ ],
2251
+ }),
2252
+ );
2253
+
2254
+ expect(response.results).to.have.length(1);
2255
+ expect(response.results[0].value.id).to.equal("1");
2256
+ checkDocument(response.results[0].value, doc1);
2257
+ });
2258
+
2259
+ it("nested partial match", async () => {
2260
+ // query nested without Nested query to match either or
2261
+
2262
+ const doc1 = new NestedMultipleFieldsArrayDocument({
2263
+ id: "1",
2264
+ array: [
2265
+ new NestedMultipleFieldsDocument({
2266
+ a: "hello",
2267
+ b: "world",
2268
+ }),
2269
+ ],
2270
+ });
2271
+ await store.put(doc1);
2272
+ await store.put(
2273
+ new NestedMultipleFieldsArrayDocument({
2274
+ id: "2",
2275
+ array: [
2276
+ new NestedMultipleFieldsDocument({
2277
+ a: "hello",
2278
+ b: "värld",
2279
+ }),
2280
+ new NestedMultipleFieldsDocument({
2281
+ a: "hej",
2282
+ b: "world",
2283
+ }),
2284
+ ],
2285
+ }),
2286
+ );
2287
+
2288
+ const response = await search(
2289
+ store,
2290
+ new SearchRequest({
2291
+ query: [
2292
+ new StringMatch({
2293
+ key: ["array", "a"],
2294
+ value: "hello",
2295
+ }),
2296
+ new StringMatch({
2297
+ key: ["array", "b"],
2298
+ value: "world",
2299
+ }),
2300
+ ],
2301
+ }),
2302
+ );
2303
+
2304
+ expect(response.results).to.have.length(2);
2305
+ checkDocument(response.results[0].value, doc1);
2306
+ });
2307
+ });
2308
+ });
2309
+ });
2310
+ });
2311
+
2312
+ /* TODO!
2313
+ describe("polymorph-root", () => {
2314
+ @variant(1)
2315
+ class DocumentNext extends Base {
2316
+
2317
+ @field({ type: "string" })
2318
+ id: string;
2319
+
2320
+ @field({ type: "string" })
2321
+ name: string;
2322
+
2323
+ @field({ type: "string" })
2324
+ anotherField: string
2325
+
2326
+ constructor(opts: Partial<DocumentNext>) {
2327
+ super()
2328
+ this.id = opts.id || uuid();
2329
+ this.name = opts.name || uuid()
2330
+ this.anotherField = opts.anotherField || uuid()
2331
+ }
2332
+ }
2333
+
2334
+
2335
+ beforeEach(async () => {
2336
+ await setup({ schema: Base });
2337
+ });
2338
+
2339
+ it("can query one of the version", async () => {
2340
+
2341
+ await store.put(new DocumentNext({ anotherField: 'hello' }))
2342
+
2343
+ const result = await search(store, new SearchRequest({ query: new StringMatch({ key: 'anotherField', value: 'hello' }) }))
2344
+ expect(result.results).to.have.length(1);
2345
+
2346
+ const [doc] = result.results;
2347
+ expect(doc.value).to.be.instanceOf(DocumentNext)
2348
+ })
2349
+
2350
+ it("can query multiple versions at once", async () => {
2351
+ let name = uuid()
2352
+ await store.put(new DocumentNext({ name }))
2353
+ await store.put(new DocumentNext({ name }))
2354
+
2355
+ const result = await search(store, new SearchRequest({ query: new StringMatch({ key: 'name', value: name }) }))
2356
+
2357
+ expect(result.results).to.have.length(2);
2358
+ for (const doc of result.results) {
2359
+ expect(doc.value).to.be.instanceOf(DocumentNext)
2360
+ }
2361
+ })
2362
+ }) */
2363
+ });
2364
+ describe("shape", () => {
2365
+ describe("simple", () => {
2366
+ beforeEach(async () => {
2367
+ await setupDefault();
2368
+ });
2369
+
2370
+ it("filter field", async () => {
2371
+ const results = await search(
2372
+ store,
2373
+ new SearchRequest({ query: [] }),
2374
+ { shape: { id: true } },
2375
+ );
2376
+ expect(results.results).to.have.length(4);
2377
+ for (const result of results.results) {
2378
+ if (shapingSupported) {
2379
+ expect(Object.keys(result.value)).to.have.length(1);
2380
+ expect(result.value["id"]).to.exist;
2381
+ } else {
2382
+ expect(
2383
+ Object.keys(result.value).length,
2384
+ ).to.be.greaterThanOrEqual(1);
2385
+ expect(result.value["id"]).to.exist;
2386
+ }
2387
+ }
2388
+ });
2389
+
2390
+ it("nested field", async () => {
2391
+ const results = await search(
2392
+ store,
2393
+ new SearchRequest({ query: [] }),
2394
+ { shape: { nestedVec: { number: true } } },
2395
+ );
2396
+ expect(results.results).to.have.length(4);
2397
+ for (const value of results.results) {
2398
+ const arr = value.value["nestedVec"];
2399
+ expect(arr).to.be.exist;
2400
+
2401
+ if (arr.length > 0) {
2402
+ for (const element of arr) {
2403
+ expect(element.number).to.exist;
2404
+ if (shapingSupported) {
2405
+ expect(Object.keys(element)).to.have.length(1);
2406
+ }
2407
+ }
2408
+ }
2409
+ }
2410
+ });
2411
+ });
2412
+
2413
+ describe("nested", () => {
2414
+ class MultifieldNested {
2415
+ @field({ type: "bool" })
2416
+ bool: boolean;
2417
+
2418
+ @field({ type: "u32" })
2419
+ number: number;
2420
+
2421
+ @field({ type: vec("string") })
2422
+ string: string[];
2423
+
2424
+ constructor(bool: boolean, number: number, string: string[]) {
2425
+ this.bool = bool;
2426
+ this.number = number;
2427
+ this.string = string;
2428
+ }
2429
+ }
2430
+
2431
+ class NestedBoolQueryDocument {
2432
+ @id({ type: "string" })
2433
+ id: string;
2434
+
2435
+ @field({ type: MultifieldNested })
2436
+ nested: MultifieldNested;
2437
+
2438
+ constructor(id: string, nested: MultifieldNested) {
2439
+ this.id = id;
2440
+ this.nested = nested;
2441
+ }
2442
+ }
2443
+
2444
+ let index: Awaited<ReturnType<typeof setup<NestedBoolQueryDocument>>>;
2445
+
2446
+ afterEach(async () => {
2447
+ await index.store.stop();
2448
+ });
2449
+
2450
+ it("nested", async () => {
2451
+ index = await setup({ schema: NestedBoolQueryDocument });
2452
+
2453
+ await index.store.put(
2454
+ new NestedBoolQueryDocument(
2455
+ "1",
2456
+ new MultifieldNested(true, 1, ["1"]),
2457
+ ),
2458
+ );
2459
+ await index.store.put(
2460
+ new NestedBoolQueryDocument(
2461
+ "2",
2462
+ new MultifieldNested(false, 2, ["2"]),
2463
+ ),
2464
+ );
2465
+
2466
+ const shapedResults = await iterate(
2467
+ index.store,
2468
+ new SearchRequest({
2469
+ query: new BoolQuery({ key: ["nested", "bool"], value: false }),
2470
+ fetch: 1,
2471
+ }),
2472
+ ).all({ shape: { id: true } });
2473
+ expect(shapedResults).to.have.length(1);
2474
+ expect(shapedResults[0].value.id).to.equal("2");
2475
+
2476
+ if (shapingSupported) {
2477
+ expect(shapedResults[0].value.nested).to.be.undefined;
2478
+ } else {
2479
+ expect(shapedResults[0].value.nested).to.exist;
2480
+ }
2481
+
2482
+ const unshapedResults = await iterate(
2483
+ index.store,
2484
+ new SearchRequest({
2485
+ query: new BoolQuery({ key: ["nested", "bool"], value: false }),
2486
+ fetch: 1,
2487
+ }),
2488
+ ).all();
2489
+ expect(unshapedResults).to.have.length(1);
2490
+ expect(unshapedResults[0].value.id).to.equal("2");
2491
+ expect(unshapedResults[0].value.nested).to.exist;
2492
+ });
2493
+
2494
+ it("nested-filtering", async () => {
2495
+ index = await setup({ schema: NestedBoolQueryDocument });
2496
+
2497
+ const d1 = new NestedBoolQueryDocument(
2498
+ "1",
2499
+ new MultifieldNested(true, 1, ["1"]),
2500
+ );
2501
+ const d2 = new NestedBoolQueryDocument(
2502
+ "2",
2503
+ new MultifieldNested(false, 2, ["2"]),
2504
+ );
2505
+
2506
+ await index.store.put(d1);
2507
+ await index.store.put(d2);
2508
+
2509
+ const unshapedResults = await iterate(
2510
+ index.store,
2511
+ new SearchRequest({
2512
+ query: new StringMatch({ key: ["id"], value: "2" }),
2513
+ fetch: 1,
2514
+ }),
2515
+ ).all();
2516
+ expect(unshapedResults).to.have.length(1);
2517
+ expect(unshapedResults[0].value.id).to.equal(d2.id);
2518
+ expect(unshapedResults[0].value.nested).to.deep.equal(d2.nested);
2519
+
2520
+ const shapedResults = await iterate(
2521
+ index.store,
2522
+ new SearchRequest({
2523
+ query: new StringMatch({ key: ["id"], value: "2" }),
2524
+ fetch: 1,
2525
+ }),
2526
+ ).all({ shape: { id: true, nested: { bool: true } } });
2527
+ expect(shapedResults).to.have.length(1);
2528
+ expect(shapedResults[0].value.id).to.equal(d2.id);
2529
+
2530
+ if (shapingSupported) {
2531
+ expect({ ...shapedResults[0].value.nested }).to.deep.equal({
2532
+ bool: false,
2533
+ });
2534
+ } else {
2535
+ expect(shapedResults[0].value.nested).to.deep.equal(d2.nested);
2536
+ }
2537
+ });
2538
+ });
2539
+
2540
+ describe("nested-poly", () => {
2541
+ abstract class Base {}
2542
+
2543
+ @variant(0)
2544
+ class MultifieldNested extends Base {
2545
+ @field({ type: "bool" })
2546
+ bool: boolean;
2547
+
2548
+ @field({ type: "u32" })
2549
+ number: number;
2550
+
2551
+ @field({ type: vec("string") })
2552
+ string: string[];
2553
+
2554
+ constructor(bool: boolean, number: number, string: string[]) {
2555
+ super();
2556
+ this.bool = bool;
2557
+ this.number = number;
2558
+ this.string = string;
2559
+ }
2560
+ }
2561
+
2562
+ @variant(1)
2563
+ class _AnotherMultifieldNested extends Base {
2564
+ @field({ type: "bool" })
2565
+ bool: boolean;
2566
+
2567
+ @field({ type: "u32" })
2568
+ number: number;
2569
+
2570
+ @field({ type: vec("string") })
2571
+ string: string[];
2572
+
2573
+ constructor(bool: boolean, number: number, string: string[]) {
2574
+ super();
2575
+ this.bool = bool;
2576
+ this.number = number;
2577
+ this.string = string;
2578
+ }
2579
+ }
2580
+
2581
+ class NestedBoolQueryDocument {
2582
+ @id({ type: "string" })
2583
+ id: string;
2584
+
2585
+ @field({ type: Base })
2586
+ nested: Base;
2587
+
2588
+ constructor(id: string, nested: Base) {
2589
+ this.id = id;
2590
+ this.nested = nested;
2591
+ }
2592
+ }
2593
+
2594
+ let index: Awaited<ReturnType<typeof setup<NestedBoolQueryDocument>>>;
2595
+
2596
+ afterEach(async () => {
2597
+ await index.store.stop();
2598
+ });
2599
+
2600
+ it("nested", async () => {
2601
+ index = await setup({ schema: NestedBoolQueryDocument });
2602
+
2603
+ await index.store.put(
2604
+ new NestedBoolQueryDocument(
2605
+ "1",
2606
+ new MultifieldNested(true, 1, ["1"]),
2607
+ ),
2608
+ );
2609
+ await index.store.put(
2610
+ new NestedBoolQueryDocument(
2611
+ "2",
2612
+ new MultifieldNested(false, 2, ["2"]),
2613
+ ),
2614
+ );
2615
+
2616
+ const shapedResults = await iterate(
2617
+ index.store,
2618
+ new SearchRequest({
2619
+ query: new BoolQuery({ key: ["nested", "bool"], value: false }),
2620
+ fetch: 1,
2621
+ }),
2622
+ ).all({ shape: { id: true } });
2623
+ expect(shapedResults).to.have.length(1);
2624
+ expect(shapedResults[0].value.id).to.equal("2");
2625
+
2626
+ if (shapingSupported) {
2627
+ expect(shapedResults[0].value.nested).to.be.undefined;
2628
+ } else {
2629
+ expect(shapedResults[0].value.nested).to.exist;
2630
+ }
2631
+
2632
+ const unshapedResults = await iterate(
2633
+ index.store,
2634
+ new SearchRequest({
2635
+ query: new BoolQuery({ key: ["nested", "bool"], value: false }),
2636
+ fetch: 1,
2637
+ }),
2638
+ ).all();
2639
+ expect(unshapedResults).to.have.length(1);
2640
+ expect(unshapedResults[0].value.id).to.equal("2");
2641
+ expect(unshapedResults[0].value.nested).to.exist;
2642
+ });
2643
+
2644
+ it("nested-filtering", async () => {
2645
+ index = await setup({ schema: NestedBoolQueryDocument });
2646
+
2647
+ const d1 = new NestedBoolQueryDocument(
2648
+ "1",
2649
+ new MultifieldNested(true, 1, ["1"]),
2650
+ );
2651
+ const d2 = new NestedBoolQueryDocument(
2652
+ "2",
2653
+ new MultifieldNested(false, 2, ["2"]),
2654
+ );
2655
+
2656
+ await index.store.put(d1);
2657
+ await index.store.put(d2);
2658
+
2659
+ const unshapedResults = await iterate(
2660
+ index.store,
2661
+ new SearchRequest({
2662
+ query: new StringMatch({ key: ["id"], value: "2" }),
2663
+ fetch: 1,
2664
+ }),
2665
+ ).all();
2666
+ expect(unshapedResults).to.have.length(1);
2667
+ expect(unshapedResults[0].value.id).to.equal(d2.id);
2668
+ expect(unshapedResults[0].value.nested).to.deep.equal(d2.nested);
2669
+
2670
+ const shapedResults = await iterate(
2671
+ index.store,
2672
+ new SearchRequest({
2673
+ query: new StringMatch({ key: ["id"], value: "2" }),
2674
+ fetch: 1,
2675
+ }),
2676
+ ).all({ shape: { id: true, nested: { bool: true } } });
2677
+ expect(shapedResults).to.have.length(1);
2678
+ expect(shapedResults[0].value.id).to.equal(d2.id);
2679
+
2680
+ if (shapingSupported) {
2681
+ expect({ ...shapedResults[0].value.nested }).to.deep.equal({
2682
+ bool: false,
2683
+ });
2684
+ } else {
2685
+ expect(shapedResults[0].value.nested).to.deep.equal(d2.nested);
2686
+ }
2687
+ });
2688
+ });
2689
+
2690
+ describe("nested-array", () => {
2691
+ class MultifieldNested {
2692
+ @field({ type: "bool" })
2693
+ bool: boolean;
2694
+
2695
+ @field({ type: "u32" })
2696
+ number: number;
2697
+
2698
+ @field({ type: vec("string") })
2699
+ string: string[];
2700
+
2701
+ constructor(bool: boolean, number: number, string: string[]) {
2702
+ this.bool = bool;
2703
+ this.number = number;
2704
+ this.string = string;
2705
+ }
2706
+ }
2707
+
2708
+ class NestedBoolQueryDocument {
2709
+ @id({ type: "string" })
2710
+ id: string;
2711
+
2712
+ @field({ type: vec(MultifieldNested) })
2713
+ nested: MultifieldNested[];
2714
+
2715
+ constructor(id: string, nested: MultifieldNested) {
2716
+ this.id = id;
2717
+ this.nested = [nested];
2718
+ }
2719
+ }
2720
+
2721
+ let index: Awaited<ReturnType<typeof setup<NestedBoolQueryDocument>>>;
2722
+
2723
+ afterEach(async () => {
2724
+ await index.store.stop();
2725
+ });
2726
+
2727
+ it("nested", async () => {
2728
+ index = await setup({ schema: NestedBoolQueryDocument });
2729
+
2730
+ await index.store.put(
2731
+ new NestedBoolQueryDocument(
2732
+ "1",
2733
+ new MultifieldNested(true, 1, ["1"]),
2734
+ ),
2735
+ );
2736
+ await index.store.put(
2737
+ new NestedBoolQueryDocument(
2738
+ "2",
2739
+ new MultifieldNested(false, 2, ["2"]),
2740
+ ),
2741
+ );
2742
+
2743
+ const shapedResults = await iterate(
2744
+ index.store,
2745
+ new SearchRequest({
2746
+ query: new BoolQuery({ key: ["nested", "bool"], value: false }),
2747
+ fetch: 1,
2748
+ }),
2749
+ ).all({ shape: { id: true } });
2750
+ expect(shapedResults).to.have.length(1);
2751
+ expect(shapedResults[0].value.id).to.equal("2");
2752
+
2753
+ if (shapingSupported) {
2754
+ expect(shapedResults[0].value.nested).to.be.undefined;
2755
+ } else {
2756
+ expect(shapedResults[0].value.nested).to.exist;
2757
+ }
2758
+
2759
+ const unshapedResults = await iterate(
2760
+ index.store,
2761
+ new SearchRequest({
2762
+ query: new BoolQuery({ key: ["nested", "bool"], value: false }),
2763
+ fetch: 1,
2764
+ }),
2765
+ ).all();
2766
+ expect(unshapedResults).to.have.length(1);
2767
+ expect(unshapedResults[0].value.id).to.equal("2");
2768
+ expect(unshapedResults[0].value.nested).to.exist;
2769
+ });
2770
+
2771
+ it("nested-filtering", async () => {
2772
+ index = await setup({ schema: NestedBoolQueryDocument });
2773
+
2774
+ const d1 = new NestedBoolQueryDocument(
2775
+ "1",
2776
+ new MultifieldNested(true, 1, ["1"]),
2777
+ );
2778
+ const d2 = new NestedBoolQueryDocument(
2779
+ "2",
2780
+ new MultifieldNested(false, 2, ["2"]),
2781
+ );
2782
+
2783
+ await index.store.put(d1);
2784
+ await index.store.put(d2);
2785
+
2786
+ const shapedResults = await iterate(
2787
+ index.store,
2788
+ new SearchRequest({
2789
+ query: new StringMatch({ key: ["id"], value: "2" }),
2790
+ fetch: 1,
2791
+ }),
2792
+ ).all({ shape: { id: true, nested: { bool: true } } });
2793
+ expect(shapedResults).to.have.length(1);
2794
+ expect(shapedResults[0].value.id).to.equal(d2.id);
2795
+
2796
+ if (shapingSupported) {
2797
+ expect({ ...shapedResults[0].value.nested[0] }).to.deep.equal({
2798
+ bool: false,
2799
+ });
2800
+ } else {
2801
+ expect(shapedResults[0].value.nested[0]).to.deep.equal(
2802
+ d2.nested[0],
2803
+ );
2804
+ }
2805
+
2806
+ const unshapedResults = await iterate(
2807
+ index.store,
2808
+ new SearchRequest({
2809
+ query: new StringMatch({ key: ["id"], value: "2" }),
2810
+ fetch: 1,
2811
+ }),
2812
+ ).all();
2813
+ expect(unshapedResults).to.have.length(1);
2814
+ expect(unshapedResults[0].value.id).to.equal(d2.id);
2815
+ expect(unshapedResults[0].value.nested[0]).to.deep.equal(
2816
+ d2.nested[0],
2817
+ );
2818
+ });
2819
+ });
2820
+ });
2821
+ });
2822
+
2823
+ describe("sort", () => {
2824
+ const put = async (id: number) => {
2825
+ const doc = new Document({
2826
+ id: String(id),
2827
+ name: String(id),
2828
+ number: BigInt(id),
2829
+ tags: [],
2830
+ });
2831
+ const resp = await store.put(doc);
2832
+ return resp;
2833
+ };
2834
+
2835
+ const checkIterate = async (
2836
+ batches: bigint[][],
2837
+ query: Query[] = [
2838
+ new IntegerCompare({
2839
+ key: "number",
2840
+ compare: Compare.GreaterOrEqual,
2841
+ value: 0n,
2842
+ }),
2843
+ ],
2844
+ sort: Sort[] = [
2845
+ new Sort({ direction: SortDirection.ASC, key: "number" }),
2846
+ ],
2847
+ ) => {
2848
+ await waitForResolved(async () => {
2849
+ const req = new SearchRequest({
2850
+ query,
2851
+ sort,
2852
+ });
2853
+ const iterator = iterate(store, req);
2854
+
2855
+ if (batches.length === 0) {
2856
+ // No fetches has been made, so we don't know whether we are done yet
2857
+ expect(iterator.done()).to.be.false;
2858
+ } else {
2859
+ for (const batch of batches) {
2860
+ expect(iterator.done()).to.be.false;
2861
+ const next = await iterator.next(batch.length);
2862
+ expect(
2863
+ next.results.map((x) => Number(x.value.number)),
2864
+ ).to.deep.equal(batch.map((x) => Number(x)));
2865
+ }
2866
+ expect(iterator.done()).to.be.true;
2867
+ }
2868
+ });
2869
+ };
2870
+
2871
+ beforeEach(async () => {
2872
+ const results = await setup({ schema: Document });
2873
+ store = results.store;
2874
+ });
2875
+
2876
+ it("empty", async () => {
2877
+ await checkIterate([]);
2878
+ });
2879
+
2880
+ // TODO make sure documents are evenly distrubted before query
2881
+ it("multiple batches", async () => {
2882
+ await put(0);
2883
+ await put(1);
2884
+ await put(2);
2885
+ expect(await store.getSize()).equal(3);
2886
+ await checkIterate([[0n], [1n], [2n]]);
2887
+ await checkIterate([[0n, 1n, 2n]]);
2888
+ await checkIterate([[0n, 1n], [2n]]);
2889
+ await checkIterate([[0n], [1n, 2n]]);
2890
+ });
2891
+
2892
+ it("sorts by order", async () => {
2893
+ await put(0);
2894
+ await put(1);
2895
+ await put(2);
2896
+ {
2897
+ const iterator = await iterate(
2898
+ store,
2899
+ new SearchRequest({
2900
+ query: [],
2901
+ sort: [new Sort({ direction: SortDirection.ASC, key: "name" })],
2902
+ }),
2903
+ );
2904
+ expect(iterator.done()).to.be.false;
2905
+ const next = await iterator.next(3);
2906
+ expect(next.results.map((x) => x.value.name)).to.deep.equal([
2907
+ "0",
2908
+ "1",
2909
+ "2",
2910
+ ]);
2911
+ expect(iterator.done()).to.be.true;
2912
+ }
2913
+ {
2914
+ const iterator = await iterate(
2915
+ store,
2916
+ new SearchRequest({
2917
+ query: [],
2918
+ sort: [new Sort({ direction: SortDirection.DESC, key: "name" })],
2919
+ }),
2920
+ );
2921
+ expect(iterator.done()).to.be.false;
2922
+ const next = await iterator.next(3);
2923
+ expect(next.results.map((x) => x.value.name)).to.deep.equal([
2924
+ "2",
2925
+ "1",
2926
+ "0",
2927
+ ]);
2928
+ expect(iterator.done()).to.be.true;
2929
+ }
2930
+ });
2931
+
2932
+ it("strings", async () => {
2933
+ await put(0);
2934
+ await put(1);
2935
+ await put(2);
2936
+
2937
+ const iterator = await iterate(
2938
+ store,
2939
+ new SearchRequest({
2940
+ query: [],
2941
+ sort: [new Sort({ direction: SortDirection.ASC, key: "name" })],
2942
+ }),
2943
+ );
2944
+ expect(iterator.done()).to.be.false;
2945
+ const next = await iterator.next(3);
2946
+ expect(next.results.map((x) => x.value.name)).to.deep.equal([
2947
+ "0",
2948
+ "1",
2949
+ "2",
2950
+ ]);
2951
+ expect(iterator.done()).to.be.true;
2952
+ });
2953
+
2954
+ describe("nested", () => {
2955
+ it("variants", async () => {
2956
+ const doc1 = new Document({
2957
+ id: "1",
2958
+ nested: new NestedValue({ number: 1 }),
2959
+ });
2960
+ const doc2 = new Document({
2961
+ id: "2",
2962
+ nested: new NestedValue({ number: 2 }),
2963
+ });
2964
+ await store.put(doc1);
2965
+ await store.put(doc2);
2966
+
2967
+ const iterator = iterate(
2968
+ store,
2969
+ new SearchRequest({
2970
+ sort: [
2971
+ new Sort({
2972
+ direction: SortDirection.DESC,
2973
+ key: ["nested", "number"],
2974
+ }),
2975
+ ],
2976
+ }),
2977
+ );
2978
+ expect(iterator.done()).to.be.false;
2979
+ const next = await iterator.next(2);
2980
+ expect(next.results.map((x) => x.value.id)).to.deep.equal(["2", "1"]);
2981
+ });
2982
+
2983
+ describe("nested-nested-invariant", () => {
2984
+ class V0 {
2985
+ @field({ type: "u64" })
2986
+ number: bigint;
2987
+
2988
+ constructor(number: bigint) {
2989
+ this.number = number;
2990
+ }
2991
+ }
2992
+
2993
+ class NestedValue {
2994
+ @field({ type: V0 })
2995
+ v0?: V0;
2996
+
2997
+ constructor(v0?: V0) {
2998
+ this.v0 = v0;
2999
+ }
3000
+ }
3001
+
3002
+ class Document {
3003
+ @id({ type: "string" })
3004
+ id: string;
3005
+
3006
+ @field({ type: NestedValue })
3007
+ nested: NestedValue;
3008
+
3009
+ constructor(id: string, nested: NestedValue) {
3010
+ this.id = id;
3011
+ this.nested = nested;
3012
+ }
3013
+ }
3014
+
3015
+ const doc1 = new Document("1", new NestedValue(new V0(1n)));
3016
+ const doc2 = new Document("2", new NestedValue(new V0(2n)));
3017
+
3018
+ beforeEach(async () => {
3019
+ await setup({ schema: Document });
3020
+ await store.put(doc1);
3021
+ await store.put(doc2);
3022
+ });
3023
+
3024
+ it("nested-variants", async () => {
3025
+ const iterator = iterate(
3026
+ store,
3027
+ new SearchRequest({
3028
+ sort: [
3029
+ new Sort({
3030
+ direction: SortDirection.DESC,
3031
+ key: ["nested", "v0", "number"],
3032
+ }),
3033
+ ],
3034
+ }),
3035
+ );
3036
+ expect(iterator.done()).to.be.false;
3037
+ const next = await iterator.next(2);
3038
+ expect(next.results.map((x) => x.value.id)).to.deep.equal([
3039
+ "2",
3040
+ "1",
3041
+ ]);
3042
+ });
3043
+ });
3044
+
3045
+ describe("variant-nested-invariant", () => {
3046
+ class V0 {
3047
+ @field({ type: "u64" })
3048
+ number: bigint;
3049
+
3050
+ constructor(number: bigint) {
3051
+ this.number = number;
3052
+ }
3053
+ }
3054
+
3055
+ class NestedValue {
3056
+ @field({ type: V0 })
3057
+ v0?: V0;
3058
+
3059
+ constructor(v0?: V0) {
3060
+ this.v0 = v0;
3061
+ }
3062
+ }
3063
+
3064
+ @variant(0)
3065
+ class DocumentV0 {
3066
+ @id({ type: "string" })
3067
+ id: string;
3068
+
3069
+ @field({ type: NestedValue })
3070
+ nested: NestedValue;
3071
+
3072
+ constructor(id: string, nested: NestedValue) {
3073
+ this.id = id;
3074
+ this.nested = nested;
3075
+ }
3076
+ }
3077
+
3078
+ const doc1 = new DocumentV0("1", new NestedValue(new V0(1n)));
3079
+ const doc2 = new DocumentV0("2", new NestedValue(new V0(2n)));
3080
+
3081
+ beforeEach(async () => {
3082
+ await setup({ schema: DocumentV0 });
3083
+ await store.put(doc1);
3084
+ await store.put(doc2);
3085
+ });
3086
+
3087
+ it("nested-variants", async () => {
3088
+ const iterator = iterate(
3089
+ store,
3090
+ new SearchRequest({
3091
+ sort: [
3092
+ new Sort({
3093
+ direction: SortDirection.DESC,
3094
+ key: ["nested", "v0", "number"],
3095
+ }),
3096
+ ],
3097
+ }),
3098
+ );
3099
+ expect(iterator.done()).to.be.false;
3100
+ const next = await iterator.next(2);
3101
+ expect(next.results.map((x) => x.value.id)).to.deep.equal([
3102
+ "2",
3103
+ "1",
3104
+ ]);
3105
+ });
3106
+ });
3107
+ /* TODO (requires sort join interleaving)
3108
+
3109
+ describe("nested-nested-variant", () => {
3110
+
3111
+ abstract class Base { }
3112
+
3113
+ @variant(0)
3114
+ class V0 extends Base {
3115
+
3116
+ @field({ type: 'u64' })
3117
+ number: bigint;
3118
+
3119
+ constructor(number: bigint) {
3120
+ super()
3121
+ this.number = number;
3122
+ }
3123
+ }
3124
+
3125
+ @variant(1)
3126
+ class V1 extends Base {
3127
+
3128
+ @field({ type: 'u64' })
3129
+ number: bigint;
3130
+
3131
+ constructor(number: bigint) {
3132
+ super()
3133
+ this.number = number;
3134
+ }
3135
+ }
3136
+
3137
+ class NestedValue {
3138
+ @field({ type: Base })
3139
+ v0: Base;
3140
+
3141
+ constructor(v0?: Base) {
3142
+ this.v0 = v0;
3143
+ }
3144
+ }
3145
+
3146
+ class Document {
3147
+ @id({ type: 'string' })
3148
+ id: string;
3149
+
3150
+ @field({ type: NestedValue })
3151
+ nested: NestedValue;
3152
+
3153
+ constructor(id: string, nested: NestedValue) {
3154
+ this.id = id;
3155
+ this.nested = nested;
3156
+ }
3157
+ }
3158
+
3159
+ const doc1 = new Document("1", new NestedValue(new V0(1n)));
3160
+ const doc2 = new Document("2", new NestedValue(new V0(2n)));
3161
+ const doc3 = new Document("3", new NestedValue(new V1(3n)));
3162
+ const doc4 = new Document("4", new NestedValue(new V1(4n)));
3163
+
3164
+ beforeEach(async () => {
3165
+ await setup({ schema: Document });
3166
+ await store.put(doc1);
3167
+ await store.put(doc2);
3168
+ await store.put(doc3);
3169
+ await store.put(doc4);
3170
+ });
3171
+
3172
+ it("nested-variants", async () => {
3173
+ const iterator = iterate(store, new SearchRequest({ sort: [new Sort({ direction: SortDirection.DESC, key: ["nested", "v0", "number"] })] }));
3174
+ expect(iterator.done()).to.be.false;
3175
+ const next = await iterator.next(4);
3176
+ expect(next.results.map((x) => x.value.id)).to.deep.equal(["4", "3", "2", "1"]);
3177
+
3178
+ })
3179
+
3180
+ })*/
3181
+
3182
+ /* TODO
3183
+ it("array sort", async () => {
3184
+
3185
+ const doc1 = new Document({
3186
+ id: "1",
3187
+ number: 101n,
3188
+ nestedVec: [new NestedValue({ number: 1 }), new NestedValue({ number: 300 })]
3189
+ });
3190
+ const doc2 = new Document({
3191
+ id: "2",
3192
+ number: 102n,
3193
+ nestedVec: [new NestedValue({ number: 2 }), new NestedValue({ number: 200 })]
3194
+ });
3195
+
3196
+ const doc3 = new Document({
3197
+ id: "3",
3198
+ number: 103n,
3199
+ nestedVec: [new NestedValue({ number: 3 }), new NestedValue({ number: 100 })]
3200
+ });
3201
+
3202
+ await store.put(doc1);
3203
+ await store.put(doc2);
3204
+ await store.put(doc3);
3205
+
3206
+ const iterator = iterate(store, new SearchRequest({ query: [new IntegerCompare({ key: 'number', compare: 'gte', value: 102n }), new Nested({ path: 'nestedVec', id: 'path-to-nested', query: [new IntegerCompare({ key: 'number', compare: 'gte', value: 200 })] })], sort: [new Sort({ direction: SortDirection.DESC, key: ["path-to-nested", "number"] })] }));
3207
+ expect(iterator.done()).to.be.false;
3208
+ const next = await iterator.next(2);
3209
+ expect(next.results.map((x) => x.value.id)).to.deep.equal(["2", "1"]);
3210
+ expect(iterator.done()).to.be.true;
3211
+ })*/
3212
+ });
3213
+
3214
+ describe("close", () => {
3215
+ it("by invoking close()", async () => {
3216
+ await put(0);
3217
+ await put(1);
3218
+ await put(2);
3219
+ const request = new SearchRequest({
3220
+ query: [],
3221
+ });
3222
+ const iterator = iterate(store, request);
3223
+ expect(iterator.done()).to.be.false;
3224
+ await iterator.next(2); // fetch some, but not all
3225
+ expect(store.getPending(request.idString)).equal(1);
3226
+ await iterator.close();
3227
+ await waitForResolved(
3228
+ () => expect(store.getPending(request.idString)).equal(undefined),
3229
+ { timeout: 3000, delayInterval: 50 },
3230
+ );
3231
+ });
3232
+
3233
+ it("end of iterator", async () => {
3234
+ await put(0);
3235
+ await put(1);
3236
+ await put(2);
3237
+ const request = new SearchRequest({
3238
+ query: [],
3239
+ });
3240
+ const iterator = await iterate(store, request);
3241
+ expect(iterator.done()).to.be.false;
3242
+ await iterator.next(3); // fetch all
3243
+ await waitForResolved(
3244
+ () => expect(store.getPending(request.idString)).equal(undefined),
3245
+ { timeout: 3000, delayInterval: 50 },
3246
+ );
3247
+ });
3248
+
3249
+ it("end of iterator, multiple nexts", async () => {
3250
+ await put(0);
3251
+ await put(1);
3252
+ await put(2);
3253
+ const request = new SearchRequest({
3254
+ query: [],
3255
+ });
3256
+ const iterator = await iterate(store, request);
3257
+ await iterator.next(2);
3258
+ await iterator.next(1);
3259
+ expect(iterator.done()).to.be.true;
3260
+ await waitForResolved(
3261
+ () => expect(store.getPending(request.idString)).equal(undefined),
3262
+ { timeout: 3000, delayInterval: 50 },
3263
+ );
3264
+ });
3265
+ });
3266
+
3267
+ // TODO test iterator.close() to stop pending promises
3268
+
3269
+ // TODO deletion while sort
3270
+
3271
+ // TODO session timeouts?
3272
+ });
3273
+
3274
+ describe("sum", () => {
3275
+ it("it returns sum", async () => {
3276
+ await setupDefault();
3277
+ const sum = await store.sum(new SumRequest({ key: "number" }));
3278
+ typeof sum === "bigint"
3279
+ ? expect(sum).to.equal(6n)
3280
+ : expect(sum).to.equal(6);
3281
+ });
3282
+
3283
+ it("it returns sum with query", async () => {
3284
+ await setupDefault();
3285
+ const sum = await store.sum(
3286
+ new SumRequest({
3287
+ key: "number",
3288
+ query: [
3289
+ new StringMatch({
3290
+ key: "tags",
3291
+ value: "world",
3292
+ method: StringMatchMethod.contains,
3293
+ caseInsensitive: true,
3294
+ }),
3295
+ ],
3296
+ }),
3297
+ );
3298
+ typeof sum === "bigint"
3299
+ ? expect(sum).to.equal(2n)
3300
+ : expect(sum).to.equal(2);
3301
+ });
3302
+ });
3303
+
3304
+ describe("count", () => {
3305
+ it("it returns count", async () => {
3306
+ await setupDefault();
3307
+ const sum = await store.count(new CountRequest());
3308
+ expect(sum).to.equal(4);
3309
+ });
3310
+
3311
+ it("it returns count with query", async () => {
3312
+ await setupDefault();
3313
+ const sum = await store.count(
3314
+ new CountRequest({
3315
+ query: [
3316
+ new StringMatch({
3317
+ key: "tags",
3318
+ value: "world",
3319
+ method: StringMatchMethod.contains,
3320
+ caseInsensitive: true,
3321
+ }),
3322
+ ],
3323
+ }),
3324
+ );
3325
+ expect(sum).to.equal(1);
3326
+ });
3327
+ });
3328
+
3329
+ describe("delete", () => {
3330
+ it("delete with query", async () => {
3331
+ await setupDefault();
3332
+ await store.del(
3333
+ new DeleteRequest({
3334
+ query: [
3335
+ new StringMatch({
3336
+ key: "tags",
3337
+ value: "world",
3338
+ method: StringMatchMethod.contains,
3339
+ caseInsensitive: true,
3340
+ }),
3341
+ ],
3342
+ }),
3343
+ );
3344
+ expect(await store.getSize()).to.equal(3);
3345
+ });
3346
+ });
3347
+
3348
+ describe("persistance", () => {
3349
+ if (type === "persist") {
3350
+ it("persists across restarts", async () => {
3351
+ const {
3352
+ store: documentStore,
3353
+ indices,
3354
+ directory,
3355
+ } = await setupDefault();
3356
+ expect(await documentStore.getSize()).equal(4);
3357
+ await indices.stop();
3358
+ const { store } = await setup({ schema: Document }, directory);
3359
+ expect(await store.getSize()).equal(4);
3360
+ });
3361
+ } else {
3362
+ it("should not persist", async () => {
3363
+ let { store } = await setup({ schema: Document });
3364
+ await store.stop();
3365
+ store = (await setup({ schema: Document })).store;
3366
+ expect(await store.getSize()).equal(0);
3367
+ });
3368
+ }
3369
+ });
3370
+
3371
+ describe("drop", () => {
3372
+ it("store", async () => {
3373
+ let { directory, indices, store } = await setupDefault();
3374
+ expect(await store.getSize()).equal(4);
3375
+ await store.drop();
3376
+ await store.start();
3377
+ expect(await store.getSize()).equal(0);
3378
+ await indices.stop();
3379
+ store = (await setup({ schema: Document }, directory)).store;
3380
+ expect(await store.getSize()).equal(0);
3381
+ });
3382
+
3383
+ it("indices", async () => {
3384
+ let { directory, indices } = await setupDefault();
3385
+
3386
+ let subindex = await indices.scope("x");
3387
+
3388
+ store = await subindex.init({ indexBy: ["id"], schema: Document });
3389
+ await store.put(
3390
+ new Document({ id: "1", name: "hello", number: 1n, tags: [] }),
3391
+ );
3392
+ await store.put(
3393
+ new Document({ id: "2", name: "hello", number: 1n, tags: [] }),
3394
+ );
3395
+ await store.put(
3396
+ new Document({ id: "3", name: "hello", number: 1n, tags: [] }),
3397
+ );
3398
+ await store.put(
3399
+ new Document({ id: "4", name: "hello", number: 1n, tags: [] }),
3400
+ );
3401
+ await store.start();
3402
+ expect(await store.getSize()).equal(4);
3403
+
3404
+ await indices.drop();
3405
+
3406
+ await store.start();
3407
+
3408
+ expect(await store.getSize()).equal(0);
3409
+
3410
+ await store.stop(); /// TODO why do w
3411
+ await indices.stop();
3412
+
3413
+ store = (await setup({ schema: Document }, directory)).store;
3414
+
3415
+ await store.start();
3416
+ expect(await store.getSize()).equal(0);
3417
+ });
3418
+ });
3419
+
3420
+ describe("scopes", () => {
3421
+ it("stop after stop", async () => {
3422
+ let { indices, store } = await setupDefault();
3423
+ expect(await store.getSize()).equal(4);
3424
+ let subindex = await indices.scope("x");
3425
+ store = await subindex.init({ indexBy: ["id"], schema: Document });
3426
+ await indices.stop();
3427
+ store = (await setup({ schema: Document })).store;
3428
+ await store.stop();
3429
+ });
3430
+
3431
+ it("re-drop", async () => {
3432
+ const scope = await createIndicies();
3433
+ await scope.start();
3434
+ const subScope = await scope.scope("subindex");
3435
+ await subScope.init({ indexBy: ["id"], schema: Document });
3436
+ await scope.drop();
3437
+ await scope.drop();
3438
+ });
3439
+
3440
+ it("isolates", async () => {
3441
+ const scope = await createIndicies();
3442
+ await scope.start();
3443
+ const scopeA = await scope.scope("a");
3444
+ const scopeB = await scope.scope("b");
3445
+ const indexA = await scopeA.init({ indexBy: ["id"], schema: Document });
3446
+ const indexB = await scopeB.init({ indexBy: ["id"], schema: Document });
3447
+ await indexA.put(
3448
+ new Document({ id: "1", name: "hello", number: 1n, tags: [] }),
3449
+ );
3450
+ await indexB.put(
3451
+ new Document({ id: "2", name: "hello", number: 1n, tags: [] }),
3452
+ );
3453
+ expect(await indexA.getSize()).equal(1);
3454
+ expect(await indexB.get(toId("1"))).to.not.exist;
3455
+ });
3456
+
3457
+ it("scope name can contain any character", async () => {
3458
+ const scope = await createIndicies();
3459
+ await scope.start();
3460
+ const scopeA = await scope.scope("a/=b");
3461
+ const indexA = await scopeA.init({ indexBy: ["id"], schema: Document });
3462
+ await indexA.put(
3463
+ new Document({ id: "1", name: "hello", number: 1n, tags: [] }),
3464
+ );
3465
+ expect(await indexA.getSize()).equal(1);
3466
+ });
3467
+
3468
+ it("drops sub scopes", async () => {
3469
+ const scope = await createIndicies();
3470
+ let subScope = await scope.scope("subindex");
3471
+ await scope.start();
3472
+ let subIndex = await subScope.init({
3473
+ indexBy: ["id"],
3474
+ schema: Document,
3475
+ });
3476
+ await subIndex.put(
3477
+ new Document({ id: "1", name: "hello", number: 1n, tags: [] }),
3478
+ );
3479
+ expect(await subIndex.getSize()).equal(1);
3480
+ await scope.drop();
3481
+
3482
+ // re-init the scope
3483
+ subScope = await scope.scope("subindex");
3484
+ subIndex = await subScope.init({ indexBy: ["id"], schema: Document });
3485
+
3486
+ expect(await subIndex.getSize()).equal(0);
3487
+ });
3488
+
3489
+ it("starts on init if scope is started", async () => {
3490
+ const scope = await createIndicies();
3491
+ await scope.start();
3492
+ const subScope = await scope.scope("subindex");
3493
+ const subIndex = await subScope.init({
3494
+ indexBy: ["id"],
3495
+ schema: Document,
3496
+ });
3497
+ await subIndex.put(
3498
+ new Document({ id: "1", name: "hello", number: 1n, tags: [] }),
3499
+ );
3500
+ });
3501
+
3502
+ it("can restart", async () => {
3503
+ const scope = await createIndicies();
3504
+ await scope.start();
3505
+ await scope.stop();
3506
+ await scope.start();
3507
+ const subIndex = await scope.init({
3508
+ indexBy: ["id"],
3509
+ schema: Document,
3510
+ });
3511
+ await subIndex.put(
3512
+ new Document({ id: "1", name: "hello", number: 1n, tags: [] }),
3513
+ );
3514
+ });
3515
+
3516
+ it("multi-scope insertion", async () => {
3517
+ class AnotherDocument {
3518
+ @id({ type: "string" })
3519
+ id: string;
3520
+
3521
+ @field({ type: "string" })
3522
+ string: string;
3523
+
3524
+ constructor(string: string) {
3525
+ this.id = string;
3526
+ this.string = string;
3527
+ }
3528
+ }
3529
+
3530
+ const scope = await createIndicies();
3531
+ await scope.start();
3532
+
3533
+ const a = await scope.scope("a");
3534
+ const aIndex = await a.init({ indexBy: ["id"], schema: Document });
3535
+
3536
+ const b = await scope.scope("b");
3537
+ const bIndex = await b.init({
3538
+ indexBy: ["id"],
3539
+ schema: AnotherDocument,
3540
+ });
3541
+
3542
+ await aIndex.put(
3543
+ new Document({ id: "a", name: "hello", number: 1n, tags: [] }),
3544
+ );
3545
+ expect(
3546
+ await aIndex.count(new CountRequest({ query: { id: "a" } })),
3547
+ ).to.eq(1);
3548
+
3549
+ await bIndex.put(new AnotherDocument("b"));
3550
+ expect(
3551
+ await bIndex.count(new CountRequest({ query: { id: "b" } })),
3552
+ ).to.eq(1);
3553
+ });
3554
+ });
3555
+ });
3556
+ };
3557
+
3558
+ /* TODO how should we do this? Should nested arrays be supported?
3559
+ // For SQLLite this will be hard but for simple index it should be possible
3560
+
3561
+ describe("multi-dimensional", () => {
3562
+ class NestedVec {
3563
+
3564
+ @field({ type: Uint8Array })
3565
+ id: Uint8Array;
3566
+
3567
+ @field({ type: option(vec(vec("u32"))) })
3568
+ matrix?: number[][];
3569
+
3570
+ constructor(properties?: {
3571
+ matrix?: number[][];
3572
+ }) {
3573
+ this.id = randomBytes(32);
3574
+ this.matrix = properties?.matrix;
3575
+ }
3576
+ }
3577
+
3578
+ it("not supported", async () => {
3579
+
3580
+ try {
3581
+ store = await setup({ schema: NestedVec });
3582
+ } catch (error: any) {
3583
+
3584
+ expect(error.message).equal("vec(vec(...)) is not supported");
3585
+ return;
3586
+ }
3587
+
3588
+ await expect(store.put(new NestedVec({ matrix: [[1, 2], [3]] }))).rejectedWith("vec(vec(...)) is not supported");
3589
+ });
3590
+ }); */
3591
+
3592
+ /* describe("concurrently", () => {
3593
+ beforeEach(async () => {
3594
+ await setupDefault();
3595
+ });
3596
+ it("can query concurrently", async () => {
3597
+ // TODO add more concurrency
3598
+ const promises: MaybePromise<IndexedResults>[] = [];
3599
+ const concurrency = 100;
3600
+ for (let i = 0; i < concurrency; i++) {
3601
+ if (i % 2 === 0) {
3602
+ promises.push(
3603
+ search(
3604
+ store,
3605
+ new SearchRequest({
3606
+ query: [
3607
+ new IntegerCompare({
3608
+ key: "number",
3609
+ compare: Compare.GreaterOrEqual,
3610
+ value: 2n
3611
+ })
3612
+ ]
3613
+ })
3614
+ )
3615
+ );
3616
+ } else {
3617
+ promises.push(
3618
+ search(
3619
+ store,
3620
+ new SearchRequest({
3621
+ query: [
3622
+ new IntegerCompare({
3623
+ key: "number",
3624
+ compare: Compare.Less,
3625
+ value: 2n
3626
+ })
3627
+ ]
3628
+ })
3629
+ )
3630
+ );
3631
+ }
3632
+ }
3633
+
3634
+ const results = await Promise.all(promises);
3635
+ for (let i = 0; i < concurrency; i++) {
3636
+ if (i % 2 === 0) {
3637
+ // query1
3638
+ expect(results[i].results).to.have.length(2);
3639
+ results[i].results.sort((a, b) =>
3640
+ Number(a.value.number! - b.value.number!)
3641
+ );
3642
+ expect(results[i].results[0].value.number).to.be.oneOf([2n, 2]);
3643
+ expect(results[i].results[1].value.number).to.be.oneOf([3n, 3]);
3644
+ } else {
3645
+ // query2
3646
+ expect(results[i].results).to.have.length(1);
3647
+ expect(results[i].results[0].value.number).to.be.oneOf([1n, 1]);
3648
+ }
3649
+ }
3650
+ });
3651
+ }); */