@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.
- package/dist/client/index.d.ts +33 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +55 -19
- package/dist/client/index.js.map +1 -1
- package/dist/client/types.js +1 -1
- package/dist/client/types.js.map +1 -1
- package/dist/component/_generated/api.d.ts +18 -11
- package/dist/component/chunks.d.ts +8 -1
- package/dist/component/chunks.d.ts.map +1 -1
- package/dist/component/chunks.js +10 -2
- package/dist/component/chunks.js.map +1 -1
- package/dist/component/embeddings/index.js +0 -1
- package/dist/component/embeddings/index.js.map +1 -1
- package/dist/component/entries.d.ts +44 -7
- package/dist/component/entries.d.ts.map +1 -1
- package/dist/component/entries.js +98 -13
- package/dist/component/entries.js.map +1 -1
- package/package.json +4 -4
- package/src/client/index.test.ts +32 -0
- package/src/client/index.ts +76 -22
- package/src/component/_generated/api.d.ts +18 -11
- package/src/component/chunks.test.ts +2 -2
- package/src/component/chunks.ts +11 -1
- package/src/component/entries.test.ts +441 -31
- package/src/component/entries.ts +117 -11
- package/src/component/setup.test.ts +15 -0
- package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
|
@@ -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 {
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
185
|
-
|
|
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 =
|
|
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.
|
|
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 =
|
|
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 =
|
|
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.
|
|
281
|
-
expect(result2.
|
|
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 =
|
|
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
|
-
|
|
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
|
});
|