@mastra/mongodb 1.0.0-beta.12 → 1.0.0-beta.14

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/CHANGELOG.md CHANGED
@@ -1,5 +1,109 @@
1
1
  # @mastra/mongodb
2
2
 
3
+ ## 1.0.0-beta.14
4
+
5
+ ### Patch Changes
6
+
7
+ - Fixed duplicate spans migration issue across all storage backends. When upgrading from older versions, existing duplicate (traceId, spanId) combinations in the spans table could prevent the unique constraint from being created. The migration deduplicates spans before adding the constraint. ([#12073](https://github.com/mastra-ai/mastra/pull/12073))
8
+
9
+ **Deduplication rules (in priority order):**
10
+ 1. Keep completed spans (those with `endedAt` set) over incomplete spans
11
+ 2. Among spans with the same completion status, keep the one with the newest `updatedAt`
12
+ 3. Use `createdAt` as the final tiebreaker
13
+
14
+ **What changed:**
15
+ - Added `migrateSpans()` method to observability stores for manual migration
16
+ - Added `checkSpansMigrationStatus()` method to check if migration is needed
17
+ - All stores use optimized single-query deduplication to avoid memory issues on large tables
18
+
19
+ **Usage example:**
20
+
21
+ ```typescript
22
+ const observability = await storage.getStore('observability');
23
+ const status = await observability.checkSpansMigrationStatus();
24
+ if (status.needsMigration) {
25
+ const result = await observability.migrateSpans();
26
+ console.log(`Migration complete: ${result.duplicatesRemoved} duplicates removed`);
27
+ }
28
+ ```
29
+
30
+ Fixes #11840
31
+
32
+ - Renamed MastraStorage to MastraCompositeStore for better clarity. The old MastraStorage name remains available as a deprecated alias for backward compatibility, but will be removed in a future version. ([#12093](https://github.com/mastra-ai/mastra/pull/12093))
33
+
34
+ **Migration:**
35
+
36
+ Update your imports and usage:
37
+
38
+ ```typescript
39
+ // Before
40
+ import { MastraStorage } from '@mastra/core/storage';
41
+
42
+ const storage = new MastraStorage({
43
+ id: 'composite',
44
+ domains: { ... }
45
+ });
46
+
47
+ // After
48
+ import { MastraCompositeStore } from '@mastra/core/storage';
49
+
50
+ const storage = new MastraCompositeStore({
51
+ id: 'composite',
52
+ domains: { ... }
53
+ });
54
+ ```
55
+
56
+ The new name better reflects that this is a composite storage implementation that routes different domains (workflows, traces, messages) to different underlying stores, avoiding confusion with the general "Mastra Storage" concept.
57
+
58
+ - Updated dependencies [[`026b848`](https://github.com/mastra-ai/mastra/commit/026b8483fbf5b6d977be8f7e6aac8d15c75558ac), [`ffa553a`](https://github.com/mastra-ai/mastra/commit/ffa553a3edc1bd17d73669fba66d6b6f4ac10897)]:
59
+ - @mastra/core@1.0.0-beta.26
60
+
61
+ ## 1.0.0-beta.13
62
+
63
+ ### Patch Changes
64
+
65
+ - Added new `listThreads` method for flexible thread filtering across all storage adapters. ([#11832](https://github.com/mastra-ai/mastra/pull/11832))
66
+
67
+ **New Features**
68
+ - Filter threads by `resourceId`, `metadata`, or both (with AND logic for metadata key-value pairs)
69
+ - All filter parameters are optional, allowing you to list all threads or filter as needed
70
+ - Full pagination and sorting support
71
+
72
+ **Example Usage**
73
+
74
+ ```typescript
75
+ // List all threads
76
+ const allThreads = await memory.listThreads({});
77
+
78
+ // Filter by resourceId only
79
+ const userThreads = await memory.listThreads({
80
+ filter: { resourceId: 'user-123' },
81
+ });
82
+
83
+ // Filter by metadata only
84
+ const supportThreads = await memory.listThreads({
85
+ filter: { metadata: { category: 'support' } },
86
+ });
87
+
88
+ // Filter by both with pagination
89
+ const filteredThreads = await memory.listThreads({
90
+ filter: {
91
+ resourceId: 'user-123',
92
+ metadata: { priority: 'high', status: 'open' },
93
+ },
94
+ orderBy: { field: 'updatedAt', direction: 'DESC' },
95
+ page: 0,
96
+ perPage: 20,
97
+ });
98
+ ```
99
+
100
+ **Security Improvements**
101
+ - Added validation to prevent SQL injection via malicious metadata keys
102
+ - Added pagination parameter validation to prevent integer overflow attacks
103
+
104
+ - Updated dependencies [[`ed3e3dd`](https://github.com/mastra-ai/mastra/commit/ed3e3ddec69d564fe2b125e083437f76331f1283), [`6833c69`](https://github.com/mastra-ai/mastra/commit/6833c69607418d257750bbcdd84638993d343539), [`47b1c16`](https://github.com/mastra-ai/mastra/commit/47b1c16a01c7ffb6765fe1e499b49092f8b7eba3), [`3a76a80`](https://github.com/mastra-ai/mastra/commit/3a76a80284cb71a0faa975abb3d4b2a9631e60cd), [`8538a0d`](https://github.com/mastra-ai/mastra/commit/8538a0d232619bf55dad7ddc2a8b0ca77c679a87), [`9312dcd`](https://github.com/mastra-ai/mastra/commit/9312dcd1c6f5b321929e7d382e763d95fdc030f5)]:
105
+ - @mastra/core@1.0.0-beta.25
106
+
3
107
  ## 1.0.0-beta.12
4
108
 
5
109
  ### Minor Changes
@@ -31,4 +31,4 @@ docs/
31
31
  ## Version
32
32
 
33
33
  Package: @mastra/mongodb
34
- Version: 1.0.0-beta.12
34
+ Version: 1.0.0-beta.14
@@ -5,7 +5,7 @@ description: Documentation for @mastra/mongodb. Includes links to type definitio
5
5
 
6
6
  # @mastra/mongodb Documentation
7
7
 
8
- > **Version**: 1.0.0-beta.12
8
+ > **Version**: 1.0.0-beta.14
9
9
  > **Package**: @mastra/mongodb
10
10
 
11
11
  ## Quick Navigation
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.0.0-beta.12",
2
+ "version": "1.0.0-beta.14",
3
3
  "package": "@mastra/mongodb",
4
4
  "exports": {},
5
5
  "modules": {}
@@ -27,7 +27,7 @@ await store.upsert({
27
27
  });
28
28
  ```
29
29
 
30
- ### Using MongoDB Atlas Vector search
30
+ <h3>Using MongoDB Atlas Vector search</h3>
31
31
 
32
32
  For detailed setup instructions and best practices, see the [official MongoDB Atlas Vector Search documentation](https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-overview/?utm_campaign=devrel&utm_source=third-party-content&utm_medium=cta&utm_content=mastra-docs).
33
33
 
@@ -55,7 +55,7 @@ await store.upsert({
55
55
  });
56
56
  ```
57
57
 
58
- ### Using PostgreSQL with pgvector
58
+ <h3>Using PostgreSQL with pgvector</h3>
59
59
 
60
60
  PostgreSQL with the pgvector extension is a good solution for teams already using PostgreSQL who want to minimize infrastructure complexity.
61
61
  For detailed setup instructions and best practices, see the [official pgvector repository](https://github.com/pgvector/pgvector).
@@ -323,7 +323,7 @@ await store.upsert({
323
323
  });
324
324
  ```
325
325
 
326
- ### Using LanceDB
326
+ <h3>Using LanceDB</h3>
327
327
 
328
328
  LanceDB is an embedded vector database built on the Lance columnar format, suitable for local development or cloud deployment.
329
329
  For detailed setup instructions and best practices, see the [official LanceDB documentation](https://lancedb.github.io/lancedb/).
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.12"};
16
+ version: "1.0.0-beta.14"};
17
17
  var MongoDBFilterTranslator = class extends filter.BaseFilterTranslator {
18
18
  getSupportedOperators() {
19
19
  return {
@@ -280,6 +280,8 @@ var MongoDBVector = class extends vector.MastraVector {
280
280
  throw new Error(`Index "${indexNameInternal}" did not become ready within timeout`);
281
281
  }
282
282
  async upsert({ indexName, vectors, metadata, ids, documents }) {
283
+ vector.validateUpsertInput("MONGODB", vectors, metadata, ids);
284
+ vector.validateVectorValues("MONGODB", vectors);
283
285
  try {
284
286
  const collection = await this.getCollection(indexName);
285
287
  this.collectionForValidation = collection;
@@ -355,8 +357,8 @@ var MongoDBVector = class extends vector.MastraVector {
355
357
  index: indexNameInternal,
356
358
  queryVector,
357
359
  path: this.embeddingFieldName,
358
- numCandidates: 100,
359
- limit: topK
360
+ numCandidates: Math.min(1e4, Math.max(100, topK)),
361
+ limit: Math.min(1e4, topK)
360
362
  };
361
363
  if (Object.keys(combinedFilter).length > 0) {
362
364
  const filterWithExclusion = {
@@ -1687,25 +1689,48 @@ var MemoryStorageMongoDB = class _MemoryStorageMongoDB extends storage.MemorySto
1687
1689
  );
1688
1690
  }
1689
1691
  }
1690
- async listThreadsByResourceId(args) {
1692
+ async listThreads(args) {
1693
+ const { page = 0, perPage: perPageInput, orderBy, filter } = args;
1694
+ try {
1695
+ this.validatePaginationInput(page, perPageInput ?? 100);
1696
+ } catch (error$1) {
1697
+ throw new error.MastraError(
1698
+ {
1699
+ id: storage.createStorageErrorId("MONGODB", "LIST_THREADS", "INVALID_PAGE"),
1700
+ domain: error.ErrorDomain.STORAGE,
1701
+ category: error.ErrorCategory.USER,
1702
+ details: { page, ...perPageInput !== void 0 && { perPage: perPageInput } }
1703
+ },
1704
+ error$1 instanceof Error ? error$1 : new Error("Invalid pagination parameters")
1705
+ );
1706
+ }
1707
+ const perPage = storage.normalizePerPage(perPageInput, 100);
1708
+ try {
1709
+ this.validateMetadataKeys(filter?.metadata);
1710
+ } catch (error$1) {
1711
+ throw new error.MastraError(
1712
+ {
1713
+ id: storage.createStorageErrorId("MONGODB", "LIST_THREADS", "INVALID_METADATA_KEY"),
1714
+ domain: error.ErrorDomain.STORAGE,
1715
+ category: error.ErrorCategory.USER,
1716
+ details: { metadataKeys: filter?.metadata ? Object.keys(filter.metadata).join(", ") : "" }
1717
+ },
1718
+ error$1 instanceof Error ? error$1 : new Error("Invalid metadata key")
1719
+ );
1720
+ }
1691
1721
  try {
1692
- const { resourceId, page = 0, perPage: perPageInput, orderBy } = args;
1693
- if (page < 0) {
1694
- throw new error.MastraError(
1695
- {
1696
- id: storage.createStorageErrorId("MONGODB", "LIST_THREADS_BY_RESOURCE_ID", "INVALID_PAGE"),
1697
- domain: error.ErrorDomain.STORAGE,
1698
- category: error.ErrorCategory.USER,
1699
- details: { page }
1700
- },
1701
- new Error("page must be >= 0")
1702
- );
1703
- }
1704
- const perPage = storage.normalizePerPage(perPageInput, 100);
1705
1722
  const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage);
1706
1723
  const { field, direction } = this.parseOrderBy(orderBy);
1707
1724
  const collection = await this.getCollection(storage.TABLE_THREADS);
1708
- const query = { resourceId };
1725
+ const query = {};
1726
+ if (filter?.resourceId) {
1727
+ query.resourceId = filter.resourceId;
1728
+ }
1729
+ if (filter?.metadata && Object.keys(filter.metadata).length > 0) {
1730
+ for (const [key, value] of Object.entries(filter.metadata)) {
1731
+ query[`metadata.${key}`] = value;
1732
+ }
1733
+ }
1709
1734
  const total = await collection.countDocuments(query);
1710
1735
  if (perPage === 0) {
1711
1736
  return {
@@ -1739,10 +1764,13 @@ var MemoryStorageMongoDB = class _MemoryStorageMongoDB extends storage.MemorySto
1739
1764
  } catch (error$1) {
1740
1765
  throw new error.MastraError(
1741
1766
  {
1742
- id: storage.createStorageErrorId("MONGODB", "LIST_THREADS_BY_RESOURCE_ID", "FAILED"),
1767
+ id: storage.createStorageErrorId("MONGODB", "LIST_THREADS", "FAILED"),
1743
1768
  domain: error.ErrorDomain.STORAGE,
1744
1769
  category: error.ErrorCategory.THIRD_PARTY,
1745
- details: { resourceId: args.resourceId }
1770
+ details: {
1771
+ ...filter?.resourceId && { resourceId: filter.resourceId },
1772
+ hasMetadataFilter: !!filter?.metadata
1773
+ }
1746
1774
  },
1747
1775
  error$1
1748
1776
  );
@@ -1924,9 +1952,221 @@ var ObservabilityMongoDB = class _ObservabilityMongoDB extends storage.Observabi
1924
1952
  }
1925
1953
  }
1926
1954
  async init() {
1955
+ const uniqueIndexExists = await this.spansUniqueIndexExists();
1956
+ if (!uniqueIndexExists) {
1957
+ const duplicateInfo = await this.checkForDuplicateSpans();
1958
+ if (duplicateInfo.hasDuplicates) {
1959
+ const errorMessage = `
1960
+ ===========================================================================
1961
+ MIGRATION REQUIRED: Duplicate spans detected in ${storage.TABLE_SPANS} collection
1962
+ ===========================================================================
1963
+
1964
+ Found ${duplicateInfo.duplicateCount} duplicate (traceId, spanId) combinations.
1965
+
1966
+ The spans collection requires a unique index on (traceId, spanId), but your
1967
+ database contains duplicate entries that must be resolved first.
1968
+
1969
+ To fix this, run the manual migration command:
1970
+
1971
+ npx mastra migrate
1972
+
1973
+ This command will:
1974
+ 1. Remove duplicate spans (keeping the most complete/recent version)
1975
+ 2. Add the required unique index
1976
+
1977
+ Note: This migration may take some time for large collections.
1978
+ ===========================================================================
1979
+ `;
1980
+ throw new error.MastraError({
1981
+ id: storage.createStorageErrorId("MONGODB", "MIGRATION_REQUIRED", "DUPLICATE_SPANS"),
1982
+ domain: error.ErrorDomain.STORAGE,
1983
+ category: error.ErrorCategory.USER,
1984
+ text: errorMessage
1985
+ });
1986
+ }
1987
+ }
1927
1988
  await this.createDefaultIndexes();
1928
1989
  await this.createCustomIndexes();
1929
1990
  }
1991
+ /**
1992
+ * Checks if the unique index on (spanId, traceId) already exists on the spans collection.
1993
+ * Used to skip deduplication when the index already exists (migration already complete).
1994
+ */
1995
+ async spansUniqueIndexExists() {
1996
+ try {
1997
+ const collection = await this.getCollection(storage.TABLE_SPANS);
1998
+ const indexes = await collection.indexes();
1999
+ return indexes.some((idx) => idx.unique === true && idx.key?.spanId === 1 && idx.key?.traceId === 1);
2000
+ } catch {
2001
+ return false;
2002
+ }
2003
+ }
2004
+ /**
2005
+ * Checks for duplicate (traceId, spanId) combinations in the spans collection.
2006
+ * Returns information about duplicates for logging/CLI purposes.
2007
+ */
2008
+ async checkForDuplicateSpans() {
2009
+ try {
2010
+ const collection = await this.getCollection(storage.TABLE_SPANS);
2011
+ const result = await collection.aggregate([
2012
+ {
2013
+ $group: {
2014
+ _id: { traceId: "$traceId", spanId: "$spanId" },
2015
+ count: { $sum: 1 }
2016
+ }
2017
+ },
2018
+ { $match: { count: { $gt: 1 } } },
2019
+ { $count: "duplicateCount" }
2020
+ ]).toArray();
2021
+ const duplicateCount = result[0]?.duplicateCount ?? 0;
2022
+ return {
2023
+ hasDuplicates: duplicateCount > 0,
2024
+ duplicateCount
2025
+ };
2026
+ } catch (error) {
2027
+ this.logger?.debug?.(`Could not check for duplicates: ${error}`);
2028
+ return { hasDuplicates: false, duplicateCount: 0 };
2029
+ }
2030
+ }
2031
+ /**
2032
+ * Manually run the spans migration to deduplicate and add the unique index.
2033
+ * This is intended to be called from the CLI when duplicates are detected.
2034
+ *
2035
+ * @returns Migration result with status and details
2036
+ */
2037
+ async migrateSpans() {
2038
+ const indexExists = await this.spansUniqueIndexExists();
2039
+ if (indexExists) {
2040
+ return {
2041
+ success: true,
2042
+ alreadyMigrated: true,
2043
+ duplicatesRemoved: 0,
2044
+ message: `Migration already complete. Unique index exists on ${storage.TABLE_SPANS} collection.`
2045
+ };
2046
+ }
2047
+ const duplicateInfo = await this.checkForDuplicateSpans();
2048
+ if (duplicateInfo.hasDuplicates) {
2049
+ this.logger?.info?.(
2050
+ `Found ${duplicateInfo.duplicateCount} duplicate (traceId, spanId) combinations. Starting deduplication...`
2051
+ );
2052
+ await this.deduplicateSpans();
2053
+ } else {
2054
+ this.logger?.info?.(`No duplicate spans found.`);
2055
+ }
2056
+ await this.createDefaultIndexes();
2057
+ await this.createCustomIndexes();
2058
+ return {
2059
+ success: true,
2060
+ alreadyMigrated: false,
2061
+ duplicatesRemoved: duplicateInfo.duplicateCount,
2062
+ 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.`
2063
+ };
2064
+ }
2065
+ /**
2066
+ * Check migration status for the spans collection.
2067
+ * Returns information about whether migration is needed.
2068
+ */
2069
+ async checkSpansMigrationStatus() {
2070
+ const indexExists = await this.spansUniqueIndexExists();
2071
+ if (indexExists) {
2072
+ return {
2073
+ needsMigration: false,
2074
+ hasDuplicates: false,
2075
+ duplicateCount: 0,
2076
+ constraintExists: true,
2077
+ tableName: storage.TABLE_SPANS
2078
+ };
2079
+ }
2080
+ const duplicateInfo = await this.checkForDuplicateSpans();
2081
+ return {
2082
+ needsMigration: true,
2083
+ hasDuplicates: duplicateInfo.hasDuplicates,
2084
+ duplicateCount: duplicateInfo.duplicateCount,
2085
+ constraintExists: false,
2086
+ tableName: storage.TABLE_SPANS
2087
+ };
2088
+ }
2089
+ /**
2090
+ * Deduplicates spans with the same (traceId, spanId) combination.
2091
+ * This is needed for databases that existed before the unique constraint was added.
2092
+ *
2093
+ * Priority for keeping spans:
2094
+ * 1. Completed spans (endedAt IS NOT NULL) over incomplete spans
2095
+ * 2. Most recent updatedAt
2096
+ * 3. Most recent createdAt (as tiebreaker)
2097
+ *
2098
+ * Note: This prioritizes migration completion over perfect data preservation.
2099
+ * Old trace data may be lost, which is acceptable for this use case.
2100
+ */
2101
+ async deduplicateSpans() {
2102
+ try {
2103
+ const collection = await this.getCollection(storage.TABLE_SPANS);
2104
+ const duplicateCheck = await collection.aggregate([
2105
+ {
2106
+ $group: {
2107
+ _id: { traceId: "$traceId", spanId: "$spanId" },
2108
+ count: { $sum: 1 }
2109
+ }
2110
+ },
2111
+ { $match: { count: { $gt: 1 } } },
2112
+ { $limit: 1 }
2113
+ ]).toArray();
2114
+ if (duplicateCheck.length === 0) {
2115
+ this.logger?.debug?.("No duplicate spans found");
2116
+ return;
2117
+ }
2118
+ this.logger?.info?.("Duplicate spans detected, starting deduplication...");
2119
+ const idsToDelete = await collection.aggregate([
2120
+ // Sort by priority (affects which document $first picks within each group)
2121
+ {
2122
+ $sort: {
2123
+ // Completed spans first (endedAt exists and is not null)
2124
+ endedAt: -1,
2125
+ updatedAt: -1,
2126
+ createdAt: -1
2127
+ }
2128
+ },
2129
+ // Group by (traceId, spanId), keeping the first (best) _id and all _ids
2130
+ {
2131
+ $group: {
2132
+ _id: { traceId: "$traceId", spanId: "$spanId" },
2133
+ keepId: { $first: "$_id" },
2134
+ // The best one to keep (after sort)
2135
+ allIds: { $push: "$_id" },
2136
+ // All ObjectIds (just 12 bytes each, not full docs)
2137
+ count: { $sum: 1 }
2138
+ }
2139
+ },
2140
+ // Only consider groups with duplicates
2141
+ { $match: { count: { $gt: 1 } } },
2142
+ // Get IDs to delete (allIds minus keepId)
2143
+ {
2144
+ $project: {
2145
+ idsToDelete: {
2146
+ $filter: {
2147
+ input: "$allIds",
2148
+ cond: { $ne: ["$$this", "$keepId"] }
2149
+ }
2150
+ }
2151
+ }
2152
+ },
2153
+ // Unwind to get flat list of IDs
2154
+ { $unwind: "$idsToDelete" },
2155
+ // Just output the ID
2156
+ { $project: { _id: "$idsToDelete" } }
2157
+ ]).toArray();
2158
+ if (idsToDelete.length === 0) {
2159
+ this.logger?.debug?.("No duplicates to delete after aggregation");
2160
+ return;
2161
+ }
2162
+ const deleteResult = await collection.deleteMany({
2163
+ _id: { $in: idsToDelete.map((d) => d._id) }
2164
+ });
2165
+ this.logger?.info?.(`Deduplication complete: removed ${deleteResult.deletedCount} duplicate spans`);
2166
+ } catch (error) {
2167
+ this.logger?.warn?.("Failed to deduplicate spans:", error);
2168
+ }
2169
+ }
1930
2170
  async dangerouslyClearAll() {
1931
2171
  const collection = await this.getCollection(storage.TABLE_SPANS);
1932
2172
  await collection.deleteMany({});
@@ -2207,7 +2447,7 @@ var ObservabilityMongoDB = class _ObservabilityMongoDB extends storage.Observabi
2207
2447
  // No children with errors
2208
2448
  }
2209
2449
  ];
2210
- const countResult = await collection.aggregate([...pipeline, { $count: "total" }]).toArray();
2450
+ const countResult = await collection.aggregate([...pipeline, { $count: "total" }], { allowDiskUse: true }).toArray();
2211
2451
  const count2 = countResult[0]?.total || 0;
2212
2452
  if (count2 === 0) {
2213
2453
  return {
@@ -2244,7 +2484,7 @@ var ObservabilityMongoDB = class _ObservabilityMongoDB extends storage.Observabi
2244
2484
  { $project: { _errorSpans: 0 } }
2245
2485
  ];
2246
2486
  }
2247
- const spans2 = await collection.aggregate(aggregationPipeline).toArray();
2487
+ const spans2 = await collection.aggregate(aggregationPipeline, { allowDiskUse: true }).toArray();
2248
2488
  return {
2249
2489
  pagination: {
2250
2490
  total: count2,
@@ -2270,18 +2510,21 @@ var ObservabilityMongoDB = class _ObservabilityMongoDB extends storage.Observabi
2270
2510
  let spans;
2271
2511
  if (sortField === "endedAt") {
2272
2512
  const nullSortValue = sortDirection === -1 ? 0 : 1;
2273
- spans = await collection.aggregate([
2274
- { $match: mongoFilter },
2275
- {
2276
- $addFields: {
2277
- _endedAtNull: { $cond: [{ $eq: ["$endedAt", null] }, nullSortValue, sortDirection === -1 ? 1 : 0] }
2278
- }
2279
- },
2280
- { $sort: { _endedAtNull: 1, [sortField]: sortDirection } },
2281
- { $skip: page * perPage },
2282
- { $limit: perPage },
2283
- { $project: { _endedAtNull: 0 } }
2284
- ]).toArray();
2513
+ spans = await collection.aggregate(
2514
+ [
2515
+ { $match: mongoFilter },
2516
+ {
2517
+ $addFields: {
2518
+ _endedAtNull: { $cond: [{ $eq: ["$endedAt", null] }, nullSortValue, sortDirection === -1 ? 1 : 0] }
2519
+ }
2520
+ },
2521
+ { $sort: { _endedAtNull: 1, [sortField]: sortDirection } },
2522
+ { $skip: page * perPage },
2523
+ { $limit: perPage },
2524
+ { $project: { _endedAtNull: 0 } }
2525
+ ],
2526
+ { allowDiskUse: true }
2527
+ ).toArray();
2285
2528
  } else {
2286
2529
  spans = await collection.find(mongoFilter).sort({ [sortField]: sortDirection }).skip(page * perPage).limit(perPage).toArray();
2287
2530
  }
@@ -3055,7 +3298,7 @@ var WorkflowsStorageMongoDB = class _WorkflowsStorageMongoDB extends storage.Wor
3055
3298
  };
3056
3299
 
3057
3300
  // src/storage/index.ts
3058
- var MongoDBStore = class extends storage.MastraStorage {
3301
+ var MongoDBStore = class extends storage.MastraCompositeStore {
3059
3302
  #connector;
3060
3303
  stores;
3061
3304
  constructor(config) {