/// import { describe, expect, test } from "vitest"; import { convexTest, type TestConvex } from "convex-test"; import schema from "./schema.js"; import { api, internal } from "./_generated/api.js"; import { modules } from "./setup.test.js"; import { insertChunks, deleteChunksPageHandler } from "./chunks.js"; import type { Id } from "./_generated/dataModel.js"; import { assert } from "convex-helpers"; type ConvexTest = TestConvex; describe("chunks", () => { async function setupTestNamespace(t: ConvexTest) { return await t.run(async (ctx) => { return ctx.db.insert("namespaces", { namespace: "test-namespace", version: 1, modelId: "test-model", dimension: 128, filterNames: [], status: { kind: "ready" }, }); }); } async function setupTestEntry( t: ConvexTest, namespaceId: Id<"namespaces">, key = "test-entry", version = 0, status: "ready" | "pending" = "ready", ) { return await t.run(async (ctx) => { return ctx.db.insert("entries", { namespaceId, key, version, status: { kind: status }, contentHash: `test-content-hash-${key}-${version}`, importance: 0.5, filterValues: [], }); }); } function createTestChunks(count = 3) { return Array.from({ length: count }, (_, i) => ({ content: { text: `Test chunk content ${i + 1}`, metadata: { index: i }, }, embedding: Array(128).fill(0.1 + i * 0.01), })); } test("inserting chunks when there's no entry throws error", async () => { const t = convexTest(schema, modules); await setupTestNamespace(t); // Try to insert chunks for a non-existent entry const nonExistentDocId = "0123456789012345678901234entries" as Id<"entries">; const chunks = createTestChunks(2); await expect( t.run(async (ctx) => { return insertChunks(ctx, { entryId: nonExistentDocId, startOrder: 0, chunks, }); }), ).rejects.toThrow(`Entry ${nonExistentDocId} not found`); }); test("overwriting chunks with insert works", async () => { const t = convexTest(schema, modules); const namespaceId = await setupTestNamespace(t); const entryId = await setupTestEntry(t, namespaceId); // Insert initial chunks const initialChunks = createTestChunks(3); await t.run(async (ctx) => { return insertChunks(ctx, { entryId, startOrder: 0, chunks: initialChunks, }); }); // Verify initial chunks exist const initialChunksList = await t.run(async (ctx) => { return ctx.db .query("chunks") .withIndex("entryId_order", (q) => q.eq("entryId", entryId)) .collect(); }); expect(initialChunksList).toHaveLength(3); // Overwrite chunks 1 and 2 with new content const overwriteChunks = [ { content: { text: "Overwritten chunk 1 content", metadata: { overwritten: true, index: 1 }, }, embedding: Array(128).fill(0.9), }, { content: { text: "Overwritten chunk 2 content", metadata: { overwritten: true, index: 2 }, }, embedding: Array(128).fill(0.8), }, ]; await t.run(async (ctx) => { return insertChunks(ctx, { entryId, startOrder: 1, chunks: overwriteChunks, }); }); // Verify total chunks is still correct (original chunk 0 + 2 overwritten) const finalChunksList = await t.run(async (ctx) => { return ctx.db .query("chunks") .withIndex("entryId_order", (q) => q.eq("entryId", entryId)) .collect(); }); expect(finalChunksList).toHaveLength(3); // Verify the overwritten chunks have new content const overwrittenChunk1 = finalChunksList.find((c) => c.order === 1); const overwrittenChunk2 = finalChunksList.find((c) => c.order === 2); expect(overwrittenChunk1).toBeDefined(); expect(overwrittenChunk2).toBeDefined(); const content1 = await t.run(async (ctx) => ctx.db.get("content", overwrittenChunk1!.contentId), ); const content2 = await t.run(async (ctx) => ctx.db.get("content", overwrittenChunk2!.contentId), ); expect(content1!.text).toBe("Overwritten chunk 1 content"); expect(content1!.metadata?.overwritten).toBe(true); expect(content2!.text).toBe("Overwritten chunk 2 content"); expect(content2!.metadata?.overwritten).toBe(true); }); test("when replacing an older version, older one is marked as replaced and only new one shows up in search results", async () => { const t = convexTest(schema, modules); const namespaceId = await setupTestNamespace(t); // Create version 1 of entry const docV1Id = await setupTestEntry(t, namespaceId, "versioned-entry", 1); // Insert chunks in version 1 const v1Chunks = createTestChunks(2); await t.run(async (ctx) => { return insertChunks(ctx, { entryId: docV1Id, startOrder: 0, chunks: v1Chunks, }); }); // Create version 2 of the same entry const docV2Id = await setupTestEntry( t, namespaceId, "versioned-entry", 2, "pending", ); // Insert chunks in version 2 (this should mark v1 chunks as replaced) const v2Chunks = createTestChunks(2); await t.run(async (ctx) => { return insertChunks(ctx, { entryId: docV2Id, startOrder: 0, chunks: v2Chunks, }); }); // Run replaceChunksPage to actually perform the replacement let isDone = false; let startOrder = 0; while (!isDone) { const result = await t.mutation(api.chunks.replaceChunksPage, { entryId: docV2Id, startOrder, }); isDone = result.status !== "pending"; startOrder = result.nextStartOrder; } // Check that v1 chunks are marked as replaced const v1ChunksList = await t.run(async (ctx) => { return ctx.db .query("chunks") .withIndex("entryId_order", (q) => q.eq("entryId", docV1Id)) .collect(); }); for (const chunk of v1ChunksList) { if (chunk.state.kind !== "pending") { expect(chunk.state.kind).toBe("replaced"); } } // Check that v2 chunks are ready const v2ChunksList = await t.run(async (ctx) => { return ctx.db .query("chunks") .withIndex("entryId_order", (q) => q.eq("entryId", docV2Id)) .collect(); }); for (const chunk of v2ChunksList) { expect(chunk.state.kind).toBe("ready"); } }); test("chunks can be created on different entries and fetched separately", async () => { const t = convexTest(schema, modules); const namespaceId = await setupTestNamespace(t); // Create two entries const doc1Id = await setupTestEntry(t, namespaceId, "doc1"); const doc2Id = await setupTestEntry(t, namespaceId, "doc2"); // Insert chunks in both entries const doc1Chunks = createTestChunks(5); const doc2Chunks = createTestChunks(3); await t.run(async (ctx) => { await insertChunks(ctx, { entryId: doc1Id, startOrder: 0, chunks: doc1Chunks, }); return insertChunks(ctx, { entryId: doc2Id, startOrder: 0, chunks: doc2Chunks, }); }); // Verify chunks exist in both entries const doc1ChunksList = await t.run(async (ctx) => { return ctx.db .query("chunks") .withIndex("entryId_order", (q) => q.eq("entryId", doc1Id)) .collect(); }); const doc2ChunksList = await t.run(async (ctx) => { return ctx.db .query("chunks") .withIndex("entryId_order", (q) => q.eq("entryId", doc2Id)) .collect(); }); expect(doc1ChunksList).toHaveLength(5); expect(doc2ChunksList).toHaveLength(3); // Verify chunk order and content expect(doc1ChunksList[0].order).toBe(0); expect(doc1ChunksList[4].order).toBe(4); expect(doc2ChunksList[0].order).toBe(0); expect(doc2ChunksList[2].order).toBe(2); // Verify chunk content const doc1Content0 = await t.run(async (ctx) => ctx.db.get("content", doc1ChunksList[0].contentId), ); const doc2Content0 = await t.run(async (ctx) => ctx.db.get("content", doc2ChunksList[0].contentId), ); expect(doc1Content0!.text).toBe("Test chunk content 1"); expect(doc2Content0!.text).toBe("Test chunk content 1"); }); test("chunks support zero-range queries", async () => { const t = convexTest(schema, modules); const namespaceId = await setupTestNamespace(t); const entryId = await setupTestEntry(t, namespaceId); // Insert chunks const chunks = createTestChunks(5); await t.run(async (ctx) => { return insertChunks(ctx, { entryId, startOrder: 0, chunks, }); }); // Get a single chunk (simulating zero range) const singleChunk = await t.run(async (ctx) => { return ctx.db .query("chunks") .withIndex("entryId_order", (q) => q.eq("entryId", entryId).eq("order", 2), ) .first(); }); expect(singleChunk).toBeDefined(); expect(singleChunk!.order).toBe(2); // Verify content const content = await t.run(async (ctx) => ctx.db.get("content", singleChunk!.contentId), ); expect(content!.text).toBe("Test chunk content 3"); }); test("deleting pages should work", async () => { const t = convexTest(schema, modules); const namespaceId = await setupTestNamespace(t); const entryId = await setupTestEntry(t, namespaceId); // Insert a large number of chunks const chunks = createTestChunks(10); await t.run(async (ctx) => { const result = await insertChunks(ctx, { entryId, startOrder: 0, chunks, }); expect(result.status).toBe("ready"); }); // Verify chunks exist const initialChunksList = await t.run(async (ctx) => { return ctx.db .query("chunks") .withIndex("entryId_order", (q) => q.eq("entryId", entryId)) .collect(); }); expect(initialChunksList).toHaveLength(10); // Delete chunks starting from order 3 const deleteResult = await t.run(async (ctx) => { return deleteChunksPageHandler(ctx, { entryId, startOrder: 3, }); }); expect(deleteResult.isDone).toBe(true); // Verify only first 3 chunks remain const remainingChunksList = await t.run(async (ctx) => { return ctx.db .query("chunks") .withIndex("entryId_order", (q) => q.eq("entryId", entryId)) .collect(); }); expect(remainingChunksList).toHaveLength(3); // Verify the remaining chunks are orders 0, 1, 2 const orders = remainingChunksList.map((c) => c.order).sort(); expect(orders).toEqual([0, 1, 2]); // Verify content was also deleted const allContent = await t.run(async (ctx) => { return ctx.db.query("content").collect(); }); // Should have only 3 content records remaining (for the 3 remaining chunks) expect(allContent).toHaveLength(3); // Verify embeddings were deleted const allEmbeddings = await t.run(async (ctx) => { return ctx.db.query("vectors_128").collect(); }); expect(allEmbeddings).toHaveLength(3); }); test("listing chunks returns correct pagination", async () => { const t = convexTest(schema, modules); const namespaceId = await setupTestNamespace(t); const entryId = await setupTestEntry(t, namespaceId); // Insert chunks const chunks = createTestChunks(5); await t.run(async (ctx) => { return insertChunks(ctx, { entryId, startOrder: 0, chunks, }); }); // Test listing with pagination const result = await t.query(api.chunks.list, { entryId, order: "asc", paginationOpts: { numItems: 3, cursor: null }, }); expect(result.page).toHaveLength(3); expect(result.isDone).toBe(false); // Verify chunk content and order expect(result.page[0].order).toBe(0); expect(result.page[0].text).toBe("Test chunk content 1"); expect(result.page[0].state).toBe("ready"); expect(result.page[1].order).toBe(1); expect(result.page[1].text).toBe("Test chunk content 2"); expect(result.page[2].order).toBe(2); expect(result.page[2].text).toBe("Test chunk content 3"); // Get next page const nextResult = await t.query(api.chunks.list, { entryId, order: "asc", paginationOpts: { numItems: 3, cursor: result.continueCursor }, }); expect(nextResult.page).toHaveLength(2); expect(nextResult.isDone).toBe(true); expect(nextResult.page[0].order).toBe(3); expect(nextResult.page[1].order).toBe(4); }); describe("getRangesOfChunks", () => { test("it returns the correct number of chunks when given a range", async () => { const t = convexTest(schema, modules); const namespaceId = await setupTestNamespace(t); const entryId = await setupTestEntry(t, namespaceId); // Insert chunks const chunks = createTestChunks(5); await t.run(async (ctx) => { const result = await insertChunks(ctx, { entryId, startOrder: 0, chunks, }); expect(result.status).toBe("ready"); }); const chunkDocs = await t.run(async (ctx) => { return ctx.db .query("chunks") .withIndex("entryId_order", (q) => q.eq("entryId", entryId)) .collect(); }); assert(chunkDocs.length === 5); assert(chunkDocs[2].state.kind === "ready"); const { ranges, entries } = await t.query( internal.chunks.getRangesOfChunks, { embeddingIds: [chunkDocs[2].state.embeddingId], chunkContext: { before: 1, after: 2 }, }, ); expect(entries).toHaveLength(1); expect(entries[0].entryId).toBe(entryId); expect(ranges).toHaveLength(1); expect(ranges[0]?.startOrder).toBe(1); expect(ranges[0]?.order).toBe(2); expect(ranges[0]?.entryId).toBe(entryId); expect(ranges[0]?.content).toHaveLength(4); expect(ranges[0]?.content[0].text).toBe("Test chunk content 2"); expect(ranges[0]?.content[1].text).toBe("Test chunk content 3"); expect(ranges[0]?.content[2].text).toBe("Test chunk content 4"); expect(ranges[0]?.content[3].text).toBe("Test chunk content 5"); }); test("works finding chunks from multiple entries", async () => { const t = convexTest(schema, modules); const namespaceId = await setupTestNamespace(t); // Create two entries const doc1Id = await setupTestEntry(t, namespaceId, "doc1"); const doc2Id = await setupTestEntry(t, namespaceId, "doc2"); // Insert chunks in both entries const doc1Chunks = createTestChunks(3); const doc2Chunks = createTestChunks(4); await t.run(async (ctx) => { await insertChunks(ctx, { entryId: doc1Id, startOrder: 0, chunks: doc1Chunks, }); await insertChunks(ctx, { entryId: doc2Id, startOrder: 0, chunks: doc2Chunks, }); }); // Get chunks from both entries const doc1ChunkDocs = await t.run(async (ctx) => { return ctx.db .query("chunks") .withIndex("entryId_order", (q) => q.eq("entryId", doc1Id)) .collect(); }); const doc2ChunkDocs = await t.run(async (ctx) => { return ctx.db .query("chunks") .withIndex("entryId_order", (q) => q.eq("entryId", doc2Id)) .collect(); }); assert(doc1ChunkDocs[1].state.kind === "ready"); assert(doc2ChunkDocs[2].state.kind === "ready"); const { ranges, entries } = await t.query( internal.chunks.getRangesOfChunks, { embeddingIds: [ doc1ChunkDocs[1].state.embeddingId, // doc1, chunk at order 1 doc2ChunkDocs[2].state.embeddingId, // doc2, chunk at order 2 ], chunkContext: { before: 1, after: 1 }, }, ); expect(entries).toHaveLength(2); expect(ranges).toHaveLength(2); // First range should be from doc1 expect(ranges[0]?.entryId).toBe(doc1Id); expect(ranges[0]?.order).toBe(1); expect(ranges[0]?.startOrder).toBe(0); expect(ranges[0]?.content).toHaveLength(3); // orders 0, 1, 2 // Second range should be from doc2 expect(ranges[1]?.entryId).toBe(doc2Id); expect(ranges[1]?.order).toBe(2); expect(ranges[1]?.startOrder).toBe(1); expect(ranges[1]?.content).toHaveLength(3); // orders 1, 2, 3 }); test("finds chunks on both a ready and replaced version of the same entry", async () => { const t = convexTest(schema, modules); const namespaceId = await setupTestNamespace(t); // Create version 1 (ready) and version 2 (ready) of the same entry // (We'll test with ready versions since pending chunks don't have embeddingIds) const docV1Id = await setupTestEntry( t, namespaceId, "versioned-entry", 1, "ready", ); // Insert chunks in version 1 const v1Chunks = createTestChunks(3); await t.run(async (ctx) => { await insertChunks(ctx, { entryId: docV1Id, startOrder: 0, chunks: v1Chunks, }); }); const docV2Id = await setupTestEntry( t, namespaceId, "versioned-entry", 2, "pending", ); // Insert chunks in version 2 const v2Chunks = createTestChunks(3); await t.run(async (ctx) => { await insertChunks(ctx, { entryId: docV2Id, startOrder: 0, chunks: v2Chunks, }); }); while (true) { const result = await t.mutation(api.chunks.replaceChunksPage, { entryId: docV2Id, startOrder: 0, }); if (result.status !== "pending") { break; } } // Get chunks from both versions const v1ChunkDocs = await t.run(async (ctx) => { return ctx.db .query("chunks") .withIndex("entryId_order", (q) => q.eq("entryId", docV1Id)) .collect(); }); const v2ChunkDocs = await t.run(async (ctx) => { return ctx.db .query("chunks") .withIndex("entryId_order", (q) => q.eq("entryId", docV2Id)) .collect(); }); expect(v1ChunkDocs[1].state.kind).toBe("replaced"); expect(v2ChunkDocs[1].state.kind).toBe("ready"); // Type guard to ensure we have ready chunks assert(v1ChunkDocs[1].state.kind === "replaced"); assert(v2ChunkDocs[1].state.kind === "ready"); const { ranges, entries } = await t.query( internal.chunks.getRangesOfChunks, { embeddingIds: [ v1ChunkDocs[1].state.embeddingId, // v1, chunk at order 1 v2ChunkDocs[1].state.embeddingId, // v2, chunk at order 1 ], chunkContext: { before: 1, after: 1 }, }, ); expect(entries).toHaveLength(2); expect(ranges).toHaveLength(2); expect(ranges[0]?.entryId).toBe(docV1Id); expect(ranges[0]?.order).toBe(1); expect(ranges[1]?.entryId).toBe(docV2Id); expect(ranges[1]?.order).toBe(1); await t.run(async (ctx) => { const chunks = await ctx.db .query("chunks") .withIndex("entryId_order", (q) => q.eq("entryId", docV2Id)) .collect(); expect(chunks).toHaveLength(3); assert(chunks[0].state.kind === "ready"); assert(chunks[1].state.kind === "ready"); assert(chunks[2].state.kind === "ready"); const embeddings = await ctx.db.query("vectors_128").collect(); // should only have 3 embeddings total expect(embeddings).toHaveLength(3); expect(embeddings[0]._id).toEqual(chunks[0].state.embeddingId); expect(embeddings[1]._id).toEqual(chunks[1].state.embeddingId); expect(embeddings[2]._id).toEqual(chunks[2].state.embeddingId); return embeddings; }); }); test("finds chunks before and after a chunk", async () => { const t = convexTest(schema, modules); const namespaceId = await setupTestNamespace(t); const entryId = await setupTestEntry(t, namespaceId); // Insert chunks const chunks = createTestChunks(7); await t.run(async (ctx) => { await insertChunks(ctx, { entryId, startOrder: 0, chunks, }); }); const chunkDocs = await t.run(async (ctx) => { return ctx.db .query("chunks") .withIndex("entryId_order", (q) => q.eq("entryId", entryId)) .collect(); }); assert(chunkDocs[3].state.kind === "ready"); const { ranges } = await t.query(internal.chunks.getRangesOfChunks, { embeddingIds: [chunkDocs[3].state.embeddingId], // chunk at order 3 chunkContext: { before: 2, after: 2 }, }); expect(ranges).toHaveLength(1); expect(ranges[0]?.order).toBe(3); expect(ranges[0]?.startOrder).toBe(1); // 3 - 2 = 1 expect(ranges[0]?.content).toHaveLength(5); // orders 1, 2, 3, 4, 5 expect(ranges[0]?.content[0].text).toBe("Test chunk content 2"); // order 1 expect(ranges[0]?.content[1].text).toBe("Test chunk content 3"); // order 2 expect(ranges[0]?.content[2].text).toBe("Test chunk content 4"); // order 3 (target) expect(ranges[0]?.content[3].text).toBe("Test chunk content 5"); // order 4 expect(ranges[0]?.content[4].text).toBe("Test chunk content 6"); // order 5 }); test("accepts ranges outside of the entry order bounds", async () => { const t = convexTest(schema, modules); const namespaceId = await setupTestNamespace(t); const entryId = await setupTestEntry(t, namespaceId); // Insert only 3 chunks (orders 0, 1, 2) const chunks = createTestChunks(3); await t.run(async (ctx) => { await insertChunks(ctx, { entryId, startOrder: 0, chunks, }); }); const chunkDocs = await t.run(async (ctx) => { return ctx.db .query("chunks") .withIndex("entryId_order", (q) => q.eq("entryId", entryId)) .collect(); }); assert(chunkDocs[2].state.kind === "ready"); // Request a large range that extends beyond entry bounds const { ranges } = await t.query(internal.chunks.getRangesOfChunks, { embeddingIds: [chunkDocs[2].state.embeddingId], // chunk at order 2 chunkContext: { before: 5, after: 5 }, // Large range }); expect(ranges).toHaveLength(1); expect(ranges[0]?.order).toBe(2); expect(ranges[0]?.startOrder).toBe(0); // Should be clamped to 0 expect(ranges[0]?.content).toHaveLength(3); // All available chunks (0, 1, 2) expect(ranges[0]?.content[0].text).toBe("Test chunk content 1"); // order 0 expect(ranges[0]?.content[1].text).toBe("Test chunk content 2"); // order 1 expect(ranges[0]?.content[2].text).toBe("Test chunk content 3"); // order 2 }); test("when two ranges overlap, the later range gets priority on the chunks in between", async () => { const t = convexTest(schema, modules); const namespaceId = await setupTestNamespace(t); const entryId = await setupTestEntry(t, namespaceId); // Insert chunks const chunks = createTestChunks(10); await t.run(async (ctx) => { await insertChunks(ctx, { entryId, startOrder: 0, chunks, }); }); const chunkDocs = await t.run(async (ctx) => { return ctx.db .query("chunks") .withIndex("entryId_order", (q) => q.eq("entryId", entryId)) .collect(); }); assert(chunkDocs[2].state.kind === "ready"); assert(chunkDocs[6].state.kind === "ready"); const { ranges } = await t.query(internal.chunks.getRangesOfChunks, { embeddingIds: [ chunkDocs[2].state.embeddingId, // chunk at order 2 chunkDocs[6].state.embeddingId, // chunk at order 6 ], chunkContext: { before: 3, after: 3 }, }); expect(ranges).toHaveLength(2); // First range (order 2): should stop before the second range's territory expect(ranges[0]?.order).toBe(2); expect(ranges[0]?.startOrder).toBe(0); // The end should be limited by the second range's before context expect(ranges[0]?.content.length).toBe(3); // orders 0, 1, 2 // Second range (order 6): should get priority for overlapping chunks expect(ranges[1]?.order).toBe(6); expect(ranges[1]?.startOrder).toBe(3); // start at 6, 3 before expect(ranges[1]?.content).toHaveLength(7); // orders 3, 4, 5, 6, 7, 8, 9 }); test("when three ranges overlap, the middle chunk gets priority on before chunk but not after chunk", async () => { const t = convexTest(schema, modules); const namespaceId = await setupTestNamespace(t); const entryId = await setupTestEntry(t, namespaceId); // Insert chunks const chunks = createTestChunks(15); await t.run(async (ctx) => { await insertChunks(ctx, { entryId, startOrder: 0, chunks, }); }); const chunkDocs = await t.run(async (ctx) => { return ctx.db .query("chunks") .withIndex("entryId_order", (q) => q.eq("entryId", entryId)) .collect(); }); assert(chunkDocs[2].state.kind === "ready"); assert(chunkDocs[7].state.kind === "ready"); assert(chunkDocs[12].state.kind === "ready"); const { ranges } = await t.query(internal.chunks.getRangesOfChunks, { embeddingIds: [ chunkDocs[2].state.embeddingId, // chunk at order 2 chunkDocs[7].state.embeddingId, // chunk at order 7 (middle) chunkDocs[12].state.embeddingId, // chunk at order 12 ], chunkContext: { before: 4, after: 4 }, }); expect(ranges).toHaveLength(3); // First range (order 2) expect(ranges[0]?.order).toBe(2); expect(ranges[0]?.startOrder).toBe(0); // Middle range (order 7): should get priority over first range's after context expect(ranges[1]?.order).toBe(7); expect(ranges[1]?.startOrder).toBe(3); // Should start after first range's territory // Last range (order 12): should get priority over middle range's after context expect(ranges[2]?.order).toBe(12); expect(ranges[2]?.startOrder).toBe(8); // Should start after middle range's territory expect(ranges[2]?.content.length).toBeLessThanOrEqual(7); // Should not extend beyond entry }); test("it works with before/after of 0", async () => { const t = convexTest(schema, modules); const namespaceId = await setupTestNamespace(t); const entryId = await setupTestEntry(t, namespaceId); // Insert chunks const chunks = createTestChunks(5); await t.run(async (ctx) => { await insertChunks(ctx, { entryId, startOrder: 0, chunks, }); }); const chunkDocs = await t.run(async (ctx) => { return ctx.db .query("chunks") .withIndex("entryId_order", (q) => q.eq("entryId", entryId)) .collect(); }); assert(chunkDocs[2].state.kind === "ready"); const { ranges } = await t.query(internal.chunks.getRangesOfChunks, { embeddingIds: [chunkDocs[2].state.embeddingId], // chunk at order 2 chunkContext: { before: 0, after: 0 }, }); expect(ranges).toHaveLength(1); expect(ranges[0]?.order).toBe(2); expect(ranges[0]?.startOrder).toBe(2); expect(ranges[0]?.content).toHaveLength(1); // Only the target chunk expect(ranges[0]?.content[0].text).toBe("Test chunk content 3"); // order 2 }); test("it returns de-duplicated entries in the order of the associated embedding ids", async () => { const t = convexTest(schema, modules); const namespaceId = await setupTestNamespace(t); // Create three entries const doc1Id = await setupTestEntry(t, namespaceId, "doc1"); const doc2Id = await setupTestEntry(t, namespaceId, "doc2"); const doc3Id = await setupTestEntry(t, namespaceId, "doc3"); // Insert chunks in all entries await t.run(async (ctx) => { await insertChunks(ctx, { entryId: doc1Id, startOrder: 0, chunks: createTestChunks(2), }); await insertChunks(ctx, { entryId: doc2Id, startOrder: 0, chunks: createTestChunks(2), }); await insertChunks(ctx, { entryId: doc3Id, startOrder: 0, chunks: createTestChunks(2), }); }); // Get chunks from all entries const [doc1Chunks, doc2Chunks, doc3Chunks] = await t.run(async (ctx) => { return Promise.all([ ctx.db .query("chunks") .withIndex("entryId_order", (q) => q.eq("entryId", doc1Id)) .collect(), ctx.db .query("chunks") .withIndex("entryId_order", (q) => q.eq("entryId", doc2Id)) .collect(), ctx.db .query("chunks") .withIndex("entryId_order", (q) => q.eq("entryId", doc3Id)) .collect(), ]); }); assert(doc1Chunks[0].state.kind === "ready"); assert(doc2Chunks[1].state.kind === "ready"); assert(doc3Chunks[0].state.kind === "ready"); assert(doc1Chunks[1].state.kind === "ready"); assert(doc2Chunks[0].state.kind === "ready"); const { entries } = await t.query(internal.chunks.getRangesOfChunks, { embeddingIds: [ doc2Chunks[1].state.embeddingId, // doc2 first doc1Chunks[0].state.embeddingId, // doc1 second doc3Chunks[0].state.embeddingId, // doc3 third doc1Chunks[1].state.embeddingId, // doc1 again (should be deduplicated) doc2Chunks[0].state.embeddingId, // doc2 again (should be deduplicated) ], chunkContext: { before: 0, after: 0 }, }); // Should return only 3 entries (deduplicated) expect(entries).toHaveLength(3); // Should be in the order they first appeared in the embedding IDs expect(entries[0].entryId).toBe(doc2Id); // First appearance expect(entries[1].entryId).toBe(doc1Id); // Second appearance expect(entries[2].entryId).toBe(doc3Id); // Third appearance }); }); });