@mastra/mongodb 0.11.0 → 0.11.1-alpha.1

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.
@@ -1,3 +1,4 @@
1
+ import { MastraError, ErrorDomain, ErrorCategory } from '@mastra/core/error';
1
2
  import { MastraVector } from '@mastra/core/vector';
2
3
  import type {
3
4
  QueryResult,
@@ -64,36 +65,76 @@ export class MongoDBVector extends MastraVector {
64
65
 
65
66
  // Public methods
66
67
  async connect(): Promise<void> {
67
- await this.client.connect();
68
+ try {
69
+ await this.client.connect();
70
+ } catch (error) {
71
+ throw new MastraError(
72
+ {
73
+ id: 'STORAGE_MONGODB_VECTOR_CONNECT_FAILED',
74
+ domain: ErrorDomain.STORAGE,
75
+ category: ErrorCategory.THIRD_PARTY,
76
+ },
77
+ error,
78
+ );
79
+ }
68
80
  }
69
81
 
70
82
  async disconnect(): Promise<void> {
71
- await this.client.close();
83
+ try {
84
+ await this.client.close();
85
+ } catch (error) {
86
+ throw new MastraError(
87
+ {
88
+ id: 'STORAGE_MONGODB_VECTOR_DISCONNECT_FAILED',
89
+ domain: ErrorDomain.STORAGE,
90
+ category: ErrorCategory.THIRD_PARTY,
91
+ },
92
+ error,
93
+ );
94
+ }
72
95
  }
73
96
 
74
97
  async createIndex({ indexName, dimension, metric = 'cosine' }: CreateIndexParams): Promise<void> {
75
- if (!Number.isInteger(dimension) || dimension <= 0) {
76
- throw new Error('Dimension must be a positive integer');
77
- }
98
+ let mongoMetric;
99
+ try {
100
+ if (!Number.isInteger(dimension) || dimension <= 0) {
101
+ throw new Error('Dimension must be a positive integer');
102
+ }
78
103
 
79
- const mongoMetric = this.mongoMetricMap[metric];
80
- if (!mongoMetric) {
81
- throw new Error(`Invalid metric: "${metric}". Must be one of: cosine, euclidean, dotproduct`);
104
+ mongoMetric = this.mongoMetricMap[metric];
105
+ if (!mongoMetric) {
106
+ throw new Error(`Invalid metric: "${metric}". Must be one of: cosine, euclidean, dotproduct`);
107
+ }
108
+ } catch (error) {
109
+ throw new MastraError(
110
+ {
111
+ id: 'STORAGE_MONGODB_VECTOR_CREATE_INDEX_INVALID_ARGS',
112
+ domain: ErrorDomain.STORAGE,
113
+ category: ErrorCategory.USER,
114
+ details: {
115
+ indexName,
116
+ dimension,
117
+ metric,
118
+ },
119
+ },
120
+ error,
121
+ );
82
122
  }
83
123
 
84
- // Check if collection exists
85
- const collectionExists = await this.db.listCollections({ name: indexName }).hasNext();
86
- if (!collectionExists) {
87
- await this.db.createCollection(indexName);
88
- }
89
- const collection = await this.getCollection(indexName);
124
+ let collection;
125
+ try {
126
+ // Check if collection exists
127
+ const collectionExists = await this.db.listCollections({ name: indexName }).hasNext();
128
+ if (!collectionExists) {
129
+ await this.db.createCollection(indexName);
130
+ }
131
+ collection = await this.getCollection(indexName);
90
132
 
91
- const indexNameInternal = `${indexName}_vector_index`;
133
+ const indexNameInternal = `${indexName}_vector_index`;
92
134
 
93
- const embeddingField = this.embeddingFieldName;
94
- const numDimensions = dimension;
135
+ const embeddingField = this.embeddingFieldName;
136
+ const numDimensions = dimension;
95
137
 
96
- try {
97
138
  // Create the search index
98
139
  await (collection as any).createSearchIndex({
99
140
  definition: {
@@ -111,12 +152,33 @@ export class MongoDBVector extends MastraVector {
111
152
  });
112
153
  } catch (error: any) {
113
154
  if (error.codeName !== 'IndexAlreadyExists') {
114
- throw error;
155
+ throw new MastraError(
156
+ {
157
+ id: 'STORAGE_MONGODB_VECTOR_CREATE_INDEX_FAILED',
158
+ domain: ErrorDomain.STORAGE,
159
+ category: ErrorCategory.THIRD_PARTY,
160
+ },
161
+ error,
162
+ );
115
163
  }
116
164
  }
117
165
 
118
- // Store the dimension and metric in a special metadata document
119
- await collection.updateOne({ _id: '__index_metadata__' }, { $set: { dimension, metric } }, { upsert: true });
166
+ try {
167
+ // Store the dimension and metric in a special metadata document
168
+ await collection?.updateOne({ _id: '__index_metadata__' }, { $set: { dimension, metric } }, { upsert: true });
169
+ } catch (error) {
170
+ throw new MastraError(
171
+ {
172
+ id: 'STORAGE_MONGODB_VECTOR_CREATE_INDEX_FAILED_STORE_METADATA',
173
+ domain: ErrorDomain.STORAGE,
174
+ category: ErrorCategory.THIRD_PARTY,
175
+ details: {
176
+ indexName,
177
+ },
178
+ },
179
+ error,
180
+ );
181
+ }
120
182
  }
121
183
 
122
184
  /**
@@ -149,55 +211,68 @@ export class MongoDBVector extends MastraVector {
149
211
  }
150
212
 
151
213
  async upsert({ indexName, vectors, metadata, ids, documents }: MongoDBUpsertVectorParams): Promise<string[]> {
152
- const collection = await this.getCollection(indexName);
153
-
154
- this.collectionForValidation = collection;
214
+ try {
215
+ const collection = await this.getCollection(indexName);
155
216
 
156
- // Get index stats to check dimension
157
- const stats = await this.describeIndex({ indexName });
217
+ this.collectionForValidation = collection;
158
218
 
159
- // Validate vector dimensions
160
- await this.validateVectorDimensions(vectors, stats.dimension);
219
+ // Get index stats to check dimension
220
+ const stats = await this.describeIndex({ indexName });
161
221
 
162
- // Generate IDs if not provided
163
- const generatedIds = ids || vectors.map(() => uuidv4());
222
+ // Validate vector dimensions
223
+ await this.validateVectorDimensions(vectors, stats.dimension);
164
224
 
165
- const operations = vectors.map((vector, idx) => {
166
- const id = generatedIds[idx];
167
- const meta = metadata?.[idx] || {};
168
- const doc = documents?.[idx];
225
+ // Generate IDs if not provided
226
+ const generatedIds = ids || vectors.map(() => uuidv4());
169
227
 
170
- // Normalize metadata - convert Date objects to ISO strings
171
- const normalizedMeta = Object.keys(meta).reduce(
172
- (acc, key) => {
173
- acc[key] = meta[key] instanceof Date ? meta[key].toISOString() : meta[key];
174
- return acc;
175
- },
176
- {} as Record<string, any>,
177
- );
228
+ const operations = vectors.map((vector, idx) => {
229
+ const id = generatedIds[idx];
230
+ const meta = metadata?.[idx] || {};
231
+ const doc = documents?.[idx];
178
232
 
179
- const updateDoc: Partial<MongoDBDocument> = {
180
- [this.embeddingFieldName]: vector,
181
- [this.metadataFieldName]: normalizedMeta,
182
- };
183
- if (doc !== undefined) {
184
- updateDoc[this.documentFieldName] = doc;
185
- }
233
+ // Normalize metadata - convert Date objects to ISO strings
234
+ const normalizedMeta = Object.keys(meta).reduce(
235
+ (acc, key) => {
236
+ acc[key] = meta[key] instanceof Date ? meta[key].toISOString() : meta[key];
237
+ return acc;
238
+ },
239
+ {} as Record<string, any>,
240
+ );
186
241
 
187
- return {
188
- updateOne: {
189
- filter: { _id: id }, // '_id' is a string as per MongoDBDocument interface
190
- update: { $set: updateDoc },
191
- upsert: true,
192
- },
193
- };
194
- });
242
+ const updateDoc: Partial<MongoDBDocument> = {
243
+ [this.embeddingFieldName]: vector,
244
+ [this.metadataFieldName]: normalizedMeta,
245
+ };
246
+ if (doc !== undefined) {
247
+ updateDoc[this.documentFieldName] = doc;
248
+ }
249
+
250
+ return {
251
+ updateOne: {
252
+ filter: { _id: id }, // '_id' is a string as per MongoDBDocument interface
253
+ update: { $set: updateDoc },
254
+ upsert: true,
255
+ },
256
+ };
257
+ });
195
258
 
196
- await collection.bulkWrite(operations);
259
+ await collection.bulkWrite(operations);
197
260
 
198
- return generatedIds;
261
+ return generatedIds;
262
+ } catch (error) {
263
+ throw new MastraError(
264
+ {
265
+ id: 'STORAGE_MONGODB_VECTOR_UPSERT_FAILED',
266
+ domain: ErrorDomain.STORAGE,
267
+ category: ErrorCategory.THIRD_PARTY,
268
+ details: {
269
+ indexName,
270
+ },
271
+ },
272
+ error,
273
+ );
274
+ }
199
275
  }
200
-
201
276
  async query({
202
277
  indexName,
203
278
  queryVector,
@@ -206,51 +281,51 @@ export class MongoDBVector extends MastraVector {
206
281
  includeVector = false,
207
282
  documentFilter,
208
283
  }: MongoDBQueryVectorParams): Promise<QueryResult[]> {
209
- const collection = await this.getCollection(indexName, true);
210
- const indexNameInternal = `${indexName}_vector_index`;
211
-
212
- // Transform the filters using MongoDBFilterTranslator
213
- const mongoFilter = this.transformFilter(filter);
214
- const documentMongoFilter = documentFilter ? { [this.documentFieldName]: documentFilter } : {};
215
-
216
- // Combine the filters
217
- let combinedFilter: any = {};
218
- if (Object.keys(mongoFilter).length > 0 && Object.keys(documentMongoFilter).length > 0) {
219
- combinedFilter = { $and: [mongoFilter, documentMongoFilter] };
220
- } else if (Object.keys(mongoFilter).length > 0) {
221
- combinedFilter = mongoFilter;
222
- } else if (Object.keys(documentMongoFilter).length > 0) {
223
- combinedFilter = documentMongoFilter;
224
- }
284
+ try {
285
+ const collection = await this.getCollection(indexName, true);
286
+ const indexNameInternal = `${indexName}_vector_index`;
287
+
288
+ // Transform the filters using MongoDBFilterTranslator
289
+ const mongoFilter = this.transformFilter(filter);
290
+ const documentMongoFilter = documentFilter ? { [this.documentFieldName]: documentFilter } : {};
291
+
292
+ // Combine the filters
293
+ let combinedFilter: any = {};
294
+ if (Object.keys(mongoFilter).length > 0 && Object.keys(documentMongoFilter).length > 0) {
295
+ combinedFilter = { $and: [mongoFilter, documentMongoFilter] };
296
+ } else if (Object.keys(mongoFilter).length > 0) {
297
+ combinedFilter = mongoFilter;
298
+ } else if (Object.keys(documentMongoFilter).length > 0) {
299
+ combinedFilter = documentMongoFilter;
300
+ }
225
301
 
226
- // Build the aggregation pipeline
227
- const pipeline = [
228
- {
229
- $vectorSearch: {
230
- index: indexNameInternal,
231
- queryVector: queryVector,
232
- path: this.embeddingFieldName,
233
- numCandidates: 100,
234
- limit: topK,
302
+ // Build the aggregation pipeline
303
+ const pipeline = [
304
+ {
305
+ $vectorSearch: {
306
+ index: indexNameInternal,
307
+ queryVector: queryVector,
308
+ path: this.embeddingFieldName,
309
+ numCandidates: 100,
310
+ limit: topK,
311
+ },
312
+ },
313
+ // Apply the filter using $match stage
314
+ ...(Object.keys(combinedFilter).length > 0 ? [{ $match: combinedFilter }] : []),
315
+ {
316
+ $set: { score: { $meta: 'vectorSearchScore' } },
235
317
  },
236
- },
237
- // Apply the filter using $match stage
238
- ...(Object.keys(combinedFilter).length > 0 ? [{ $match: combinedFilter }] : []),
239
- {
240
- $set: { score: { $meta: 'vectorSearchScore' } },
241
- },
242
- {
243
- $project: {
244
- _id: 1,
245
- score: 1,
246
- metadata: `$${this.metadataFieldName}`,
247
- document: `$${this.documentFieldName}`,
248
- ...(includeVector && { vector: `$${this.embeddingFieldName}` }),
318
+ {
319
+ $project: {
320
+ _id: 1,
321
+ score: 1,
322
+ metadata: `$${this.metadataFieldName}`,
323
+ document: `$${this.documentFieldName}`,
324
+ ...(includeVector && { vector: `$${this.embeddingFieldName}` }),
325
+ },
249
326
  },
250
- },
251
- ];
327
+ ];
252
328
 
253
- try {
254
329
  const results = await collection.aggregate(pipeline).toArray();
255
330
 
256
331
  return results.map((result: any) => ({
@@ -261,14 +336,34 @@ export class MongoDBVector extends MastraVector {
261
336
  document: result.document,
262
337
  }));
263
338
  } catch (error) {
264
- console.error('Error during vector search:', error);
265
- throw error;
339
+ throw new MastraError(
340
+ {
341
+ id: 'STORAGE_MONGODB_VECTOR_QUERY_FAILED',
342
+ domain: ErrorDomain.STORAGE,
343
+ category: ErrorCategory.THIRD_PARTY,
344
+ details: {
345
+ indexName,
346
+ },
347
+ },
348
+ error,
349
+ );
266
350
  }
267
351
  }
268
352
 
269
353
  async listIndexes(): Promise<string[]> {
270
- const collections = await this.db.listCollections().toArray();
271
- return collections.map(col => col.name);
354
+ try {
355
+ const collections = await this.db.listCollections().toArray();
356
+ return collections.map(col => col.name);
357
+ } catch (error) {
358
+ throw new MastraError(
359
+ {
360
+ id: 'STORAGE_MONGODB_VECTOR_LIST_INDEXES_FAILED',
361
+ domain: ErrorDomain.STORAGE,
362
+ category: ErrorCategory.THIRD_PARTY,
363
+ },
364
+ error,
365
+ );
366
+ }
272
367
  }
273
368
 
274
369
  /**
@@ -278,31 +373,59 @@ export class MongoDBVector extends MastraVector {
278
373
  * @returns A promise that resolves to the index statistics including dimension, count and metric
279
374
  */
280
375
  async describeIndex({ indexName }: DescribeIndexParams): Promise<IndexStats> {
281
- const collection = await this.getCollection(indexName, true);
376
+ try {
377
+ const collection = await this.getCollection(indexName, true);
282
378
 
283
- // Get the count of documents, excluding the metadata document
284
- const count = await collection.countDocuments({ _id: { $ne: '__index_metadata__' } });
379
+ // Get the count of documents, excluding the metadata document
380
+ const count = await collection.countDocuments({ _id: { $ne: '__index_metadata__' } });
285
381
 
286
- // Retrieve the dimension and metric from the metadata document
287
- const metadataDoc = await collection.findOne({ _id: '__index_metadata__' });
288
- const dimension = metadataDoc?.dimension || 0;
289
- const metric = metadataDoc?.metric || 'cosine';
382
+ // Retrieve the dimension and metric from the metadata document
383
+ const metadataDoc = await collection.findOne({ _id: '__index_metadata__' });
384
+ const dimension = metadataDoc?.dimension || 0;
385
+ const metric = metadataDoc?.metric || 'cosine';
290
386
 
291
- return {
292
- dimension,
293
- count,
294
- metric: metric as 'cosine' | 'euclidean' | 'dotproduct',
295
- };
387
+ return {
388
+ dimension,
389
+ count,
390
+ metric: metric as 'cosine' | 'euclidean' | 'dotproduct',
391
+ };
392
+ } catch (error) {
393
+ throw new MastraError(
394
+ {
395
+ id: 'STORAGE_MONGODB_VECTOR_DESCRIBE_INDEX_FAILED',
396
+ domain: ErrorDomain.STORAGE,
397
+ category: ErrorCategory.THIRD_PARTY,
398
+ details: {
399
+ indexName,
400
+ },
401
+ },
402
+ error,
403
+ );
404
+ }
296
405
  }
297
406
 
298
407
  async deleteIndex({ indexName }: DeleteIndexParams): Promise<void> {
299
408
  const collection = await this.getCollection(indexName, false); // Do not throw error if collection doesn't exist
300
- if (collection) {
301
- await collection.drop();
302
- this.collections.delete(indexName);
303
- } else {
304
- // Optionally, you can log or handle the case where the collection doesn't exist
305
- throw new Error(`Index (Collection) "${indexName}" does not exist`);
409
+ try {
410
+ if (collection) {
411
+ await collection.drop();
412
+ this.collections.delete(indexName);
413
+ } else {
414
+ // Optionally, you can log or handle the case where the collection doesn't exist
415
+ throw new Error(`Index (Collection) "${indexName}" does not exist`);
416
+ }
417
+ } catch (error) {
418
+ throw new MastraError(
419
+ {
420
+ id: 'STORAGE_MONGODB_VECTOR_DELETE_INDEX_FAILED',
421
+ domain: ErrorDomain.STORAGE,
422
+ category: ErrorCategory.THIRD_PARTY,
423
+ details: {
424
+ indexName,
425
+ },
426
+ },
427
+ error,
428
+ );
306
429
  }
307
430
  }
308
431
 
@@ -347,7 +470,18 @@ export class MongoDBVector extends MastraVector {
347
470
 
348
471
  await collection.findOneAndUpdate({ _id: id }, { $set: updateDoc });
349
472
  } catch (error: any) {
350
- throw new Error(`Failed to update vector by id: ${id} for index name: ${indexName}: ${error.message}`);
473
+ throw new MastraError(
474
+ {
475
+ id: 'STORAGE_MONGODB_VECTOR_UPDATE_VECTOR_FAILED',
476
+ domain: ErrorDomain.STORAGE,
477
+ category: ErrorCategory.THIRD_PARTY,
478
+ details: {
479
+ indexName,
480
+ id,
481
+ },
482
+ },
483
+ error,
484
+ );
351
485
  }
352
486
  }
353
487
 
@@ -363,7 +497,18 @@ export class MongoDBVector extends MastraVector {
363
497
  const collection = await this.getCollection(indexName, true);
364
498
  await collection.deleteOne({ _id: id });
365
499
  } catch (error: any) {
366
- throw new Error(`Failed to delete vector by id: ${id} for index name: ${indexName}: ${error.message}`);
500
+ throw new MastraError(
501
+ {
502
+ id: 'STORAGE_MONGODB_VECTOR_DELETE_VECTOR_FAILED',
503
+ domain: ErrorDomain.STORAGE,
504
+ category: ErrorCategory.THIRD_PARTY,
505
+ details: {
506
+ indexName,
507
+ id,
508
+ },
509
+ },
510
+ error,
511
+ );
367
512
  }
368
513
  }
369
514