@convex-dev/rag 0.3.3-alpha.0 → 0.3.3

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.
@@ -1,10 +1,10 @@
1
1
  /// <reference types="vite/client" />
2
2
 
3
- import { describe, expect, test } from "vitest";
3
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
4
4
  import { convexTest, type TestConvex } from "convex-test";
5
5
  import schema from "./schema.js";
6
- import { api } from "./_generated/api.js";
7
- import { modules } from "./setup.test.js";
6
+ import { api, internal } from "./_generated/api.js";
7
+ import { initConvexTest } from "./setup.test.js";
8
8
  import type { Id } from "./_generated/dataModel.js";
9
9
 
10
10
  type ConvexTest = TestConvex<typeof schema>;
@@ -21,6 +21,13 @@ describe("entries", () => {
21
21
  return namespace.namespaceId;
22
22
  }
23
23
 
24
+ beforeEach(async () => {
25
+ vi.useFakeTimers();
26
+ });
27
+ afterEach(() => {
28
+ vi.useRealTimers();
29
+ });
30
+
24
31
  function testEntryArgs(namespaceId: Id<"namespaces">, key = "test-entry") {
25
32
  return {
26
33
  namespaceId,
@@ -33,7 +40,7 @@ describe("entries", () => {
33
40
  }
34
41
 
35
42
  test("add creates a new entry when none exists", async () => {
36
- const t = convexTest(schema, modules);
43
+ const t = initConvexTest();
37
44
  const namespaceId = await setupTestNamespace(t);
38
45
 
39
46
  const entry = testEntryArgs(namespaceId);
@@ -46,7 +53,6 @@ describe("entries", () => {
46
53
  expect(result.created).toBe(true);
47
54
  expect(result.status).toBe("ready");
48
55
  expect(result.entryId).toBeDefined();
49
- expect(result.replacedEntry).toBeNull();
50
56
 
51
57
  // Verify the entry was actually created
52
58
  const createdDoc = await t.run(async (ctx) => {
@@ -60,7 +66,7 @@ describe("entries", () => {
60
66
  });
61
67
 
62
68
  test("add returns existing entry when adding identical content", async () => {
63
- const t = convexTest(schema, modules);
69
+ const t = initConvexTest();
64
70
  const namespaceId = await setupTestNamespace(t);
65
71
 
66
72
  const entry = testEntryArgs(namespaceId);
@@ -73,7 +79,6 @@ describe("entries", () => {
73
79
 
74
80
  expect(firstResult.created).toBe(true);
75
81
  expect(firstResult.status).toBe("ready");
76
- expect(firstResult.replacedEntry).toBeNull();
77
82
 
78
83
  // Second add with identical content
79
84
  const secondResult = await t.mutation(api.entries.add, {
@@ -84,7 +89,6 @@ describe("entries", () => {
84
89
  expect(secondResult.created).toBe(false);
85
90
  expect(secondResult.status).toBe("ready");
86
91
  expect(secondResult.entryId).toBe(firstResult.entryId);
87
- expect(secondResult.replacedEntry).toBeNull();
88
92
 
89
93
  // Verify no new entry was created
90
94
  const allDocs = await t.run(async (ctx) => {
@@ -104,7 +108,7 @@ describe("entries", () => {
104
108
  });
105
109
 
106
110
  test("add creates new version when content hash changes", async () => {
107
- const t = convexTest(schema, modules);
111
+ const t = initConvexTest();
108
112
  const namespaceId = await setupTestNamespace(t);
109
113
 
110
114
  const entry = testEntryArgs(namespaceId);
@@ -116,7 +120,6 @@ describe("entries", () => {
116
120
  });
117
121
 
118
122
  expect(firstResult.created).toBe(true);
119
- expect(firstResult.replacedEntry).toBeNull();
120
123
 
121
124
  // Second add with different content hash
122
125
  const modifiedEntry = {
@@ -131,11 +134,7 @@ describe("entries", () => {
131
134
 
132
135
  expect(secondResult.created).toBe(true);
133
136
  expect(secondResult.entryId).not.toBe(firstResult.entryId);
134
- // When creating a entry as "ready" initially, replacedEntry is null
135
- // Replacement only happens during pending -> ready transitions
136
- expect(secondResult.replacedEntry).toMatchObject({
137
- entryId: firstResult.entryId,
138
- });
137
+ expect(secondResult.status).toBe("pending");
139
138
 
140
139
  // Verify both entries exist with different versions
141
140
  const allDocs = await t.run(async (ctx) => {
@@ -157,7 +156,7 @@ describe("entries", () => {
157
156
  });
158
157
 
159
158
  test("add creates new version when importance changes", async () => {
160
- const t = convexTest(schema, modules);
159
+ const t = initConvexTest();
161
160
  const namespaceId = await setupTestNamespace(t);
162
161
 
163
162
  const entry = testEntryArgs(namespaceId);
@@ -167,6 +166,12 @@ describe("entries", () => {
167
166
  entry,
168
167
  allChunks: [],
169
168
  });
169
+ expect(firstResult.status).toBe("ready");
170
+ const first = await t.run(async (ctx) => {
171
+ return ctx.db.get(firstResult.entryId);
172
+ })!;
173
+ expect(first?.version).toBe(0);
174
+ expect(first?.status.kind).toBe("ready");
170
175
 
171
176
  // Second add with different importance
172
177
  const modifiedEntry = {
@@ -181,9 +186,12 @@ describe("entries", () => {
181
186
 
182
187
  expect(secondResult.created).toBe(true);
183
188
  expect(secondResult.entryId).not.toBe(firstResult.entryId);
184
- expect(secondResult.replacedEntry).toMatchObject({
185
- entryId: firstResult.entryId,
186
- });
189
+ const second = await t.run(async (ctx) => {
190
+ return ctx.db.get(secondResult.entryId);
191
+ })!;
192
+ expect(second?.version).toBe(1);
193
+ expect(second?.status.kind).toBe("pending");
194
+ expect(secondResult.status).toBe("pending");
187
195
 
188
196
  // Verify new version was created
189
197
  const newDoc = await t.run(async (ctx) => {
@@ -195,7 +203,7 @@ describe("entries", () => {
195
203
  });
196
204
 
197
205
  test("add creates new version when filter values change", async () => {
198
- const t = convexTest(schema, modules);
206
+ const t = initConvexTest();
199
207
  const namespaceId = await setupTestNamespace(t, ["category"]); // Add filter name
200
208
 
201
209
  const entry = testEntryArgs(namespaceId);
@@ -205,6 +213,7 @@ describe("entries", () => {
205
213
  entry,
206
214
  allChunks: [],
207
215
  });
216
+ expect(firstResult.status).toBe("ready");
208
217
 
209
218
  // Second add with different filter values
210
219
  const modifiedEntry = {
@@ -219,9 +228,7 @@ describe("entries", () => {
219
228
 
220
229
  expect(secondResult.created).toBe(true);
221
230
  expect(secondResult.entryId).not.toBe(firstResult.entryId);
222
- expect(secondResult.replacedEntry).toMatchObject({
223
- entryId: firstResult.entryId,
224
- });
231
+ expect(secondResult.status).toBe("pending");
225
232
 
226
233
  // Verify new version was created with correct filter values
227
234
  const newDoc = await t.run(async (ctx) => {
@@ -235,7 +242,7 @@ describe("entries", () => {
235
242
  });
236
243
 
237
244
  test("add without allChunks creates pending entry", async () => {
238
- const t = convexTest(schema, modules);
245
+ const t = initConvexTest();
239
246
  const namespaceId = await setupTestNamespace(t);
240
247
 
241
248
  const entry = testEntryArgs(namespaceId);
@@ -247,7 +254,6 @@ describe("entries", () => {
247
254
 
248
255
  expect(result.created).toBe(true);
249
256
  expect(result.status).toBe("pending");
250
- expect(result.replacedEntry).toBeNull();
251
257
 
252
258
  // Verify the entry was created with pending status
253
259
  const createdDoc = await t.run(async (ctx) => {
@@ -258,7 +264,7 @@ describe("entries", () => {
258
264
  });
259
265
 
260
266
  test("multiple entries with different keys can coexist", async () => {
261
- const t = convexTest(schema, modules);
267
+ const t = initConvexTest();
262
268
  const namespaceId = await setupTestNamespace(t);
263
269
 
264
270
  const entry1 = testEntryArgs(namespaceId, "doc1");
@@ -277,8 +283,8 @@ describe("entries", () => {
277
283
  expect(result1.created).toBe(true);
278
284
  expect(result2.created).toBe(true);
279
285
  expect(result1.entryId).not.toBe(result2.entryId);
280
- expect(result1.replacedEntry).toBeNull();
281
- expect(result2.replacedEntry).toBeNull();
286
+ expect(result1.status).toBe("ready");
287
+ expect(result2.status).toBe("ready");
282
288
 
283
289
  // Verify both entries exist
284
290
  const allDocs = await t.run(async (ctx) => {
@@ -294,7 +300,7 @@ describe("entries", () => {
294
300
  });
295
301
 
296
302
  test("pending to ready transition populates replacedEntry", async () => {
297
- const t = convexTest(schema, modules);
303
+ const t = initConvexTest();
298
304
  const namespaceId = await setupTestNamespace(t);
299
305
 
300
306
  const entry = testEntryArgs(namespaceId);
@@ -307,7 +313,6 @@ describe("entries", () => {
307
313
 
308
314
  expect(firstResult.created).toBe(true);
309
315
  expect(firstResult.status).toBe("ready");
310
- expect(firstResult.replacedEntry).toBeNull();
311
316
 
312
317
  // Second add - create as pending (no allChunks)
313
318
  const modifiedEntry = {
@@ -322,7 +327,12 @@ describe("entries", () => {
322
327
 
323
328
  expect(pendingResult.created).toBe(true);
324
329
  expect(pendingResult.status).toBe("pending");
325
- expect(pendingResult.replacedEntry).toBeNull();
330
+
331
+ const chunksResult = await t.mutation(api.chunks.replaceChunksPage, {
332
+ entryId: pendingResult.entryId,
333
+ startOrder: 0,
334
+ });
335
+ expect(chunksResult.status).toBe("ready");
326
336
 
327
337
  // Promote to ready - this should replace the first entry
328
338
  const promoteResult = await t.mutation(api.entries.promoteToReady, {
@@ -338,4 +348,404 @@ describe("entries", () => {
338
348
  });
339
349
  expect(firstDoc!.status.kind).toBe("replaced");
340
350
  });
351
+
352
+ test("deleteAsync deletes entry and all chunks", async () => {
353
+ const t = initConvexTest();
354
+ const namespaceId = await setupTestNamespace(t);
355
+
356
+ const entry = testEntryArgs(namespaceId);
357
+
358
+ // Create entry with chunks
359
+ const testChunks = [
360
+ {
361
+ content: { text: "chunk 1 content", metadata: { type: "text" } },
362
+ embedding: Array.from({ length: 128 }, () => Math.random()),
363
+ searchableText: "chunk 1 content",
364
+ },
365
+ {
366
+ content: { text: "chunk 2 content", metadata: { type: "text" } },
367
+ embedding: Array.from({ length: 128 }, () => Math.random()),
368
+ searchableText: "chunk 2 content",
369
+ },
370
+ ];
371
+
372
+ const result = await t.mutation(api.entries.add, {
373
+ entry,
374
+ allChunks: testChunks,
375
+ });
376
+
377
+ expect(result.created).toBe(true);
378
+ expect(result.status).toBe("ready");
379
+
380
+ // Verify entry and chunks exist before deletion
381
+ const entryBefore = await t.run(async (ctx) => {
382
+ return ctx.db.get(result.entryId);
383
+ });
384
+ expect(entryBefore).toBeDefined();
385
+
386
+ const chunksBefore = await t.run(async (ctx) => {
387
+ return ctx.db
388
+ .query("chunks")
389
+ .filter((q) => q.eq(q.field("entryId"), result.entryId))
390
+ .collect();
391
+ });
392
+ expect(chunksBefore).toHaveLength(2);
393
+
394
+ // Delete the entry
395
+ await t.mutation(api.entries.deleteAsync, {
396
+ entryId: result.entryId,
397
+ startOrder: 0,
398
+ });
399
+
400
+ // Wait for async deletion to complete by repeatedly checking
401
+ await t.finishInProgressScheduledFunctions();
402
+
403
+ // Verify entry is deleted
404
+ const entryAfter = await t.run(async (ctx) => {
405
+ return ctx.db.get(result.entryId);
406
+ });
407
+ expect(entryAfter).toBeNull();
408
+
409
+ // Verify chunks are deleted
410
+ const chunksAfter = await t.run(async (ctx) => {
411
+ return ctx.db
412
+ .query("chunks")
413
+ .filter((q) => q.eq(q.field("entryId"), result.entryId))
414
+ .collect();
415
+ });
416
+ expect(chunksAfter).toHaveLength(0);
417
+ });
418
+
419
+ test("deleteSync deletes entry and all chunks synchronously", async () => {
420
+ const t = initConvexTest();
421
+ const namespaceId = await setupTestNamespace(t);
422
+
423
+ const entry = testEntryArgs(namespaceId);
424
+
425
+ // Create entry with chunks
426
+ const testChunks = [
427
+ {
428
+ content: { text: "sync chunk 1", metadata: { type: "text" } },
429
+ embedding: Array.from({ length: 128 }, () => Math.random()),
430
+ searchableText: "sync chunk 1",
431
+ },
432
+ {
433
+ content: { text: "sync chunk 2", metadata: { type: "text" } },
434
+ embedding: Array.from({ length: 128 }, () => Math.random()),
435
+ searchableText: "sync chunk 2",
436
+ },
437
+ ];
438
+
439
+ const result = await t.mutation(api.entries.add, {
440
+ entry,
441
+ allChunks: testChunks,
442
+ });
443
+
444
+ expect(result.created).toBe(true);
445
+ expect(result.status).toBe("ready");
446
+
447
+ // Verify entry and chunks exist before deletion
448
+ const entryBefore = await t.run(async (ctx) => {
449
+ return ctx.db.get(result.entryId);
450
+ });
451
+ expect(entryBefore).toBeDefined();
452
+
453
+ const chunksBefore = await t.run(async (ctx) => {
454
+ return ctx.db
455
+ .query("chunks")
456
+ .filter((q) => q.eq(q.field("entryId"), result.entryId))
457
+ .collect();
458
+ });
459
+ expect(chunksBefore).toHaveLength(2);
460
+
461
+ // Delete the entry synchronously
462
+ await t.action(api.entries.deleteSync, {
463
+ entryId: result.entryId,
464
+ });
465
+
466
+ // Verify entry is deleted
467
+ const entryAfter = await t.run(async (ctx) => {
468
+ return ctx.db.get(result.entryId);
469
+ });
470
+ expect(entryAfter).toBeNull();
471
+
472
+ // Verify chunks are deleted
473
+ const chunksAfter = await t.run(async (ctx) => {
474
+ return ctx.db
475
+ .query("chunks")
476
+ .filter((q) => q.eq(q.field("entryId"), result.entryId))
477
+ .collect();
478
+ });
479
+ expect(chunksAfter).toHaveLength(0);
480
+ });
481
+
482
+ test("deleteByKeyAsync deletes all entries with the given key", async () => {
483
+ const t = initConvexTest();
484
+ const namespaceId = await setupTestNamespace(t);
485
+
486
+ const entry1 = testEntryArgs(namespaceId, "shared-key");
487
+ const entry2 = {
488
+ ...testEntryArgs(namespaceId, "shared-key"),
489
+ contentHash: "hash456",
490
+ };
491
+ const entry3 = testEntryArgs(namespaceId, "different-key");
492
+
493
+ // Create multiple entries with same key and one with different key
494
+ const result1 = await t.mutation(api.entries.add, {
495
+ entry: entry1,
496
+ allChunks: [
497
+ {
498
+ content: { text: "content 1" },
499
+ embedding: Array.from({ length: 128 }, () => Math.random()),
500
+ },
501
+ ],
502
+ });
503
+
504
+ const result2 = await t.mutation(api.entries.add, {
505
+ entry: entry2,
506
+ allChunks: [
507
+ {
508
+ content: { text: "content 2" },
509
+ embedding: Array.from({ length: 128 }, () => Math.random()),
510
+ },
511
+ ],
512
+ });
513
+
514
+ const result3 = await t.mutation(api.entries.add, {
515
+ entry: entry3,
516
+ allChunks: [
517
+ {
518
+ content: { text: "content 3" },
519
+ embedding: Array.from({ length: 128 }, () => Math.random()),
520
+ },
521
+ ],
522
+ });
523
+
524
+ // Verify all entries exist
525
+ const entriesBefore = await t.run(async (ctx) => {
526
+ return ctx.db
527
+ .query("entries")
528
+ .filter((q) => q.eq(q.field("namespaceId"), namespaceId))
529
+ .collect();
530
+ });
531
+ expect(entriesBefore).toHaveLength(3);
532
+ const sharedBefore = await t.query(
533
+ internal.entries.getEntriesForNamespaceByKey,
534
+ {
535
+ namespaceId,
536
+ key: "shared-key",
537
+ }
538
+ );
539
+ expect(sharedBefore).toHaveLength(2);
540
+
541
+ // Delete entries by key
542
+ await t.mutation(api.entries.deleteByKeyAsync, {
543
+ namespaceId,
544
+ key: "shared-key",
545
+ });
546
+
547
+ // Wait for async deletion to complete
548
+ await t.finishAllScheduledFunctions(vi.runAllTimers);
549
+
550
+ // Verify only entries with "shared-key" are deleted
551
+ const entriesAfter = await t.run(async (ctx) => {
552
+ return ctx.db
553
+ .query("entries")
554
+ .filter((q) => q.eq(q.field("namespaceId"), namespaceId))
555
+ .collect();
556
+ });
557
+ expect(entriesAfter).toHaveLength(1);
558
+ expect(entriesAfter[0].key).toBe("different-key");
559
+ expect(entriesAfter[0]._id).toBe(result3.entryId);
560
+
561
+ const sharedAfter = await t.query(
562
+ internal.entries.getEntriesForNamespaceByKey,
563
+ { namespaceId, key: "shared-key" }
564
+ );
565
+ expect(sharedAfter).toHaveLength(0);
566
+
567
+ // Verify chunks from deleted entries are also deleted
568
+ const chunksAfter = await t.run(async (ctx) => {
569
+ return ctx.db.query("chunks").collect();
570
+ });
571
+ expect(chunksAfter).toHaveLength(1); // Only chunk from entry3 should remain
572
+ });
573
+
574
+ test("deleteByKeySync deletes all entries with the given key synchronously", async () => {
575
+ const t = initConvexTest();
576
+ const namespaceId = await setupTestNamespace(t);
577
+
578
+ const entry1 = testEntryArgs(namespaceId, "sync-key");
579
+ const entry2 = {
580
+ ...testEntryArgs(namespaceId, "sync-key"),
581
+ contentHash: "hash789",
582
+ };
583
+ const entry3 = testEntryArgs(namespaceId, "keep-key");
584
+
585
+ // Create multiple entries with same key and one with different key
586
+ const result1 = await t.mutation(api.entries.add, {
587
+ entry: entry1,
588
+ allChunks: [
589
+ {
590
+ content: { text: "sync content 1" },
591
+ embedding: Array.from({ length: 128 }, () => Math.random()),
592
+ },
593
+ ],
594
+ });
595
+
596
+ const result2 = await t.mutation(api.entries.add, {
597
+ entry: entry2,
598
+ allChunks: [
599
+ {
600
+ content: { text: "sync content 2" },
601
+ embedding: Array.from({ length: 128 }, () => Math.random()),
602
+ },
603
+ ],
604
+ });
605
+
606
+ const result3 = await t.mutation(api.entries.add, {
607
+ entry: entry3,
608
+ allChunks: [
609
+ {
610
+ content: { text: "sync content 3" },
611
+ embedding: Array.from({ length: 128 }, () => Math.random()),
612
+ },
613
+ ],
614
+ });
615
+
616
+ // Verify all entries exist
617
+ const entriesBefore = await t.run(async (ctx) => {
618
+ return ctx.db
619
+ .query("entries")
620
+ .filter((q) => q.eq(q.field("namespaceId"), namespaceId))
621
+ .collect();
622
+ });
623
+ expect(entriesBefore).toHaveLength(3);
624
+
625
+ // Delete entries by key synchronously
626
+ await t.action(api.entries.deleteByKeySync, {
627
+ namespaceId,
628
+ key: "sync-key",
629
+ });
630
+
631
+ // Verify only entries with "sync-key" are deleted
632
+ const entriesAfter = await t.run(async (ctx) => {
633
+ return ctx.db
634
+ .query("entries")
635
+ .filter((q) => q.eq(q.field("namespaceId"), namespaceId))
636
+ .collect();
637
+ });
638
+ expect(entriesAfter).toHaveLength(1);
639
+ expect(entriesAfter[0].key).toBe("keep-key");
640
+ expect(entriesAfter[0]._id).toBe(result3.entryId);
641
+
642
+ // Verify chunks from deleted entries are also deleted
643
+ const chunksAfter = await t.run(async (ctx) => {
644
+ return ctx.db.query("chunks").collect();
645
+ });
646
+ expect(chunksAfter).toHaveLength(1); // Only chunk from entry3 should remain
647
+ });
648
+
649
+ test("deleteByKeyAsync handles entries without key gracefully", async () => {
650
+ const t = initConvexTest();
651
+ const namespaceId = await setupTestNamespace(t);
652
+
653
+ const entryWithKey = testEntryArgs(namespaceId, "has-key");
654
+ const entryWithoutKey = { ...testEntryArgs(namespaceId), key: undefined };
655
+
656
+ // Create entries
657
+ const result1 = await t.mutation(api.entries.add, {
658
+ entry: entryWithKey,
659
+ allChunks: [],
660
+ });
661
+
662
+ const result2 = await t.mutation(api.entries.add, {
663
+ entry: entryWithoutKey,
664
+ allChunks: [],
665
+ });
666
+
667
+ // Delete by key - should only affect entries with that key
668
+ await t.mutation(api.entries.deleteByKeyAsync, {
669
+ namespaceId,
670
+ key: "has-key",
671
+ });
672
+
673
+ await t.finishAllScheduledFunctions(vi.runAllTimers);
674
+
675
+ // Verify only the entry with the specified key is deleted
676
+ const entriesAfter = await t.run(async (ctx) => {
677
+ return ctx.db
678
+ .query("entries")
679
+ .filter((q) => q.eq(q.field("namespaceId"), namespaceId))
680
+ .collect();
681
+ });
682
+ expect(entriesAfter).toHaveLength(1);
683
+ expect(entriesAfter[0]._id).toBe(result2.entryId);
684
+ expect(entriesAfter[0].key).toBeUndefined();
685
+ });
686
+
687
+ test("deleteByKeyAsync with beforeVersion parameter", async () => {
688
+ const t = initConvexTest();
689
+ const namespaceId = await setupTestNamespace(t);
690
+
691
+ const entry = testEntryArgs(namespaceId, "versioned-key");
692
+
693
+ // Create multiple versions of the same entry
694
+ const result1 = await t.mutation(api.entries.add, {
695
+ entry,
696
+ allChunks: [],
697
+ });
698
+
699
+ const result2 = await t.mutation(api.entries.add, {
700
+ entry: { ...entry, contentHash: "hash456" },
701
+ allChunks: [],
702
+ });
703
+
704
+ const result3 = await t.mutation(api.entries.add, {
705
+ entry: { ...entry, contentHash: "hash789" },
706
+ allChunks: [],
707
+ });
708
+
709
+ // Get the versions to understand ordering
710
+ const allEntries = await t.run(async (ctx) => {
711
+ return ctx.db
712
+ .query("entries")
713
+ .filter((q) =>
714
+ q.and(
715
+ q.eq(q.field("namespaceId"), namespaceId),
716
+ q.eq(q.field("key"), "versioned-key")
717
+ )
718
+ )
719
+ .collect();
720
+ });
721
+
722
+ const sortedEntries = allEntries.sort((a, b) => a.version - b.version);
723
+ expect(sortedEntries).toHaveLength(3);
724
+
725
+ // Delete entries before version 2 (should delete version 0 and 1)
726
+ await t.mutation(api.entries.deleteByKeyAsync, {
727
+ namespaceId,
728
+ key: "versioned-key",
729
+ beforeVersion: 2,
730
+ });
731
+
732
+ await t.finishAllScheduledFunctions(vi.runAllTimers);
733
+
734
+ // Should only have the latest version (version 2) remaining
735
+ const remainingEntries = await t.run(async (ctx) => {
736
+ return ctx.db
737
+ .query("entries")
738
+ .filter((q) =>
739
+ q.and(
740
+ q.eq(q.field("namespaceId"), namespaceId),
741
+ q.eq(q.field("key"), "versioned-key")
742
+ )
743
+ )
744
+ .collect();
745
+ });
746
+
747
+ expect(remainingEntries).toHaveLength(1);
748
+ expect(remainingEntries[0].version).toBe(2);
749
+ expect(remainingEntries[0]._id).toBe(result3.entryId);
750
+ });
341
751
  });