@kernl-sdk/turbopuffer 0.1.3 → 0.1.4
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +6 -0
- package/dist/handle.js +1 -1
- package/dist/search.js +1 -1
- package/package.json +5 -5
- package/dist/__tests__/filters.integration.test.d.ts +0 -8
- package/dist/__tests__/filters.integration.test.d.ts.map +0 -1
- package/dist/__tests__/filters.integration.test.js +0 -502
- package/dist/__tests__/integration.test.d.ts +0 -2
- package/dist/__tests__/integration.test.d.ts.map +0 -1
- package/dist/__tests__/integration.test.js +0 -343
- package/dist/__tests__/lifecycle.integration.test.d.ts +0 -8
- package/dist/__tests__/lifecycle.integration.test.d.ts.map +0 -1
- package/dist/__tests__/lifecycle.integration.test.js +0 -385
- package/dist/__tests__/query.integration.test.d.ts +0 -8
- package/dist/__tests__/query.integration.test.d.ts.map +0 -1
- package/dist/__tests__/query.integration.test.js +0 -423
- package/dist/convert.d.ts +0 -68
- package/dist/convert.d.ts.map +0 -1
- package/dist/convert.js +0 -333
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
2
|
-
import { TurbopufferSearchIndex } from "../search.js";
|
|
3
|
-
const TURBOPUFFER_API_KEY = process.env.TURBOPUFFER_API_KEY;
|
|
4
|
-
const TURBOPUFFER_REGION = process.env.TURBOPUFFER_REGION ?? "api";
|
|
5
|
-
describe("TurbopufferSearchIndex integration", () => {
|
|
6
|
-
if (!TURBOPUFFER_API_KEY) {
|
|
7
|
-
it.skip("requires TURBOPUFFER_API_KEY to be set", () => { });
|
|
8
|
-
return;
|
|
9
|
-
}
|
|
10
|
-
let tpuf;
|
|
11
|
-
const testIndexId = `kernl-test-${Date.now()}`;
|
|
12
|
-
beforeAll(() => {
|
|
13
|
-
tpuf = new TurbopufferSearchIndex({
|
|
14
|
-
apiKey: TURBOPUFFER_API_KEY,
|
|
15
|
-
region: TURBOPUFFER_REGION,
|
|
16
|
-
});
|
|
17
|
-
});
|
|
18
|
-
afterAll(async () => {
|
|
19
|
-
// Clean up test index
|
|
20
|
-
try {
|
|
21
|
-
await tpuf.deleteIndex(testIndexId);
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
// Ignore errors if index doesn't exist
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
describe("createIndex", () => {
|
|
28
|
-
it("creates a new index with schema", async () => {
|
|
29
|
-
await tpuf.createIndex({
|
|
30
|
-
id: testIndexId,
|
|
31
|
-
schema: {
|
|
32
|
-
content: { type: "string", fts: true },
|
|
33
|
-
vector: { type: "vector", dimensions: 384 },
|
|
34
|
-
category: { type: "string", filterable: true },
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
// Verify the index was created by describing it
|
|
38
|
-
const stats = await tpuf.describeIndex(testIndexId);
|
|
39
|
-
expect(stats.id).toBe(testIndexId);
|
|
40
|
-
expect(stats.status).toBe("ready");
|
|
41
|
-
});
|
|
42
|
-
it("throws if vector field is not named 'vector'", async () => {
|
|
43
|
-
await expect(tpuf.createIndex({
|
|
44
|
-
id: `${testIndexId}-invalid`,
|
|
45
|
-
schema: {
|
|
46
|
-
embedding: { type: "vector", dimensions: 384 },
|
|
47
|
-
},
|
|
48
|
-
})).rejects.toThrow(/requires vector fields to be named "vector"/);
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
describe("listIndexes", () => {
|
|
52
|
-
it("returns a CursorPage of indexes", async () => {
|
|
53
|
-
const page = await tpuf.listIndexes();
|
|
54
|
-
expect(page).toBeDefined();
|
|
55
|
-
expect(page.items).toBeDefined();
|
|
56
|
-
expect(Array.isArray(page.items)).toBe(true);
|
|
57
|
-
// Each item should have an id
|
|
58
|
-
for (const idx of page.items) {
|
|
59
|
-
expect(typeof idx.id).toBe("string");
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
it("supports pagination with limit", async () => {
|
|
63
|
-
const page = await tpuf.listIndexes({ limit: 2 });
|
|
64
|
-
expect(page.items.length).toBeLessThanOrEqual(2);
|
|
65
|
-
});
|
|
66
|
-
it("supports prefix filtering", async () => {
|
|
67
|
-
const page = await tpuf.listIndexes({ prefix: "kernl-test-" });
|
|
68
|
-
expect(page).toBeDefined();
|
|
69
|
-
expect(Array.isArray(page.items)).toBe(true);
|
|
70
|
-
// Should find our test index
|
|
71
|
-
const found = page.items.some((idx) => idx.id === testIndexId);
|
|
72
|
-
expect(found).toBe(true);
|
|
73
|
-
// All results should match prefix
|
|
74
|
-
for (const idx of page.items) {
|
|
75
|
-
expect(idx.id.startsWith("kernl-test-")).toBe(true);
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
it("supports async iteration via collect()", async () => {
|
|
79
|
-
const page = await tpuf.listIndexes({ limit: 5 });
|
|
80
|
-
const all = await page.collect();
|
|
81
|
-
expect(Array.isArray(all)).toBe(true);
|
|
82
|
-
expect(all.length).toBeGreaterThanOrEqual(page.items.length);
|
|
83
|
-
});
|
|
84
|
-
it("supports for-await iteration", async () => {
|
|
85
|
-
const page = await tpuf.listIndexes({ limit: 3 });
|
|
86
|
-
const collected = [];
|
|
87
|
-
for await (const idx of page) {
|
|
88
|
-
collected.push(idx.id);
|
|
89
|
-
if (collected.length >= 3)
|
|
90
|
-
break;
|
|
91
|
-
}
|
|
92
|
-
expect(collected.length).toBeLessThanOrEqual(3);
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
describe("describeIndex", () => {
|
|
96
|
-
it("returns stats for the test index", async () => {
|
|
97
|
-
const stats = await tpuf.describeIndex(testIndexId);
|
|
98
|
-
expect(stats.id).toBe(testIndexId);
|
|
99
|
-
expect(typeof stats.count).toBe("number");
|
|
100
|
-
expect(stats.count).toBeGreaterThanOrEqual(0);
|
|
101
|
-
expect(stats.status).toBe("ready");
|
|
102
|
-
if (stats.sizeb !== undefined) {
|
|
103
|
-
expect(typeof stats.sizeb).toBe("number");
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
it("throws for non-existent index", async () => {
|
|
107
|
-
await expect(tpuf.describeIndex("non-existent-index-12345")).rejects.toThrow();
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
describe("deleteIndex", () => {
|
|
111
|
-
it("deletes an existing index", async () => {
|
|
112
|
-
// Create a temporary index to delete
|
|
113
|
-
const tempId = `kernl-test-delete-${Date.now()}`;
|
|
114
|
-
await tpuf.createIndex({
|
|
115
|
-
id: tempId,
|
|
116
|
-
schema: {
|
|
117
|
-
text: { type: "string" },
|
|
118
|
-
},
|
|
119
|
-
});
|
|
120
|
-
// Verify it exists
|
|
121
|
-
const stats = await tpuf.describeIndex(tempId);
|
|
122
|
-
expect(stats.id).toBe(tempId);
|
|
123
|
-
// Delete it
|
|
124
|
-
await tpuf.deleteIndex(tempId);
|
|
125
|
-
// Verify it's gone
|
|
126
|
-
await expect(tpuf.describeIndex(tempId)).rejects.toThrow();
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
describe("upsert", () => {
|
|
130
|
-
it("inserts a document with vector", async () => {
|
|
131
|
-
const vec = new Array(384).fill(0.1);
|
|
132
|
-
const index = tpuf.index(testIndexId);
|
|
133
|
-
await index.upsert({
|
|
134
|
-
id: "doc-1",
|
|
135
|
-
fields: {
|
|
136
|
-
content: "Hello world",
|
|
137
|
-
vector: { kind: "vector", values: vec },
|
|
138
|
-
category: "greeting",
|
|
139
|
-
},
|
|
140
|
-
});
|
|
141
|
-
// Verify via query
|
|
142
|
-
const hits = await index.query({
|
|
143
|
-
query: [{ vector: vec }],
|
|
144
|
-
topK: 10,
|
|
145
|
-
include: ["category"],
|
|
146
|
-
});
|
|
147
|
-
expect(hits.length).toBeGreaterThanOrEqual(1);
|
|
148
|
-
const doc = hits.find((h) => h.id === "doc-1");
|
|
149
|
-
expect(doc).toBeDefined();
|
|
150
|
-
expect(doc?.document?.category).toBe("greeting");
|
|
151
|
-
});
|
|
152
|
-
it("updates an existing document", async () => {
|
|
153
|
-
const vec = new Array(384).fill(0.2);
|
|
154
|
-
const index = tpuf.index(testIndexId);
|
|
155
|
-
// Upsert same id with different content
|
|
156
|
-
await index.upsert({
|
|
157
|
-
id: "doc-1",
|
|
158
|
-
fields: {
|
|
159
|
-
content: "Updated content",
|
|
160
|
-
vector: { kind: "vector", values: vec },
|
|
161
|
-
category: "updated",
|
|
162
|
-
},
|
|
163
|
-
});
|
|
164
|
-
// Verify via query
|
|
165
|
-
const hits = await index.query({
|
|
166
|
-
query: [{ vector: vec }],
|
|
167
|
-
topK: 10,
|
|
168
|
-
include: ["category"],
|
|
169
|
-
});
|
|
170
|
-
const doc = hits.find((h) => h.id === "doc-1");
|
|
171
|
-
expect(doc).toBeDefined();
|
|
172
|
-
expect(doc?.document?.category).toBe("updated");
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
describe("upsert (multiple)", () => {
|
|
176
|
-
it("inserts multiple documents", async () => {
|
|
177
|
-
const index = tpuf.index(testIndexId);
|
|
178
|
-
await index.upsert([
|
|
179
|
-
{
|
|
180
|
-
id: "doc-2",
|
|
181
|
-
fields: {
|
|
182
|
-
content: "Second document",
|
|
183
|
-
vector: { kind: "vector", values: new Array(384).fill(0.3) },
|
|
184
|
-
category: "test",
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
{
|
|
188
|
-
id: "doc-3",
|
|
189
|
-
fields: {
|
|
190
|
-
content: "Third document",
|
|
191
|
-
vector: { kind: "vector", values: new Array(384).fill(0.4) },
|
|
192
|
-
category: "test",
|
|
193
|
-
},
|
|
194
|
-
},
|
|
195
|
-
]);
|
|
196
|
-
// Verify via query with filter
|
|
197
|
-
const hits = await index.query({
|
|
198
|
-
query: [{ vector: new Array(384).fill(0.3) }],
|
|
199
|
-
topK: 10,
|
|
200
|
-
filter: { category: "test" },
|
|
201
|
-
include: true,
|
|
202
|
-
});
|
|
203
|
-
expect(hits.length).toBeGreaterThanOrEqual(2);
|
|
204
|
-
const ids = hits.map((h) => h.id);
|
|
205
|
-
expect(ids).toContain("doc-2");
|
|
206
|
-
expect(ids).toContain("doc-3");
|
|
207
|
-
});
|
|
208
|
-
it("handles empty array", async () => {
|
|
209
|
-
const index = tpuf.index(testIndexId);
|
|
210
|
-
// Should not throw
|
|
211
|
-
await index.upsert([]);
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
describe("query", () => {
|
|
215
|
-
it("performs vector search", async () => {
|
|
216
|
-
const index = tpuf.index(testIndexId);
|
|
217
|
-
const hits = await index.query({
|
|
218
|
-
query: [{ vector: new Array(384).fill(0.1) }],
|
|
219
|
-
topK: 5,
|
|
220
|
-
});
|
|
221
|
-
expect(hits.length).toBeGreaterThan(0);
|
|
222
|
-
expect(hits[0]).toHaveProperty("id");
|
|
223
|
-
expect(hits[0]).toHaveProperty("score");
|
|
224
|
-
expect(hits[0]).toHaveProperty("index", testIndexId);
|
|
225
|
-
});
|
|
226
|
-
it("returns requested fields", async () => {
|
|
227
|
-
const index = tpuf.index(testIndexId);
|
|
228
|
-
const hits = await index.query({
|
|
229
|
-
query: [{ vector: new Array(384).fill(0.1) }],
|
|
230
|
-
topK: 5,
|
|
231
|
-
include: ["content", "category"],
|
|
232
|
-
});
|
|
233
|
-
expect(hits.length).toBeGreaterThan(0);
|
|
234
|
-
expect(hits[0].document).toBeDefined();
|
|
235
|
-
expect(hits[0].document).toHaveProperty("content");
|
|
236
|
-
expect(hits[0].document).toHaveProperty("category");
|
|
237
|
-
});
|
|
238
|
-
it("filters results", async () => {
|
|
239
|
-
const index = tpuf.index(testIndexId);
|
|
240
|
-
const hits = await index.query({
|
|
241
|
-
query: [{ vector: new Array(384).fill(0.3) }],
|
|
242
|
-
topK: 10,
|
|
243
|
-
filter: { category: "test" },
|
|
244
|
-
include: ["category"],
|
|
245
|
-
});
|
|
246
|
-
expect(hits.length).toBeGreaterThan(0);
|
|
247
|
-
for (const hit of hits) {
|
|
248
|
-
expect(hit.document?.category).toBe("test");
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
it("supports AND filters", async () => {
|
|
252
|
-
const index = tpuf.index(testIndexId);
|
|
253
|
-
const hits = await index.query({
|
|
254
|
-
query: [{ vector: new Array(384).fill(0.3) }],
|
|
255
|
-
topK: 10,
|
|
256
|
-
filter: {
|
|
257
|
-
$and: [
|
|
258
|
-
{ category: "test" },
|
|
259
|
-
{ id: { $in: ["doc-2", "doc-3"] } },
|
|
260
|
-
],
|
|
261
|
-
},
|
|
262
|
-
include: ["category"],
|
|
263
|
-
});
|
|
264
|
-
expect(hits.length).toBeGreaterThanOrEqual(0);
|
|
265
|
-
for (const hit of hits) {
|
|
266
|
-
expect(hit.document?.category).toBe("test");
|
|
267
|
-
}
|
|
268
|
-
});
|
|
269
|
-
});
|
|
270
|
-
describe("delete", () => {
|
|
271
|
-
it("deletes a document by id", async () => {
|
|
272
|
-
const vec = new Array(384).fill(0.5);
|
|
273
|
-
const index = tpuf.index(testIndexId);
|
|
274
|
-
// Insert a document to delete
|
|
275
|
-
await index.upsert({
|
|
276
|
-
id: "doc-to-delete",
|
|
277
|
-
fields: {
|
|
278
|
-
content: "Delete me",
|
|
279
|
-
vector: { kind: "vector", values: vec },
|
|
280
|
-
category: "deletable",
|
|
281
|
-
},
|
|
282
|
-
});
|
|
283
|
-
// Verify it exists
|
|
284
|
-
let hits = await index.query({
|
|
285
|
-
query: [{ vector: vec }],
|
|
286
|
-
topK: 10,
|
|
287
|
-
filter: { id: "doc-to-delete" },
|
|
288
|
-
});
|
|
289
|
-
expect(hits.some((h) => h.id === "doc-to-delete")).toBe(true);
|
|
290
|
-
// Delete it
|
|
291
|
-
await index.delete("doc-to-delete");
|
|
292
|
-
// Verify it's gone
|
|
293
|
-
hits = await index.query({
|
|
294
|
-
query: [{ vector: vec }],
|
|
295
|
-
topK: 10,
|
|
296
|
-
filter: { id: "doc-to-delete" },
|
|
297
|
-
});
|
|
298
|
-
expect(hits.some((h) => h.id === "doc-to-delete")).toBe(false);
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
describe("delete (multiple)", () => {
|
|
302
|
-
it("deletes multiple documents by ids", async () => {
|
|
303
|
-
const vec = new Array(384).fill(0.6);
|
|
304
|
-
const index = tpuf.index(testIndexId);
|
|
305
|
-
// Insert documents to delete
|
|
306
|
-
await index.upsert([
|
|
307
|
-
{
|
|
308
|
-
id: "bulk-del-1",
|
|
309
|
-
fields: {
|
|
310
|
-
content: "Bulk delete 1",
|
|
311
|
-
vector: { kind: "vector", values: vec },
|
|
312
|
-
category: "bulk-delete",
|
|
313
|
-
},
|
|
314
|
-
},
|
|
315
|
-
{
|
|
316
|
-
id: "bulk-del-2",
|
|
317
|
-
fields: {
|
|
318
|
-
content: "Bulk delete 2",
|
|
319
|
-
vector: { kind: "vector", values: vec },
|
|
320
|
-
category: "bulk-delete",
|
|
321
|
-
},
|
|
322
|
-
},
|
|
323
|
-
]);
|
|
324
|
-
// Verify they exist
|
|
325
|
-
let hits = await index.query({
|
|
326
|
-
query: [{ vector: vec }],
|
|
327
|
-
topK: 10,
|
|
328
|
-
filter: { category: "bulk-delete" },
|
|
329
|
-
});
|
|
330
|
-
expect(hits.length).toBeGreaterThanOrEqual(2);
|
|
331
|
-
// Delete by ids
|
|
332
|
-
await index.delete(["bulk-del-1", "bulk-del-2"]);
|
|
333
|
-
// Verify they're gone
|
|
334
|
-
hits = await index.query({
|
|
335
|
-
query: [{ vector: vec }],
|
|
336
|
-
topK: 10,
|
|
337
|
-
filter: { category: "bulk-delete" },
|
|
338
|
-
});
|
|
339
|
-
expect(hits.some((h) => h.id === "bulk-del-1")).toBe(false);
|
|
340
|
-
expect(hits.some((h) => h.id === "bulk-del-2")).toBe(false);
|
|
341
|
-
});
|
|
342
|
-
});
|
|
343
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"lifecycle.integration.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/lifecycle.integration.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|