@mastra/mongodb 1.0.0-beta.9 → 1.1.0-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.cjs CHANGED
@@ -13,7 +13,7 @@ var evals = require('@mastra/core/evals');
13
13
 
14
14
  // package.json
15
15
  var package_default = {
16
- version: "1.0.0-beta.9"};
16
+ version: "1.1.0-alpha.0"};
17
17
  var MongoDBFilterTranslator = class extends filter.BaseFilterTranslator {
18
18
  getSupportedOperators() {
19
19
  return {
@@ -107,7 +107,7 @@ var MongoDBVector = class extends vector.MastraVector {
107
107
  client;
108
108
  db;
109
109
  collections;
110
- embeddingFieldName = "embedding";
110
+ embeddingFieldName;
111
111
  metadataFieldName = "metadata";
112
112
  documentFieldName = "document";
113
113
  collectionForValidation = null;
@@ -116,8 +116,11 @@ var MongoDBVector = class extends vector.MastraVector {
116
116
  euclidean: "euclidean",
117
117
  dotproduct: "dotProduct"
118
118
  };
119
- constructor({ id, uri, dbName, options }) {
119
+ constructor({ id, uri, dbName, options, embeddingFieldPath }) {
120
120
  super({ id });
121
+ if (!uri) {
122
+ throw new Error('MongoDBVector requires a connection string. Provide "uri" in the constructor options.');
123
+ }
121
124
  const client = new mongodb.MongoClient(uri, {
122
125
  ...options,
123
126
  driverInfo: {
@@ -128,6 +131,7 @@ var MongoDBVector = class extends vector.MastraVector {
128
131
  this.client = client;
129
132
  this.db = this.client.db(dbName);
130
133
  this.collections = /* @__PURE__ */ new Map();
134
+ this.embeddingFieldName = embeddingFieldPath ?? "embedding";
131
135
  }
132
136
  // Public methods
133
137
  async connect() {
@@ -276,6 +280,8 @@ var MongoDBVector = class extends vector.MastraVector {
276
280
  throw new Error(`Index "${indexNameInternal}" did not become ready within timeout`);
277
281
  }
278
282
  async upsert({ indexName, vectors, metadata, ids, documents }) {
283
+ vector.validateUpsertInput("MONGODB", vectors, metadata, ids);
284
+ vector.validateVectorValues("MONGODB", vectors);
279
285
  try {
280
286
  const collection = await this.getCollection(indexName);
281
287
  this.collectionForValidation = collection;
@@ -351,8 +357,8 @@ var MongoDBVector = class extends vector.MastraVector {
351
357
  index: indexNameInternal,
352
358
  queryVector,
353
359
  path: this.embeddingFieldName,
354
- numCandidates: 100,
355
- limit: topK
360
+ numCandidates: Math.min(1e4, Math.max(100, topK)),
361
+ limit: Math.min(1e4, topK)
356
362
  };
357
363
  if (Object.keys(combinedFilter).length > 0) {
358
364
  const filterWithExclusion = {
@@ -859,11 +865,21 @@ function resolveMongoDBConfig(config) {
859
865
  );
860
866
  }
861
867
  }
868
+ const connectionString = config.uri ?? config.url;
869
+ if (!connectionString) {
870
+ throw new error.MastraError({
871
+ id: storage.createStorageErrorId("MONGODB", "CONSTRUCTOR", "MISSING_URI"),
872
+ domain: error.ErrorDomain.STORAGE,
873
+ category: error.ErrorCategory.USER,
874
+ details: { dbName: config?.dbName },
875
+ text: 'MongoDBStore requires a connection string. Provide "uri" (recommended) or "url" in the constructor options.'
876
+ });
877
+ }
862
878
  try {
863
879
  return MongoDBConnector.fromDatabaseConfig({
864
880
  id: "id" in config ? config.id : "domain",
865
881
  options: config.options,
866
- url: config.url,
882
+ url: connectionString,
867
883
  dbName: config.dbName
868
884
  });
869
885
  } catch (error$1) {
@@ -872,7 +888,7 @@ function resolveMongoDBConfig(config) {
872
888
  id: storage.createStorageErrorId("MONGODB", "CONSTRUCTOR", "FAILED"),
873
889
  domain: error.ErrorDomain.STORAGE,
874
890
  category: error.ErrorCategory.USER,
875
- details: { url: config?.url, dbName: config?.dbName }
891
+ details: { uri: config?.uri ?? "", url: config?.url ?? "", dbName: config?.dbName ?? "" }
876
892
  },
877
893
  error$1
878
894
  );
@@ -883,7 +899,7 @@ var MongoDBAgentsStorage = class _MongoDBAgentsStorage extends storage.AgentsSto
883
899
  #skipDefaultIndexes;
884
900
  #indexes;
885
901
  /** Collections managed by this domain */
886
- static MANAGED_COLLECTIONS = [storage.TABLE_AGENTS];
902
+ static MANAGED_COLLECTIONS = [storage.TABLE_AGENTS, storage.TABLE_AGENT_VERSIONS];
887
903
  constructor(config) {
888
904
  super();
889
905
  this.#connector = resolveMongoDBConfig(config);
@@ -903,7 +919,10 @@ var MongoDBAgentsStorage = class _MongoDBAgentsStorage extends storage.AgentsSto
903
919
  return [
904
920
  { collection: storage.TABLE_AGENTS, keys: { id: 1 }, options: { unique: true } },
905
921
  { collection: storage.TABLE_AGENTS, keys: { createdAt: -1 } },
906
- { collection: storage.TABLE_AGENTS, keys: { updatedAt: -1 } }
922
+ { collection: storage.TABLE_AGENTS, keys: { updatedAt: -1 } },
923
+ { collection: storage.TABLE_AGENT_VERSIONS, keys: { id: 1 }, options: { unique: true } },
924
+ { collection: storage.TABLE_AGENT_VERSIONS, keys: { agentId: 1, versionNumber: -1 }, options: { unique: true } },
925
+ { collection: storage.TABLE_AGENT_VERSIONS, keys: { agentId: 1, createdAt: -1 } }
907
926
  ];
908
927
  }
909
928
  async createDefaultIndexes() {
@@ -940,8 +959,10 @@ var MongoDBAgentsStorage = class _MongoDBAgentsStorage extends storage.AgentsSto
940
959
  await this.createCustomIndexes();
941
960
  }
942
961
  async dangerouslyClearAll() {
943
- const collection = await this.getCollection(storage.TABLE_AGENTS);
944
- await collection.deleteMany({});
962
+ const versionsCollection = await this.getCollection(storage.TABLE_AGENT_VERSIONS);
963
+ await versionsCollection.deleteMany({});
964
+ const agentsCollection = await this.getCollection(storage.TABLE_AGENTS);
965
+ await agentsCollection.deleteMany({});
945
966
  }
946
967
  async getAgentById({ id }) {
947
968
  try {
@@ -1027,6 +1048,9 @@ var MongoDBAgentsStorage = class _MongoDBAgentsStorage extends storage.AgentsSto
1027
1048
  if (updates.outputProcessors !== void 0) updateDoc.outputProcessors = updates.outputProcessors;
1028
1049
  if (updates.memory !== void 0) updateDoc.memory = updates.memory;
1029
1050
  if (updates.scorers !== void 0) updateDoc.scorers = updates.scorers;
1051
+ if (updates.integrationTools !== void 0) updateDoc.integrationTools = updates.integrationTools;
1052
+ if (updates.ownerId !== void 0) updateDoc.ownerId = updates.ownerId;
1053
+ if (updates.activeVersionId !== void 0) updateDoc.activeVersionId = updates.activeVersionId;
1030
1054
  if (updates.metadata !== void 0) {
1031
1055
  const existingMetadata = existingAgent.metadata || {};
1032
1056
  updateDoc.metadata = { ...existingMetadata, ...updates.metadata };
@@ -1060,6 +1084,7 @@ var MongoDBAgentsStorage = class _MongoDBAgentsStorage extends storage.AgentsSto
1060
1084
  }
1061
1085
  async deleteAgent({ id }) {
1062
1086
  try {
1087
+ await this.deleteVersionsByAgentId(id);
1063
1088
  const collection = await this.getCollection(storage.TABLE_AGENTS);
1064
1089
  await collection.deleteOne({ id });
1065
1090
  } catch (error$1) {
@@ -1143,6 +1168,212 @@ var MongoDBAgentsStorage = class _MongoDBAgentsStorage extends storage.AgentsSto
1143
1168
  ...agent
1144
1169
  };
1145
1170
  }
1171
+ // ==========================================================================
1172
+ // Agent Version Methods
1173
+ // ==========================================================================
1174
+ async createVersion(input) {
1175
+ try {
1176
+ const collection = await this.getCollection(storage.TABLE_AGENT_VERSIONS);
1177
+ const now = /* @__PURE__ */ new Date();
1178
+ const versionDoc = {
1179
+ id: input.id,
1180
+ agentId: input.agentId,
1181
+ versionNumber: input.versionNumber,
1182
+ name: input.name ?? void 0,
1183
+ snapshot: input.snapshot,
1184
+ changedFields: input.changedFields ?? void 0,
1185
+ changeMessage: input.changeMessage ?? void 0,
1186
+ createdAt: now
1187
+ };
1188
+ await collection.insertOne(versionDoc);
1189
+ return {
1190
+ ...input,
1191
+ createdAt: now
1192
+ };
1193
+ } catch (error$1) {
1194
+ throw new error.MastraError(
1195
+ {
1196
+ id: storage.createStorageErrorId("MONGODB", "CREATE_VERSION", "FAILED"),
1197
+ domain: error.ErrorDomain.STORAGE,
1198
+ category: error.ErrorCategory.THIRD_PARTY,
1199
+ details: { versionId: input.id, agentId: input.agentId }
1200
+ },
1201
+ error$1
1202
+ );
1203
+ }
1204
+ }
1205
+ async getVersion(id) {
1206
+ try {
1207
+ const collection = await this.getCollection(storage.TABLE_AGENT_VERSIONS);
1208
+ const result = await collection.findOne({ id });
1209
+ if (!result) {
1210
+ return null;
1211
+ }
1212
+ return this.transformVersion(result);
1213
+ } catch (error$1) {
1214
+ throw new error.MastraError(
1215
+ {
1216
+ id: storage.createStorageErrorId("MONGODB", "GET_VERSION", "FAILED"),
1217
+ domain: error.ErrorDomain.STORAGE,
1218
+ category: error.ErrorCategory.THIRD_PARTY,
1219
+ details: { versionId: id }
1220
+ },
1221
+ error$1
1222
+ );
1223
+ }
1224
+ }
1225
+ async getVersionByNumber(agentId, versionNumber) {
1226
+ try {
1227
+ const collection = await this.getCollection(storage.TABLE_AGENT_VERSIONS);
1228
+ const result = await collection.findOne({ agentId, versionNumber });
1229
+ if (!result) {
1230
+ return null;
1231
+ }
1232
+ return this.transformVersion(result);
1233
+ } catch (error$1) {
1234
+ throw new error.MastraError(
1235
+ {
1236
+ id: storage.createStorageErrorId("MONGODB", "GET_VERSION_BY_NUMBER", "FAILED"),
1237
+ domain: error.ErrorDomain.STORAGE,
1238
+ category: error.ErrorCategory.THIRD_PARTY,
1239
+ details: { agentId, versionNumber }
1240
+ },
1241
+ error$1
1242
+ );
1243
+ }
1244
+ }
1245
+ async getLatestVersion(agentId) {
1246
+ try {
1247
+ const collection = await this.getCollection(storage.TABLE_AGENT_VERSIONS);
1248
+ const result = await collection.find({ agentId }).sort({ versionNumber: -1 }).limit(1).toArray();
1249
+ if (!result || result.length === 0) {
1250
+ return null;
1251
+ }
1252
+ return this.transformVersion(result[0]);
1253
+ } catch (error$1) {
1254
+ throw new error.MastraError(
1255
+ {
1256
+ id: storage.createStorageErrorId("MONGODB", "GET_LATEST_VERSION", "FAILED"),
1257
+ domain: error.ErrorDomain.STORAGE,
1258
+ category: error.ErrorCategory.THIRD_PARTY,
1259
+ details: { agentId }
1260
+ },
1261
+ error$1
1262
+ );
1263
+ }
1264
+ }
1265
+ async listVersions(input) {
1266
+ const { agentId, page = 0, perPage: perPageInput, orderBy } = input;
1267
+ if (page < 0) {
1268
+ throw new error.MastraError(
1269
+ {
1270
+ id: storage.createStorageErrorId("MONGODB", "LIST_VERSIONS", "INVALID_PAGE"),
1271
+ domain: error.ErrorDomain.STORAGE,
1272
+ category: error.ErrorCategory.USER,
1273
+ details: { page }
1274
+ },
1275
+ new Error("page must be >= 0")
1276
+ );
1277
+ }
1278
+ const perPage = storage.normalizePerPage(perPageInput, 20);
1279
+ const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage);
1280
+ try {
1281
+ const { field, direction } = this.parseVersionOrderBy(orderBy);
1282
+ const collection = await this.getCollection(storage.TABLE_AGENT_VERSIONS);
1283
+ const total = await collection.countDocuments({ agentId });
1284
+ if (total === 0 || perPage === 0) {
1285
+ return {
1286
+ versions: [],
1287
+ total,
1288
+ page,
1289
+ perPage: perPageForResponse,
1290
+ hasMore: false
1291
+ };
1292
+ }
1293
+ const sortOrder = direction === "ASC" ? 1 : -1;
1294
+ let cursor = collection.find({ agentId }).sort({ [field]: sortOrder }).skip(offset);
1295
+ if (perPageInput !== false) {
1296
+ cursor = cursor.limit(perPage);
1297
+ }
1298
+ const results = await cursor.toArray();
1299
+ const versions = results.map((doc) => this.transformVersion(doc));
1300
+ return {
1301
+ versions,
1302
+ total,
1303
+ page,
1304
+ perPage: perPageForResponse,
1305
+ hasMore: perPageInput !== false && offset + perPage < total
1306
+ };
1307
+ } catch (error$1) {
1308
+ throw new error.MastraError(
1309
+ {
1310
+ id: storage.createStorageErrorId("MONGODB", "LIST_VERSIONS", "FAILED"),
1311
+ domain: error.ErrorDomain.STORAGE,
1312
+ category: error.ErrorCategory.THIRD_PARTY,
1313
+ details: { agentId }
1314
+ },
1315
+ error$1
1316
+ );
1317
+ }
1318
+ }
1319
+ async deleteVersion(id) {
1320
+ try {
1321
+ const collection = await this.getCollection(storage.TABLE_AGENT_VERSIONS);
1322
+ await collection.deleteOne({ id });
1323
+ } catch (error$1) {
1324
+ throw new error.MastraError(
1325
+ {
1326
+ id: storage.createStorageErrorId("MONGODB", "DELETE_VERSION", "FAILED"),
1327
+ domain: error.ErrorDomain.STORAGE,
1328
+ category: error.ErrorCategory.THIRD_PARTY,
1329
+ details: { versionId: id }
1330
+ },
1331
+ error$1
1332
+ );
1333
+ }
1334
+ }
1335
+ async deleteVersionsByAgentId(agentId) {
1336
+ try {
1337
+ const collection = await this.getCollection(storage.TABLE_AGENT_VERSIONS);
1338
+ await collection.deleteMany({ agentId });
1339
+ } catch (error$1) {
1340
+ throw new error.MastraError(
1341
+ {
1342
+ id: storage.createStorageErrorId("MONGODB", "DELETE_VERSIONS_BY_AGENT_ID", "FAILED"),
1343
+ domain: error.ErrorDomain.STORAGE,
1344
+ category: error.ErrorCategory.THIRD_PARTY,
1345
+ details: { agentId }
1346
+ },
1347
+ error$1
1348
+ );
1349
+ }
1350
+ }
1351
+ async countVersions(agentId) {
1352
+ try {
1353
+ const collection = await this.getCollection(storage.TABLE_AGENT_VERSIONS);
1354
+ return await collection.countDocuments({ agentId });
1355
+ } catch (error$1) {
1356
+ throw new error.MastraError(
1357
+ {
1358
+ id: storage.createStorageErrorId("MONGODB", "COUNT_VERSIONS", "FAILED"),
1359
+ domain: error.ErrorDomain.STORAGE,
1360
+ category: error.ErrorCategory.THIRD_PARTY,
1361
+ details: { agentId }
1362
+ },
1363
+ error$1
1364
+ );
1365
+ }
1366
+ }
1367
+ // ==========================================================================
1368
+ // Private Helper Methods
1369
+ // ==========================================================================
1370
+ transformVersion(doc) {
1371
+ const { _id, ...version } = doc;
1372
+ return {
1373
+ ...version,
1374
+ createdAt: version.createdAt instanceof Date ? version.createdAt : new Date(version.createdAt)
1375
+ };
1376
+ }
1146
1377
  };
1147
1378
  function formatDateForMongoDB(date) {
1148
1379
  return typeof date === "string" ? new Date(date) : date;
@@ -1337,10 +1568,12 @@ var MemoryStorageMongoDB = class _MemoryStorageMongoDB extends storage.MemorySto
1337
1568
  query.resourceId = resourceId;
1338
1569
  }
1339
1570
  if (filter?.dateRange?.start) {
1340
- query.createdAt = { ...query.createdAt, $gte: filter.dateRange.start };
1571
+ const startOp = filter.dateRange.startExclusive ? "$gt" : "$gte";
1572
+ query.createdAt = { ...query.createdAt, [startOp]: formatDateForMongoDB(filter.dateRange.start) };
1341
1573
  }
1342
1574
  if (filter?.dateRange?.end) {
1343
- query.createdAt = { ...query.createdAt, $lte: filter.dateRange.end };
1575
+ const endOp = filter.dateRange.endExclusive ? "$lt" : "$lte";
1576
+ query.createdAt = { ...query.createdAt, [endOp]: formatDateForMongoDB(filter.dateRange.end) };
1344
1577
  }
1345
1578
  const total = await collection.countDocuments(query);
1346
1579
  const messages = [];
@@ -1671,25 +1904,48 @@ var MemoryStorageMongoDB = class _MemoryStorageMongoDB extends storage.MemorySto
1671
1904
  );
1672
1905
  }
1673
1906
  }
1674
- async listThreadsByResourceId(args) {
1907
+ async listThreads(args) {
1908
+ const { page = 0, perPage: perPageInput, orderBy, filter } = args;
1909
+ try {
1910
+ this.validatePaginationInput(page, perPageInput ?? 100);
1911
+ } catch (error$1) {
1912
+ throw new error.MastraError(
1913
+ {
1914
+ id: storage.createStorageErrorId("MONGODB", "LIST_THREADS", "INVALID_PAGE"),
1915
+ domain: error.ErrorDomain.STORAGE,
1916
+ category: error.ErrorCategory.USER,
1917
+ details: { page, ...perPageInput !== void 0 && { perPage: perPageInput } }
1918
+ },
1919
+ error$1 instanceof Error ? error$1 : new Error("Invalid pagination parameters")
1920
+ );
1921
+ }
1922
+ const perPage = storage.normalizePerPage(perPageInput, 100);
1923
+ try {
1924
+ this.validateMetadataKeys(filter?.metadata);
1925
+ } catch (error$1) {
1926
+ throw new error.MastraError(
1927
+ {
1928
+ id: storage.createStorageErrorId("MONGODB", "LIST_THREADS", "INVALID_METADATA_KEY"),
1929
+ domain: error.ErrorDomain.STORAGE,
1930
+ category: error.ErrorCategory.USER,
1931
+ details: { metadataKeys: filter?.metadata ? Object.keys(filter.metadata).join(", ") : "" }
1932
+ },
1933
+ error$1 instanceof Error ? error$1 : new Error("Invalid metadata key")
1934
+ );
1935
+ }
1675
1936
  try {
1676
- const { resourceId, page = 0, perPage: perPageInput, orderBy } = args;
1677
- if (page < 0) {
1678
- throw new error.MastraError(
1679
- {
1680
- id: storage.createStorageErrorId("MONGODB", "LIST_THREADS_BY_RESOURCE_ID", "INVALID_PAGE"),
1681
- domain: error.ErrorDomain.STORAGE,
1682
- category: error.ErrorCategory.USER,
1683
- details: { page }
1684
- },
1685
- new Error("page must be >= 0")
1686
- );
1687
- }
1688
- const perPage = storage.normalizePerPage(perPageInput, 100);
1689
1937
  const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage);
1690
1938
  const { field, direction } = this.parseOrderBy(orderBy);
1691
1939
  const collection = await this.getCollection(storage.TABLE_THREADS);
1692
- const query = { resourceId };
1940
+ const query = {};
1941
+ if (filter?.resourceId) {
1942
+ query.resourceId = filter.resourceId;
1943
+ }
1944
+ if (filter?.metadata && Object.keys(filter.metadata).length > 0) {
1945
+ for (const [key, value] of Object.entries(filter.metadata)) {
1946
+ query[`metadata.${key}`] = value;
1947
+ }
1948
+ }
1693
1949
  const total = await collection.countDocuments(query);
1694
1950
  if (perPage === 0) {
1695
1951
  return {
@@ -1723,10 +1979,13 @@ var MemoryStorageMongoDB = class _MemoryStorageMongoDB extends storage.MemorySto
1723
1979
  } catch (error$1) {
1724
1980
  throw new error.MastraError(
1725
1981
  {
1726
- id: storage.createStorageErrorId("MONGODB", "LIST_THREADS_BY_RESOURCE_ID", "FAILED"),
1982
+ id: storage.createStorageErrorId("MONGODB", "LIST_THREADS", "FAILED"),
1727
1983
  domain: error.ErrorDomain.STORAGE,
1728
1984
  category: error.ErrorCategory.THIRD_PARTY,
1729
- details: { resourceId: args.resourceId }
1985
+ details: {
1986
+ ...filter?.resourceId && { resourceId: filter.resourceId },
1987
+ hasMetadataFilter: !!filter?.metadata
1988
+ }
1730
1989
  },
1731
1990
  error$1
1732
1991
  );
@@ -1908,8 +2167,220 @@ var ObservabilityMongoDB = class _ObservabilityMongoDB extends storage.Observabi
1908
2167
  }
1909
2168
  }
1910
2169
  async init() {
2170
+ const uniqueIndexExists = await this.spansUniqueIndexExists();
2171
+ if (!uniqueIndexExists) {
2172
+ const duplicateInfo = await this.checkForDuplicateSpans();
2173
+ if (duplicateInfo.hasDuplicates) {
2174
+ const errorMessage = `
2175
+ ===========================================================================
2176
+ MIGRATION REQUIRED: Duplicate spans detected in ${storage.TABLE_SPANS} collection
2177
+ ===========================================================================
2178
+
2179
+ Found ${duplicateInfo.duplicateCount} duplicate (traceId, spanId) combinations.
2180
+
2181
+ The spans collection requires a unique index on (traceId, spanId), but your
2182
+ database contains duplicate entries that must be resolved first.
2183
+
2184
+ To fix this, run the manual migration command:
2185
+
2186
+ npx mastra migrate
2187
+
2188
+ This command will:
2189
+ 1. Remove duplicate spans (keeping the most complete/recent version)
2190
+ 2. Add the required unique index
2191
+
2192
+ Note: This migration may take some time for large collections.
2193
+ ===========================================================================
2194
+ `;
2195
+ throw new error.MastraError({
2196
+ id: storage.createStorageErrorId("MONGODB", "MIGRATION_REQUIRED", "DUPLICATE_SPANS"),
2197
+ domain: error.ErrorDomain.STORAGE,
2198
+ category: error.ErrorCategory.USER,
2199
+ text: errorMessage
2200
+ });
2201
+ }
2202
+ }
2203
+ await this.createDefaultIndexes();
2204
+ await this.createCustomIndexes();
2205
+ }
2206
+ /**
2207
+ * Checks if the unique index on (spanId, traceId) already exists on the spans collection.
2208
+ * Used to skip deduplication when the index already exists (migration already complete).
2209
+ */
2210
+ async spansUniqueIndexExists() {
2211
+ try {
2212
+ const collection = await this.getCollection(storage.TABLE_SPANS);
2213
+ const indexes = await collection.indexes();
2214
+ return indexes.some((idx) => idx.unique === true && idx.key?.spanId === 1 && idx.key?.traceId === 1);
2215
+ } catch {
2216
+ return false;
2217
+ }
2218
+ }
2219
+ /**
2220
+ * Checks for duplicate (traceId, spanId) combinations in the spans collection.
2221
+ * Returns information about duplicates for logging/CLI purposes.
2222
+ */
2223
+ async checkForDuplicateSpans() {
2224
+ try {
2225
+ const collection = await this.getCollection(storage.TABLE_SPANS);
2226
+ const result = await collection.aggregate([
2227
+ {
2228
+ $group: {
2229
+ _id: { traceId: "$traceId", spanId: "$spanId" },
2230
+ count: { $sum: 1 }
2231
+ }
2232
+ },
2233
+ { $match: { count: { $gt: 1 } } },
2234
+ { $count: "duplicateCount" }
2235
+ ]).toArray();
2236
+ const duplicateCount = result[0]?.duplicateCount ?? 0;
2237
+ return {
2238
+ hasDuplicates: duplicateCount > 0,
2239
+ duplicateCount
2240
+ };
2241
+ } catch (error) {
2242
+ this.logger?.debug?.(`Could not check for duplicates: ${error}`);
2243
+ return { hasDuplicates: false, duplicateCount: 0 };
2244
+ }
2245
+ }
2246
+ /**
2247
+ * Manually run the spans migration to deduplicate and add the unique index.
2248
+ * This is intended to be called from the CLI when duplicates are detected.
2249
+ *
2250
+ * @returns Migration result with status and details
2251
+ */
2252
+ async migrateSpans() {
2253
+ const indexExists = await this.spansUniqueIndexExists();
2254
+ if (indexExists) {
2255
+ return {
2256
+ success: true,
2257
+ alreadyMigrated: true,
2258
+ duplicatesRemoved: 0,
2259
+ message: `Migration already complete. Unique index exists on ${storage.TABLE_SPANS} collection.`
2260
+ };
2261
+ }
2262
+ const duplicateInfo = await this.checkForDuplicateSpans();
2263
+ if (duplicateInfo.hasDuplicates) {
2264
+ this.logger?.info?.(
2265
+ `Found ${duplicateInfo.duplicateCount} duplicate (traceId, spanId) combinations. Starting deduplication...`
2266
+ );
2267
+ await this.deduplicateSpans();
2268
+ } else {
2269
+ this.logger?.info?.(`No duplicate spans found.`);
2270
+ }
1911
2271
  await this.createDefaultIndexes();
1912
2272
  await this.createCustomIndexes();
2273
+ return {
2274
+ success: true,
2275
+ alreadyMigrated: false,
2276
+ duplicatesRemoved: duplicateInfo.duplicateCount,
2277
+ message: duplicateInfo.hasDuplicates ? `Migration complete. Removed duplicates and added unique index to ${storage.TABLE_SPANS} collection.` : `Migration complete. Added unique index to ${storage.TABLE_SPANS} collection.`
2278
+ };
2279
+ }
2280
+ /**
2281
+ * Check migration status for the spans collection.
2282
+ * Returns information about whether migration is needed.
2283
+ */
2284
+ async checkSpansMigrationStatus() {
2285
+ const indexExists = await this.spansUniqueIndexExists();
2286
+ if (indexExists) {
2287
+ return {
2288
+ needsMigration: false,
2289
+ hasDuplicates: false,
2290
+ duplicateCount: 0,
2291
+ constraintExists: true,
2292
+ tableName: storage.TABLE_SPANS
2293
+ };
2294
+ }
2295
+ const duplicateInfo = await this.checkForDuplicateSpans();
2296
+ return {
2297
+ needsMigration: true,
2298
+ hasDuplicates: duplicateInfo.hasDuplicates,
2299
+ duplicateCount: duplicateInfo.duplicateCount,
2300
+ constraintExists: false,
2301
+ tableName: storage.TABLE_SPANS
2302
+ };
2303
+ }
2304
+ /**
2305
+ * Deduplicates spans with the same (traceId, spanId) combination.
2306
+ * This is needed for databases that existed before the unique constraint was added.
2307
+ *
2308
+ * Priority for keeping spans:
2309
+ * 1. Completed spans (endedAt IS NOT NULL) over incomplete spans
2310
+ * 2. Most recent updatedAt
2311
+ * 3. Most recent createdAt (as tiebreaker)
2312
+ *
2313
+ * Note: This prioritizes migration completion over perfect data preservation.
2314
+ * Old trace data may be lost, which is acceptable for this use case.
2315
+ */
2316
+ async deduplicateSpans() {
2317
+ try {
2318
+ const collection = await this.getCollection(storage.TABLE_SPANS);
2319
+ const duplicateCheck = await collection.aggregate([
2320
+ {
2321
+ $group: {
2322
+ _id: { traceId: "$traceId", spanId: "$spanId" },
2323
+ count: { $sum: 1 }
2324
+ }
2325
+ },
2326
+ { $match: { count: { $gt: 1 } } },
2327
+ { $limit: 1 }
2328
+ ]).toArray();
2329
+ if (duplicateCheck.length === 0) {
2330
+ this.logger?.debug?.("No duplicate spans found");
2331
+ return;
2332
+ }
2333
+ this.logger?.info?.("Duplicate spans detected, starting deduplication...");
2334
+ const idsToDelete = await collection.aggregate([
2335
+ // Sort by priority (affects which document $first picks within each group)
2336
+ {
2337
+ $sort: {
2338
+ // Completed spans first (endedAt exists and is not null)
2339
+ endedAt: -1,
2340
+ updatedAt: -1,
2341
+ createdAt: -1
2342
+ }
2343
+ },
2344
+ // Group by (traceId, spanId), keeping the first (best) _id and all _ids
2345
+ {
2346
+ $group: {
2347
+ _id: { traceId: "$traceId", spanId: "$spanId" },
2348
+ keepId: { $first: "$_id" },
2349
+ // The best one to keep (after sort)
2350
+ allIds: { $push: "$_id" },
2351
+ // All ObjectIds (just 12 bytes each, not full docs)
2352
+ count: { $sum: 1 }
2353
+ }
2354
+ },
2355
+ // Only consider groups with duplicates
2356
+ { $match: { count: { $gt: 1 } } },
2357
+ // Get IDs to delete (allIds minus keepId)
2358
+ {
2359
+ $project: {
2360
+ idsToDelete: {
2361
+ $filter: {
2362
+ input: "$allIds",
2363
+ cond: { $ne: ["$$this", "$keepId"] }
2364
+ }
2365
+ }
2366
+ }
2367
+ },
2368
+ // Unwind to get flat list of IDs
2369
+ { $unwind: "$idsToDelete" },
2370
+ // Just output the ID
2371
+ { $project: { _id: "$idsToDelete" } }
2372
+ ]).toArray();
2373
+ if (idsToDelete.length === 0) {
2374
+ this.logger?.debug?.("No duplicates to delete after aggregation");
2375
+ return;
2376
+ }
2377
+ const deleteResult = await collection.deleteMany({
2378
+ _id: { $in: idsToDelete.map((d) => d._id) }
2379
+ });
2380
+ this.logger?.info?.(`Deduplication complete: removed ${deleteResult.deletedCount} duplicate spans`);
2381
+ } catch (error) {
2382
+ this.logger?.warn?.("Failed to deduplicate spans:", error);
2383
+ }
1913
2384
  }
1914
2385
  async dangerouslyClearAll() {
1915
2386
  const collection = await this.getCollection(storage.TABLE_SPANS);
@@ -2191,7 +2662,7 @@ var ObservabilityMongoDB = class _ObservabilityMongoDB extends storage.Observabi
2191
2662
  // No children with errors
2192
2663
  }
2193
2664
  ];
2194
- const countResult = await collection.aggregate([...pipeline, { $count: "total" }]).toArray();
2665
+ const countResult = await collection.aggregate([...pipeline, { $count: "total" }], { allowDiskUse: true }).toArray();
2195
2666
  const count2 = countResult[0]?.total || 0;
2196
2667
  if (count2 === 0) {
2197
2668
  return {
@@ -2228,7 +2699,7 @@ var ObservabilityMongoDB = class _ObservabilityMongoDB extends storage.Observabi
2228
2699
  { $project: { _errorSpans: 0 } }
2229
2700
  ];
2230
2701
  }
2231
- const spans2 = await collection.aggregate(aggregationPipeline).toArray();
2702
+ const spans2 = await collection.aggregate(aggregationPipeline, { allowDiskUse: true }).toArray();
2232
2703
  return {
2233
2704
  pagination: {
2234
2705
  total: count2,
@@ -2236,7 +2707,7 @@ var ObservabilityMongoDB = class _ObservabilityMongoDB extends storage.Observabi
2236
2707
  perPage,
2237
2708
  hasMore: (page + 1) * perPage < count2
2238
2709
  },
2239
- spans: spans2.map((span) => this.transformSpanFromMongo(span))
2710
+ spans: storage.toTraceSpans(spans2.map((span) => this.transformSpanFromMongo(span)))
2240
2711
  };
2241
2712
  }
2242
2713
  const count = await collection.countDocuments(mongoFilter);
@@ -2254,18 +2725,21 @@ var ObservabilityMongoDB = class _ObservabilityMongoDB extends storage.Observabi
2254
2725
  let spans;
2255
2726
  if (sortField === "endedAt") {
2256
2727
  const nullSortValue = sortDirection === -1 ? 0 : 1;
2257
- spans = await collection.aggregate([
2258
- { $match: mongoFilter },
2259
- {
2260
- $addFields: {
2261
- _endedAtNull: { $cond: [{ $eq: ["$endedAt", null] }, nullSortValue, sortDirection === -1 ? 1 : 0] }
2262
- }
2263
- },
2264
- { $sort: { _endedAtNull: 1, [sortField]: sortDirection } },
2265
- { $skip: page * perPage },
2266
- { $limit: perPage },
2267
- { $project: { _endedAtNull: 0 } }
2268
- ]).toArray();
2728
+ spans = await collection.aggregate(
2729
+ [
2730
+ { $match: mongoFilter },
2731
+ {
2732
+ $addFields: {
2733
+ _endedAtNull: { $cond: [{ $eq: ["$endedAt", null] }, nullSortValue, sortDirection === -1 ? 1 : 0] }
2734
+ }
2735
+ },
2736
+ { $sort: { _endedAtNull: 1, [sortField]: sortDirection } },
2737
+ { $skip: page * perPage },
2738
+ { $limit: perPage },
2739
+ { $project: { _endedAtNull: 0 } }
2740
+ ],
2741
+ { allowDiskUse: true }
2742
+ ).toArray();
2269
2743
  } else {
2270
2744
  spans = await collection.find(mongoFilter).sort({ [sortField]: sortDirection }).skip(page * perPage).limit(perPage).toArray();
2271
2745
  }
@@ -2276,7 +2750,7 @@ var ObservabilityMongoDB = class _ObservabilityMongoDB extends storage.Observabi
2276
2750
  perPage,
2277
2751
  hasMore: (page + 1) * perPage < count
2278
2752
  },
2279
- spans: spans.map((span) => this.transformSpanFromMongo(span))
2753
+ spans: storage.toTraceSpans(spans.map((span) => this.transformSpanFromMongo(span)))
2280
2754
  };
2281
2755
  } catch (error$1) {
2282
2756
  throw new error.MastraError(
@@ -2581,7 +3055,7 @@ var ScoresStorageMongoDB = class _ScoresStorageMongoDB extends storage.ScoresSto
2581
3055
  };
2582
3056
  }
2583
3057
  const end = perPageInput === false ? total : start + perPage;
2584
- let cursor = collection.find(query).sort({ createdAt: "desc" }).skip(start);
3058
+ let cursor = collection.find(query).sort({ createdAt: -1 }).skip(start);
2585
3059
  if (perPageInput !== false) {
2586
3060
  cursor = cursor.limit(perPage);
2587
3061
  }
@@ -2630,7 +3104,7 @@ var ScoresStorageMongoDB = class _ScoresStorageMongoDB extends storage.ScoresSto
2630
3104
  };
2631
3105
  }
2632
3106
  const end = perPageInput === false ? total : start + perPage;
2633
- let cursor = collection.find({ runId }).sort({ createdAt: "desc" }).skip(start);
3107
+ let cursor = collection.find({ runId }).sort({ createdAt: -1 }).skip(start);
2634
3108
  if (perPageInput !== false) {
2635
3109
  cursor = cursor.limit(perPage);
2636
3110
  }
@@ -2680,7 +3154,7 @@ var ScoresStorageMongoDB = class _ScoresStorageMongoDB extends storage.ScoresSto
2680
3154
  };
2681
3155
  }
2682
3156
  const end = perPageInput === false ? total : start + perPage;
2683
- let cursor = collection.find({ entityId, entityType }).sort({ createdAt: "desc" }).skip(start);
3157
+ let cursor = collection.find({ entityId, entityType }).sort({ createdAt: -1 }).skip(start);
2684
3158
  if (perPageInput !== false) {
2685
3159
  cursor = cursor.limit(perPage);
2686
3160
  }
@@ -2731,7 +3205,7 @@ var ScoresStorageMongoDB = class _ScoresStorageMongoDB extends storage.ScoresSto
2731
3205
  };
2732
3206
  }
2733
3207
  const end = perPageInput === false ? total : start + perPage;
2734
- let cursor = collection.find(query).sort({ createdAt: "desc" }).skip(start);
3208
+ let cursor = collection.find(query).sort({ createdAt: -1 }).skip(start);
2735
3209
  if (perPageInput !== false) {
2736
3210
  cursor = cursor.limit(perPage);
2737
3211
  }
@@ -3024,7 +3498,7 @@ var WorkflowsStorageMongoDB = class _WorkflowsStorageMongoDB extends storage.Wor
3024
3498
  try {
3025
3499
  parsedSnapshot = typeof row.snapshot === "string" ? storage.safelyParseJSON(row.snapshot) : row.snapshot;
3026
3500
  } catch (e) {
3027
- console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
3501
+ this.logger.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
3028
3502
  }
3029
3503
  }
3030
3504
  return {
@@ -3039,7 +3513,7 @@ var WorkflowsStorageMongoDB = class _WorkflowsStorageMongoDB extends storage.Wor
3039
3513
  };
3040
3514
 
3041
3515
  // src/storage/index.ts
3042
- var MongoDBStore = class extends storage.MastraStorage {
3516
+ var MongoDBStore = class extends storage.MastraCompositeStore {
3043
3517
  #connector;
3044
3518
  stores;
3045
3519
  constructor(config) {