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