@mastra/turbopuffer 1.2.0 → 1.2.1-alpha.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/LICENSE.md +6 -4
- package/dist/docs/SKILL.md +2 -2
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/reference-vectors-turbopuffer.md +3 -1
- package/dist/index.cjs +575 -681
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +572 -677
- package/dist/index.js.map +1 -1
- package/dist/vector/filter.d.ts.map +1 -1
- package/dist/vector/index.d.ts.map +1 -1
- package/package.json +12 -12
- package/CHANGELOG.md +0 -1559
package/dist/index.js
CHANGED
|
@@ -1,683 +1,578 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createVectorErrorId } from
|
|
3
|
-
import { MastraVector } from
|
|
4
|
-
import { Turbopuffer } from
|
|
5
|
-
import { BaseFilterTranslator } from
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { ErrorCategory, ErrorDomain, MastraError } from "@mastra/core/error";
|
|
2
|
+
import { createVectorErrorId } from "@mastra/core/storage";
|
|
3
|
+
import { MastraVector } from "@mastra/core/vector";
|
|
4
|
+
import { Turbopuffer } from "@turbopuffer/turbopuffer";
|
|
5
|
+
import { BaseFilterTranslator } from "@mastra/core/vector/filter";
|
|
6
|
+
//#region src/vector/filter.ts
|
|
7
|
+
/**
|
|
8
|
+
* Translator for converting Mastra filters to Turbopuffer format
|
|
9
|
+
*
|
|
10
|
+
* Mastra filters: { field: { $gt: 10 } }
|
|
11
|
+
* Turbopuffer filters: ["And", [["field", "Gt", 10]]]
|
|
12
|
+
*/
|
|
8
13
|
var TurbopufferFilterTranslator = class extends BaseFilterTranslator {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Normalize a value for comparison operations
|
|
152
|
-
*/
|
|
153
|
-
normalizeValue(value) {
|
|
154
|
-
if (value instanceof Date) {
|
|
155
|
-
return value.toISOString();
|
|
156
|
-
}
|
|
157
|
-
return value;
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Normalize array values
|
|
161
|
-
*/
|
|
162
|
-
normalizeArrayValues(values) {
|
|
163
|
-
return values.map((value) => this.normalizeValue(value));
|
|
164
|
-
}
|
|
14
|
+
getSupportedOperators() {
|
|
15
|
+
return {
|
|
16
|
+
...BaseFilterTranslator.DEFAULT_OPERATORS,
|
|
17
|
+
logical: ["$and", "$or"],
|
|
18
|
+
array: [
|
|
19
|
+
"$in",
|
|
20
|
+
"$nin",
|
|
21
|
+
"$all"
|
|
22
|
+
],
|
|
23
|
+
element: ["$exists"],
|
|
24
|
+
regex: [],
|
|
25
|
+
custom: []
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Map Mastra operators to Turbopuffer operators
|
|
30
|
+
*/
|
|
31
|
+
operatorMap = {
|
|
32
|
+
$eq: "Eq",
|
|
33
|
+
$ne: "NotEq",
|
|
34
|
+
$gt: "Gt",
|
|
35
|
+
$gte: "Gte",
|
|
36
|
+
$lt: "Lt",
|
|
37
|
+
$lte: "Lte",
|
|
38
|
+
$in: "In",
|
|
39
|
+
$nin: "NotIn"
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Convert the Mastra filter to Turbopuffer format
|
|
43
|
+
*/
|
|
44
|
+
translate(filter) {
|
|
45
|
+
if (this.isEmpty(filter)) return;
|
|
46
|
+
this.validateFilter(filter);
|
|
47
|
+
const result = this.translateNode(filter);
|
|
48
|
+
if (!Array.isArray(result) || result.length !== 2 || result[0] !== "And" && result[0] !== "Or") return ["And", [result]];
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Recursively translate a filter node
|
|
53
|
+
*/
|
|
54
|
+
translateNode(node) {
|
|
55
|
+
if (node === null || node === void 0 || Object.keys(node).length === 0) return ["And", []];
|
|
56
|
+
if (this.isPrimitive(node)) throw new Error("Direct primitive values not valid in this context for Turbopuffer");
|
|
57
|
+
if (Array.isArray(node)) throw new Error("Direct array values not valid in this context for Turbopuffer");
|
|
58
|
+
const entries = Object.entries(node);
|
|
59
|
+
if (entries.length === 0) return ["And", []];
|
|
60
|
+
const [key, value] = entries[0];
|
|
61
|
+
if (key && this.isLogicalOperator(key)) return this.translateLogical(key, value);
|
|
62
|
+
if (entries.length > 1) return ["And", entries.map(([field, fieldValue]) => this.translateFieldCondition(field, fieldValue))];
|
|
63
|
+
return this.translateFieldCondition(key, value);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Translate a field condition
|
|
67
|
+
*/
|
|
68
|
+
translateFieldCondition(field, value) {
|
|
69
|
+
if (value instanceof Date) return [
|
|
70
|
+
field,
|
|
71
|
+
"Eq",
|
|
72
|
+
this.normalizeValue(value)
|
|
73
|
+
];
|
|
74
|
+
if (this.isPrimitive(value)) return [
|
|
75
|
+
field,
|
|
76
|
+
"Eq",
|
|
77
|
+
this.normalizeValue(value)
|
|
78
|
+
];
|
|
79
|
+
if (Array.isArray(value)) return [
|
|
80
|
+
field,
|
|
81
|
+
"In",
|
|
82
|
+
this.normalizeArrayValues(value)
|
|
83
|
+
];
|
|
84
|
+
if (typeof value === "object" && value !== null) {
|
|
85
|
+
const operators = Object.keys(value);
|
|
86
|
+
if (operators.length > 1) if (operators.every((op) => this.isOperator(op))) return ["And", operators.map((op) => this.translateOperator(field, op, value[op]))];
|
|
87
|
+
else return ["And", operators.map((op) => {
|
|
88
|
+
const nestedField = `${field}.${op}`;
|
|
89
|
+
return this.translateFieldCondition(nestedField, value[op]);
|
|
90
|
+
})];
|
|
91
|
+
const op = operators[0];
|
|
92
|
+
if (op && this.isOperator(op)) return this.translateOperator(field, op, value[op]);
|
|
93
|
+
if (op && !this.isOperator(op)) {
|
|
94
|
+
const nestedField = `${field}.${op}`;
|
|
95
|
+
return this.translateFieldCondition(nestedField, value[op]);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
throw new Error(`Unsupported filter format for field: ${field}`);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Translate a logical operator
|
|
102
|
+
*/
|
|
103
|
+
translateLogical(operator, conditions) {
|
|
104
|
+
const logicalOp = operator === "$and" ? "And" : "Or";
|
|
105
|
+
if (!Array.isArray(conditions)) throw new Error(`Logical operator ${operator} requires an array of conditions`);
|
|
106
|
+
return [logicalOp, conditions.map((condition) => {
|
|
107
|
+
if (typeof condition !== "object" || condition === null) throw new Error(`Invalid condition for logical operator ${operator}`);
|
|
108
|
+
return this.translateNode(condition);
|
|
109
|
+
})];
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Translate a specific operator
|
|
113
|
+
*/
|
|
114
|
+
translateOperator(field, operator, value) {
|
|
115
|
+
if (operator && this.operatorMap[operator]) return [
|
|
116
|
+
field,
|
|
117
|
+
this.operatorMap[operator],
|
|
118
|
+
this.normalizeValue(value)
|
|
119
|
+
];
|
|
120
|
+
switch (operator) {
|
|
121
|
+
case "$exists": return value ? [
|
|
122
|
+
field,
|
|
123
|
+
"NotEq",
|
|
124
|
+
null
|
|
125
|
+
] : [
|
|
126
|
+
field,
|
|
127
|
+
"Eq",
|
|
128
|
+
null
|
|
129
|
+
];
|
|
130
|
+
case "$all":
|
|
131
|
+
if (!Array.isArray(value) || value.length === 0) throw new Error("$all operator requires a non-empty array");
|
|
132
|
+
return ["And", value.map((item) => [
|
|
133
|
+
field,
|
|
134
|
+
"In",
|
|
135
|
+
[this.normalizeValue(item)]
|
|
136
|
+
])];
|
|
137
|
+
default: throw new Error(`Unsupported operator: ${operator || "undefined"}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Normalize a value for comparison operations
|
|
142
|
+
*/
|
|
143
|
+
normalizeValue(value) {
|
|
144
|
+
if (value instanceof Date) return value.toISOString();
|
|
145
|
+
return value;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Normalize array values
|
|
149
|
+
*/
|
|
150
|
+
normalizeArrayValues(values) {
|
|
151
|
+
return values.map((value) => this.normalizeValue(value));
|
|
152
|
+
}
|
|
165
153
|
};
|
|
166
|
-
|
|
167
|
-
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region src/vector/index.ts
|
|
168
156
|
var TurbopufferVector = class extends MastraVector {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
id: createVectorErrorId("TURBOPUFFER", "DELETE_VECTOR", "FAILED"),
|
|
587
|
-
domain: ErrorDomain.STORAGE,
|
|
588
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
589
|
-
details: { indexName }
|
|
590
|
-
},
|
|
591
|
-
error
|
|
592
|
-
);
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
async deleteVectors({ indexName, filter, ids }) {
|
|
596
|
-
if (ids && filter) {
|
|
597
|
-
throw new MastraError({
|
|
598
|
-
id: createVectorErrorId("TURBOPUFFER", "DELETE_VECTORS", "MUTUALLY_EXCLUSIVE"),
|
|
599
|
-
domain: ErrorDomain.STORAGE,
|
|
600
|
-
category: ErrorCategory.USER,
|
|
601
|
-
text: "ids and filter are mutually exclusive",
|
|
602
|
-
details: { indexName }
|
|
603
|
-
});
|
|
604
|
-
}
|
|
605
|
-
if (!ids && !filter) {
|
|
606
|
-
throw new MastraError({
|
|
607
|
-
id: createVectorErrorId("TURBOPUFFER", "DELETE_VECTORS", "NO_TARGET"),
|
|
608
|
-
domain: ErrorDomain.STORAGE,
|
|
609
|
-
category: ErrorCategory.USER,
|
|
610
|
-
text: "Either filter or ids must be provided",
|
|
611
|
-
details: { indexName }
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
if (ids && ids.length === 0) {
|
|
615
|
-
throw new MastraError({
|
|
616
|
-
id: createVectorErrorId("TURBOPUFFER", "DELETE_VECTORS", "EMPTY_IDS"),
|
|
617
|
-
domain: ErrorDomain.STORAGE,
|
|
618
|
-
category: ErrorCategory.USER,
|
|
619
|
-
text: "ids array cannot be empty",
|
|
620
|
-
details: { indexName }
|
|
621
|
-
});
|
|
622
|
-
}
|
|
623
|
-
if (filter && Object.keys(filter).length === 0) {
|
|
624
|
-
throw new MastraError({
|
|
625
|
-
id: createVectorErrorId("TURBOPUFFER", "DELETE_VECTORS", "EMPTY_FILTER"),
|
|
626
|
-
domain: ErrorDomain.STORAGE,
|
|
627
|
-
category: ErrorCategory.USER,
|
|
628
|
-
text: "Filter cannot be an empty object",
|
|
629
|
-
details: { indexName }
|
|
630
|
-
});
|
|
631
|
-
}
|
|
632
|
-
try {
|
|
633
|
-
const namespace = this.client.namespace(indexName);
|
|
634
|
-
let idsToDelete = [];
|
|
635
|
-
if (ids) {
|
|
636
|
-
idsToDelete = ids;
|
|
637
|
-
} else if (filter) {
|
|
638
|
-
const createIndex = this.createIndexCache.get(indexName);
|
|
639
|
-
if (!createIndex) {
|
|
640
|
-
throw new Error(`createIndex() not called for this index`);
|
|
641
|
-
}
|
|
642
|
-
const dummyVector = new Array(createIndex.dimension).fill(1 / Math.sqrt(createIndex.dimension));
|
|
643
|
-
const translatedFilter = this.filterTranslator.translate(filter);
|
|
644
|
-
const results = await namespace.query({
|
|
645
|
-
rank_by: ["vector", "ANN", dummyVector],
|
|
646
|
-
top_k: 1e4,
|
|
647
|
-
// Get all matching vectors
|
|
648
|
-
filters: translatedFilter,
|
|
649
|
-
include_attributes: []
|
|
650
|
-
});
|
|
651
|
-
idsToDelete = (results.rows ?? []).map((r) => String(r.id));
|
|
652
|
-
}
|
|
653
|
-
if (idsToDelete.length === 0) {
|
|
654
|
-
this.logger.info(`No vectors matched the criteria for deletion in ${indexName}`);
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
const batchSize = 1e3;
|
|
658
|
-
for (let i = 0; i < idsToDelete.length; i += batchSize) {
|
|
659
|
-
const batch = idsToDelete.slice(i, i + batchSize);
|
|
660
|
-
await namespace.write({ deletes: batch });
|
|
661
|
-
}
|
|
662
|
-
} catch (error) {
|
|
663
|
-
if (error instanceof MastraError) throw error;
|
|
664
|
-
throw new MastraError(
|
|
665
|
-
{
|
|
666
|
-
id: createVectorErrorId("TURBOPUFFER", "DELETE_VECTORS", "FAILED"),
|
|
667
|
-
domain: ErrorDomain.STORAGE,
|
|
668
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
669
|
-
details: {
|
|
670
|
-
indexName,
|
|
671
|
-
...filter && { filter: JSON.stringify(filter) },
|
|
672
|
-
...ids && { idsCount: ids.length }
|
|
673
|
-
}
|
|
674
|
-
},
|
|
675
|
-
error
|
|
676
|
-
);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
157
|
+
client;
|
|
158
|
+
filterTranslator;
|
|
159
|
+
createIndexCache = /* @__PURE__ */ new Map();
|
|
160
|
+
opts;
|
|
161
|
+
constructor(opts) {
|
|
162
|
+
super({ id: opts.id });
|
|
163
|
+
this.filterTranslator = new TurbopufferFilterTranslator();
|
|
164
|
+
this.opts = opts;
|
|
165
|
+
this.client = new Turbopuffer(opts);
|
|
166
|
+
}
|
|
167
|
+
async createIndex({ indexName, dimension, metric }) {
|
|
168
|
+
metric = metric ?? "cosine";
|
|
169
|
+
let distanceMetric = "cosine_distance";
|
|
170
|
+
try {
|
|
171
|
+
if (this.createIndexCache.has(indexName)) {
|
|
172
|
+
const expected = this.createIndexCache.get(indexName);
|
|
173
|
+
if (dimension !== expected.dimension || metric !== expected.metric) throw new Error(`createIndex() called more than once with inconsistent inputs. Index ${indexName} expected dimensions=${expected.dimension} and metric=${expected.metric} but got dimensions=${dimension} and metric=${metric}`);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (dimension <= 0) throw new Error("Dimension must be a positive integer");
|
|
177
|
+
switch (metric) {
|
|
178
|
+
case "cosine":
|
|
179
|
+
distanceMetric = "cosine_distance";
|
|
180
|
+
break;
|
|
181
|
+
case "euclidean":
|
|
182
|
+
distanceMetric = "euclidean_squared";
|
|
183
|
+
break;
|
|
184
|
+
case "dotproduct": throw new Error("dotproduct is not supported in Turbopuffer");
|
|
185
|
+
}
|
|
186
|
+
} catch (error) {
|
|
187
|
+
throw new MastraError({
|
|
188
|
+
id: createVectorErrorId("TURBOPUFFER", "CREATE_INDEX", "INVALID_ARGS"),
|
|
189
|
+
domain: ErrorDomain.STORAGE,
|
|
190
|
+
category: ErrorCategory.USER,
|
|
191
|
+
details: {
|
|
192
|
+
indexName,
|
|
193
|
+
dimension,
|
|
194
|
+
metric
|
|
195
|
+
}
|
|
196
|
+
}, error);
|
|
197
|
+
}
|
|
198
|
+
this.createIndexCache.set(indexName, {
|
|
199
|
+
indexName,
|
|
200
|
+
dimension,
|
|
201
|
+
metric,
|
|
202
|
+
tpufDistanceMetric: distanceMetric
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
async upsert({ indexName, vectors, metadata, ids }) {
|
|
206
|
+
let index;
|
|
207
|
+
let createIndex;
|
|
208
|
+
try {
|
|
209
|
+
if (vectors.length === 0) throw new Error("upsert() called with empty vectors");
|
|
210
|
+
index = this.client.namespace(indexName);
|
|
211
|
+
createIndex = this.createIndexCache.get(indexName);
|
|
212
|
+
if (!createIndex) throw new Error(`createIndex() not called for this index`);
|
|
213
|
+
} catch (error) {
|
|
214
|
+
throw new MastraError({
|
|
215
|
+
id: createVectorErrorId("TURBOPUFFER", "UPSERT", "INVALID_ARGS"),
|
|
216
|
+
domain: ErrorDomain.STORAGE,
|
|
217
|
+
category: ErrorCategory.USER,
|
|
218
|
+
details: { indexName }
|
|
219
|
+
}, error);
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
const distanceMetric = createIndex.tpufDistanceMetric;
|
|
223
|
+
const vectorIds = ids || vectors.map(() => crypto.randomUUID());
|
|
224
|
+
const records = vectors.map((vector, i) => ({
|
|
225
|
+
id: vectorIds[i],
|
|
226
|
+
vector,
|
|
227
|
+
...metadata?.[i] || {}
|
|
228
|
+
}));
|
|
229
|
+
const batchSize = 100;
|
|
230
|
+
for (let i = 0; i < records.length; i += batchSize) {
|
|
231
|
+
const writeOptions = {
|
|
232
|
+
upsert_rows: records.slice(i, i + batchSize),
|
|
233
|
+
distance_metric: distanceMetric
|
|
234
|
+
};
|
|
235
|
+
const schemaConfig = this.opts.schemaConfigForIndex?.(indexName);
|
|
236
|
+
if (schemaConfig) {
|
|
237
|
+
writeOptions.schema = schemaConfig.schema;
|
|
238
|
+
if (vectors[0]?.length !== schemaConfig.dimensions) throw new Error(`Turbopuffer index ${indexName} was configured with dimensions=${schemaConfig.dimensions} but attempting to upsert vectors[0].length=${vectors[0]?.length}`);
|
|
239
|
+
}
|
|
240
|
+
await index.write(writeOptions);
|
|
241
|
+
}
|
|
242
|
+
return vectorIds;
|
|
243
|
+
} catch (error) {
|
|
244
|
+
throw new MastraError({
|
|
245
|
+
id: createVectorErrorId("TURBOPUFFER", "UPSERT", "FAILED"),
|
|
246
|
+
domain: ErrorDomain.STORAGE,
|
|
247
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
248
|
+
details: { indexName }
|
|
249
|
+
}, error);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
async query({ indexName, queryVector, topK, filter, includeVector, consistency }) {
|
|
253
|
+
if (!queryVector) throw new MastraError({
|
|
254
|
+
id: createVectorErrorId("TURBOPUFFER", "QUERY", "MISSING_VECTOR"),
|
|
255
|
+
text: "queryVector is required for Turbopuffer queries. Metadata-only queries are not supported by this vector store.",
|
|
256
|
+
domain: ErrorDomain.STORAGE,
|
|
257
|
+
category: ErrorCategory.USER,
|
|
258
|
+
details: { indexName }
|
|
259
|
+
});
|
|
260
|
+
let createIndex;
|
|
261
|
+
try {
|
|
262
|
+
const schemaConfig = this.opts.schemaConfigForIndex?.(indexName);
|
|
263
|
+
if (schemaConfig) {
|
|
264
|
+
if (queryVector.length !== schemaConfig.dimensions) throw new Error(`Turbopuffer index ${indexName} was configured with dimensions=${schemaConfig.dimensions} but attempting to query with queryVector.length=${queryVector.length}`);
|
|
265
|
+
}
|
|
266
|
+
createIndex = this.createIndexCache.get(indexName);
|
|
267
|
+
if (!createIndex) throw new Error(`createIndex() not called for this index`);
|
|
268
|
+
} catch (error) {
|
|
269
|
+
throw new MastraError({
|
|
270
|
+
id: createVectorErrorId("TURBOPUFFER", "QUERY", "INVALID_ARGS"),
|
|
271
|
+
domain: ErrorDomain.STORAGE,
|
|
272
|
+
category: ErrorCategory.USER,
|
|
273
|
+
details: { indexName }
|
|
274
|
+
}, error);
|
|
275
|
+
}
|
|
276
|
+
const distanceMetric = createIndex.tpufDistanceMetric;
|
|
277
|
+
try {
|
|
278
|
+
const index = this.client.namespace(indexName);
|
|
279
|
+
const translatedFilter = this.filterTranslator.translate(filter);
|
|
280
|
+
return ((await index.query({
|
|
281
|
+
distance_metric: distanceMetric,
|
|
282
|
+
rank_by: [
|
|
283
|
+
"vector",
|
|
284
|
+
"ANN",
|
|
285
|
+
queryVector
|
|
286
|
+
],
|
|
287
|
+
top_k: topK,
|
|
288
|
+
filters: translatedFilter,
|
|
289
|
+
vector_encoding: includeVector ? "float" : void 0,
|
|
290
|
+
include_attributes: true,
|
|
291
|
+
consistency: { level: consistency ?? this.opts.consistency ?? "strong" }
|
|
292
|
+
})).rows ?? []).map((item) => {
|
|
293
|
+
const { id, vector, $dist, ...metadata } = item;
|
|
294
|
+
return {
|
|
295
|
+
id: String(id),
|
|
296
|
+
score: typeof $dist === "number" ? $dist : 0,
|
|
297
|
+
metadata,
|
|
298
|
+
...includeVector && Array.isArray(vector) ? { vector } : {}
|
|
299
|
+
};
|
|
300
|
+
});
|
|
301
|
+
} catch (error) {
|
|
302
|
+
throw new MastraError({
|
|
303
|
+
id: createVectorErrorId("TURBOPUFFER", "QUERY", "FAILED"),
|
|
304
|
+
domain: ErrorDomain.STORAGE,
|
|
305
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
306
|
+
details: { indexName }
|
|
307
|
+
}, error);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
async listIndexes() {
|
|
311
|
+
try {
|
|
312
|
+
return (await this.client.namespaces({})).namespaces.map((namespace) => namespace.id);
|
|
313
|
+
} catch (error) {
|
|
314
|
+
throw new MastraError({
|
|
315
|
+
id: createVectorErrorId("TURBOPUFFER", "LIST_INDEXES", "FAILED"),
|
|
316
|
+
domain: ErrorDomain.STORAGE,
|
|
317
|
+
category: ErrorCategory.THIRD_PARTY
|
|
318
|
+
}, error);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Retrieves statistics about a vector index.
|
|
323
|
+
*
|
|
324
|
+
* @param {string} indexName - The name of the index to describe
|
|
325
|
+
* @returns A promise that resolves to the index statistics including dimension, count and metric
|
|
326
|
+
*/
|
|
327
|
+
async describeIndex({ indexName }) {
|
|
328
|
+
try {
|
|
329
|
+
const metadata = await this.client.namespace(indexName).metadata();
|
|
330
|
+
const createIndex = this.createIndexCache.get(indexName);
|
|
331
|
+
if (!createIndex) throw new Error(`createIndex() not called for this index`);
|
|
332
|
+
return {
|
|
333
|
+
dimension: createIndex.dimension,
|
|
334
|
+
count: metadata.approx_row_count,
|
|
335
|
+
metric: createIndex.metric
|
|
336
|
+
};
|
|
337
|
+
} catch (error) {
|
|
338
|
+
throw new MastraError({
|
|
339
|
+
id: createVectorErrorId("TURBOPUFFER", "DESCRIBE_INDEX", "FAILED"),
|
|
340
|
+
domain: ErrorDomain.STORAGE,
|
|
341
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
342
|
+
details: { indexName }
|
|
343
|
+
}, error);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
async deleteIndex({ indexName }) {
|
|
347
|
+
try {
|
|
348
|
+
await this.client.namespace(indexName).deleteAll();
|
|
349
|
+
this.createIndexCache.delete(indexName);
|
|
350
|
+
} catch (error) {
|
|
351
|
+
throw new MastraError({
|
|
352
|
+
id: createVectorErrorId("TURBOPUFFER", "DELETE_INDEX", "FAILED"),
|
|
353
|
+
domain: ErrorDomain.STORAGE,
|
|
354
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
355
|
+
details: { indexName }
|
|
356
|
+
}, error);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Updates a vector by its ID or filter with the provided vector and/or metadata.
|
|
361
|
+
* @param indexName - The name of the index containing the vector.
|
|
362
|
+
* @param id - The ID of the vector to update.
|
|
363
|
+
* @param filter - The filter to match vectors to update.
|
|
364
|
+
* @param update - An object containing the vector and/or metadata to update.
|
|
365
|
+
* @param update.vector - An optional array of numbers representing the new vector.
|
|
366
|
+
* @param update.metadata - An optional record containing the new metadata.
|
|
367
|
+
* @returns A promise that resolves when the update is complete.
|
|
368
|
+
* @throws Will throw an error if no updates are provided or if the update operation fails.
|
|
369
|
+
*/
|
|
370
|
+
async updateVector({ indexName, id, filter, update }) {
|
|
371
|
+
if (id && filter) throw new MastraError({
|
|
372
|
+
id: createVectorErrorId("TURBOPUFFER", "UPDATE_VECTOR", "MUTUALLY_EXCLUSIVE"),
|
|
373
|
+
domain: ErrorDomain.STORAGE,
|
|
374
|
+
category: ErrorCategory.USER,
|
|
375
|
+
text: "id and filter are mutually exclusive",
|
|
376
|
+
details: { indexName }
|
|
377
|
+
});
|
|
378
|
+
if (!id && !filter) throw new MastraError({
|
|
379
|
+
id: createVectorErrorId("TURBOPUFFER", "UPDATE_VECTOR", "NO_TARGET"),
|
|
380
|
+
domain: ErrorDomain.STORAGE,
|
|
381
|
+
category: ErrorCategory.USER,
|
|
382
|
+
text: "Either id or filter must be provided",
|
|
383
|
+
details: { indexName }
|
|
384
|
+
});
|
|
385
|
+
if (!update.vector && !update.metadata) throw new MastraError({
|
|
386
|
+
id: createVectorErrorId("TURBOPUFFER", "UPDATE_VECTOR", "NO_PAYLOAD"),
|
|
387
|
+
domain: ErrorDomain.STORAGE,
|
|
388
|
+
category: ErrorCategory.USER,
|
|
389
|
+
text: "No update data provided",
|
|
390
|
+
details: {
|
|
391
|
+
indexName,
|
|
392
|
+
...id && { id }
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
let namespace;
|
|
396
|
+
let createIndex;
|
|
397
|
+
let distanceMetric;
|
|
398
|
+
try {
|
|
399
|
+
namespace = this.client.namespace(indexName);
|
|
400
|
+
createIndex = this.createIndexCache.get(indexName);
|
|
401
|
+
if (!createIndex) throw new Error(`createIndex() not called for this index`);
|
|
402
|
+
distanceMetric = createIndex.tpufDistanceMetric;
|
|
403
|
+
} catch (error) {
|
|
404
|
+
throw new MastraError({
|
|
405
|
+
id: createVectorErrorId("TURBOPUFFER", "UPDATE_VECTOR", "INVALID_ARGS"),
|
|
406
|
+
domain: ErrorDomain.STORAGE,
|
|
407
|
+
category: ErrorCategory.USER,
|
|
408
|
+
details: { indexName }
|
|
409
|
+
}, error);
|
|
410
|
+
}
|
|
411
|
+
try {
|
|
412
|
+
let idsToUpdate = [];
|
|
413
|
+
if (id) idsToUpdate = [id];
|
|
414
|
+
else if (filter) {
|
|
415
|
+
if (Object.keys(filter).length === 0) throw new MastraError({
|
|
416
|
+
id: createVectorErrorId("TURBOPUFFER", "UPDATE_VECTOR", "EMPTY_FILTER"),
|
|
417
|
+
domain: ErrorDomain.STORAGE,
|
|
418
|
+
category: ErrorCategory.USER,
|
|
419
|
+
text: "Filter cannot be an empty object",
|
|
420
|
+
details: { indexName }
|
|
421
|
+
});
|
|
422
|
+
const dummyVector = new Array(createIndex.dimension).fill(1 / Math.sqrt(createIndex.dimension));
|
|
423
|
+
const translatedFilter = this.filterTranslator.translate(filter);
|
|
424
|
+
const rows = (await namespace.query({
|
|
425
|
+
rank_by: [
|
|
426
|
+
"vector",
|
|
427
|
+
"ANN",
|
|
428
|
+
dummyVector
|
|
429
|
+
],
|
|
430
|
+
top_k: 1e4,
|
|
431
|
+
filters: translatedFilter,
|
|
432
|
+
vector_encoding: update.vector ? void 0 : "float",
|
|
433
|
+
include_attributes: true
|
|
434
|
+
})).rows ?? [];
|
|
435
|
+
idsToUpdate = rows.map((r) => String(r.id));
|
|
436
|
+
if (!update.vector || !update.metadata) {
|
|
437
|
+
for (const result of rows) {
|
|
438
|
+
const { id: resultId, vector, $dist, ...metadata } = result;
|
|
439
|
+
const record = { id: resultId };
|
|
440
|
+
if (update.vector) record.vector = update.vector;
|
|
441
|
+
else if (Array.isArray(vector)) record.vector = vector;
|
|
442
|
+
if (update.metadata) Object.assign(record, update.metadata);
|
|
443
|
+
else Object.assign(record, metadata);
|
|
444
|
+
await namespace.write({
|
|
445
|
+
upsert_rows: [record],
|
|
446
|
+
distance_metric: distanceMetric
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (idsToUpdate.length === 0) {
|
|
453
|
+
this.logger.info(`No vectors matched the criteria for update in ${indexName}`);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
const records = idsToUpdate.map((vecId) => ({
|
|
457
|
+
id: vecId,
|
|
458
|
+
...update.vector ? { vector: update.vector } : {},
|
|
459
|
+
...update.metadata || {}
|
|
460
|
+
}));
|
|
461
|
+
const batchSize = 1e3;
|
|
462
|
+
for (let i = 0; i < records.length; i += batchSize) {
|
|
463
|
+
const batch = records.slice(i, i + batchSize);
|
|
464
|
+
await namespace.write({
|
|
465
|
+
upsert_rows: batch,
|
|
466
|
+
distance_metric: distanceMetric
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
} catch (error) {
|
|
470
|
+
if (error instanceof MastraError) throw error;
|
|
471
|
+
throw new MastraError({
|
|
472
|
+
id: createVectorErrorId("TURBOPUFFER", "UPDATE_VECTOR", "FAILED"),
|
|
473
|
+
domain: ErrorDomain.STORAGE,
|
|
474
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
475
|
+
details: {
|
|
476
|
+
indexName,
|
|
477
|
+
...id && { id },
|
|
478
|
+
...filter && { filter: JSON.stringify(filter) }
|
|
479
|
+
}
|
|
480
|
+
}, error);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Deletes a vector by its ID.
|
|
485
|
+
* @param indexName - The name of the index containing the vector.
|
|
486
|
+
* @param id - The ID of the vector to delete.
|
|
487
|
+
* @returns A promise that resolves when the deletion is complete.
|
|
488
|
+
* @throws Will throw an error if the deletion operation fails.
|
|
489
|
+
*/
|
|
490
|
+
async deleteVector({ indexName, id }) {
|
|
491
|
+
try {
|
|
492
|
+
await this.client.namespace(indexName).write({ deletes: [id] });
|
|
493
|
+
} catch (error) {
|
|
494
|
+
throw new MastraError({
|
|
495
|
+
id: createVectorErrorId("TURBOPUFFER", "DELETE_VECTOR", "FAILED"),
|
|
496
|
+
domain: ErrorDomain.STORAGE,
|
|
497
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
498
|
+
details: { indexName }
|
|
499
|
+
}, error);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
async deleteVectors({ indexName, filter, ids }) {
|
|
503
|
+
if (ids && filter) throw new MastraError({
|
|
504
|
+
id: createVectorErrorId("TURBOPUFFER", "DELETE_VECTORS", "MUTUALLY_EXCLUSIVE"),
|
|
505
|
+
domain: ErrorDomain.STORAGE,
|
|
506
|
+
category: ErrorCategory.USER,
|
|
507
|
+
text: "ids and filter are mutually exclusive",
|
|
508
|
+
details: { indexName }
|
|
509
|
+
});
|
|
510
|
+
if (!ids && !filter) throw new MastraError({
|
|
511
|
+
id: createVectorErrorId("TURBOPUFFER", "DELETE_VECTORS", "NO_TARGET"),
|
|
512
|
+
domain: ErrorDomain.STORAGE,
|
|
513
|
+
category: ErrorCategory.USER,
|
|
514
|
+
text: "Either filter or ids must be provided",
|
|
515
|
+
details: { indexName }
|
|
516
|
+
});
|
|
517
|
+
if (ids && ids.length === 0) throw new MastraError({
|
|
518
|
+
id: createVectorErrorId("TURBOPUFFER", "DELETE_VECTORS", "EMPTY_IDS"),
|
|
519
|
+
domain: ErrorDomain.STORAGE,
|
|
520
|
+
category: ErrorCategory.USER,
|
|
521
|
+
text: "ids array cannot be empty",
|
|
522
|
+
details: { indexName }
|
|
523
|
+
});
|
|
524
|
+
if (filter && Object.keys(filter).length === 0) throw new MastraError({
|
|
525
|
+
id: createVectorErrorId("TURBOPUFFER", "DELETE_VECTORS", "EMPTY_FILTER"),
|
|
526
|
+
domain: ErrorDomain.STORAGE,
|
|
527
|
+
category: ErrorCategory.USER,
|
|
528
|
+
text: "Filter cannot be an empty object",
|
|
529
|
+
details: { indexName }
|
|
530
|
+
});
|
|
531
|
+
try {
|
|
532
|
+
const namespace = this.client.namespace(indexName);
|
|
533
|
+
let idsToDelete = [];
|
|
534
|
+
if (ids) idsToDelete = ids;
|
|
535
|
+
else if (filter) {
|
|
536
|
+
const createIndex = this.createIndexCache.get(indexName);
|
|
537
|
+
if (!createIndex) throw new Error(`createIndex() not called for this index`);
|
|
538
|
+
const dummyVector = new Array(createIndex.dimension).fill(1 / Math.sqrt(createIndex.dimension));
|
|
539
|
+
const translatedFilter = this.filterTranslator.translate(filter);
|
|
540
|
+
idsToDelete = ((await namespace.query({
|
|
541
|
+
rank_by: [
|
|
542
|
+
"vector",
|
|
543
|
+
"ANN",
|
|
544
|
+
dummyVector
|
|
545
|
+
],
|
|
546
|
+
top_k: 1e4,
|
|
547
|
+
filters: translatedFilter,
|
|
548
|
+
include_attributes: []
|
|
549
|
+
})).rows ?? []).map((r) => String(r.id));
|
|
550
|
+
}
|
|
551
|
+
if (idsToDelete.length === 0) {
|
|
552
|
+
this.logger.info(`No vectors matched the criteria for deletion in ${indexName}`);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const batchSize = 1e3;
|
|
556
|
+
for (let i = 0; i < idsToDelete.length; i += batchSize) {
|
|
557
|
+
const batch = idsToDelete.slice(i, i + batchSize);
|
|
558
|
+
await namespace.write({ deletes: batch });
|
|
559
|
+
}
|
|
560
|
+
} catch (error) {
|
|
561
|
+
if (error instanceof MastraError) throw error;
|
|
562
|
+
throw new MastraError({
|
|
563
|
+
id: createVectorErrorId("TURBOPUFFER", "DELETE_VECTORS", "FAILED"),
|
|
564
|
+
domain: ErrorDomain.STORAGE,
|
|
565
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
566
|
+
details: {
|
|
567
|
+
indexName,
|
|
568
|
+
...filter && { filter: JSON.stringify(filter) },
|
|
569
|
+
...ids && { idsCount: ids.length }
|
|
570
|
+
}
|
|
571
|
+
}, error);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
679
574
|
};
|
|
680
|
-
|
|
575
|
+
//#endregion
|
|
681
576
|
export { TurbopufferVector };
|
|
682
|
-
|
|
577
|
+
|
|
683
578
|
//# sourceMappingURL=index.js.map
|