@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 +134 -0
- package/dist/index.cjs +427 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +63 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +402 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# @vectororm/adapter-turbopuffer
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@vectororm/adapter-turbopuffer)
|
|
4
|
+
[](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":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|