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