@mastra/mongodb 0.12.0 → 0.12.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,272 +1,175 @@
1
- import { MessageList } from '@mastra/core/agent';
2
1
  import type { MastraMessageContentV2 } from '@mastra/core/agent';
3
2
  import { ErrorDomain, ErrorCategory, MastraError } from '@mastra/core/error';
4
- import type { MetricResult, TestInfo } from '@mastra/core/eval';
5
3
  import type { MastraMessageV1, MastraMessageV2, StorageThreadType } from '@mastra/core/memory';
4
+ import type { ScoreRowData } from '@mastra/core/scores';
6
5
  import type {
7
6
  EvalRow,
7
+ PaginationArgs,
8
8
  PaginationInfo,
9
9
  StorageColumn,
10
10
  StorageGetMessagesArg,
11
11
  StorageGetTracesArg,
12
12
  TABLE_NAMES,
13
13
  WorkflowRun,
14
+ WorkflowRuns,
15
+ StorageResourceType,
16
+ StorageDomains,
17
+ StoragePagination,
18
+ StorageGetTracesPaginatedArg,
14
19
  } from '@mastra/core/storage';
15
- import {
16
- MastraStorage,
17
- TABLE_EVALS,
18
- TABLE_MESSAGES,
19
- TABLE_THREADS,
20
- TABLE_TRACES,
21
- TABLE_WORKFLOW_SNAPSHOT,
22
- } from '@mastra/core/storage';
20
+ import { MastraStorage } from '@mastra/core/storage';
23
21
  import type { Trace } from '@mastra/core/telemetry';
24
22
  import type { WorkflowRunState } from '@mastra/core/workflows';
25
- import type { Db, MongoClientOptions } from 'mongodb';
26
- import { MongoClient } from 'mongodb';
23
+ import { MongoDBConnector } from './connectors/MongoDBConnector';
24
+ import { LegacyEvalsMongoDB } from './domains/legacy-evals';
25
+ import { MemoryStorageMongoDB } from './domains/memory';
26
+ import { StoreOperationsMongoDB } from './domains/operations';
27
+ import { ScoresStorageMongoDB } from './domains/scores';
28
+ import { TracesStorageMongoDB } from './domains/traces';
29
+ import { WorkflowsStorageMongoDB } from './domains/workflows';
30
+ import type { MongoDBConfig } from './types';
27
31
 
28
- function safelyParseJSON(jsonString: string): any {
29
- try {
30
- return JSON.parse(jsonString);
31
- } catch {
32
- return {};
33
- }
34
- }
32
+ export class MongoDBStore extends MastraStorage {
33
+ #connector: MongoDBConnector;
35
34
 
36
- export interface MongoDBConfig {
37
- url: string;
38
- dbName: string;
39
- options?: MongoClientOptions;
40
- }
35
+ stores: StorageDomains;
41
36
 
42
- export class MongoDBStore extends MastraStorage {
43
- #isConnected = false;
44
- #client: MongoClient;
45
- #db: Db | undefined;
46
- readonly #dbName: string;
37
+ public get supports(): {
38
+ selectByIncludeResourceScope: boolean;
39
+ resourceWorkingMemory: boolean;
40
+ hasColumn: boolean;
41
+ createTable: boolean;
42
+ } {
43
+ return {
44
+ selectByIncludeResourceScope: true,
45
+ resourceWorkingMemory: true,
46
+ hasColumn: false,
47
+ createTable: false,
48
+ };
49
+ }
47
50
 
48
51
  constructor(config: MongoDBConfig) {
49
52
  super({ name: 'MongoDBStore' });
50
- this.#isConnected = false;
53
+
54
+ this.stores = {} as StorageDomains;
51
55
 
52
56
  try {
53
- if (!config.url?.trim().length) {
54
- throw new Error(
55
- 'MongoDBStore: url must be provided and cannot be empty. Passing an empty string may cause fallback to local MongoDB defaults.',
56
- );
57
+ if ('connectorHandler' in config) {
58
+ this.#connector = MongoDBConnector.fromConnectionHandler(config.connectorHandler);
59
+ return;
57
60
  }
61
+ } catch (error) {
62
+ throw new MastraError(
63
+ {
64
+ id: 'STORAGE_MONGODB_STORE_CONSTRUCTOR_FAILED',
65
+ domain: ErrorDomain.STORAGE,
66
+ category: ErrorCategory.USER,
67
+ details: { connectionHandler: true },
68
+ },
69
+ error,
70
+ );
71
+ }
58
72
 
59
- if (!config.dbName?.trim().length) {
60
- throw new Error(
61
- 'MongoDBStore: dbName must be provided and cannot be empty. Passing an empty string may cause fallback to local MongoDB defaults.',
62
- );
63
- }
73
+ try {
74
+ this.#connector = MongoDBConnector.fromDatabaseConfig({
75
+ options: config.options,
76
+ url: config.url,
77
+ dbName: config.dbName,
78
+ });
64
79
  } catch (error) {
65
80
  throw new MastraError(
66
81
  {
67
82
  id: 'STORAGE_MONGODB_STORE_CONSTRUCTOR_FAILED',
68
83
  domain: ErrorDomain.STORAGE,
69
84
  category: ErrorCategory.USER,
70
- details: { url: config.url, dbName: config.dbName },
85
+ details: { url: config?.url, dbName: config?.dbName },
71
86
  },
72
87
  error,
73
88
  );
74
89
  }
75
90
 
76
- this.#dbName = config.dbName;
77
- this.#client = new MongoClient(config.url, config.options);
78
- }
91
+ const operations = new StoreOperationsMongoDB({
92
+ connector: this.#connector,
93
+ });
79
94
 
80
- private async getConnection(): Promise<Db> {
81
- if (this.#isConnected) {
82
- return this.#db!;
83
- }
95
+ const memory = new MemoryStorageMongoDB({
96
+ operations,
97
+ });
84
98
 
85
- await this.#client.connect();
86
- this.#db = this.#client.db(this.#dbName);
87
- this.#isConnected = true;
88
- return this.#db;
89
- }
99
+ const traces = new TracesStorageMongoDB({
100
+ operations,
101
+ });
102
+
103
+ const legacyEvals = new LegacyEvalsMongoDB({
104
+ operations,
105
+ });
90
106
 
91
- private async getCollection(collectionName: string) {
92
- const db = await this.getConnection();
93
- return db.collection(collectionName);
107
+ const scores = new ScoresStorageMongoDB({
108
+ operations,
109
+ });
110
+
111
+ const workflows = new WorkflowsStorageMongoDB({
112
+ operations,
113
+ });
114
+
115
+ this.stores = {
116
+ operations,
117
+ memory,
118
+ traces,
119
+ legacyEvals,
120
+ scores,
121
+ workflows,
122
+ };
94
123
  }
95
124
 
96
- async createTable(): Promise<void> {
97
- // Nothing to do here, MongoDB is schemaless
125
+ async createTable({
126
+ tableName,
127
+ schema,
128
+ }: {
129
+ tableName: TABLE_NAMES;
130
+ schema: Record<string, StorageColumn>;
131
+ }): Promise<void> {
132
+ return this.stores.operations.createTable({ tableName, schema });
98
133
  }
99
134
 
100
- /**
101
- * No-op: This backend is schemaless and does not require schema changes.
102
- * @param tableName Name of the table
103
- * @param schema Schema of the table
104
- * @param ifNotExists Array of column names to add if they don't exist
105
- */
106
135
  async alterTable(_args: {
107
136
  tableName: TABLE_NAMES;
108
137
  schema: Record<string, StorageColumn>;
109
138
  ifNotExists: string[];
110
139
  }): Promise<void> {
111
- // Nothing to do here, MongoDB is schemaless
140
+ return this.stores.operations.alterTable(_args);
141
+ }
142
+
143
+ async dropTable({ tableName }: { tableName: TABLE_NAMES }): Promise<void> {
144
+ return this.stores.operations.dropTable({ tableName });
112
145
  }
113
146
 
114
147
  async clearTable({ tableName }: { tableName: TABLE_NAMES }): Promise<void> {
115
- try {
116
- const collection = await this.getCollection(tableName);
117
- await collection.deleteMany({});
118
- } catch (error) {
119
- if (error instanceof Error) {
120
- const matstraError = new MastraError(
121
- {
122
- id: 'STORAGE_MONGODB_STORE_CLEAR_TABLE_FAILED',
123
- domain: ErrorDomain.STORAGE,
124
- category: ErrorCategory.THIRD_PARTY,
125
- details: { tableName },
126
- },
127
- error,
128
- );
129
- this.logger.error(matstraError.message);
130
- this.logger?.trackException(matstraError);
131
- }
132
- }
148
+ return this.stores.operations.clearTable({ tableName });
133
149
  }
134
150
 
135
151
  async insert({ tableName, record }: { tableName: TABLE_NAMES; record: Record<string, any> }): Promise<void> {
136
- try {
137
- const collection = await this.getCollection(tableName);
138
- await collection.insertOne(record);
139
- } catch (error) {
140
- if (error instanceof Error) {
141
- const matstraError = new MastraError(
142
- {
143
- id: 'STORAGE_MONGODB_STORE_INSERT_FAILED',
144
- domain: ErrorDomain.STORAGE,
145
- category: ErrorCategory.THIRD_PARTY,
146
- details: { tableName },
147
- },
148
- error,
149
- );
150
- this.logger.error(matstraError.message);
151
- this.logger?.trackException(matstraError);
152
- }
153
- }
152
+ return this.stores.operations.insert({ tableName, record });
154
153
  }
155
154
 
156
155
  async batchInsert({ tableName, records }: { tableName: TABLE_NAMES; records: Record<string, any>[] }): Promise<void> {
157
- if (!records.length) {
158
- return;
159
- }
160
-
161
- try {
162
- const collection = await this.getCollection(tableName);
163
- await collection.insertMany(records);
164
- } catch (error) {
165
- throw new MastraError(
166
- {
167
- id: 'STORAGE_MONGODB_STORE_BATCH_INSERT_FAILED',
168
- domain: ErrorDomain.STORAGE,
169
- category: ErrorCategory.THIRD_PARTY,
170
- details: { tableName },
171
- },
172
- error,
173
- );
174
- }
156
+ return this.stores.operations.batchInsert({ tableName, records });
175
157
  }
176
158
 
177
159
  async load<R>({ tableName, keys }: { tableName: TABLE_NAMES; keys: Record<string, string> }): Promise<R | null> {
178
- this.logger.info(`Loading ${tableName} with keys ${JSON.stringify(keys)}`);
179
- try {
180
- const collection = await this.getCollection(tableName);
181
- return (await collection.find(keys).toArray()) as R;
182
- } catch (error) {
183
- throw new MastraError(
184
- {
185
- id: 'STORAGE_MONGODB_STORE_LOAD_FAILED',
186
- domain: ErrorDomain.STORAGE,
187
- category: ErrorCategory.THIRD_PARTY,
188
- details: { tableName },
189
- },
190
- error,
191
- );
192
- }
160
+ return this.stores.operations.load({ tableName, keys });
193
161
  }
194
162
 
195
163
  async getThreadById({ threadId }: { threadId: string }): Promise<StorageThreadType | null> {
196
- try {
197
- const collection = await this.getCollection(TABLE_THREADS);
198
- const result = await collection.findOne<any>({ id: threadId });
199
- if (!result) {
200
- return null;
201
- }
202
-
203
- return {
204
- ...result,
205
- metadata: typeof result.metadata === 'string' ? JSON.parse(result.metadata) : result.metadata,
206
- };
207
- } catch (error) {
208
- throw new MastraError(
209
- {
210
- id: 'STORAGE_MONGODB_STORE_GET_THREAD_BY_ID_FAILED',
211
- domain: ErrorDomain.STORAGE,
212
- category: ErrorCategory.THIRD_PARTY,
213
- details: { threadId },
214
- },
215
- error,
216
- );
217
- }
164
+ return this.stores.memory.getThreadById({ threadId });
218
165
  }
219
166
 
220
167
  async getThreadsByResourceId({ resourceId }: { resourceId: string }): Promise<StorageThreadType[]> {
221
- try {
222
- const collection = await this.getCollection(TABLE_THREADS);
223
- const results = await collection.find<any>({ resourceId }).toArray();
224
- if (!results.length) {
225
- return [];
226
- }
227
-
228
- return results.map(result => ({
229
- ...result,
230
- metadata: typeof result.metadata === 'string' ? JSON.parse(result.metadata) : result.metadata,
231
- }));
232
- } catch (error) {
233
- throw new MastraError(
234
- {
235
- id: 'STORAGE_MONGODB_STORE_GET_THREADS_BY_RESOURCE_ID_FAILED',
236
- domain: ErrorDomain.STORAGE,
237
- category: ErrorCategory.THIRD_PARTY,
238
- details: { resourceId },
239
- },
240
- error,
241
- );
242
- }
168
+ return this.stores.memory.getThreadsByResourceId({ resourceId });
243
169
  }
244
170
 
245
171
  async saveThread({ thread }: { thread: StorageThreadType }): Promise<StorageThreadType> {
246
- try {
247
- const collection = await this.getCollection(TABLE_THREADS);
248
- await collection.updateOne(
249
- { id: thread.id },
250
- {
251
- $set: {
252
- ...thread,
253
- metadata: JSON.stringify(thread.metadata),
254
- },
255
- },
256
- { upsert: true },
257
- );
258
- return thread;
259
- } catch (error) {
260
- throw new MastraError(
261
- {
262
- id: 'STORAGE_MONGODB_STORE_SAVE_THREAD_FAILED',
263
- domain: ErrorDomain.STORAGE,
264
- category: ErrorCategory.THIRD_PARTY,
265
- details: { threadId: thread.id },
266
- },
267
- error,
268
- );
269
- }
172
+ return this.stores.memory.saveThread({ thread });
270
173
  }
271
174
 
272
175
  async updateThread({
@@ -278,71 +181,11 @@ export class MongoDBStore extends MastraStorage {
278
181
  title: string;
279
182
  metadata: Record<string, unknown>;
280
183
  }): Promise<StorageThreadType> {
281
- const thread = await this.getThreadById({ threadId: id });
282
- if (!thread) {
283
- throw new MastraError({
284
- id: 'STORAGE_MONGODB_STORE_UPDATE_THREAD_NOT_FOUND',
285
- domain: ErrorDomain.STORAGE,
286
- category: ErrorCategory.THIRD_PARTY,
287
- details: { threadId: id },
288
- text: `Thread ${id} not found`,
289
- });
290
- }
291
-
292
- const updatedThread = {
293
- ...thread,
294
- title,
295
- metadata: {
296
- ...thread.metadata,
297
- ...metadata,
298
- },
299
- };
300
-
301
- try {
302
- const collection = await this.getCollection(TABLE_THREADS);
303
- await collection.updateOne(
304
- { id },
305
- {
306
- $set: {
307
- title,
308
- metadata: JSON.stringify(updatedThread.metadata),
309
- },
310
- },
311
- );
312
- } catch (error) {
313
- throw new MastraError(
314
- {
315
- id: 'STORAGE_MONGODB_STORE_UPDATE_THREAD_FAILED',
316
- domain: ErrorDomain.STORAGE,
317
- category: ErrorCategory.THIRD_PARTY,
318
- details: { threadId: id },
319
- },
320
- error,
321
- );
322
- }
323
-
324
- return updatedThread;
184
+ return this.stores.memory.updateThread({ id, title, metadata });
325
185
  }
326
186
 
327
187
  async deleteThread({ threadId }: { threadId: string }): Promise<void> {
328
- try {
329
- // First, delete all messages associated with the thread
330
- const collectionMessages = await this.getCollection(TABLE_MESSAGES);
331
- await collectionMessages.deleteMany({ thread_id: threadId });
332
- // Then delete the thread itself
333
- const collectionThreads = await this.getCollection(TABLE_THREADS);
334
- await collectionThreads.deleteOne({ id: threadId });
335
- } catch (error) {
336
- throw new MastraError(
337
- {
338
- id: 'STORAGE_MONGODB_STORE_DELETE_THREAD_FAILED',
339
- domain: ErrorDomain.STORAGE,
340
- category: ErrorCategory.THIRD_PARTY,
341
- details: { threadId },
342
- },
343
- error,
344
- );
345
- }
188
+ return this.stores.memory.deleteThread({ threadId });
346
189
  }
347
190
 
348
191
  public async getMessages(args: StorageGetMessagesArg & { format?: 'v1' }): Promise<MastraMessageV1[]>;
@@ -354,353 +197,71 @@ export class MongoDBStore extends MastraStorage {
354
197
  }: StorageGetMessagesArg & {
355
198
  format?: 'v1' | 'v2';
356
199
  }): Promise<MastraMessageV1[] | MastraMessageV2[]> {
357
- try {
358
- const limit = this.resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
359
- const include = selectBy?.include || [];
360
- let messages: MastraMessageV2[] = [];
361
- let allMessages: MastraMessageV2[] = [];
362
- const collection = await this.getCollection(TABLE_MESSAGES);
363
- // Get all messages from the thread ordered by creation date descending
364
- allMessages = (await collection.find({ thread_id: threadId }).sort({ createdAt: -1 }).toArray()).map((row: any) =>
365
- this.parseRow(row),
366
- );
367
-
368
- // If there are messages to include, select the messages around the included IDs
369
- if (include.length) {
370
- // Map IDs to their position in the ordered array
371
- const idToIndex = new Map<string, number>();
372
- allMessages.forEach((msg, idx) => {
373
- idToIndex.set(msg.id, idx);
374
- });
375
-
376
- const selectedIndexes = new Set<number>();
377
- for (const inc of include) {
378
- const idx = idToIndex.get(inc.id);
379
- if (idx === undefined) continue;
380
- // Previous messages
381
- for (let i = 1; i <= (inc.withPreviousMessages || 0); i++) {
382
- if (idx + i < allMessages.length) selectedIndexes.add(idx + i);
383
- }
384
- // Included message
385
- selectedIndexes.add(idx);
386
- // Next messages
387
- for (let i = 1; i <= (inc.withNextMessages || 0); i++) {
388
- if (idx - i >= 0) selectedIndexes.add(idx - i);
389
- }
390
- }
391
- // Add the selected messages, filtering out undefined
392
- messages.push(
393
- ...Array.from(selectedIndexes)
394
- .map(i => allMessages[i])
395
- .filter((m): m is MastraMessageV2 => !!m),
396
- );
397
- }
398
-
399
- // Get the remaining messages, excluding those already selected
400
- const excludeIds = new Set(messages.map(m => m.id));
401
- for (const msg of allMessages) {
402
- if (messages.length >= limit) break;
403
- if (!excludeIds.has(msg.id)) {
404
- messages.push(msg);
405
- }
406
- }
407
-
408
- // Sort all messages by creation date ascending
409
- messages.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
410
-
411
- const list = new MessageList().add(messages.slice(0, limit), 'memory');
412
- if (format === `v2`) return list.get.all.v2();
413
- return list.get.all.v1();
414
- } catch (error) {
415
- throw new MastraError(
416
- {
417
- id: 'STORAGE_MONGODB_STORE_GET_MESSAGES_FAILED',
418
- domain: ErrorDomain.STORAGE,
419
- category: ErrorCategory.THIRD_PARTY,
420
- details: { threadId },
421
- },
422
- error,
423
- );
424
- }
200
+ return this.stores.memory.getMessages({ threadId, selectBy, format });
425
201
  }
426
202
 
427
203
  async saveMessages(args: { messages: MastraMessageV1[]; format?: undefined | 'v1' }): Promise<MastraMessageV1[]>;
428
204
  async saveMessages(args: { messages: MastraMessageV2[]; format: 'v2' }): Promise<MastraMessageV2[]>;
429
- async saveMessages({
430
- messages,
431
- format,
432
- }:
433
- | { messages: MastraMessageV1[]; format?: undefined | 'v1' }
434
- | { messages: MastraMessageV2[]; format: 'v2' }): Promise<MastraMessageV2[] | MastraMessageV1[]> {
435
- if (!messages.length) {
436
- return messages;
437
- }
438
-
439
- const threadId = messages[0]?.threadId;
440
- if (!threadId) {
441
- this.logger.error('Thread ID is required to save messages');
442
- throw new Error('Thread ID is required');
443
- }
444
-
445
- try {
446
- // Prepare batch statements for all messages
447
- const messagesToInsert = messages.map(message => {
448
- const time = message.createdAt || new Date();
449
- return {
450
- id: message.id,
451
- thread_id: threadId,
452
- content: typeof message.content === 'string' ? message.content : JSON.stringify(message.content),
453
- role: message.role,
454
- type: message.type,
455
- resourceId: message.resourceId,
456
- createdAt: time instanceof Date ? time.toISOString() : time,
457
- };
458
- });
459
-
460
- // Execute message inserts and thread update in parallel for better performance
461
- const collection = await this.getCollection(TABLE_MESSAGES);
462
- const threadsCollection = await this.getCollection(TABLE_THREADS);
463
-
464
- await Promise.all([
465
- collection.bulkWrite(
466
- messagesToInsert.map(msg => ({
467
- updateOne: {
468
- filter: { id: msg.id },
469
- update: { $set: msg },
470
- upsert: true,
471
- },
472
- })),
473
- ),
474
- threadsCollection.updateOne({ id: threadId }, { $set: { updatedAt: new Date() } }),
475
- ]);
476
-
477
- const list = new MessageList().add(messages, 'memory');
478
- if (format === `v2`) return list.get.all.v2();
479
- return list.get.all.v1();
480
- } catch (error) {
481
- this.logger.error('Failed to save messages in database: ' + (error as { message: string })?.message);
482
- throw error;
483
- }
205
+ async saveMessages(
206
+ args: { messages: MastraMessageV1[]; format?: undefined | 'v1' } | { messages: MastraMessageV2[]; format: 'v2' },
207
+ ): Promise<MastraMessageV2[] | MastraMessageV1[]> {
208
+ return this.stores.memory.saveMessages(args);
484
209
  }
485
210
 
486
- async getTraces(
487
- {
488
- name,
489
- scope,
490
- page,
491
- perPage,
492
- attributes,
493
- filters,
494
- }: {
495
- name?: string;
496
- scope?: string;
497
- page: number;
498
- perPage: number;
499
- attributes?: Record<string, string>;
500
- filters?: Record<string, any>;
501
- } = {
502
- page: 0,
503
- perPage: 100,
504
- },
505
- ): Promise<any[]> {
506
- const limit = perPage;
507
- const offset = page * perPage;
508
-
509
- const query: any = {};
510
- if (name) {
511
- query['name'] = new RegExp(name);
512
- }
211
+ async getThreadsByResourceIdPaginated(_args: {
212
+ resourceId: string;
213
+ page: number;
214
+ perPage: number;
215
+ }): Promise<PaginationInfo & { threads: StorageThreadType[] }> {
216
+ return this.stores.memory.getThreadsByResourceIdPaginated(_args);
217
+ }
513
218
 
514
- if (scope) {
515
- query['scope'] = scope;
516
- }
219
+ async getMessagesPaginated(
220
+ _args: StorageGetMessagesArg,
221
+ ): Promise<PaginationInfo & { messages: MastraMessageV1[] | MastraMessageV2[] }> {
222
+ return this.stores.memory.getMessagesPaginated(_args);
223
+ }
517
224
 
518
- if (attributes) {
519
- query['$and'] = Object.entries(attributes).map(([key, value]) => ({
520
- attributes: new RegExp(`\"${key}\":\"${value}\"`),
521
- }));
522
- }
225
+ async updateMessages(_args: {
226
+ messages: Partial<Omit<MastraMessageV2, 'createdAt'>> &
227
+ {
228
+ id: string;
229
+ content?: { metadata?: MastraMessageContentV2['metadata']; content?: MastraMessageContentV2['content'] };
230
+ }[];
231
+ }): Promise<MastraMessageV2[]> {
232
+ return this.stores.memory.updateMessages(_args);
233
+ }
523
234
 
524
- if (filters) {
525
- Object.entries(filters).forEach(([key, value]) => {
526
- query[key] = value;
527
- });
528
- }
235
+ async getTraces(args: StorageGetTracesArg): Promise<Trace[]> {
236
+ return this.stores.traces.getTraces(args);
237
+ }
529
238
 
530
- try {
531
- const collection = await this.getCollection(TABLE_TRACES);
532
- const result = await collection
533
- .find(query, {
534
- sort: { startTime: -1 },
535
- })
536
- .limit(limit)
537
- .skip(offset)
538
- .toArray();
539
-
540
- return result.map(row => ({
541
- id: row.id,
542
- parentSpanId: row.parentSpanId,
543
- traceId: row.traceId,
544
- name: row.name,
545
- scope: row.scope,
546
- kind: row.kind,
547
- status: safelyParseJSON(row.status as string),
548
- events: safelyParseJSON(row.events as string),
549
- links: safelyParseJSON(row.links as string),
550
- attributes: safelyParseJSON(row.attributes as string),
551
- startTime: row.startTime,
552
- endTime: row.endTime,
553
- other: safelyParseJSON(row.other as string),
554
- createdAt: row.createdAt,
555
- })) as any;
556
- } catch (error) {
557
- throw new MastraError(
558
- {
559
- id: 'STORAGE_MONGODB_STORE_GET_TRACES_FAILED',
560
- domain: ErrorDomain.STORAGE,
561
- category: ErrorCategory.THIRD_PARTY,
562
- },
563
- error,
564
- );
565
- }
239
+ async getTracesPaginated(args: StorageGetTracesPaginatedArg): Promise<PaginationInfo & { traces: Trace[] }> {
240
+ return this.stores.traces.getTracesPaginated(args);
566
241
  }
567
242
 
568
- async getWorkflowRuns({
569
- workflowName,
570
- fromDate,
571
- toDate,
572
- limit,
573
- offset,
574
- }: {
243
+ async getWorkflowRuns(args?: {
575
244
  workflowName?: string;
576
245
  fromDate?: Date;
577
246
  toDate?: Date;
578
247
  limit?: number;
579
248
  offset?: number;
580
- } = {}): Promise<{
581
- runs: Array<{
582
- workflowName: string;
583
- runId: string;
584
- snapshot: WorkflowRunState | string;
585
- createdAt: Date;
586
- updatedAt: Date;
587
- }>;
588
- total: number;
589
- }> {
590
- const query: any = {};
591
- if (workflowName) {
592
- query['workflow_name'] = workflowName;
593
- }
594
-
595
- if (fromDate || toDate) {
596
- query['createdAt'] = {};
597
- if (fromDate) {
598
- query['createdAt']['$gte'] = fromDate;
599
- }
600
- if (toDate) {
601
- query['createdAt']['$lte'] = toDate;
602
- }
603
- }
604
-
605
- try {
606
- const collection = await this.getCollection(TABLE_WORKFLOW_SNAPSHOT);
607
- let total = 0;
608
- // Only get total count when using pagination
609
- if (limit !== undefined && offset !== undefined) {
610
- total = await collection.countDocuments(query);
611
- }
612
-
613
- // Get results
614
- const request = collection.find(query).sort({ createdAt: 'desc' });
615
- if (limit) {
616
- request.limit(limit);
617
- }
618
-
619
- if (offset) {
620
- request.skip(offset);
621
- }
622
-
623
- const result = await request.toArray();
624
- const runs = result.map(row => {
625
- let parsedSnapshot: WorkflowRunState | string = row.snapshot;
626
- if (typeof parsedSnapshot === 'string') {
627
- try {
628
- parsedSnapshot = JSON.parse(row.snapshot as string) as WorkflowRunState;
629
- } catch (e) {
630
- // If parsing fails, return the raw snapshot string
631
- console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
632
- }
633
- }
634
-
635
- return {
636
- workflowName: row.workflow_name as string,
637
- runId: row.run_id as string,
638
- snapshot: parsedSnapshot,
639
- createdAt: new Date(row.createdAt as string),
640
- updatedAt: new Date(row.updatedAt as string),
641
- };
642
- });
249
+ resourceId?: string;
250
+ }): Promise<WorkflowRuns> {
251
+ return this.stores.workflows.getWorkflowRuns(args);
252
+ }
643
253
 
644
- // Use runs.length as total when not paginating
645
- return { runs, total: total || runs.length };
646
- } catch (error) {
647
- throw new MastraError(
648
- {
649
- id: 'STORAGE_MONGODB_STORE_GET_WORKFLOW_RUNS_FAILED',
650
- domain: ErrorDomain.STORAGE,
651
- category: ErrorCategory.THIRD_PARTY,
652
- },
653
- error,
654
- );
655
- }
254
+ async getEvals(
255
+ options: {
256
+ agentName?: string;
257
+ type?: 'test' | 'live';
258
+ } & PaginationArgs = {},
259
+ ): Promise<PaginationInfo & { evals: EvalRow[] }> {
260
+ return this.stores.legacyEvals.getEvals(options);
656
261
  }
657
262
 
658
263
  async getEvalsByAgentName(agentName: string, type?: 'test' | 'live'): Promise<EvalRow[]> {
659
- try {
660
- const query: any = {
661
- agent_name: agentName,
662
- };
663
-
664
- if (type === 'test') {
665
- query['test_info'] = { $ne: null };
666
- // is not possible to filter by test_info.testPath because it is not a json field
667
- // query['test_info.testPath'] = { $ne: null };
668
- }
669
-
670
- if (type === 'live') {
671
- // is not possible to filter by test_info.testPath because it is not a json field
672
- query['test_info'] = null;
673
- }
674
-
675
- const collection = await this.getCollection(TABLE_EVALS);
676
- const documents = await collection.find(query).sort({ created_at: 'desc' }).toArray();
677
- const result = documents.map(row => this.transformEvalRow(row));
678
- // Post filter to remove if test_info.testPath is null
679
- return result.filter(row => {
680
- if (type === 'live') {
681
- return !Boolean(row.testInfo?.testPath);
682
- }
683
-
684
- if (type === 'test') {
685
- return row.testInfo?.testPath !== null;
686
- }
687
- return true;
688
- });
689
- } catch (error) {
690
- // Handle case where table doesn't exist yet
691
- if (error instanceof Error && error.message.includes('no such table')) {
692
- return [];
693
- }
694
- throw new MastraError(
695
- {
696
- id: 'STORAGE_MONGODB_STORE_GET_EVALS_BY_AGENT_NAME_FAILED',
697
- domain: ErrorDomain.STORAGE,
698
- category: ErrorCategory.THIRD_PARTY,
699
- details: { agentName },
700
- },
701
- error,
702
- );
703
- }
264
+ return this.stores.legacyEvals.getEvalsByAgentName(agentName, type);
704
265
  }
705
266
 
706
267
  async persistWorkflowSnapshot({
@@ -712,33 +273,7 @@ export class MongoDBStore extends MastraStorage {
712
273
  runId: string;
713
274
  snapshot: WorkflowRunState;
714
275
  }): Promise<void> {
715
- try {
716
- const now = new Date().toISOString();
717
- const collection = await this.getCollection(TABLE_WORKFLOW_SNAPSHOT);
718
- await collection.updateOne(
719
- { workflow_name: workflowName, run_id: runId },
720
- {
721
- $set: {
722
- snapshot: JSON.stringify(snapshot),
723
- updatedAt: now,
724
- },
725
- $setOnInsert: {
726
- createdAt: now,
727
- },
728
- },
729
- { upsert: true },
730
- );
731
- } catch (error) {
732
- throw new MastraError(
733
- {
734
- id: 'STORAGE_MONGODB_STORE_PERSIST_WORKFLOW_SNAPSHOT_FAILED',
735
- domain: ErrorDomain.STORAGE,
736
- category: ErrorCategory.THIRD_PARTY,
737
- details: { workflowName, runId },
738
- },
739
- error,
740
- );
741
- }
276
+ return this.stores.workflows.persistWorkflowSnapshot({ workflowName, runId, snapshot });
742
277
  }
743
278
 
744
279
  async loadWorkflowSnapshot({
@@ -748,31 +283,7 @@ export class MongoDBStore extends MastraStorage {
748
283
  workflowName: string;
749
284
  runId: string;
750
285
  }): Promise<WorkflowRunState | null> {
751
- try {
752
- const result = await this.load<any[]>({
753
- tableName: TABLE_WORKFLOW_SNAPSHOT,
754
- keys: {
755
- workflow_name: workflowName,
756
- run_id: runId,
757
- },
758
- });
759
-
760
- if (!result?.length) {
761
- return null;
762
- }
763
-
764
- return JSON.parse(result[0].snapshot);
765
- } catch (error) {
766
- throw new MastraError(
767
- {
768
- id: 'STORAGE_MONGODB_STORE_LOAD_WORKFLOW_SNAPSHOT_FAILED',
769
- domain: ErrorDomain.STORAGE,
770
- category: ErrorCategory.THIRD_PARTY,
771
- details: { workflowName, runId },
772
- },
773
- error,
774
- );
775
- }
286
+ return this.stores.workflows.loadWorkflowSnapshot({ workflowName, runId });
776
287
  }
777
288
 
778
289
  async getWorkflowRunById({
@@ -782,164 +293,95 @@ export class MongoDBStore extends MastraStorage {
782
293
  runId: string;
783
294
  workflowName?: string;
784
295
  }): Promise<WorkflowRun | null> {
785
- try {
786
- const query: any = {};
787
- if (runId) {
788
- query['run_id'] = runId;
789
- }
790
-
791
- if (workflowName) {
792
- query['workflow_name'] = workflowName;
793
- }
794
-
795
- const collection = await this.getCollection(TABLE_WORKFLOW_SNAPSHOT);
796
- const result = await collection.findOne(query);
797
- if (!result) {
798
- return null;
799
- }
296
+ return this.stores.workflows.getWorkflowRunById({ runId, workflowName });
297
+ }
800
298
 
801
- return this.parseWorkflowRun(result);
299
+ async close(): Promise<void> {
300
+ try {
301
+ await this.#connector.close();
802
302
  } catch (error) {
803
303
  throw new MastraError(
804
304
  {
805
- id: 'STORAGE_MONGODB_STORE_GET_WORKFLOW_RUN_BY_ID_FAILED',
305
+ id: 'STORAGE_MONGODB_STORE_CLOSE_FAILED',
806
306
  domain: ErrorDomain.STORAGE,
807
- category: ErrorCategory.THIRD_PARTY,
808
- details: { runId },
307
+ category: ErrorCategory.USER,
809
308
  },
810
309
  error,
811
310
  );
812
311
  }
813
312
  }
814
313
 
815
- private parseWorkflowRun(row: any): WorkflowRun {
816
- let parsedSnapshot: WorkflowRunState | string = row.snapshot as string;
817
- if (typeof parsedSnapshot === 'string') {
818
- try {
819
- parsedSnapshot = JSON.parse(row.snapshot as string) as WorkflowRunState;
820
- } catch (e) {
821
- // If parsing fails, return the raw snapshot string
822
- console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
823
- }
824
- }
825
-
826
- return {
827
- workflowName: row.workflow_name,
828
- runId: row.run_id,
829
- snapshot: parsedSnapshot,
830
- createdAt: row.createdAt,
831
- updatedAt: row.updatedAt,
832
- resourceId: row.resourceId,
833
- };
314
+ /**
315
+ * SCORERS
316
+ */
317
+ async getScoreById({ id }: { id: string }): Promise<ScoreRowData | null> {
318
+ return this.stores.scores.getScoreById({ id });
834
319
  }
835
320
 
836
- private parseRow(row: any): MastraMessageV2 {
837
- let content = row.content;
838
- try {
839
- content = JSON.parse(row.content);
840
- } catch {
841
- // use content as is if it's not JSON
842
- }
843
- return {
844
- id: row.id,
845
- content,
846
- role: row.role,
847
- type: row.type,
848
- createdAt: new Date(row.createdAt as string),
849
- threadId: row.thread_id,
850
- resourceId: row.resourceId,
851
- } as MastraMessageV2;
852
- }
853
-
854
- private transformEvalRow(row: Record<string, any>): EvalRow {
855
- let testInfoValue = null;
856
- if (row.test_info) {
857
- try {
858
- testInfoValue = typeof row.test_info === 'string' ? JSON.parse(row.test_info) : row.test_info;
859
- } catch (e) {
860
- console.warn('Failed to parse test_info:', e);
861
- }
862
- }
863
- const resultValue = JSON.parse(row.result as string);
864
- if (!resultValue || typeof resultValue !== 'object' || !('score' in resultValue)) {
865
- throw new MastraError({
866
- id: 'STORAGE_MONGODB_STORE_INVALID_METRIC_FORMAT',
867
- text: `Invalid MetricResult format: ${JSON.stringify(resultValue)}`,
868
- domain: ErrorDomain.STORAGE,
869
- category: ErrorCategory.USER,
870
- });
871
- }
321
+ async saveScore(score: Omit<ScoreRowData, 'id' | 'createdAt' | 'updatedAt'>): Promise<{ score: ScoreRowData }> {
322
+ return this.stores.scores.saveScore(score);
323
+ }
872
324
 
873
- return {
874
- input: row.input as string,
875
- output: row.output as string,
876
- result: resultValue as MetricResult,
877
- agentName: row.agent_name as string,
878
- metricName: row.metric_name as string,
879
- instructions: row.instructions as string,
880
- testInfo: testInfoValue as TestInfo,
881
- globalRunId: row.global_run_id as string,
882
- runId: row.run_id as string,
883
- createdAt: row.created_at as string,
884
- };
325
+ async getScoresByRunId({
326
+ runId,
327
+ pagination,
328
+ }: {
329
+ runId: string;
330
+ pagination: StoragePagination;
331
+ }): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
332
+ return this.stores.scores.getScoresByRunId({ runId, pagination });
885
333
  }
886
334
 
887
- async getTracesPaginated(_args: StorageGetTracesArg): Promise<PaginationInfo & { traces: Trace[] }> {
888
- throw new MastraError({
889
- id: 'STORAGE_MONGODB_STORE_GET_TRACES_PAGINATED_FAILED',
890
- domain: ErrorDomain.STORAGE,
891
- category: ErrorCategory.THIRD_PARTY,
892
- text: 'Method not implemented.',
893
- });
335
+ async getScoresByEntityId({
336
+ entityId,
337
+ entityType,
338
+ pagination,
339
+ }: {
340
+ pagination: StoragePagination;
341
+ entityId: string;
342
+ entityType: string;
343
+ }): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
344
+ return this.stores.scores.getScoresByEntityId({ entityId, entityType, pagination });
894
345
  }
895
346
 
896
- async getThreadsByResourceIdPaginated(_args: {
897
- resourceId: string;
898
- page?: number;
899
- perPage?: number;
900
- }): Promise<PaginationInfo & { threads: StorageThreadType[] }> {
901
- throw new MastraError({
902
- id: 'STORAGE_MONGODB_STORE_GET_THREADS_BY_RESOURCE_ID_PAGINATED_FAILED',
903
- domain: ErrorDomain.STORAGE,
904
- category: ErrorCategory.THIRD_PARTY,
905
- text: 'Method not implemented.',
906
- });
347
+ async getScoresByScorerId({
348
+ scorerId,
349
+ pagination,
350
+ entityId,
351
+ entityType,
352
+ }: {
353
+ scorerId: string;
354
+ pagination: StoragePagination;
355
+ entityId?: string;
356
+ entityType?: string;
357
+ }): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
358
+ return this.stores.scores.getScoresByScorerId({ scorerId, pagination, entityId, entityType });
907
359
  }
908
360
 
909
- async getMessagesPaginated(
910
- _args: StorageGetMessagesArg,
911
- ): Promise<PaginationInfo & { messages: MastraMessageV1[] | MastraMessageV2[] }> {
912
- throw new MastraError({
913
- id: 'STORAGE_MONGODB_STORE_GET_MESSAGES_PAGINATED_FAILED',
914
- domain: ErrorDomain.STORAGE,
915
- category: ErrorCategory.THIRD_PARTY,
916
- text: 'Method not implemented.',
917
- });
361
+ /**
362
+ * RESOURCES
363
+ */
364
+ async getResourceById({ resourceId }: { resourceId: string }): Promise<StorageResourceType | null> {
365
+ return this.stores.memory.getResourceById({ resourceId });
918
366
  }
919
367
 
920
- async close(): Promise<void> {
921
- try {
922
- await this.#client.close();
923
- } catch (error) {
924
- throw new MastraError(
925
- {
926
- id: 'STORAGE_MONGODB_STORE_CLOSE_FAILED',
927
- domain: ErrorDomain.STORAGE,
928
- category: ErrorCategory.USER,
929
- },
930
- error,
931
- );
932
- }
368
+ async saveResource({ resource }: { resource: StorageResourceType }): Promise<StorageResourceType> {
369
+ return this.stores.memory.saveResource({ resource });
933
370
  }
934
371
 
935
- async updateMessages(_args: {
936
- messages: Partial<Omit<MastraMessageV2, 'createdAt'>> &
937
- {
938
- id: string;
939
- content?: { metadata?: MastraMessageContentV2['metadata']; content?: MastraMessageContentV2['content'] };
940
- }[];
941
- }): Promise<MastraMessageV2[]> {
942
- this.logger.error('updateMessages is not yet implemented in MongoDBStore');
943
- throw new Error('Method not implemented');
372
+ async updateResource({
373
+ resourceId,
374
+ workingMemory,
375
+ metadata,
376
+ }: {
377
+ resourceId: string;
378
+ workingMemory?: string;
379
+ metadata?: Record<string, unknown>;
380
+ }): Promise<StorageResourceType> {
381
+ return this.stores.memory.updateResource({
382
+ resourceId,
383
+ workingMemory,
384
+ metadata,
385
+ });
944
386
  }
945
387
  }