@vectororm/adapter-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/README.md ADDED
@@ -0,0 +1,134 @@
1
+ # @vectororm/adapter-turbopuffer
2
+
3
+ [![npm](https://img.shields.io/npm/v/@vectororm/adapter-turbopuffer)](https://www.npmjs.com/package/@vectororm/adapter-turbopuffer)
4
+ [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
5
+
6
+ [Turbopuffer](https://turbopuffer.com/) adapter for [Glyph VectorORM](https://github.com/aviramroi/VectorORM).
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ npm install @vectororm/adapter-turbopuffer @vectororm/core
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ ```typescript
17
+ import { TurbopufferAdapter } from '@vectororm/adapter-turbopuffer';
18
+
19
+ // Create adapter
20
+ const adapter = new TurbopufferAdapter({
21
+ apiKey: process.env.TURBOPUFFER_API_KEY || 'your-api-key',
22
+ // Optional: custom base URL
23
+ baseUrl: 'https://api.turbopuffer.com',
24
+ });
25
+
26
+ // Connect
27
+ await adapter.connect();
28
+
29
+ // Create a collection (namespace)
30
+ await adapter.createCollection('my-vectors', 128, 'cosine');
31
+
32
+ // Upsert vectors
33
+ await adapter.upsert('my-vectors', [
34
+ {
35
+ id: 'vec1',
36
+ embedding: [0.1, 0.2, ...],
37
+ metadata: { title: 'Document 1', category: 'tech' }
38
+ }
39
+ ]);
40
+
41
+ // Search
42
+ const results = await adapter.search('my-vectors', queryVector, {
43
+ topK: 10,
44
+ filter: { field: 'category', op: 'eq', value: 'tech' }
45
+ });
46
+
47
+ // Disconnect
48
+ await adapter.disconnect();
49
+ ```
50
+
51
+ ## Configuration
52
+
53
+ ### TurbopufferConfig
54
+
55
+ - `apiKey` (required): Your Turbopuffer API key
56
+ - `baseUrl` (optional): Custom API base URL (defaults to `https://api.turbopuffer.com`)
57
+
58
+ ## Features
59
+
60
+ - Full CRUD operations on vectors
61
+ - Metadata filtering with compound AND/OR filters
62
+ - Vector similarity search
63
+ - Metadata updates
64
+ - Batch operations
65
+ - Async iteration over collections
66
+ - Multiple distance metrics (cosine, euclidean)
67
+
68
+ ## Supported Operations
69
+
70
+ ### Connection Management
71
+ - `connect()` - Establish connection to Turbopuffer
72
+ - `disconnect()` - Close connection
73
+ - `isConnected()` - Check connection status
74
+
75
+ ### Collection Management
76
+ - `createCollection(name, dimension, metric)` - Create namespace
77
+ - `deleteCollection(name)` - Delete namespace
78
+ - `collectionExists(name)` - Check if namespace exists
79
+ - `getCollectionStats(name)` - Get vector count and stats
80
+
81
+ ### Vector Operations
82
+ - `upsert(collection, records)` - Insert or update vectors
83
+ - `fetch(collection, ids)` - Fetch vectors by IDs
84
+ - `delete(collection, ids)` - Delete vectors by IDs
85
+ - `search(collection, queryVector, options)` - Vector similarity search
86
+ - `updateMetadata(collection, updates)` - Update vector metadata
87
+ - `iterate(collection, options)` - Async iteration over vectors
88
+
89
+ ### Filter Translation
90
+ - Converts UniversalFilter to Turbopuffer filter format
91
+ - Supports: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`
92
+ - Supports compound `and`/`or` filters
93
+ - Supports nested filter combinations
94
+
95
+ ## Implementation Notes
96
+
97
+ ### REST API
98
+ This adapter uses Turbopuffer's REST API directly with `fetch` (no SDK dependency) to avoid Node.js version constraints. The implementation is compatible with Node.js 18+.
99
+
100
+ ### Namespaces
101
+ Turbopuffer uses "namespaces" instead of "collections". This adapter transparently maps collection operations to namespace operations.
102
+
103
+ ### Distance Metrics
104
+ - `cosine` → `cosine_distance`
105
+ - `euclidean` → `euclidean_squared`
106
+
107
+ ### Pagination
108
+ Turbopuffer uses attribute-based pagination. The `iterate()` method paginates by ID using greater-than filters.
109
+
110
+ ### Fetch Operation
111
+ Turbopuffer doesn't have a direct fetch-by-ID endpoint. The adapter uses filtered queries to implement this functionality.
112
+
113
+ ## Limitations
114
+
115
+ See [TECH_DEBT.md](./TECH_DEBT.md) for known limitations and future enhancements.
116
+
117
+ ## Testing
118
+
119
+ ```bash
120
+ # Run unit tests
121
+ npm test
122
+
123
+ # Run tests in watch mode
124
+ npm run test:watch
125
+ ```
126
+
127
+ ## Documentation
128
+
129
+ - [API Guide](https://github.com/aviramroi/VectorORM/blob/main/docs/guide.md)
130
+ - [Full Project](https://github.com/aviramroi/VectorORM)
131
+
132
+ ## License
133
+
134
+ Apache-2.0
package/dist/index.cjs ADDED
@@ -0,0 +1,427 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ TurbopufferAdapter: () => TurbopufferAdapter
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/turbopuffer-adapter.ts
28
+ var import_core = require("@vectororm/core");
29
+ var TurbopufferAdapter = class extends import_core.VectorDBAdapter {
30
+ config;
31
+ baseUrl;
32
+ connected = false;
33
+ namespaceMetrics = /* @__PURE__ */ new Map();
34
+ constructor(config) {
35
+ super();
36
+ if (!config.apiKey) {
37
+ throw new Error(
38
+ "TurbopufferAdapter: apiKey is required in config or TURBOPUFFER_API_KEY environment variable"
39
+ );
40
+ }
41
+ this.config = config;
42
+ this.baseUrl = config.baseUrl || "https://api.turbopuffer.com";
43
+ }
44
+ // ============================================================================
45
+ // HTTP HELPERS
46
+ // ============================================================================
47
+ async request(method, path, body) {
48
+ const url = `${this.baseUrl}${path}`;
49
+ const headers = {
50
+ "Authorization": `Bearer ${this.config.apiKey}`,
51
+ "Content-Type": "application/json"
52
+ };
53
+ const response = await fetch(url, {
54
+ method,
55
+ headers,
56
+ body: body ? JSON.stringify(body) : void 0
57
+ });
58
+ if (!response.ok) {
59
+ const errorText = await response.text();
60
+ let errorMessage = `Turbopuffer API error: ${response.status} ${response.statusText}`;
61
+ try {
62
+ const errorData = JSON.parse(errorText);
63
+ if (errorData.error) {
64
+ errorMessage += ` - ${errorData.error}`;
65
+ }
66
+ } catch {
67
+ if (errorText) {
68
+ errorMessage += ` - ${errorText}`;
69
+ }
70
+ }
71
+ throw new Error(errorMessage);
72
+ }
73
+ const text = await response.text();
74
+ return text ? JSON.parse(text) : null;
75
+ }
76
+ // ============================================================================
77
+ // CONNECTION MANAGEMENT
78
+ // ============================================================================
79
+ async connect() {
80
+ try {
81
+ await this.request("GET", "/v2/namespaces");
82
+ this.connected = true;
83
+ } catch (error) {
84
+ throw new Error(
85
+ `Turbopuffer connection failed: ${error instanceof Error ? error.message : String(error)}`,
86
+ { cause: error }
87
+ );
88
+ }
89
+ }
90
+ async disconnect() {
91
+ this.connected = false;
92
+ this.namespaceMetrics.clear();
93
+ }
94
+ async isConnected() {
95
+ return this.connected;
96
+ }
97
+ // ============================================================================
98
+ // COLLECTION MANAGEMENT
99
+ // ============================================================================
100
+ async createCollection(name, dimension, metric = "cosine") {
101
+ if (!this.connected) {
102
+ throw new Error("Not connected. Call connect() first.");
103
+ }
104
+ try {
105
+ const distanceMetric = metric === "euclidean" ? "euclidean_squared" : "cosine_distance";
106
+ this.namespaceMetrics.set(name, { dimension, metric });
107
+ await this.request("POST", `/v2/namespaces/${name}`, {
108
+ upsert_rows: [{
109
+ id: "__init__",
110
+ vector: new Array(dimension).fill(0),
111
+ attributes: { __init__: true }
112
+ }],
113
+ distance_metric: distanceMetric
114
+ });
115
+ await this.request("POST", `/v2/namespaces/${name}`, {
116
+ deletes: ["__init__"]
117
+ });
118
+ } catch (error) {
119
+ throw new Error(
120
+ `Failed to create Turbopuffer namespace ${name}: ${error instanceof Error ? error.message : String(error)}`,
121
+ { cause: error }
122
+ );
123
+ }
124
+ }
125
+ async deleteCollection(name) {
126
+ if (!this.connected) {
127
+ throw new Error("Not connected. Call connect() first.");
128
+ }
129
+ try {
130
+ await this.request("DELETE", `/v2/namespaces/${name}`);
131
+ this.namespaceMetrics.delete(name);
132
+ } catch (error) {
133
+ throw new Error(
134
+ `Failed to delete Turbopuffer namespace ${name}: ${error instanceof Error ? error.message : String(error)}`,
135
+ { cause: error }
136
+ );
137
+ }
138
+ }
139
+ async collectionExists(name) {
140
+ if (!this.connected) {
141
+ throw new Error("Not connected. Call connect() first.");
142
+ }
143
+ try {
144
+ const response = await this.request("GET", "/v2/namespaces");
145
+ const namespaces = response.namespaces || [];
146
+ return namespaces.some((ns) => ns.name === name);
147
+ } catch (error) {
148
+ throw new Error(
149
+ `Failed to check if Turbopuffer namespace ${name} exists: ${error instanceof Error ? error.message : String(error)}`,
150
+ { cause: error }
151
+ );
152
+ }
153
+ }
154
+ async getCollectionStats(name) {
155
+ if (!this.connected) {
156
+ throw new Error("Not connected. Call connect() first.");
157
+ }
158
+ try {
159
+ const result = await this.request("POST", `/v2/namespaces/${name}/query`, {
160
+ top_k: 0,
161
+ aggregate_by: { Count: "*" }
162
+ });
163
+ const vectorCount = result.aggregations?.Count ?? 0;
164
+ let dimension = this.namespaceMetrics.get(name)?.dimension ?? 0;
165
+ let metric = this.namespaceMetrics.get(name)?.metric ?? "cosine";
166
+ if (dimension === 0 && vectorCount > 0) {
167
+ const sample = await this.request("POST", `/v2/namespaces/${name}/query`, {
168
+ top_k: 1,
169
+ include_vectors: true
170
+ });
171
+ if (sample.rows && sample.rows.length > 0) {
172
+ dimension = sample.rows[0].vector?.length ?? 0;
173
+ }
174
+ }
175
+ return {
176
+ vectorCount: typeof vectorCount === "number" ? vectorCount : 0,
177
+ dimension,
178
+ metric
179
+ };
180
+ } catch (error) {
181
+ throw new Error(
182
+ `Failed to get Turbopuffer namespace stats for ${name}: ${error instanceof Error ? error.message : String(error)}`,
183
+ { cause: error }
184
+ );
185
+ }
186
+ }
187
+ // ============================================================================
188
+ // VECTOR OPERATIONS
189
+ // ============================================================================
190
+ async upsert(collection, records) {
191
+ if (!this.connected) {
192
+ throw new Error("Not connected. Call connect() first.");
193
+ }
194
+ try {
195
+ const storedMetric = this.namespaceMetrics.get(collection);
196
+ const distanceMetric = storedMetric?.metric === "euclidean" ? "euclidean_squared" : "cosine_distance";
197
+ const rows = records.map((record) => ({
198
+ id: record.id,
199
+ vector: record.embedding,
200
+ ...record.metadata
201
+ }));
202
+ await this.request("POST", `/v2/namespaces/${collection}`, {
203
+ upsert_rows: rows,
204
+ distance_metric: distanceMetric
205
+ });
206
+ } catch (error) {
207
+ throw new Error(
208
+ `Failed to upsert vectors to Turbopuffer namespace ${collection}: ${error instanceof Error ? error.message : String(error)}`,
209
+ { cause: error }
210
+ );
211
+ }
212
+ }
213
+ async fetch(collection, ids) {
214
+ if (!this.connected) {
215
+ throw new Error("Not connected. Call connect() first.");
216
+ }
217
+ try {
218
+ const results = [];
219
+ if (ids.length > 0) {
220
+ const result = await this.request("POST", `/v2/namespaces/${collection}/query`, {
221
+ top_k: ids.length,
222
+ filters: ["id", "In", ids],
223
+ include_vectors: true
224
+ });
225
+ if (result.rows) {
226
+ for (const row of result.rows) {
227
+ const { id, vector, ...metadata } = row;
228
+ results.push({
229
+ id: String(id),
230
+ embedding: vector || [],
231
+ metadata
232
+ });
233
+ }
234
+ }
235
+ }
236
+ return results;
237
+ } catch (error) {
238
+ throw new Error(
239
+ `Failed to fetch vectors from Turbopuffer namespace ${collection}: ${error instanceof Error ? error.message : String(error)}`,
240
+ { cause: error }
241
+ );
242
+ }
243
+ }
244
+ async delete(collection, ids) {
245
+ if (!this.connected) {
246
+ throw new Error("Not connected. Call connect() first.");
247
+ }
248
+ try {
249
+ await this.request("POST", `/v2/namespaces/${collection}`, {
250
+ deletes: ids
251
+ });
252
+ } catch (error) {
253
+ throw new Error(
254
+ `Failed to delete vectors from Turbopuffer namespace ${collection}: ${error instanceof Error ? error.message : String(error)}`,
255
+ { cause: error }
256
+ );
257
+ }
258
+ }
259
+ // ============================================================================
260
+ // METADATA OPERATIONS
261
+ // ============================================================================
262
+ async updateMetadata(collection, updates) {
263
+ if (!this.connected) {
264
+ throw new Error("Not connected. Call connect() first.");
265
+ }
266
+ try {
267
+ for (const update of updates) {
268
+ const existing = await this.fetch(collection, [update.id]);
269
+ if (existing.length === 0) {
270
+ throw new Error(`Vector ${update.id} not found for metadata update`);
271
+ }
272
+ const merged = { ...existing[0].metadata, ...update.metadata };
273
+ await this.request("POST", `/v2/namespaces/${collection}`, {
274
+ upsert_rows: [{
275
+ id: update.id,
276
+ vector: existing[0].embedding,
277
+ ...merged
278
+ }]
279
+ });
280
+ }
281
+ } catch (error) {
282
+ throw new Error(
283
+ `Failed to update metadata in Turbopuffer namespace ${collection}: ${error instanceof Error ? error.message : String(error)}`,
284
+ { cause: error }
285
+ );
286
+ }
287
+ }
288
+ // ============================================================================
289
+ // SEARCH OPERATIONS
290
+ // ============================================================================
291
+ async search(collection, queryVector, options) {
292
+ if (!this.connected) {
293
+ throw new Error("Not connected. Call connect() first.");
294
+ }
295
+ try {
296
+ const turbopufferFilter = options?.filter ? this.translateFilter(options.filter) : void 0;
297
+ const result = await this.request("POST", `/v2/namespaces/${collection}/query`, {
298
+ rank_by: ["vector", "ANN", queryVector],
299
+ top_k: options?.topK || 10,
300
+ filters: turbopufferFilter,
301
+ include_vectors: options?.includeValues || false
302
+ });
303
+ const records = [];
304
+ if (result.rows) {
305
+ for (const row of result.rows) {
306
+ const { id, vector, $dist, ...metadata } = row;
307
+ const record = {
308
+ id: String(id),
309
+ embedding: options?.includeValues ? vector || [] : [],
310
+ metadata: options?.includeMetadata !== false ? metadata : {},
311
+ score: $dist !== void 0 ? 1 / (1 + $dist) : void 0
312
+ };
313
+ records.push(record);
314
+ }
315
+ }
316
+ return { records };
317
+ } catch (error) {
318
+ throw new Error(
319
+ `Failed to search Turbopuffer namespace ${collection}: ${error instanceof Error ? error.message : String(error)}`,
320
+ { cause: error }
321
+ );
322
+ }
323
+ }
324
+ // ============================================================================
325
+ // FILTER TRANSLATION
326
+ // ============================================================================
327
+ translateFilter(filter) {
328
+ if ("and" in filter) {
329
+ const conditions = filter.and;
330
+ const translated = conditions.map((c) => this.translateFilter(c));
331
+ return ["And", translated];
332
+ }
333
+ if ("or" in filter) {
334
+ const conditions = filter.or;
335
+ const translated = conditions.map((c) => this.translateFilter(c));
336
+ return ["Or", translated];
337
+ }
338
+ const { field, op, value } = filter;
339
+ const operatorMap = {
340
+ eq: "Eq",
341
+ ne: "Neq",
342
+ gt: "Gt",
343
+ gte: "Gte",
344
+ lt: "Lt",
345
+ lte: "Lte",
346
+ in: "In",
347
+ nin: "Nin"
348
+ };
349
+ const turbopufferOp = operatorMap[op];
350
+ if (!turbopufferOp) {
351
+ throw new Error(
352
+ `Unsupported filter operator: ${op}`,
353
+ { cause: { filter } }
354
+ );
355
+ }
356
+ return [field, turbopufferOp, value];
357
+ }
358
+ // ============================================================================
359
+ // ITERATION
360
+ // ============================================================================
361
+ async *iterate(collection, options) {
362
+ if (!this.connected) {
363
+ throw new Error("Not connected. Call connect() first.");
364
+ }
365
+ try {
366
+ const batchSize = options?.batchSize || 100;
367
+ const turbopufferFilter = options?.filter ? this.translateFilter(options.filter) : void 0;
368
+ let lastId = null;
369
+ let hasMore = true;
370
+ while (hasMore) {
371
+ let filters = turbopufferFilter;
372
+ if (lastId !== null) {
373
+ const paginationFilter = ["id", "Gt", lastId];
374
+ if (filters) {
375
+ filters = ["And", [filters, paginationFilter]];
376
+ } else {
377
+ filters = paginationFilter;
378
+ }
379
+ }
380
+ const result = await this.request("POST", `/v2/namespaces/${collection}/query`, {
381
+ top_k: batchSize,
382
+ filters,
383
+ include_vectors: true,
384
+ rank_by: ["id", "Asc"]
385
+ });
386
+ if (result.rows && result.rows.length > 0) {
387
+ const records = [];
388
+ for (const row of result.rows) {
389
+ const { id, vector, ...metadata } = row;
390
+ records.push({
391
+ id: String(id),
392
+ embedding: vector || [],
393
+ metadata
394
+ });
395
+ lastId = String(id);
396
+ }
397
+ yield records;
398
+ hasMore = result.rows.length === batchSize;
399
+ } else {
400
+ hasMore = false;
401
+ }
402
+ }
403
+ } catch (error) {
404
+ throw new Error(
405
+ `Failed to iterate Turbopuffer namespace ${collection}: ${error instanceof Error ? error.message : String(error)}`,
406
+ { cause: error }
407
+ );
408
+ }
409
+ }
410
+ // ============================================================================
411
+ // CAPABILITY FLAGS
412
+ // ============================================================================
413
+ supportsMetadataUpdate() {
414
+ return true;
415
+ }
416
+ supportsFiltering() {
417
+ return true;
418
+ }
419
+ supportsBatchOperations() {
420
+ return true;
421
+ }
422
+ };
423
+ // Annotate the CommonJS export names for ESM import in node:
424
+ 0 && (module.exports = {
425
+ TurbopufferAdapter
426
+ });
427
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/turbopuffer-adapter.ts"],"sourcesContent":["// Turbopuffer adapter exports\nexport { TurbopufferAdapter } from './turbopuffer-adapter';\nexport type { TurbopufferConfig } from './types';\n","import {\n VectorDBAdapter,\n type VectorRecord,\n type SearchResult,\n type UniversalFilter,\n type CollectionStats,\n type MetadataUpdate,\n type DistanceMetric,\n} from '@vectororm/core';\nimport type { TurbopufferConfig } from './types.js';\n\n/**\n * TurbopufferAdapter implements VectorDBAdapter for Turbopuffer vector database.\n *\n * Uses REST API with fetch (no SDK dependency to avoid Node version constraints).\n * Supports all VectorORM features including CRUD operations, filtering,\n * and metadata updates.\n */\nexport class TurbopufferAdapter extends VectorDBAdapter {\n private config: TurbopufferConfig;\n private baseUrl: string;\n private connected: boolean = false;\n private namespaceMetrics: Map<string, { dimension: number; metric: DistanceMetric }> = new Map();\n\n constructor(config: TurbopufferConfig) {\n super();\n\n // Validate required config\n if (!config.apiKey) {\n throw new Error(\n 'TurbopufferAdapter: apiKey is required in config or TURBOPUFFER_API_KEY environment variable'\n );\n }\n\n this.config = config;\n this.baseUrl = config.baseUrl || 'https://api.turbopuffer.com';\n }\n\n // ============================================================================\n // HTTP HELPERS\n // ============================================================================\n\n private async request(\n method: string,\n path: string,\n body?: any\n ): Promise<any> {\n const url = `${this.baseUrl}${path}`;\n\n const headers: Record<string, string> = {\n 'Authorization': `Bearer ${this.config.apiKey}`,\n 'Content-Type': 'application/json',\n };\n\n const response = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n let errorMessage = `Turbopuffer API error: ${response.status} ${response.statusText}`;\n\n try {\n const errorData = JSON.parse(errorText);\n if (errorData.error) {\n errorMessage += ` - ${errorData.error}`;\n }\n } catch {\n if (errorText) {\n errorMessage += ` - ${errorText}`;\n }\n }\n\n throw new Error(errorMessage);\n }\n\n const text = await response.text();\n return text ? JSON.parse(text) : null;\n }\n\n // ============================================================================\n // CONNECTION MANAGEMENT\n // ============================================================================\n\n async connect(): Promise<void> {\n try {\n // Verify connection by listing namespaces\n await this.request('GET', '/v2/namespaces');\n this.connected = true;\n } catch (error) {\n throw new Error(\n `Turbopuffer connection failed: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error }\n );\n }\n }\n\n async disconnect(): Promise<void> {\n this.connected = false;\n this.namespaceMetrics.clear();\n }\n\n async isConnected(): Promise<boolean> {\n return this.connected;\n }\n\n // ============================================================================\n // COLLECTION MANAGEMENT\n // ============================================================================\n\n async createCollection(\n name: string,\n dimension: number,\n metric: DistanceMetric = 'cosine'\n ): Promise<void> {\n if (!this.connected) {\n throw new Error('Not connected. Call connect() first.');\n }\n\n try {\n // Turbopuffer creates namespaces implicitly on first upsert\n // We'll store metadata locally and create with a dummy vector\n const distanceMetric = metric === 'euclidean' ? 'euclidean_squared' : 'cosine_distance';\n\n // Store metrics for later use\n this.namespaceMetrics.set(name, { dimension, metric });\n\n // Create namespace with initial dummy vector to set schema\n await this.request('POST', `/v2/namespaces/${name}`, {\n upsert_rows: [{\n id: '__init__',\n vector: new Array(dimension).fill(0),\n attributes: { __init__: true }\n }],\n distance_metric: distanceMetric,\n });\n\n // Delete the initialization vector\n await this.request('POST', `/v2/namespaces/${name}`, {\n deletes: ['__init__'],\n });\n } catch (error) {\n throw new Error(\n `Failed to create Turbopuffer namespace ${name}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error }\n );\n }\n }\n\n async deleteCollection(name: string): Promise<void> {\n if (!this.connected) {\n throw new Error('Not connected. Call connect() first.');\n }\n\n try {\n await this.request('DELETE', `/v2/namespaces/${name}`);\n this.namespaceMetrics.delete(name);\n } catch (error) {\n throw new Error(\n `Failed to delete Turbopuffer namespace ${name}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error }\n );\n }\n }\n\n async collectionExists(name: string): Promise<boolean> {\n if (!this.connected) {\n throw new Error('Not connected. Call connect() first.');\n }\n\n try {\n const response = await this.request('GET', '/v2/namespaces');\n const namespaces = response.namespaces || [];\n return namespaces.some((ns: any) => ns.name === name);\n } catch (error) {\n throw new Error(\n `Failed to check if Turbopuffer namespace ${name} exists: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error }\n );\n }\n }\n\n async getCollectionStats(name: string): Promise<CollectionStats> {\n if (!this.connected) {\n throw new Error('Not connected. Call connect() first.');\n }\n\n try {\n // Use query with limit 0 to get aggregation stats\n const result = await this.request('POST', `/v2/namespaces/${name}/query`, {\n top_k: 0,\n aggregate_by: { Count: '*' },\n });\n\n const vectorCount = result.aggregations?.Count ?? 0;\n\n // Get dimension from stored metrics or estimate from a sample vector\n let dimension = this.namespaceMetrics.get(name)?.dimension ?? 0;\n let metric = this.namespaceMetrics.get(name)?.metric ?? 'cosine';\n\n if (dimension === 0 && vectorCount > 0) {\n // Query one vector to get dimension\n const sample = await this.request('POST', `/v2/namespaces/${name}/query`, {\n top_k: 1,\n include_vectors: true,\n });\n\n if (sample.rows && sample.rows.length > 0) {\n dimension = sample.rows[0].vector?.length ?? 0;\n }\n }\n\n return {\n vectorCount: typeof vectorCount === 'number' ? vectorCount : 0,\n dimension,\n metric,\n };\n } catch (error) {\n throw new Error(\n `Failed to get Turbopuffer namespace stats for ${name}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error }\n );\n }\n }\n\n // ============================================================================\n // VECTOR OPERATIONS\n // ============================================================================\n\n async upsert(collection: string, records: VectorRecord[]): Promise<void> {\n if (!this.connected) {\n throw new Error('Not connected. Call connect() first.');\n }\n\n try {\n // Get or infer distance metric\n const storedMetric = this.namespaceMetrics.get(collection);\n const distanceMetric = storedMetric?.metric === 'euclidean'\n ? 'euclidean_squared'\n : 'cosine_distance';\n\n // Convert VectorRecord[] to Turbopuffer format\n const rows = records.map((record) => ({\n id: record.id,\n vector: record.embedding,\n ...record.metadata,\n }));\n\n await this.request('POST', `/v2/namespaces/${collection}`, {\n upsert_rows: rows,\n distance_metric: distanceMetric,\n });\n } catch (error) {\n throw new Error(\n `Failed to upsert vectors to Turbopuffer namespace ${collection}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error }\n );\n }\n }\n\n async fetch(collection: string, ids: string[]): Promise<VectorRecord[]> {\n if (!this.connected) {\n throw new Error('Not connected. Call connect() first.');\n }\n\n try {\n // Turbopuffer doesn't have a direct fetch by IDs\n // We need to use query with filters for each ID\n const results: VectorRecord[] = [];\n\n // Query with In filter for multiple IDs\n if (ids.length > 0) {\n const result = await this.request('POST', `/v2/namespaces/${collection}/query`, {\n top_k: ids.length,\n filters: ['id', 'In', ids],\n include_vectors: true,\n });\n\n if (result.rows) {\n for (const row of result.rows) {\n const { id, vector, ...metadata } = row;\n results.push({\n id: String(id),\n embedding: vector || [],\n metadata,\n });\n }\n }\n }\n\n return results;\n } catch (error) {\n throw new Error(\n `Failed to fetch vectors from Turbopuffer namespace ${collection}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error }\n );\n }\n }\n\n async delete(collection: string, ids: string[]): Promise<void> {\n if (!this.connected) {\n throw new Error('Not connected. Call connect() first.');\n }\n\n try {\n await this.request('POST', `/v2/namespaces/${collection}`, {\n deletes: ids,\n });\n } catch (error) {\n throw new Error(\n `Failed to delete vectors from Turbopuffer namespace ${collection}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error }\n );\n }\n }\n\n // ============================================================================\n // METADATA OPERATIONS\n // ============================================================================\n\n async updateMetadata(\n collection: string,\n updates: MetadataUpdate[]\n ): Promise<void> {\n if (!this.connected) {\n throw new Error('Not connected. Call connect() first.');\n }\n\n try {\n // Turbopuffer supports patch operations\n // We'll fetch existing records and patch them\n for (const update of updates) {\n // Fetch the existing record\n const existing = await this.fetch(collection, [update.id]);\n\n if (existing.length === 0) {\n throw new Error(`Vector ${update.id} not found for metadata update`);\n }\n\n // Merge metadata\n const merged = { ...existing[0].metadata, ...update.metadata };\n\n // Upsert with updated metadata\n await this.request('POST', `/v2/namespaces/${collection}`, {\n upsert_rows: [{\n id: update.id,\n vector: existing[0].embedding,\n ...merged,\n }],\n });\n }\n } catch (error) {\n throw new Error(\n `Failed to update metadata in Turbopuffer namespace ${collection}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error }\n );\n }\n }\n\n // ============================================================================\n // SEARCH OPERATIONS\n // ============================================================================\n\n async search(\n collection: string,\n queryVector: number[],\n options?: {\n topK?: number;\n filter?: UniversalFilter;\n includeMetadata?: boolean;\n includeValues?: boolean;\n }\n ): Promise<SearchResult> {\n if (!this.connected) {\n throw new Error('Not connected. Call connect() first.');\n }\n\n try {\n const turbopufferFilter = options?.filter\n ? this.translateFilter(options.filter)\n : undefined;\n\n const result = await this.request('POST', `/v2/namespaces/${collection}/query`, {\n rank_by: ['vector', 'ANN', queryVector],\n top_k: options?.topK || 10,\n filters: turbopufferFilter,\n include_vectors: options?.includeValues || false,\n });\n\n const records: VectorRecord[] = [];\n\n if (result.rows) {\n for (const row of result.rows) {\n const { id, vector, $dist, ...metadata } = row;\n const record: VectorRecord = {\n id: String(id),\n embedding: options?.includeValues ? (vector || []) : [],\n metadata: options?.includeMetadata !== false ? metadata : {},\n score: $dist !== undefined ? (1 / (1 + $dist)) : undefined,\n };\n\n records.push(record);\n }\n }\n\n return { records };\n } catch (error) {\n throw new Error(\n `Failed to search Turbopuffer namespace ${collection}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error }\n );\n }\n }\n\n // ============================================================================\n // FILTER TRANSLATION\n // ============================================================================\n\n translateFilter(filter: UniversalFilter): any {\n // Handle compound AND filter\n if ('and' in filter) {\n const conditions = filter.and;\n\n // Convert all conditions\n const translated = conditions.map((c) => this.translateFilter(c));\n\n return ['And', translated];\n }\n\n // Handle compound OR filter\n if ('or' in filter) {\n const conditions = filter.or;\n\n const translated = conditions.map((c) => this.translateFilter(c));\n\n return ['Or', translated];\n }\n\n // Handle basic filter condition\n const { field, op, value } = filter as any;\n\n // Operator mapping\n const operatorMap: Record<string, string> = {\n eq: 'Eq',\n ne: 'Neq',\n gt: 'Gt',\n gte: 'Gte',\n lt: 'Lt',\n lte: 'Lte',\n in: 'In',\n nin: 'Nin',\n };\n\n const turbopufferOp = operatorMap[op];\n if (!turbopufferOp) {\n throw new Error(\n `Unsupported filter operator: ${op}`,\n { cause: { filter } }\n );\n }\n\n return [field, turbopufferOp, value];\n }\n\n // ============================================================================\n // ITERATION\n // ============================================================================\n\n async *iterate(\n collection: string,\n options?: {\n batchSize?: number;\n filter?: UniversalFilter;\n }\n ): AsyncIterableIterator<VectorRecord[]> {\n if (!this.connected) {\n throw new Error('Not connected. Call connect() first.');\n }\n\n try {\n const batchSize = options?.batchSize || 100;\n\n const turbopufferFilter = options?.filter\n ? this.translateFilter(options.filter)\n : undefined;\n\n // Turbopuffer uses attribute-based pagination\n // We'll paginate by ID using greater-than filters\n let lastId: string | null = null;\n let hasMore = true;\n\n while (hasMore) {\n // Build filters for pagination\n let filters = turbopufferFilter;\n\n if (lastId !== null) {\n const paginationFilter = ['id', 'Gt', lastId];\n\n if (filters) {\n // Combine with existing filters\n filters = ['And', [filters, paginationFilter]];\n } else {\n filters = paginationFilter;\n }\n }\n\n const result = await this.request('POST', `/v2/namespaces/${collection}/query`, {\n top_k: batchSize,\n filters,\n include_vectors: true,\n rank_by: ['id', 'Asc'],\n });\n\n if (result.rows && result.rows.length > 0) {\n const records: VectorRecord[] = [];\n\n for (const row of result.rows) {\n const { id, vector, ...metadata } = row;\n records.push({\n id: String(id),\n embedding: vector || [],\n metadata,\n });\n\n lastId = String(id);\n }\n\n yield records;\n\n // Check if we got fewer records than requested\n hasMore = result.rows.length === batchSize;\n } else {\n hasMore = false;\n }\n }\n } catch (error) {\n throw new Error(\n `Failed to iterate Turbopuffer namespace ${collection}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error }\n );\n }\n }\n\n // ============================================================================\n // CAPABILITY FLAGS\n // ============================================================================\n\n supportsMetadataUpdate(): boolean {\n return true; // Turbopuffer supports metadata updates via upsert\n }\n\n supportsFiltering(): boolean {\n return true; // Turbopuffer supports metadata filtering\n }\n\n supportsBatchOperations(): boolean {\n return true; // Turbopuffer supports batch operations\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,63 @@
1
+ import { VectorDBAdapter, DistanceMetric, CollectionStats, VectorRecord, MetadataUpdate, UniversalFilter, SearchResult } from '@vectororm/core';
2
+
3
+ /**
4
+ * Configuration for TurbopufferAdapter.
5
+ *
6
+ * Supports hybrid config: explicit values or environment variables.
7
+ */
8
+ interface TurbopufferConfig {
9
+ /**
10
+ * Turbopuffer API key.
11
+ * Falls back to TURBOPUFFER_API_KEY environment variable.
12
+ */
13
+ apiKey: string;
14
+ /**
15
+ * Base URL for Turbopuffer API (optional).
16
+ * Defaults to https://api.turbopuffer.com
17
+ * Falls back to TURBOPUFFER_BASE_URL environment variable.
18
+ */
19
+ baseUrl?: string;
20
+ }
21
+
22
+ /**
23
+ * TurbopufferAdapter implements VectorDBAdapter for Turbopuffer vector database.
24
+ *
25
+ * Uses REST API with fetch (no SDK dependency to avoid Node version constraints).
26
+ * Supports all VectorORM features including CRUD operations, filtering,
27
+ * and metadata updates.
28
+ */
29
+ declare class TurbopufferAdapter extends VectorDBAdapter {
30
+ private config;
31
+ private baseUrl;
32
+ private connected;
33
+ private namespaceMetrics;
34
+ constructor(config: TurbopufferConfig);
35
+ private request;
36
+ connect(): Promise<void>;
37
+ disconnect(): Promise<void>;
38
+ isConnected(): Promise<boolean>;
39
+ createCollection(name: string, dimension: number, metric?: DistanceMetric): Promise<void>;
40
+ deleteCollection(name: string): Promise<void>;
41
+ collectionExists(name: string): Promise<boolean>;
42
+ getCollectionStats(name: string): Promise<CollectionStats>;
43
+ upsert(collection: string, records: VectorRecord[]): Promise<void>;
44
+ fetch(collection: string, ids: string[]): Promise<VectorRecord[]>;
45
+ delete(collection: string, ids: string[]): Promise<void>;
46
+ updateMetadata(collection: string, updates: MetadataUpdate[]): Promise<void>;
47
+ search(collection: string, queryVector: number[], options?: {
48
+ topK?: number;
49
+ filter?: UniversalFilter;
50
+ includeMetadata?: boolean;
51
+ includeValues?: boolean;
52
+ }): Promise<SearchResult>;
53
+ translateFilter(filter: UniversalFilter): any;
54
+ iterate(collection: string, options?: {
55
+ batchSize?: number;
56
+ filter?: UniversalFilter;
57
+ }): AsyncIterableIterator<VectorRecord[]>;
58
+ supportsMetadataUpdate(): boolean;
59
+ supportsFiltering(): boolean;
60
+ supportsBatchOperations(): boolean;
61
+ }
62
+
63
+ export { TurbopufferAdapter, type TurbopufferConfig };
@@ -0,0 +1,63 @@
1
+ import { VectorDBAdapter, DistanceMetric, CollectionStats, VectorRecord, MetadataUpdate, UniversalFilter, SearchResult } from '@vectororm/core';
2
+
3
+ /**
4
+ * Configuration for TurbopufferAdapter.
5
+ *
6
+ * Supports hybrid config: explicit values or environment variables.
7
+ */
8
+ interface TurbopufferConfig {
9
+ /**
10
+ * Turbopuffer API key.
11
+ * Falls back to TURBOPUFFER_API_KEY environment variable.
12
+ */
13
+ apiKey: string;
14
+ /**
15
+ * Base URL for Turbopuffer API (optional).
16
+ * Defaults to https://api.turbopuffer.com
17
+ * Falls back to TURBOPUFFER_BASE_URL environment variable.
18
+ */
19
+ baseUrl?: string;
20
+ }
21
+
22
+ /**
23
+ * TurbopufferAdapter implements VectorDBAdapter for Turbopuffer vector database.
24
+ *
25
+ * Uses REST API with fetch (no SDK dependency to avoid Node version constraints).
26
+ * Supports all VectorORM features including CRUD operations, filtering,
27
+ * and metadata updates.
28
+ */
29
+ declare class TurbopufferAdapter extends VectorDBAdapter {
30
+ private config;
31
+ private baseUrl;
32
+ private connected;
33
+ private namespaceMetrics;
34
+ constructor(config: TurbopufferConfig);
35
+ private request;
36
+ connect(): Promise<void>;
37
+ disconnect(): Promise<void>;
38
+ isConnected(): Promise<boolean>;
39
+ createCollection(name: string, dimension: number, metric?: DistanceMetric): Promise<void>;
40
+ deleteCollection(name: string): Promise<void>;
41
+ collectionExists(name: string): Promise<boolean>;
42
+ getCollectionStats(name: string): Promise<CollectionStats>;
43
+ upsert(collection: string, records: VectorRecord[]): Promise<void>;
44
+ fetch(collection: string, ids: string[]): Promise<VectorRecord[]>;
45
+ delete(collection: string, ids: string[]): Promise<void>;
46
+ updateMetadata(collection: string, updates: MetadataUpdate[]): Promise<void>;
47
+ search(collection: string, queryVector: number[], options?: {
48
+ topK?: number;
49
+ filter?: UniversalFilter;
50
+ includeMetadata?: boolean;
51
+ includeValues?: boolean;
52
+ }): Promise<SearchResult>;
53
+ translateFilter(filter: UniversalFilter): any;
54
+ iterate(collection: string, options?: {
55
+ batchSize?: number;
56
+ filter?: UniversalFilter;
57
+ }): AsyncIterableIterator<VectorRecord[]>;
58
+ supportsMetadataUpdate(): boolean;
59
+ supportsFiltering(): boolean;
60
+ supportsBatchOperations(): boolean;
61
+ }
62
+
63
+ export { TurbopufferAdapter, type TurbopufferConfig };
package/dist/index.js ADDED
@@ -0,0 +1,402 @@
1
+ // src/turbopuffer-adapter.ts
2
+ import {
3
+ VectorDBAdapter
4
+ } from "@vectororm/core";
5
+ var TurbopufferAdapter = class extends VectorDBAdapter {
6
+ config;
7
+ baseUrl;
8
+ connected = false;
9
+ namespaceMetrics = /* @__PURE__ */ new Map();
10
+ constructor(config) {
11
+ super();
12
+ if (!config.apiKey) {
13
+ throw new Error(
14
+ "TurbopufferAdapter: apiKey is required in config or TURBOPUFFER_API_KEY environment variable"
15
+ );
16
+ }
17
+ this.config = config;
18
+ this.baseUrl = config.baseUrl || "https://api.turbopuffer.com";
19
+ }
20
+ // ============================================================================
21
+ // HTTP HELPERS
22
+ // ============================================================================
23
+ async request(method, path, body) {
24
+ const url = `${this.baseUrl}${path}`;
25
+ const headers = {
26
+ "Authorization": `Bearer ${this.config.apiKey}`,
27
+ "Content-Type": "application/json"
28
+ };
29
+ const response = await fetch(url, {
30
+ method,
31
+ headers,
32
+ body: body ? JSON.stringify(body) : void 0
33
+ });
34
+ if (!response.ok) {
35
+ const errorText = await response.text();
36
+ let errorMessage = `Turbopuffer API error: ${response.status} ${response.statusText}`;
37
+ try {
38
+ const errorData = JSON.parse(errorText);
39
+ if (errorData.error) {
40
+ errorMessage += ` - ${errorData.error}`;
41
+ }
42
+ } catch {
43
+ if (errorText) {
44
+ errorMessage += ` - ${errorText}`;
45
+ }
46
+ }
47
+ throw new Error(errorMessage);
48
+ }
49
+ const text = await response.text();
50
+ return text ? JSON.parse(text) : null;
51
+ }
52
+ // ============================================================================
53
+ // CONNECTION MANAGEMENT
54
+ // ============================================================================
55
+ async connect() {
56
+ try {
57
+ await this.request("GET", "/v2/namespaces");
58
+ this.connected = true;
59
+ } catch (error) {
60
+ throw new Error(
61
+ `Turbopuffer connection failed: ${error instanceof Error ? error.message : String(error)}`,
62
+ { cause: error }
63
+ );
64
+ }
65
+ }
66
+ async disconnect() {
67
+ this.connected = false;
68
+ this.namespaceMetrics.clear();
69
+ }
70
+ async isConnected() {
71
+ return this.connected;
72
+ }
73
+ // ============================================================================
74
+ // COLLECTION MANAGEMENT
75
+ // ============================================================================
76
+ async createCollection(name, dimension, metric = "cosine") {
77
+ if (!this.connected) {
78
+ throw new Error("Not connected. Call connect() first.");
79
+ }
80
+ try {
81
+ const distanceMetric = metric === "euclidean" ? "euclidean_squared" : "cosine_distance";
82
+ this.namespaceMetrics.set(name, { dimension, metric });
83
+ await this.request("POST", `/v2/namespaces/${name}`, {
84
+ upsert_rows: [{
85
+ id: "__init__",
86
+ vector: new Array(dimension).fill(0),
87
+ attributes: { __init__: true }
88
+ }],
89
+ distance_metric: distanceMetric
90
+ });
91
+ await this.request("POST", `/v2/namespaces/${name}`, {
92
+ deletes: ["__init__"]
93
+ });
94
+ } catch (error) {
95
+ throw new Error(
96
+ `Failed to create Turbopuffer namespace ${name}: ${error instanceof Error ? error.message : String(error)}`,
97
+ { cause: error }
98
+ );
99
+ }
100
+ }
101
+ async deleteCollection(name) {
102
+ if (!this.connected) {
103
+ throw new Error("Not connected. Call connect() first.");
104
+ }
105
+ try {
106
+ await this.request("DELETE", `/v2/namespaces/${name}`);
107
+ this.namespaceMetrics.delete(name);
108
+ } catch (error) {
109
+ throw new Error(
110
+ `Failed to delete Turbopuffer namespace ${name}: ${error instanceof Error ? error.message : String(error)}`,
111
+ { cause: error }
112
+ );
113
+ }
114
+ }
115
+ async collectionExists(name) {
116
+ if (!this.connected) {
117
+ throw new Error("Not connected. Call connect() first.");
118
+ }
119
+ try {
120
+ const response = await this.request("GET", "/v2/namespaces");
121
+ const namespaces = response.namespaces || [];
122
+ return namespaces.some((ns) => ns.name === name);
123
+ } catch (error) {
124
+ throw new Error(
125
+ `Failed to check if Turbopuffer namespace ${name} exists: ${error instanceof Error ? error.message : String(error)}`,
126
+ { cause: error }
127
+ );
128
+ }
129
+ }
130
+ async getCollectionStats(name) {
131
+ if (!this.connected) {
132
+ throw new Error("Not connected. Call connect() first.");
133
+ }
134
+ try {
135
+ const result = await this.request("POST", `/v2/namespaces/${name}/query`, {
136
+ top_k: 0,
137
+ aggregate_by: { Count: "*" }
138
+ });
139
+ const vectorCount = result.aggregations?.Count ?? 0;
140
+ let dimension = this.namespaceMetrics.get(name)?.dimension ?? 0;
141
+ let metric = this.namespaceMetrics.get(name)?.metric ?? "cosine";
142
+ if (dimension === 0 && vectorCount > 0) {
143
+ const sample = await this.request("POST", `/v2/namespaces/${name}/query`, {
144
+ top_k: 1,
145
+ include_vectors: true
146
+ });
147
+ if (sample.rows && sample.rows.length > 0) {
148
+ dimension = sample.rows[0].vector?.length ?? 0;
149
+ }
150
+ }
151
+ return {
152
+ vectorCount: typeof vectorCount === "number" ? vectorCount : 0,
153
+ dimension,
154
+ metric
155
+ };
156
+ } catch (error) {
157
+ throw new Error(
158
+ `Failed to get Turbopuffer namespace stats for ${name}: ${error instanceof Error ? error.message : String(error)}`,
159
+ { cause: error }
160
+ );
161
+ }
162
+ }
163
+ // ============================================================================
164
+ // VECTOR OPERATIONS
165
+ // ============================================================================
166
+ async upsert(collection, records) {
167
+ if (!this.connected) {
168
+ throw new Error("Not connected. Call connect() first.");
169
+ }
170
+ try {
171
+ const storedMetric = this.namespaceMetrics.get(collection);
172
+ const distanceMetric = storedMetric?.metric === "euclidean" ? "euclidean_squared" : "cosine_distance";
173
+ const rows = records.map((record) => ({
174
+ id: record.id,
175
+ vector: record.embedding,
176
+ ...record.metadata
177
+ }));
178
+ await this.request("POST", `/v2/namespaces/${collection}`, {
179
+ upsert_rows: rows,
180
+ distance_metric: distanceMetric
181
+ });
182
+ } catch (error) {
183
+ throw new Error(
184
+ `Failed to upsert vectors to Turbopuffer namespace ${collection}: ${error instanceof Error ? error.message : String(error)}`,
185
+ { cause: error }
186
+ );
187
+ }
188
+ }
189
+ async fetch(collection, ids) {
190
+ if (!this.connected) {
191
+ throw new Error("Not connected. Call connect() first.");
192
+ }
193
+ try {
194
+ const results = [];
195
+ if (ids.length > 0) {
196
+ const result = await this.request("POST", `/v2/namespaces/${collection}/query`, {
197
+ top_k: ids.length,
198
+ filters: ["id", "In", ids],
199
+ include_vectors: true
200
+ });
201
+ if (result.rows) {
202
+ for (const row of result.rows) {
203
+ const { id, vector, ...metadata } = row;
204
+ results.push({
205
+ id: String(id),
206
+ embedding: vector || [],
207
+ metadata
208
+ });
209
+ }
210
+ }
211
+ }
212
+ return results;
213
+ } catch (error) {
214
+ throw new Error(
215
+ `Failed to fetch vectors from Turbopuffer namespace ${collection}: ${error instanceof Error ? error.message : String(error)}`,
216
+ { cause: error }
217
+ );
218
+ }
219
+ }
220
+ async delete(collection, ids) {
221
+ if (!this.connected) {
222
+ throw new Error("Not connected. Call connect() first.");
223
+ }
224
+ try {
225
+ await this.request("POST", `/v2/namespaces/${collection}`, {
226
+ deletes: ids
227
+ });
228
+ } catch (error) {
229
+ throw new Error(
230
+ `Failed to delete vectors from Turbopuffer namespace ${collection}: ${error instanceof Error ? error.message : String(error)}`,
231
+ { cause: error }
232
+ );
233
+ }
234
+ }
235
+ // ============================================================================
236
+ // METADATA OPERATIONS
237
+ // ============================================================================
238
+ async updateMetadata(collection, updates) {
239
+ if (!this.connected) {
240
+ throw new Error("Not connected. Call connect() first.");
241
+ }
242
+ try {
243
+ for (const update of updates) {
244
+ const existing = await this.fetch(collection, [update.id]);
245
+ if (existing.length === 0) {
246
+ throw new Error(`Vector ${update.id} not found for metadata update`);
247
+ }
248
+ const merged = { ...existing[0].metadata, ...update.metadata };
249
+ await this.request("POST", `/v2/namespaces/${collection}`, {
250
+ upsert_rows: [{
251
+ id: update.id,
252
+ vector: existing[0].embedding,
253
+ ...merged
254
+ }]
255
+ });
256
+ }
257
+ } catch (error) {
258
+ throw new Error(
259
+ `Failed to update metadata in Turbopuffer namespace ${collection}: ${error instanceof Error ? error.message : String(error)}`,
260
+ { cause: error }
261
+ );
262
+ }
263
+ }
264
+ // ============================================================================
265
+ // SEARCH OPERATIONS
266
+ // ============================================================================
267
+ async search(collection, queryVector, options) {
268
+ if (!this.connected) {
269
+ throw new Error("Not connected. Call connect() first.");
270
+ }
271
+ try {
272
+ const turbopufferFilter = options?.filter ? this.translateFilter(options.filter) : void 0;
273
+ const result = await this.request("POST", `/v2/namespaces/${collection}/query`, {
274
+ rank_by: ["vector", "ANN", queryVector],
275
+ top_k: options?.topK || 10,
276
+ filters: turbopufferFilter,
277
+ include_vectors: options?.includeValues || false
278
+ });
279
+ const records = [];
280
+ if (result.rows) {
281
+ for (const row of result.rows) {
282
+ const { id, vector, $dist, ...metadata } = row;
283
+ const record = {
284
+ id: String(id),
285
+ embedding: options?.includeValues ? vector || [] : [],
286
+ metadata: options?.includeMetadata !== false ? metadata : {},
287
+ score: $dist !== void 0 ? 1 / (1 + $dist) : void 0
288
+ };
289
+ records.push(record);
290
+ }
291
+ }
292
+ return { records };
293
+ } catch (error) {
294
+ throw new Error(
295
+ `Failed to search Turbopuffer namespace ${collection}: ${error instanceof Error ? error.message : String(error)}`,
296
+ { cause: error }
297
+ );
298
+ }
299
+ }
300
+ // ============================================================================
301
+ // FILTER TRANSLATION
302
+ // ============================================================================
303
+ translateFilter(filter) {
304
+ if ("and" in filter) {
305
+ const conditions = filter.and;
306
+ const translated = conditions.map((c) => this.translateFilter(c));
307
+ return ["And", translated];
308
+ }
309
+ if ("or" in filter) {
310
+ const conditions = filter.or;
311
+ const translated = conditions.map((c) => this.translateFilter(c));
312
+ return ["Or", translated];
313
+ }
314
+ const { field, op, value } = filter;
315
+ const operatorMap = {
316
+ eq: "Eq",
317
+ ne: "Neq",
318
+ gt: "Gt",
319
+ gte: "Gte",
320
+ lt: "Lt",
321
+ lte: "Lte",
322
+ in: "In",
323
+ nin: "Nin"
324
+ };
325
+ const turbopufferOp = operatorMap[op];
326
+ if (!turbopufferOp) {
327
+ throw new Error(
328
+ `Unsupported filter operator: ${op}`,
329
+ { cause: { filter } }
330
+ );
331
+ }
332
+ return [field, turbopufferOp, value];
333
+ }
334
+ // ============================================================================
335
+ // ITERATION
336
+ // ============================================================================
337
+ async *iterate(collection, options) {
338
+ if (!this.connected) {
339
+ throw new Error("Not connected. Call connect() first.");
340
+ }
341
+ try {
342
+ const batchSize = options?.batchSize || 100;
343
+ const turbopufferFilter = options?.filter ? this.translateFilter(options.filter) : void 0;
344
+ let lastId = null;
345
+ let hasMore = true;
346
+ while (hasMore) {
347
+ let filters = turbopufferFilter;
348
+ if (lastId !== null) {
349
+ const paginationFilter = ["id", "Gt", lastId];
350
+ if (filters) {
351
+ filters = ["And", [filters, paginationFilter]];
352
+ } else {
353
+ filters = paginationFilter;
354
+ }
355
+ }
356
+ const result = await this.request("POST", `/v2/namespaces/${collection}/query`, {
357
+ top_k: batchSize,
358
+ filters,
359
+ include_vectors: true,
360
+ rank_by: ["id", "Asc"]
361
+ });
362
+ if (result.rows && result.rows.length > 0) {
363
+ const records = [];
364
+ for (const row of result.rows) {
365
+ const { id, vector, ...metadata } = row;
366
+ records.push({
367
+ id: String(id),
368
+ embedding: vector || [],
369
+ metadata
370
+ });
371
+ lastId = String(id);
372
+ }
373
+ yield records;
374
+ hasMore = result.rows.length === batchSize;
375
+ } else {
376
+ hasMore = false;
377
+ }
378
+ }
379
+ } catch (error) {
380
+ throw new Error(
381
+ `Failed to iterate Turbopuffer namespace ${collection}: ${error instanceof Error ? error.message : String(error)}`,
382
+ { cause: error }
383
+ );
384
+ }
385
+ }
386
+ // ============================================================================
387
+ // CAPABILITY FLAGS
388
+ // ============================================================================
389
+ supportsMetadataUpdate() {
390
+ return true;
391
+ }
392
+ supportsFiltering() {
393
+ return true;
394
+ }
395
+ supportsBatchOperations() {
396
+ return true;
397
+ }
398
+ };
399
+ export {
400
+ TurbopufferAdapter
401
+ };
402
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/turbopuffer-adapter.ts"],"sourcesContent":["import {\n VectorDBAdapter,\n type VectorRecord,\n type SearchResult,\n type UniversalFilter,\n type CollectionStats,\n type MetadataUpdate,\n type DistanceMetric,\n} from '@vectororm/core';\nimport type { TurbopufferConfig } from './types.js';\n\n/**\n * TurbopufferAdapter implements VectorDBAdapter for Turbopuffer vector database.\n *\n * Uses REST API with fetch (no SDK dependency to avoid Node version constraints).\n * Supports all VectorORM features including CRUD operations, filtering,\n * and metadata updates.\n */\nexport class TurbopufferAdapter extends VectorDBAdapter {\n private config: TurbopufferConfig;\n private baseUrl: string;\n private connected: boolean = false;\n private namespaceMetrics: Map<string, { dimension: number; metric: DistanceMetric }> = new Map();\n\n constructor(config: TurbopufferConfig) {\n super();\n\n // Validate required config\n if (!config.apiKey) {\n throw new Error(\n 'TurbopufferAdapter: apiKey is required in config or TURBOPUFFER_API_KEY environment variable'\n );\n }\n\n this.config = config;\n this.baseUrl = config.baseUrl || 'https://api.turbopuffer.com';\n }\n\n // ============================================================================\n // HTTP HELPERS\n // ============================================================================\n\n private async request(\n method: string,\n path: string,\n body?: any\n ): Promise<any> {\n const url = `${this.baseUrl}${path}`;\n\n const headers: Record<string, string> = {\n 'Authorization': `Bearer ${this.config.apiKey}`,\n 'Content-Type': 'application/json',\n };\n\n const response = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n let errorMessage = `Turbopuffer API error: ${response.status} ${response.statusText}`;\n\n try {\n const errorData = JSON.parse(errorText);\n if (errorData.error) {\n errorMessage += ` - ${errorData.error}`;\n }\n } catch {\n if (errorText) {\n errorMessage += ` - ${errorText}`;\n }\n }\n\n throw new Error(errorMessage);\n }\n\n const text = await response.text();\n return text ? JSON.parse(text) : null;\n }\n\n // ============================================================================\n // CONNECTION MANAGEMENT\n // ============================================================================\n\n async connect(): Promise<void> {\n try {\n // Verify connection by listing namespaces\n await this.request('GET', '/v2/namespaces');\n this.connected = true;\n } catch (error) {\n throw new Error(\n `Turbopuffer connection failed: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error }\n );\n }\n }\n\n async disconnect(): Promise<void> {\n this.connected = false;\n this.namespaceMetrics.clear();\n }\n\n async isConnected(): Promise<boolean> {\n return this.connected;\n }\n\n // ============================================================================\n // COLLECTION MANAGEMENT\n // ============================================================================\n\n async createCollection(\n name: string,\n dimension: number,\n metric: DistanceMetric = 'cosine'\n ): Promise<void> {\n if (!this.connected) {\n throw new Error('Not connected. Call connect() first.');\n }\n\n try {\n // Turbopuffer creates namespaces implicitly on first upsert\n // We'll store metadata locally and create with a dummy vector\n const distanceMetric = metric === 'euclidean' ? 'euclidean_squared' : 'cosine_distance';\n\n // Store metrics for later use\n this.namespaceMetrics.set(name, { dimension, metric });\n\n // Create namespace with initial dummy vector to set schema\n await this.request('POST', `/v2/namespaces/${name}`, {\n upsert_rows: [{\n id: '__init__',\n vector: new Array(dimension).fill(0),\n attributes: { __init__: true }\n }],\n distance_metric: distanceMetric,\n });\n\n // Delete the initialization vector\n await this.request('POST', `/v2/namespaces/${name}`, {\n deletes: ['__init__'],\n });\n } catch (error) {\n throw new Error(\n `Failed to create Turbopuffer namespace ${name}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error }\n );\n }\n }\n\n async deleteCollection(name: string): Promise<void> {\n if (!this.connected) {\n throw new Error('Not connected. Call connect() first.');\n }\n\n try {\n await this.request('DELETE', `/v2/namespaces/${name}`);\n this.namespaceMetrics.delete(name);\n } catch (error) {\n throw new Error(\n `Failed to delete Turbopuffer namespace ${name}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error }\n );\n }\n }\n\n async collectionExists(name: string): Promise<boolean> {\n if (!this.connected) {\n throw new Error('Not connected. Call connect() first.');\n }\n\n try {\n const response = await this.request('GET', '/v2/namespaces');\n const namespaces = response.namespaces || [];\n return namespaces.some((ns: any) => ns.name === name);\n } catch (error) {\n throw new Error(\n `Failed to check if Turbopuffer namespace ${name} exists: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error }\n );\n }\n }\n\n async getCollectionStats(name: string): Promise<CollectionStats> {\n if (!this.connected) {\n throw new Error('Not connected. Call connect() first.');\n }\n\n try {\n // Use query with limit 0 to get aggregation stats\n const result = await this.request('POST', `/v2/namespaces/${name}/query`, {\n top_k: 0,\n aggregate_by: { Count: '*' },\n });\n\n const vectorCount = result.aggregations?.Count ?? 0;\n\n // Get dimension from stored metrics or estimate from a sample vector\n let dimension = this.namespaceMetrics.get(name)?.dimension ?? 0;\n let metric = this.namespaceMetrics.get(name)?.metric ?? 'cosine';\n\n if (dimension === 0 && vectorCount > 0) {\n // Query one vector to get dimension\n const sample = await this.request('POST', `/v2/namespaces/${name}/query`, {\n top_k: 1,\n include_vectors: true,\n });\n\n if (sample.rows && sample.rows.length > 0) {\n dimension = sample.rows[0].vector?.length ?? 0;\n }\n }\n\n return {\n vectorCount: typeof vectorCount === 'number' ? vectorCount : 0,\n dimension,\n metric,\n };\n } catch (error) {\n throw new Error(\n `Failed to get Turbopuffer namespace stats for ${name}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error }\n );\n }\n }\n\n // ============================================================================\n // VECTOR OPERATIONS\n // ============================================================================\n\n async upsert(collection: string, records: VectorRecord[]): Promise<void> {\n if (!this.connected) {\n throw new Error('Not connected. Call connect() first.');\n }\n\n try {\n // Get or infer distance metric\n const storedMetric = this.namespaceMetrics.get(collection);\n const distanceMetric = storedMetric?.metric === 'euclidean'\n ? 'euclidean_squared'\n : 'cosine_distance';\n\n // Convert VectorRecord[] to Turbopuffer format\n const rows = records.map((record) => ({\n id: record.id,\n vector: record.embedding,\n ...record.metadata,\n }));\n\n await this.request('POST', `/v2/namespaces/${collection}`, {\n upsert_rows: rows,\n distance_metric: distanceMetric,\n });\n } catch (error) {\n throw new Error(\n `Failed to upsert vectors to Turbopuffer namespace ${collection}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error }\n );\n }\n }\n\n async fetch(collection: string, ids: string[]): Promise<VectorRecord[]> {\n if (!this.connected) {\n throw new Error('Not connected. Call connect() first.');\n }\n\n try {\n // Turbopuffer doesn't have a direct fetch by IDs\n // We need to use query with filters for each ID\n const results: VectorRecord[] = [];\n\n // Query with In filter for multiple IDs\n if (ids.length > 0) {\n const result = await this.request('POST', `/v2/namespaces/${collection}/query`, {\n top_k: ids.length,\n filters: ['id', 'In', ids],\n include_vectors: true,\n });\n\n if (result.rows) {\n for (const row of result.rows) {\n const { id, vector, ...metadata } = row;\n results.push({\n id: String(id),\n embedding: vector || [],\n metadata,\n });\n }\n }\n }\n\n return results;\n } catch (error) {\n throw new Error(\n `Failed to fetch vectors from Turbopuffer namespace ${collection}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error }\n );\n }\n }\n\n async delete(collection: string, ids: string[]): Promise<void> {\n if (!this.connected) {\n throw new Error('Not connected. Call connect() first.');\n }\n\n try {\n await this.request('POST', `/v2/namespaces/${collection}`, {\n deletes: ids,\n });\n } catch (error) {\n throw new Error(\n `Failed to delete vectors from Turbopuffer namespace ${collection}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error }\n );\n }\n }\n\n // ============================================================================\n // METADATA OPERATIONS\n // ============================================================================\n\n async updateMetadata(\n collection: string,\n updates: MetadataUpdate[]\n ): Promise<void> {\n if (!this.connected) {\n throw new Error('Not connected. Call connect() first.');\n }\n\n try {\n // Turbopuffer supports patch operations\n // We'll fetch existing records and patch them\n for (const update of updates) {\n // Fetch the existing record\n const existing = await this.fetch(collection, [update.id]);\n\n if (existing.length === 0) {\n throw new Error(`Vector ${update.id} not found for metadata update`);\n }\n\n // Merge metadata\n const merged = { ...existing[0].metadata, ...update.metadata };\n\n // Upsert with updated metadata\n await this.request('POST', `/v2/namespaces/${collection}`, {\n upsert_rows: [{\n id: update.id,\n vector: existing[0].embedding,\n ...merged,\n }],\n });\n }\n } catch (error) {\n throw new Error(\n `Failed to update metadata in Turbopuffer namespace ${collection}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error }\n );\n }\n }\n\n // ============================================================================\n // SEARCH OPERATIONS\n // ============================================================================\n\n async search(\n collection: string,\n queryVector: number[],\n options?: {\n topK?: number;\n filter?: UniversalFilter;\n includeMetadata?: boolean;\n includeValues?: boolean;\n }\n ): Promise<SearchResult> {\n if (!this.connected) {\n throw new Error('Not connected. Call connect() first.');\n }\n\n try {\n const turbopufferFilter = options?.filter\n ? this.translateFilter(options.filter)\n : undefined;\n\n const result = await this.request('POST', `/v2/namespaces/${collection}/query`, {\n rank_by: ['vector', 'ANN', queryVector],\n top_k: options?.topK || 10,\n filters: turbopufferFilter,\n include_vectors: options?.includeValues || false,\n });\n\n const records: VectorRecord[] = [];\n\n if (result.rows) {\n for (const row of result.rows) {\n const { id, vector, $dist, ...metadata } = row;\n const record: VectorRecord = {\n id: String(id),\n embedding: options?.includeValues ? (vector || []) : [],\n metadata: options?.includeMetadata !== false ? metadata : {},\n score: $dist !== undefined ? (1 / (1 + $dist)) : undefined,\n };\n\n records.push(record);\n }\n }\n\n return { records };\n } catch (error) {\n throw new Error(\n `Failed to search Turbopuffer namespace ${collection}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error }\n );\n }\n }\n\n // ============================================================================\n // FILTER TRANSLATION\n // ============================================================================\n\n translateFilter(filter: UniversalFilter): any {\n // Handle compound AND filter\n if ('and' in filter) {\n const conditions = filter.and;\n\n // Convert all conditions\n const translated = conditions.map((c) => this.translateFilter(c));\n\n return ['And', translated];\n }\n\n // Handle compound OR filter\n if ('or' in filter) {\n const conditions = filter.or;\n\n const translated = conditions.map((c) => this.translateFilter(c));\n\n return ['Or', translated];\n }\n\n // Handle basic filter condition\n const { field, op, value } = filter as any;\n\n // Operator mapping\n const operatorMap: Record<string, string> = {\n eq: 'Eq',\n ne: 'Neq',\n gt: 'Gt',\n gte: 'Gte',\n lt: 'Lt',\n lte: 'Lte',\n in: 'In',\n nin: 'Nin',\n };\n\n const turbopufferOp = operatorMap[op];\n if (!turbopufferOp) {\n throw new Error(\n `Unsupported filter operator: ${op}`,\n { cause: { filter } }\n );\n }\n\n return [field, turbopufferOp, value];\n }\n\n // ============================================================================\n // ITERATION\n // ============================================================================\n\n async *iterate(\n collection: string,\n options?: {\n batchSize?: number;\n filter?: UniversalFilter;\n }\n ): AsyncIterableIterator<VectorRecord[]> {\n if (!this.connected) {\n throw new Error('Not connected. Call connect() first.');\n }\n\n try {\n const batchSize = options?.batchSize || 100;\n\n const turbopufferFilter = options?.filter\n ? this.translateFilter(options.filter)\n : undefined;\n\n // Turbopuffer uses attribute-based pagination\n // We'll paginate by ID using greater-than filters\n let lastId: string | null = null;\n let hasMore = true;\n\n while (hasMore) {\n // Build filters for pagination\n let filters = turbopufferFilter;\n\n if (lastId !== null) {\n const paginationFilter = ['id', 'Gt', lastId];\n\n if (filters) {\n // Combine with existing filters\n filters = ['And', [filters, paginationFilter]];\n } else {\n filters = paginationFilter;\n }\n }\n\n const result = await this.request('POST', `/v2/namespaces/${collection}/query`, {\n top_k: batchSize,\n filters,\n include_vectors: true,\n rank_by: ['id', 'Asc'],\n });\n\n if (result.rows && result.rows.length > 0) {\n const records: VectorRecord[] = [];\n\n for (const row of result.rows) {\n const { id, vector, ...metadata } = row;\n records.push({\n id: String(id),\n embedding: vector || [],\n metadata,\n });\n\n lastId = String(id);\n }\n\n yield records;\n\n // Check if we got fewer records than requested\n hasMore = result.rows.length === batchSize;\n } else {\n hasMore = false;\n }\n }\n } catch (error) {\n throw new Error(\n `Failed to iterate Turbopuffer namespace ${collection}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error }\n );\n }\n }\n\n // ============================================================================\n // CAPABILITY FLAGS\n // ============================================================================\n\n supportsMetadataUpdate(): boolean {\n return true; // Turbopuffer supports metadata updates via upsert\n }\n\n supportsFiltering(): boolean {\n return true; // Turbopuffer supports metadata filtering\n }\n\n supportsBatchOperations(): boolean {\n return true; // Turbopuffer supports batch operations\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@vectororm/adapter-turbopuffer",
3
+ "version": "0.1.0",
4
+ "description": "Turbopuffer adapter for Glyph VectorORM",
5
+ "author": "Aviram Roisman",
6
+ "license": "Apache-2.0",
7
+ "homepage": "https://github.com/aviramroi/VectorORM",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/aviramroi/VectorORM.git",
11
+ "directory": "packages/adapter-turbopuffer"
12
+ },
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "type": "module",
17
+ "main": "./dist/index.js",
18
+ "module": "./dist/index.mjs",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "require": "./dist/index.js",
24
+ "import": "./dist/index.mjs"
25
+ }
26
+ },
27
+ "files": ["dist", "README.md"],
28
+ "engines": {
29
+ "node": ">=18.0.0"
30
+ },
31
+ "scripts": {
32
+ "build": "tsup",
33
+ "test": "vitest run",
34
+ "test:watch": "vitest",
35
+ "test:integration": "vitest run --config vitest.integration.config.ts"
36
+ },
37
+ "dependencies": {
38
+ "@vectororm/core": "*"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^20.10.0",
42
+ "tsup": "^8.0.0",
43
+ "typescript": "^5.3.3",
44
+ "vitest": "^1.0.0"
45
+ },
46
+ "peerDependencies": {
47
+ "@vectororm/core": "^0.1.0"
48
+ },
49
+ "keywords": [
50
+ "glyph",
51
+ "vectororm",
52
+ "turbopuffer",
53
+ "vector-database",
54
+ "adapter"
55
+ ]
56
+ }