@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.
@@ -0,0 +1,152 @@
1
+ import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
2
+ import { safelyParseJSON, StoreOperations, TABLE_SCHEMAS } from '@mastra/core/storage';
3
+ import type { StorageColumn, TABLE_NAMES } from '@mastra/core/storage';
4
+ import type { ConnectorHandler } from '../../connectors/base';
5
+
6
+ export interface MongoDBOperationsConfig {
7
+ connector: ConnectorHandler;
8
+ }
9
+ export class StoreOperationsMongoDB extends StoreOperations {
10
+ readonly #connector: ConnectorHandler;
11
+
12
+ constructor(config: MongoDBOperationsConfig) {
13
+ super();
14
+ this.#connector = config.connector;
15
+ }
16
+
17
+ async getCollection(collectionName: string) {
18
+ return this.#connector.getCollection(collectionName);
19
+ }
20
+
21
+ async hasColumn(_table: string, _column: string): Promise<boolean> {
22
+ // MongoDB is schemaless, so we can assume any column exists
23
+ // We could check a sample document, but for now return true
24
+ return true;
25
+ }
26
+
27
+ async createTable(): Promise<void> {
28
+ // Nothing to do here, MongoDB is schemaless
29
+ }
30
+
31
+ async alterTable(_args: {
32
+ tableName: TABLE_NAMES;
33
+ schema: Record<string, StorageColumn>;
34
+ ifNotExists: string[];
35
+ }): Promise<void> {
36
+ // Nothing to do here, MongoDB is schemaless
37
+ }
38
+
39
+ async clearTable({ tableName }: { tableName: TABLE_NAMES }): Promise<void> {
40
+ try {
41
+ const collection = await this.getCollection(tableName);
42
+ await collection.deleteMany({});
43
+ } catch (error) {
44
+ if (error instanceof Error) {
45
+ const matstraError = new MastraError(
46
+ {
47
+ id: 'STORAGE_MONGODB_STORE_CLEAR_TABLE_FAILED',
48
+ domain: ErrorDomain.STORAGE,
49
+ category: ErrorCategory.THIRD_PARTY,
50
+ details: { tableName },
51
+ },
52
+ error,
53
+ );
54
+ this.logger.error(matstraError.message);
55
+ this.logger?.trackException(matstraError);
56
+ }
57
+ }
58
+ }
59
+
60
+ async dropTable({ tableName }: { tableName: TABLE_NAMES }): Promise<void> {
61
+ try {
62
+ const collection = await this.getCollection(tableName);
63
+ await collection.drop();
64
+ } catch (error) {
65
+ // Collection might not exist, which is fine
66
+ if (error instanceof Error && error.message.includes('ns not found')) {
67
+ return;
68
+ }
69
+ throw new MastraError(
70
+ {
71
+ id: 'MONGODB_STORE_DROP_TABLE_FAILED',
72
+ domain: ErrorDomain.STORAGE,
73
+ category: ErrorCategory.THIRD_PARTY,
74
+ details: { tableName },
75
+ },
76
+ error,
77
+ );
78
+ }
79
+ }
80
+
81
+ async insert({ tableName, record }: { tableName: TABLE_NAMES; record: Record<string, any> }): Promise<void> {
82
+ try {
83
+ const collection = await this.getCollection(tableName);
84
+
85
+ const schema = TABLE_SCHEMAS[tableName];
86
+
87
+ const recordToInsert = Object.fromEntries(
88
+ Object.entries(schema).map(([key, value]) => {
89
+ if (value.type === 'jsonb' && record[key] && typeof record[key] === 'string') {
90
+ return [key, safelyParseJSON(record[key])];
91
+ }
92
+ return [key, record[key]];
93
+ }),
94
+ );
95
+
96
+ await collection.insertOne(recordToInsert);
97
+ } catch (error) {
98
+ if (error instanceof Error) {
99
+ const matstraError = new MastraError(
100
+ {
101
+ id: 'STORAGE_MONGODB_STORE_INSERT_FAILED',
102
+ domain: ErrorDomain.STORAGE,
103
+ category: ErrorCategory.THIRD_PARTY,
104
+ details: { tableName },
105
+ },
106
+ error,
107
+ );
108
+ this.logger.error(matstraError.message);
109
+ this.logger?.trackException(matstraError);
110
+ }
111
+ }
112
+ }
113
+
114
+ async batchInsert({ tableName, records }: { tableName: TABLE_NAMES; records: Record<string, any>[] }): Promise<void> {
115
+ if (!records.length) {
116
+ return;
117
+ }
118
+
119
+ try {
120
+ const collection = await this.getCollection(tableName);
121
+ await collection.insertMany(records);
122
+ } catch (error) {
123
+ throw new MastraError(
124
+ {
125
+ id: 'STORAGE_MONGODB_STORE_BATCH_INSERT_FAILED',
126
+ domain: ErrorDomain.STORAGE,
127
+ category: ErrorCategory.THIRD_PARTY,
128
+ details: { tableName },
129
+ },
130
+ error,
131
+ );
132
+ }
133
+ }
134
+
135
+ async load<R>({ tableName, keys }: { tableName: TABLE_NAMES; keys: Record<string, string> }): Promise<R | null> {
136
+ this.logger.info(`Loading ${tableName} with keys ${JSON.stringify(keys)}`);
137
+ try {
138
+ const collection = await this.getCollection(tableName);
139
+ return (await collection.find(keys).toArray()) as R;
140
+ } catch (error) {
141
+ throw new MastraError(
142
+ {
143
+ id: 'STORAGE_MONGODB_STORE_LOAD_FAILED',
144
+ domain: ErrorDomain.STORAGE,
145
+ category: ErrorCategory.THIRD_PARTY,
146
+ details: { tableName },
147
+ },
148
+ error,
149
+ );
150
+ }
151
+ }
152
+ }
@@ -0,0 +1,379 @@
1
+ import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
2
+ import type { ScoreRowData, ScoringEntityType, ScoringSource } from '@mastra/core/scores';
3
+ import { ScoresStorage, TABLE_SCORERS, safelyParseJSON } from '@mastra/core/storage';
4
+ import type { PaginationInfo, StoragePagination } from '@mastra/core/storage';
5
+ import type { StoreOperationsMongoDB } from '../operations';
6
+
7
+ function transformScoreRow(row: Record<string, any>): ScoreRowData {
8
+ let scorerValue: any = null;
9
+ if (row.scorer) {
10
+ try {
11
+ scorerValue = typeof row.scorer === 'string' ? safelyParseJSON(row.scorer) : row.scorer;
12
+ } catch (e) {
13
+ console.warn('Failed to parse scorer:', e);
14
+ }
15
+ }
16
+
17
+ let extractStepResultValue: any = null;
18
+ if (row.extractStepResult) {
19
+ try {
20
+ extractStepResultValue =
21
+ typeof row.extractStepResult === 'string' ? safelyParseJSON(row.extractStepResult) : row.extractStepResult;
22
+ } catch (e) {
23
+ console.warn('Failed to parse extractStepResult:', e);
24
+ }
25
+ }
26
+
27
+ let analyzeStepResultValue: any = null;
28
+ if (row.analyzeStepResult) {
29
+ try {
30
+ analyzeStepResultValue =
31
+ typeof row.analyzeStepResult === 'string' ? safelyParseJSON(row.analyzeStepResult) : row.analyzeStepResult;
32
+ } catch (e) {
33
+ console.warn('Failed to parse analyzeStepResult:', e);
34
+ }
35
+ }
36
+
37
+ let inputValue: any = null;
38
+ if (row.input) {
39
+ try {
40
+ inputValue = typeof row.input === 'string' ? safelyParseJSON(row.input) : row.input;
41
+ } catch (e) {
42
+ console.warn('Failed to parse input:', e);
43
+ }
44
+ }
45
+
46
+ let outputValue: any = null;
47
+ if (row.output) {
48
+ try {
49
+ outputValue = typeof row.output === 'string' ? safelyParseJSON(row.output) : row.output;
50
+ } catch (e) {
51
+ console.warn('Failed to parse output:', e);
52
+ }
53
+ }
54
+
55
+ let entityValue: any = null;
56
+ if (row.entity) {
57
+ try {
58
+ entityValue = typeof row.entity === 'string' ? safelyParseJSON(row.entity) : row.entity;
59
+ } catch (e) {
60
+ console.warn('Failed to parse entity:', e);
61
+ }
62
+ }
63
+
64
+ let runtimeContextValue: any = null;
65
+ if (row.runtimeContext) {
66
+ try {
67
+ runtimeContextValue =
68
+ typeof row.runtimeContext === 'string' ? safelyParseJSON(row.runtimeContext) : row.runtimeContext;
69
+ } catch (e) {
70
+ console.warn('Failed to parse runtimeContext:', e);
71
+ }
72
+ }
73
+
74
+ return {
75
+ id: row.id as string,
76
+ entityId: row.entityId as string,
77
+ entityType: row.entityType as ScoringEntityType,
78
+ scorerId: row.scorerId as string,
79
+ traceId: row.traceId as string,
80
+ runId: row.runId as string,
81
+ scorer: scorerValue,
82
+ extractStepResult: extractStepResultValue,
83
+ analyzeStepResult: analyzeStepResultValue,
84
+ score: row.score as number,
85
+ reason: row.reason as string,
86
+ extractPrompt: row.extractPrompt as string,
87
+ analyzePrompt: row.analyzePrompt as string,
88
+ reasonPrompt: row.reasonPrompt as string,
89
+ input: inputValue,
90
+ output: outputValue,
91
+ additionalContext: row.additionalContext,
92
+ runtimeContext: runtimeContextValue,
93
+ entity: entityValue,
94
+ source: row.source as ScoringSource,
95
+ resourceId: row.resourceId as string,
96
+ threadId: row.threadId as string,
97
+ createdAt: new Date(row.createdAt),
98
+ updatedAt: new Date(row.updatedAt),
99
+ };
100
+ }
101
+
102
+ export class ScoresStorageMongoDB extends ScoresStorage {
103
+ private operations: StoreOperationsMongoDB;
104
+
105
+ constructor({ operations }: { operations: StoreOperationsMongoDB }) {
106
+ super();
107
+ this.operations = operations;
108
+ }
109
+
110
+ async getScoreById({ id }: { id: string }): Promise<ScoreRowData | null> {
111
+ try {
112
+ const collection = await this.operations.getCollection(TABLE_SCORERS);
113
+ const document = await collection.findOne({ id });
114
+
115
+ if (!document) {
116
+ return null;
117
+ }
118
+
119
+ return transformScoreRow(document);
120
+ } catch (error) {
121
+ throw new MastraError(
122
+ {
123
+ id: 'STORAGE_MONGODB_STORE_GET_SCORE_BY_ID_FAILED',
124
+ domain: ErrorDomain.STORAGE,
125
+ category: ErrorCategory.THIRD_PARTY,
126
+ details: { id },
127
+ },
128
+ error,
129
+ );
130
+ }
131
+ }
132
+
133
+ async saveScore(score: Omit<ScoreRowData, 'id' | 'createdAt' | 'updatedAt'>): Promise<{ score: ScoreRowData }> {
134
+ try {
135
+ const now = new Date();
136
+ const scoreId = `score-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
137
+
138
+ const scoreData = {
139
+ id: scoreId,
140
+ entityId: score.entityId,
141
+ entityType: score.entityType,
142
+ scorerId: score.scorerId,
143
+ traceId: score.traceId || '',
144
+ runId: score.runId,
145
+ scorer: typeof score.scorer === 'string' ? score.scorer : JSON.stringify(score.scorer),
146
+ extractStepResult:
147
+ typeof score.extractStepResult === 'string'
148
+ ? score.extractStepResult
149
+ : JSON.stringify(score.extractStepResult),
150
+ analyzeStepResult:
151
+ typeof score.analyzeStepResult === 'string'
152
+ ? score.analyzeStepResult
153
+ : JSON.stringify(score.analyzeStepResult),
154
+ score: score.score,
155
+ reason: score.reason,
156
+ extractPrompt: score.extractPrompt,
157
+ analyzePrompt: score.analyzePrompt,
158
+ reasonPrompt: score.reasonPrompt,
159
+ input: typeof score.input === 'string' ? score.input : JSON.stringify(score.input),
160
+ output: typeof score.output === 'string' ? score.output : JSON.stringify(score.output),
161
+ additionalContext: score.additionalContext,
162
+ runtimeContext:
163
+ typeof score.runtimeContext === 'string' ? score.runtimeContext : JSON.stringify(score.runtimeContext),
164
+ entity: typeof score.entity === 'string' ? score.entity : JSON.stringify(score.entity),
165
+ source: score.source,
166
+ resourceId: score.resourceId || '',
167
+ threadId: score.threadId || '',
168
+ createdAt: now,
169
+ updatedAt: now,
170
+ };
171
+
172
+ const collection = await this.operations.getCollection(TABLE_SCORERS);
173
+ await collection.insertOne(scoreData);
174
+
175
+ const savedScore: ScoreRowData = {
176
+ ...score,
177
+ id: scoreId,
178
+ createdAt: now,
179
+ updatedAt: now,
180
+ };
181
+
182
+ return { score: savedScore };
183
+ } catch (error) {
184
+ throw new MastraError(
185
+ {
186
+ id: 'STORAGE_MONGODB_STORE_SAVE_SCORE_FAILED',
187
+ domain: ErrorDomain.STORAGE,
188
+ category: ErrorCategory.THIRD_PARTY,
189
+ details: { scorerId: score.scorerId, runId: score.runId },
190
+ },
191
+ error,
192
+ );
193
+ }
194
+ }
195
+
196
+ async getScoresByScorerId({
197
+ scorerId,
198
+ pagination,
199
+ entityId,
200
+ entityType,
201
+ }: {
202
+ scorerId: string;
203
+ pagination: StoragePagination;
204
+ entityId?: string;
205
+ entityType?: string;
206
+ }): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
207
+ try {
208
+ const query: any = { scorerId };
209
+
210
+ if (entityId) {
211
+ query.entityId = entityId;
212
+ }
213
+
214
+ if (entityType) {
215
+ query.entityType = entityType;
216
+ }
217
+
218
+ const collection = await this.operations.getCollection(TABLE_SCORERS);
219
+ const total = await collection.countDocuments(query);
220
+ const currentOffset = pagination.page * pagination.perPage;
221
+
222
+ if (total === 0) {
223
+ return {
224
+ scores: [],
225
+ pagination: {
226
+ total: 0,
227
+ page: pagination.page,
228
+ perPage: pagination.perPage,
229
+ hasMore: false,
230
+ },
231
+ };
232
+ }
233
+
234
+ const documents = await collection
235
+ .find(query)
236
+ .sort({ createdAt: 'desc' })
237
+ .skip(currentOffset)
238
+ .limit(pagination.perPage)
239
+ .toArray();
240
+
241
+ const scores = documents.map(row => transformScoreRow(row));
242
+ const hasMore = currentOffset + scores.length < total;
243
+
244
+ return {
245
+ scores,
246
+ pagination: {
247
+ total,
248
+ page: pagination.page,
249
+ perPage: pagination.perPage,
250
+ hasMore,
251
+ },
252
+ };
253
+ } catch (error) {
254
+ throw new MastraError(
255
+ {
256
+ id: 'STORAGE_MONGODB_STORE_GET_SCORES_BY_SCORER_ID_FAILED',
257
+ domain: ErrorDomain.STORAGE,
258
+ category: ErrorCategory.THIRD_PARTY,
259
+ details: { scorerId, page: pagination.page, perPage: pagination.perPage },
260
+ },
261
+ error,
262
+ );
263
+ }
264
+ }
265
+
266
+ async getScoresByRunId({
267
+ runId,
268
+ pagination,
269
+ }: {
270
+ runId: string;
271
+ pagination: StoragePagination;
272
+ }): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
273
+ try {
274
+ const collection = await this.operations.getCollection(TABLE_SCORERS);
275
+ const total = await collection.countDocuments({ runId });
276
+ const currentOffset = pagination.page * pagination.perPage;
277
+
278
+ if (total === 0) {
279
+ return {
280
+ scores: [],
281
+ pagination: {
282
+ total: 0,
283
+ page: pagination.page,
284
+ perPage: pagination.perPage,
285
+ hasMore: false,
286
+ },
287
+ };
288
+ }
289
+
290
+ const documents = await collection
291
+ .find({ runId })
292
+ .sort({ createdAt: 'desc' })
293
+ .skip(currentOffset)
294
+ .limit(pagination.perPage)
295
+ .toArray();
296
+
297
+ const scores = documents.map(row => transformScoreRow(row));
298
+ const hasMore = currentOffset + scores.length < total;
299
+
300
+ return {
301
+ scores,
302
+ pagination: {
303
+ total,
304
+ page: pagination.page,
305
+ perPage: pagination.perPage,
306
+ hasMore,
307
+ },
308
+ };
309
+ } catch (error) {
310
+ throw new MastraError(
311
+ {
312
+ id: 'STORAGE_MONGODB_STORE_GET_SCORES_BY_RUN_ID_FAILED',
313
+ domain: ErrorDomain.STORAGE,
314
+ category: ErrorCategory.THIRD_PARTY,
315
+ details: { runId, page: pagination.page, perPage: pagination.perPage },
316
+ },
317
+ error,
318
+ );
319
+ }
320
+ }
321
+
322
+ async getScoresByEntityId({
323
+ entityId,
324
+ entityType,
325
+ pagination,
326
+ }: {
327
+ pagination: StoragePagination;
328
+ entityId: string;
329
+ entityType: string;
330
+ }): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
331
+ try {
332
+ const collection = await this.operations.getCollection(TABLE_SCORERS);
333
+ const total = await collection.countDocuments({ entityId, entityType });
334
+ const currentOffset = pagination.page * pagination.perPage;
335
+
336
+ if (total === 0) {
337
+ return {
338
+ scores: [],
339
+ pagination: {
340
+ total: 0,
341
+ page: pagination.page,
342
+ perPage: pagination.perPage,
343
+ hasMore: false,
344
+ },
345
+ };
346
+ }
347
+
348
+ const documents = await collection
349
+ .find({ entityId, entityType })
350
+ .sort({ createdAt: 'desc' })
351
+ .skip(currentOffset)
352
+ .limit(pagination.perPage)
353
+ .toArray();
354
+
355
+ const scores = documents.map(row => transformScoreRow(row));
356
+ const hasMore = currentOffset + scores.length < total;
357
+
358
+ return {
359
+ scores,
360
+ pagination: {
361
+ total,
362
+ page: pagination.page,
363
+ perPage: pagination.perPage,
364
+ hasMore,
365
+ },
366
+ };
367
+ } catch (error) {
368
+ throw new MastraError(
369
+ {
370
+ id: 'STORAGE_MONGODB_STORE_GET_SCORES_BY_ENTITY_ID_FAILED',
371
+ domain: ErrorDomain.STORAGE,
372
+ category: ErrorCategory.THIRD_PARTY,
373
+ details: { entityId, entityType, page: pagination.page, perPage: pagination.perPage },
374
+ },
375
+ error,
376
+ );
377
+ }
378
+ }
379
+ }
@@ -0,0 +1,142 @@
1
+ import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
2
+ import type { PaginationInfo, StorageGetTracesArg, StorageGetTracesPaginatedArg } from '@mastra/core/storage';
3
+ import { TABLE_TRACES, TracesStorage, safelyParseJSON } from '@mastra/core/storage';
4
+ import type { Trace } from '@mastra/core/telemetry';
5
+ import type { StoreOperationsMongoDB } from '../operations';
6
+
7
+ export class TracesStorageMongoDB extends TracesStorage {
8
+ private operations: StoreOperationsMongoDB;
9
+
10
+ constructor({ operations }: { operations: StoreOperationsMongoDB }) {
11
+ super();
12
+ this.operations = operations;
13
+ }
14
+
15
+ async getTraces(args: StorageGetTracesArg): Promise<Trace[]> {
16
+ if (args.fromDate || args.toDate) {
17
+ (args as any).dateRange = {
18
+ start: args.fromDate,
19
+ end: args.toDate,
20
+ };
21
+ }
22
+ try {
23
+ const result = await this.getTracesPaginated(args);
24
+ return result.traces;
25
+ } catch (error) {
26
+ throw new MastraError(
27
+ {
28
+ id: 'STORAGE_MONGODB_STORE_GET_TRACES_FAILED',
29
+ domain: ErrorDomain.STORAGE,
30
+ category: ErrorCategory.THIRD_PARTY,
31
+ },
32
+ error,
33
+ );
34
+ }
35
+ }
36
+
37
+ async getTracesPaginated(args: StorageGetTracesPaginatedArg): Promise<PaginationInfo & { traces: Trace[] }> {
38
+ const { name, scope, page = 0, perPage = 100, attributes, filters, dateRange } = args;
39
+ const fromDate = dateRange?.start;
40
+ const toDate = dateRange?.end;
41
+ const currentOffset = page * perPage;
42
+
43
+ const query: any = {};
44
+ if (name) {
45
+ query['name'] = new RegExp(name);
46
+ }
47
+
48
+ if (scope) {
49
+ query['scope'] = scope;
50
+ }
51
+
52
+ if (attributes) {
53
+ query['$and'] = Object.entries(attributes).map(([key, value]) => ({
54
+ [`attributes.${key}`]: value,
55
+ }));
56
+ }
57
+
58
+ if (filters) {
59
+ Object.entries(filters).forEach(([key, value]) => {
60
+ query[key] = value;
61
+ });
62
+ }
63
+
64
+ if (fromDate || toDate) {
65
+ query['createdAt'] = {};
66
+ if (fromDate) {
67
+ query['createdAt']['$gte'] = fromDate;
68
+ }
69
+ if (toDate) {
70
+ query['createdAt']['$lte'] = toDate;
71
+ }
72
+ }
73
+
74
+ try {
75
+ const collection = await this.operations.getCollection(TABLE_TRACES);
76
+
77
+ // Get total count
78
+ const total = await collection.countDocuments(query);
79
+
80
+ if (total === 0) {
81
+ return {
82
+ traces: [],
83
+ total: 0,
84
+ page,
85
+ perPage,
86
+ hasMore: false,
87
+ };
88
+ }
89
+
90
+ // Get results with pagination
91
+ const result = await collection
92
+ .find(query, {
93
+ sort: { startTime: -1 },
94
+ })
95
+ .limit(perPage)
96
+ .skip(currentOffset)
97
+ .toArray();
98
+
99
+ const traces = result.map(row => ({
100
+ id: row.id,
101
+ parentSpanId: row.parentSpanId,
102
+ traceId: row.traceId,
103
+ name: row.name,
104
+ scope: row.scope,
105
+ kind: row.kind,
106
+ status: safelyParseJSON(row.status as string),
107
+ events: safelyParseJSON(row.events as string),
108
+ links: safelyParseJSON(row.links as string),
109
+ attributes: safelyParseJSON(row.attributes as string),
110
+ startTime: row.startTime,
111
+ endTime: row.endTime,
112
+ other: safelyParseJSON(row.other as string),
113
+ createdAt: row.createdAt,
114
+ })) as Trace[];
115
+
116
+ return {
117
+ traces,
118
+ total,
119
+ page,
120
+ perPage,
121
+ hasMore: currentOffset + traces.length < total,
122
+ };
123
+ } catch (error) {
124
+ throw new MastraError(
125
+ {
126
+ id: 'STORAGE_MONGODB_STORE_GET_TRACES_PAGINATED_FAILED',
127
+ domain: ErrorDomain.STORAGE,
128
+ category: ErrorCategory.THIRD_PARTY,
129
+ },
130
+ error,
131
+ );
132
+ }
133
+ }
134
+
135
+ async batchTraceInsert({ records }: { records: Record<string, any>[] }): Promise<void> {
136
+ this.logger.debug('Batch inserting traces', { count: records.length });
137
+ await this.operations.batchInsert({
138
+ tableName: TABLE_TRACES,
139
+ records,
140
+ });
141
+ }
142
+ }
@@ -0,0 +1,43 @@
1
+ import type { IMastraLogger } from '@mastra/core/logger';
2
+
3
+ export function formatDateForMongoDB(date: Date | string): Date {
4
+ return typeof date === 'string' ? new Date(date) : date;
5
+ }
6
+
7
+ export function createExecuteOperationWithRetry({
8
+ logger,
9
+ maxRetries = 3,
10
+ initialBackoffMs = 100,
11
+ }: {
12
+ logger: IMastraLogger;
13
+ maxRetries?: number;
14
+ initialBackoffMs?: number;
15
+ }) {
16
+ return async function executeOperationWithRetry<T>(
17
+ operationFn: () => Promise<T>,
18
+ operationDescription: string,
19
+ ): Promise<T> {
20
+ let retries = 0;
21
+
22
+ while (true) {
23
+ try {
24
+ return await operationFn();
25
+ } catch (error: any) {
26
+ if (
27
+ ((error.message && error.message.includes('connection')) || error.code === 'ECONNRESET') &&
28
+ retries < maxRetries
29
+ ) {
30
+ retries++;
31
+ const backoffTime = initialBackoffMs * Math.pow(2, retries - 1);
32
+ logger.warn(
33
+ `MongoDBStore: Encountered connection error during ${operationDescription}. Retrying (${retries}/${maxRetries}) in ${backoffTime}ms...`,
34
+ );
35
+ await new Promise(resolve => setTimeout(resolve, backoffTime));
36
+ } else {
37
+ logger.error(`MongoDBStore: Error during ${operationDescription} after ${retries} retries: ${error}`);
38
+ throw error;
39
+ }
40
+ }
41
+ }
42
+ };
43
+ }