@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 +104 -0
- package/dist/docs/README.md +1 -1
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/SOURCE_MAP.json +1 -1
- package/dist/docs/rag/01-vector-databases.md +3 -3
- package/dist/index.cjs +278 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +280 -37
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/memory/index.d.ts +2 -2
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/index.d.ts +46 -0
- package/dist/storage/domains/observability/index.d.ts.map +1 -1
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/vector/index.d.ts.map +1 -1
- package/package.json +4 -4
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
|
package/dist/docs/README.md
CHANGED
package/dist/docs/SKILL.md
CHANGED
|
@@ -27,7 +27,7 @@ await store.upsert({
|
|
|
27
27
|
});
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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 = {
|
|
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", "
|
|
1767
|
+
id: storage.createStorageErrorId("MONGODB", "LIST_THREADS", "FAILED"),
|
|
1743
1768
|
domain: error.ErrorDomain.STORAGE,
|
|
1744
1769
|
category: error.ErrorCategory.THIRD_PARTY,
|
|
1745
|
-
details: {
|
|
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
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
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.
|
|
3301
|
+
var MongoDBStore = class extends storage.MastraCompositeStore {
|
|
3059
3302
|
#connector;
|
|
3060
3303
|
stores;
|
|
3061
3304
|
constructor(config) {
|