@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.
- package/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-check-types.log +60 -0
- package/CHANGELOG.md +33 -0
- package/LICENSE +201 -0
- package/README.md +60 -0
- package/dist/__tests__/convert.test.d.ts +2 -0
- package/dist/__tests__/convert.test.d.ts.map +1 -0
- package/dist/__tests__/convert.test.js +346 -0
- package/dist/__tests__/filter.test.d.ts +8 -0
- package/dist/__tests__/filter.test.d.ts.map +1 -0
- package/dist/__tests__/filter.test.js +649 -0
- package/dist/__tests__/filters.integration.test.d.ts +8 -0
- package/dist/__tests__/filters.integration.test.d.ts.map +1 -0
- package/dist/__tests__/filters.integration.test.js +502 -0
- package/dist/__tests__/integration/filters.integration.test.d.ts +8 -0
- package/dist/__tests__/integration/filters.integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/filters.integration.test.js +475 -0
- package/dist/__tests__/integration/integration.test.d.ts +2 -0
- package/dist/__tests__/integration/integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/integration.test.js +329 -0
- package/dist/__tests__/integration/lifecycle.integration.test.d.ts +8 -0
- package/dist/__tests__/integration/lifecycle.integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/lifecycle.integration.test.js +370 -0
- package/dist/__tests__/integration/memory.integration.test.d.ts +2 -0
- package/dist/__tests__/integration/memory.integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/memory.integration.test.js +287 -0
- package/dist/__tests__/integration/query.integration.test.d.ts +8 -0
- package/dist/__tests__/integration/query.integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/query.integration.test.js +385 -0
- package/dist/__tests__/integration.test.d.ts +2 -0
- package/dist/__tests__/integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration.test.js +343 -0
- package/dist/__tests__/lifecycle.integration.test.d.ts +8 -0
- package/dist/__tests__/lifecycle.integration.test.d.ts.map +1 -0
- package/dist/__tests__/lifecycle.integration.test.js +385 -0
- package/dist/__tests__/query.integration.test.d.ts +8 -0
- package/dist/__tests__/query.integration.test.d.ts.map +1 -0
- package/dist/__tests__/query.integration.test.js +423 -0
- package/dist/__tests__/query.test.d.ts +8 -0
- package/dist/__tests__/query.test.d.ts.map +1 -0
- package/dist/__tests__/query.test.js +472 -0
- package/dist/convert/document.d.ts +20 -0
- package/dist/convert/document.d.ts.map +1 -0
- package/dist/convert/document.js +72 -0
- package/dist/convert/filter.d.ts +15 -0
- package/dist/convert/filter.d.ts.map +1 -0
- package/dist/convert/filter.js +109 -0
- package/dist/convert/index.d.ts +8 -0
- package/dist/convert/index.d.ts.map +1 -0
- package/dist/convert/index.js +7 -0
- package/dist/convert/query.d.ts +22 -0
- package/dist/convert/query.d.ts.map +1 -0
- package/dist/convert/query.js +111 -0
- package/dist/convert/schema.d.ts +39 -0
- package/dist/convert/schema.d.ts.map +1 -0
- package/dist/convert/schema.js +124 -0
- package/dist/convert.d.ts +68 -0
- package/dist/convert.d.ts.map +1 -0
- package/dist/convert.js +333 -0
- package/dist/handle.d.ts +34 -0
- package/dist/handle.d.ts.map +1 -0
- package/dist/handle.js +72 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/search.d.ts +85 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +167 -0
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +57 -0
- package/src/__tests__/convert.test.ts +425 -0
- package/src/__tests__/filter.test.ts +730 -0
- package/src/__tests__/integration/filters.integration.test.ts +558 -0
- package/src/__tests__/integration/integration.test.ts +399 -0
- package/src/__tests__/integration/lifecycle.integration.test.ts +464 -0
- package/src/__tests__/integration/memory.integration.test.ts +353 -0
- package/src/__tests__/integration/query.integration.test.ts +471 -0
- package/src/__tests__/query.test.ts +636 -0
- package/src/convert/document.ts +95 -0
- package/src/convert/filter.ts +123 -0
- package/src/convert/index.ts +8 -0
- package/src/convert/query.ts +151 -0
- package/src/convert/schema.ts +163 -0
- package/src/handle.ts +104 -0
- package/src/index.ts +31 -0
- package/src/search.ts +207 -0
- package/src/types.ts +14 -0
- package/tsconfig.json +13 -0
- package/vitest.config.ts +15 -0
package/src/search.ts
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turbopuffer SearchIndex implementation.
|
|
3
|
+
*
|
|
4
|
+
* Adapts the Turbopuffer vector database to the kernl SearchIndex interface.
|
|
5
|
+
* Turbopuffer uses "namespaces" as the equivalent of our "indexes".
|
|
6
|
+
*
|
|
7
|
+
* @see https://turbopuffer.com/docs
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import Turbopuffer from "@turbopuffer/turbopuffer";
|
|
11
|
+
|
|
12
|
+
import type {
|
|
13
|
+
SearchIndex,
|
|
14
|
+
IndexHandle,
|
|
15
|
+
NewIndexParams,
|
|
16
|
+
ListIndexesParams,
|
|
17
|
+
IndexSummary,
|
|
18
|
+
IndexStats,
|
|
19
|
+
UnknownDocument,
|
|
20
|
+
SearchCapabilities,
|
|
21
|
+
} from "@kernl-sdk/retrieval";
|
|
22
|
+
import { CursorPage, type CursorPageResponse } from "@kernl-sdk/shared";
|
|
23
|
+
|
|
24
|
+
import { TurbopufferConfig } from "./types";
|
|
25
|
+
import { TurbopufferIndexHandle } from "./handle";
|
|
26
|
+
import { INDEX_SCHEMA, SIMILARITY } from "./convert";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Turbopuffer search index adapter.
|
|
30
|
+
*
|
|
31
|
+
* Implements the kernl SearchIndex interface backed by Turbopuffer's
|
|
32
|
+
* vector database. Supports vector search, full-text search (BM25),
|
|
33
|
+
* and hybrid queries.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* const tpuf = turbopuffer({
|
|
38
|
+
* apiKey: "your-api-key",
|
|
39
|
+
* region: "us-east-1",
|
|
40
|
+
* });
|
|
41
|
+
*
|
|
42
|
+
* const docs = tpuf.index("my-index");
|
|
43
|
+
*
|
|
44
|
+
* await docs.upsert({
|
|
45
|
+
* id: "doc-1",
|
|
46
|
+
* text: "Hello world",
|
|
47
|
+
* vector: [0.1, 0.2, ...],
|
|
48
|
+
* });
|
|
49
|
+
*
|
|
50
|
+
* const hits = await docs.query({
|
|
51
|
+
* query: [{ vector: [0.1, 0.2, ...] }],
|
|
52
|
+
* topK: 10,
|
|
53
|
+
* });
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export class TurbopufferSearchIndex implements SearchIndex {
|
|
57
|
+
readonly id = "turbopuffer";
|
|
58
|
+
|
|
59
|
+
private readonly client: Turbopuffer;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create a new Turbopuffer search index.
|
|
63
|
+
*/
|
|
64
|
+
constructor(config: TurbopufferConfig) {
|
|
65
|
+
this.client = new Turbopuffer({
|
|
66
|
+
apiKey: config.apiKey,
|
|
67
|
+
region: config.region,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* ---- Index lifecycle ---- */
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Create a new index (namespace) in Turbopuffer.
|
|
75
|
+
*/
|
|
76
|
+
async createIndex(params: NewIndexParams): Promise<void> {
|
|
77
|
+
const ns = this.client.namespace(params.id);
|
|
78
|
+
const schema = INDEX_SCHEMA.encode(params.schema);
|
|
79
|
+
|
|
80
|
+
const placeholder = "__kernl_placeholder__";
|
|
81
|
+
const vfield = params.schema.vector;
|
|
82
|
+
const vector = vfield?.type === "vector";
|
|
83
|
+
|
|
84
|
+
// implicit creation via first write
|
|
85
|
+
await ns.write({
|
|
86
|
+
schema,
|
|
87
|
+
distance_metric: vector
|
|
88
|
+
? SIMILARITY.encode(vfield.similarity)
|
|
89
|
+
: undefined,
|
|
90
|
+
upsert_rows: [
|
|
91
|
+
{
|
|
92
|
+
id: placeholder,
|
|
93
|
+
vector: vector ? new Array(vfield.dimensions).fill(0) : undefined,
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
await ns.write({ deletes: [placeholder] });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* List indexes (namespaces) with optional pagination and prefix filtering.
|
|
103
|
+
*/
|
|
104
|
+
async listIndexes(
|
|
105
|
+
params: ListIndexesParams = {},
|
|
106
|
+
): Promise<CursorPage<IndexSummary, ListIndexesParams>> {
|
|
107
|
+
const loader = async (
|
|
108
|
+
p: ListIndexesParams,
|
|
109
|
+
): Promise<CursorPageResponse<IndexSummary>> => {
|
|
110
|
+
const page = await this.client.namespaces({
|
|
111
|
+
prefix: p.prefix,
|
|
112
|
+
cursor: p.cursor,
|
|
113
|
+
page_size: p.limit,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const indexes: IndexSummary[] = [];
|
|
117
|
+
for await (const ns of page) {
|
|
118
|
+
indexes.push({ id: ns.id });
|
|
119
|
+
if (p.limit !== undefined && indexes.length >= p.limit) {
|
|
120
|
+
break; // respect limit if specified
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
data: indexes,
|
|
126
|
+
next: page.next_cursor ?? null,
|
|
127
|
+
last: !page.next_cursor,
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const response = await loader(params);
|
|
132
|
+
|
|
133
|
+
return new CursorPage({
|
|
134
|
+
params,
|
|
135
|
+
response,
|
|
136
|
+
loader,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get metadata and statistics about an index (namespace).
|
|
142
|
+
*/
|
|
143
|
+
async describeIndex(id: string): Promise<IndexStats> {
|
|
144
|
+
const ns = this.client.namespace(id);
|
|
145
|
+
const metadata = await ns.metadata();
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
id,
|
|
149
|
+
count: metadata.approx_row_count,
|
|
150
|
+
sizeb: metadata.approx_logical_bytes,
|
|
151
|
+
status: "ready",
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Delete an index (namespace) and all its documents.
|
|
157
|
+
*/
|
|
158
|
+
async deleteIndex(id: string): Promise<void> {
|
|
159
|
+
const ns = this.client.namespace(id);
|
|
160
|
+
await ns.deleteAll();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* ---- Index handle ---- */
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get a handle for operating on a specific index.
|
|
167
|
+
*/
|
|
168
|
+
index<TDocument = UnknownDocument>(id: string): IndexHandle<TDocument> {
|
|
169
|
+
return new TurbopufferIndexHandle<TDocument>(this.client, id);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Bind an existing resource as an index.
|
|
174
|
+
*
|
|
175
|
+
* Not supported by Turbopuffer - indexes are created implicitly.
|
|
176
|
+
*/
|
|
177
|
+
async bindIndex(_id: string, _config: unknown): Promise<void> {
|
|
178
|
+
throw new Error("bindIndex not supported by Turbopuffer");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* ---- Utility ---- */
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Warm/preload an index for faster queries.
|
|
185
|
+
*/
|
|
186
|
+
async warm(id: string): Promise<void> {
|
|
187
|
+
const ns = this.client.namespace(id);
|
|
188
|
+
await ns.hintCacheWarm();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Turbopuffer capabilities.
|
|
193
|
+
*
|
|
194
|
+
* Note: Turbopuffer supports text (BM25) and vector (ANN) separately,
|
|
195
|
+
* but not hybrid fusion (text + vector in the same query).
|
|
196
|
+
*/
|
|
197
|
+
capabilities(): SearchCapabilities {
|
|
198
|
+
return {
|
|
199
|
+
modes: new Set(["vector", "text"]),
|
|
200
|
+
multiSignal: false, // Can't mix text + vector signals
|
|
201
|
+
multiVector: false, // Only one ANN query per request
|
|
202
|
+
multiText: true, // Can fuse multiple BM25 queries
|
|
203
|
+
filters: true,
|
|
204
|
+
orderBy: true,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
}
|
package/src/types.ts
ADDED
package/tsconfig.json
ADDED
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineConfig } from "vitest/config";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
test: {
|
|
6
|
+
globals: true,
|
|
7
|
+
environment: "node",
|
|
8
|
+
exclude: ["**/node_modules/**", "**/dist/**"],
|
|
9
|
+
},
|
|
10
|
+
resolve: {
|
|
11
|
+
alias: {
|
|
12
|
+
"@": path.resolve(__dirname, "./src"),
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
});
|