@kernl-sdk/turbopuffer 0.1.0

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.
Files changed (91) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-check-types.log +60 -0
  3. package/CHANGELOG.md +33 -0
  4. package/LICENSE +201 -0
  5. package/README.md +60 -0
  6. package/dist/__tests__/convert.test.d.ts +2 -0
  7. package/dist/__tests__/convert.test.d.ts.map +1 -0
  8. package/dist/__tests__/convert.test.js +346 -0
  9. package/dist/__tests__/filter.test.d.ts +8 -0
  10. package/dist/__tests__/filter.test.d.ts.map +1 -0
  11. package/dist/__tests__/filter.test.js +649 -0
  12. package/dist/__tests__/filters.integration.test.d.ts +8 -0
  13. package/dist/__tests__/filters.integration.test.d.ts.map +1 -0
  14. package/dist/__tests__/filters.integration.test.js +502 -0
  15. package/dist/__tests__/integration/filters.integration.test.d.ts +8 -0
  16. package/dist/__tests__/integration/filters.integration.test.d.ts.map +1 -0
  17. package/dist/__tests__/integration/filters.integration.test.js +475 -0
  18. package/dist/__tests__/integration/integration.test.d.ts +2 -0
  19. package/dist/__tests__/integration/integration.test.d.ts.map +1 -0
  20. package/dist/__tests__/integration/integration.test.js +329 -0
  21. package/dist/__tests__/integration/lifecycle.integration.test.d.ts +8 -0
  22. package/dist/__tests__/integration/lifecycle.integration.test.d.ts.map +1 -0
  23. package/dist/__tests__/integration/lifecycle.integration.test.js +370 -0
  24. package/dist/__tests__/integration/memory.integration.test.d.ts +2 -0
  25. package/dist/__tests__/integration/memory.integration.test.d.ts.map +1 -0
  26. package/dist/__tests__/integration/memory.integration.test.js +287 -0
  27. package/dist/__tests__/integration/query.integration.test.d.ts +8 -0
  28. package/dist/__tests__/integration/query.integration.test.d.ts.map +1 -0
  29. package/dist/__tests__/integration/query.integration.test.js +385 -0
  30. package/dist/__tests__/integration.test.d.ts +2 -0
  31. package/dist/__tests__/integration.test.d.ts.map +1 -0
  32. package/dist/__tests__/integration.test.js +343 -0
  33. package/dist/__tests__/lifecycle.integration.test.d.ts +8 -0
  34. package/dist/__tests__/lifecycle.integration.test.d.ts.map +1 -0
  35. package/dist/__tests__/lifecycle.integration.test.js +385 -0
  36. package/dist/__tests__/query.integration.test.d.ts +8 -0
  37. package/dist/__tests__/query.integration.test.d.ts.map +1 -0
  38. package/dist/__tests__/query.integration.test.js +423 -0
  39. package/dist/__tests__/query.test.d.ts +8 -0
  40. package/dist/__tests__/query.test.d.ts.map +1 -0
  41. package/dist/__tests__/query.test.js +472 -0
  42. package/dist/convert/document.d.ts +20 -0
  43. package/dist/convert/document.d.ts.map +1 -0
  44. package/dist/convert/document.js +72 -0
  45. package/dist/convert/filter.d.ts +15 -0
  46. package/dist/convert/filter.d.ts.map +1 -0
  47. package/dist/convert/filter.js +109 -0
  48. package/dist/convert/index.d.ts +8 -0
  49. package/dist/convert/index.d.ts.map +1 -0
  50. package/dist/convert/index.js +7 -0
  51. package/dist/convert/query.d.ts +22 -0
  52. package/dist/convert/query.d.ts.map +1 -0
  53. package/dist/convert/query.js +111 -0
  54. package/dist/convert/schema.d.ts +39 -0
  55. package/dist/convert/schema.d.ts.map +1 -0
  56. package/dist/convert/schema.js +124 -0
  57. package/dist/convert.d.ts +68 -0
  58. package/dist/convert.d.ts.map +1 -0
  59. package/dist/convert.js +333 -0
  60. package/dist/handle.d.ts +34 -0
  61. package/dist/handle.d.ts.map +1 -0
  62. package/dist/handle.js +72 -0
  63. package/dist/index.d.ts +27 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +26 -0
  66. package/dist/search.d.ts +85 -0
  67. package/dist/search.d.ts.map +1 -0
  68. package/dist/search.js +167 -0
  69. package/dist/types.d.ts +14 -0
  70. package/dist/types.d.ts.map +1 -0
  71. package/dist/types.js +1 -0
  72. package/package.json +57 -0
  73. package/src/__tests__/convert.test.ts +425 -0
  74. package/src/__tests__/filter.test.ts +730 -0
  75. package/src/__tests__/integration/filters.integration.test.ts +558 -0
  76. package/src/__tests__/integration/integration.test.ts +399 -0
  77. package/src/__tests__/integration/lifecycle.integration.test.ts +464 -0
  78. package/src/__tests__/integration/memory.integration.test.ts +353 -0
  79. package/src/__tests__/integration/query.integration.test.ts +471 -0
  80. package/src/__tests__/query.test.ts +636 -0
  81. package/src/convert/document.ts +95 -0
  82. package/src/convert/filter.ts +123 -0
  83. package/src/convert/index.ts +8 -0
  84. package/src/convert/query.ts +151 -0
  85. package/src/convert/schema.ts +163 -0
  86. package/src/handle.ts +104 -0
  87. package/src/index.ts +31 -0
  88. package/src/search.ts +207 -0
  89. package/src/types.ts +14 -0
  90. package/tsconfig.json +13 -0
  91. package/vitest.config.ts +15 -0
@@ -0,0 +1,329 @@
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
+ content: "Hello world",
136
+ vector: vec,
137
+ category: "greeting",
138
+ });
139
+ // Verify via query
140
+ const hits = await index.query({
141
+ query: [{ vector: vec }],
142
+ topK: 10,
143
+ include: ["category"],
144
+ });
145
+ expect(hits.length).toBeGreaterThanOrEqual(1);
146
+ const doc = hits.find((h) => h.id === "doc-1");
147
+ expect(doc).toBeDefined();
148
+ expect(doc?.document?.category).toBe("greeting");
149
+ });
150
+ it("updates an existing document", async () => {
151
+ const vec = new Array(384).fill(0.2);
152
+ const index = tpuf.index(testIndexId);
153
+ // Upsert same id with different content
154
+ await index.upsert({
155
+ id: "doc-1",
156
+ content: "Updated content",
157
+ vector: vec,
158
+ category: "updated",
159
+ });
160
+ // Verify via query
161
+ const hits = await index.query({
162
+ query: [{ vector: vec }],
163
+ topK: 10,
164
+ include: ["category"],
165
+ });
166
+ const doc = hits.find((h) => h.id === "doc-1");
167
+ expect(doc).toBeDefined();
168
+ expect(doc?.document?.category).toBe("updated");
169
+ });
170
+ });
171
+ describe("upsert (multiple)", () => {
172
+ it("inserts multiple documents", async () => {
173
+ const index = tpuf.index(testIndexId);
174
+ await index.upsert([
175
+ {
176
+ id: "doc-2",
177
+ content: "Second document",
178
+ vector: new Array(384).fill(0.3),
179
+ category: "test",
180
+ },
181
+ {
182
+ id: "doc-3",
183
+ content: "Third document",
184
+ vector: new Array(384).fill(0.4),
185
+ category: "test",
186
+ },
187
+ ]);
188
+ // Verify via query with filter
189
+ const hits = await index.query({
190
+ query: [{ vector: new Array(384).fill(0.3) }],
191
+ topK: 10,
192
+ filter: { category: "test" },
193
+ include: true,
194
+ });
195
+ expect(hits.length).toBeGreaterThanOrEqual(2);
196
+ const ids = hits.map((h) => h.id);
197
+ expect(ids).toContain("doc-2");
198
+ expect(ids).toContain("doc-3");
199
+ });
200
+ it("handles empty array", async () => {
201
+ const index = tpuf.index(testIndexId);
202
+ // Should not throw
203
+ await index.upsert([]);
204
+ });
205
+ });
206
+ describe("query", () => {
207
+ it("performs vector search", async () => {
208
+ const index = tpuf.index(testIndexId);
209
+ const hits = await index.query({
210
+ query: [{ vector: new Array(384).fill(0.1) }],
211
+ topK: 5,
212
+ });
213
+ expect(hits.length).toBeGreaterThan(0);
214
+ expect(hits[0]).toHaveProperty("id");
215
+ expect(hits[0]).toHaveProperty("score");
216
+ expect(hits[0]).toHaveProperty("index", testIndexId);
217
+ });
218
+ it("returns requested fields", async () => {
219
+ const index = tpuf.index(testIndexId);
220
+ const hits = await index.query({
221
+ query: [{ vector: new Array(384).fill(0.1) }],
222
+ topK: 5,
223
+ include: ["content", "category"],
224
+ });
225
+ expect(hits.length).toBeGreaterThan(0);
226
+ expect(hits[0].document).toBeDefined();
227
+ expect(hits[0].document).toHaveProperty("content");
228
+ expect(hits[0].document).toHaveProperty("category");
229
+ });
230
+ it("filters results", async () => {
231
+ const index = tpuf.index(testIndexId);
232
+ const hits = await index.query({
233
+ query: [{ vector: new Array(384).fill(0.3) }],
234
+ topK: 10,
235
+ filter: { category: "test" },
236
+ include: ["category"],
237
+ });
238
+ expect(hits.length).toBeGreaterThan(0);
239
+ for (const hit of hits) {
240
+ expect(hit.document?.category).toBe("test");
241
+ }
242
+ });
243
+ it("supports AND filters", async () => {
244
+ const index = tpuf.index(testIndexId);
245
+ const hits = await index.query({
246
+ query: [{ vector: new Array(384).fill(0.3) }],
247
+ topK: 10,
248
+ filter: {
249
+ $and: [
250
+ { category: "test" },
251
+ { id: { $in: ["doc-2", "doc-3"] } },
252
+ ],
253
+ },
254
+ include: ["category"],
255
+ });
256
+ expect(hits.length).toBeGreaterThanOrEqual(0);
257
+ for (const hit of hits) {
258
+ expect(hit.document?.category).toBe("test");
259
+ }
260
+ });
261
+ });
262
+ describe("delete", () => {
263
+ it("deletes a document by id", async () => {
264
+ const vec = new Array(384).fill(0.5);
265
+ const index = tpuf.index(testIndexId);
266
+ // Insert a document to delete
267
+ await index.upsert({
268
+ id: "doc-to-delete",
269
+ content: "Delete me",
270
+ vector: vec,
271
+ category: "deletable",
272
+ });
273
+ // Verify it exists
274
+ let hits = await index.query({
275
+ query: [{ vector: vec }],
276
+ topK: 10,
277
+ filter: { id: "doc-to-delete" },
278
+ });
279
+ expect(hits.some((h) => h.id === "doc-to-delete")).toBe(true);
280
+ // Delete it
281
+ await index.delete("doc-to-delete");
282
+ // Verify it's gone
283
+ hits = await index.query({
284
+ query: [{ vector: vec }],
285
+ topK: 10,
286
+ filter: { id: "doc-to-delete" },
287
+ });
288
+ expect(hits.some((h) => h.id === "doc-to-delete")).toBe(false);
289
+ });
290
+ });
291
+ describe("delete (multiple)", () => {
292
+ it("deletes multiple documents by ids", async () => {
293
+ const vec = new Array(384).fill(0.6);
294
+ const index = tpuf.index(testIndexId);
295
+ // Insert documents to delete
296
+ await index.upsert([
297
+ {
298
+ id: "bulk-del-1",
299
+ content: "Bulk delete 1",
300
+ vector: vec,
301
+ category: "bulk-delete",
302
+ },
303
+ {
304
+ id: "bulk-del-2",
305
+ content: "Bulk delete 2",
306
+ vector: vec,
307
+ category: "bulk-delete",
308
+ },
309
+ ]);
310
+ // Verify they exist
311
+ let hits = await index.query({
312
+ query: [{ vector: vec }],
313
+ topK: 10,
314
+ filter: { category: "bulk-delete" },
315
+ });
316
+ expect(hits.length).toBeGreaterThanOrEqual(2);
317
+ // Delete by ids
318
+ await index.delete(["bulk-del-1", "bulk-del-2"]);
319
+ // Verify they're gone
320
+ hits = await index.query({
321
+ query: [{ vector: vec }],
322
+ topK: 10,
323
+ filter: { category: "bulk-delete" },
324
+ });
325
+ expect(hits.some((h) => h.id === "bulk-del-1")).toBe(false);
326
+ expect(hits.some((h) => h.id === "bulk-del-2")).toBe(false);
327
+ });
328
+ });
329
+ });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Index and handle lifecycle edge case integration tests.
3
+ *
4
+ * Tests edge cases for index lifecycle operations and document handling
5
+ * against real Turbopuffer API.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=lifecycle.integration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.integration.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/integration/lifecycle.integration.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}