@mastra/mongodb 1.23.1-alpha.0 → 1.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/reference-storage-composite.md +34 -0
- package/dist/docs/references/reference-vectors-mongodb.md +2 -1
- package/dist/index.cjs +432 -118
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +433 -119
- package/dist/index.js.map +1 -1
- package/dist/storage/connectors/MongoDBConnector.d.ts +10 -3
- package/dist/storage/connectors/MongoDBConnector.d.ts.map +1 -1
- package/dist/storage/domains/memory/index.d.ts +28 -11
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/run-fencing.d.ts +39 -0
- package/dist/storage/domains/run-fencing.d.ts.map +1 -0
- package/dist/storage/domains/workflows/index.d.ts +26 -9
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/dist/storage/retention.d.ts +9 -5
- package/dist/storage/retention.d.ts.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { v4 } from "@lukeed/uuid";
|
|
2
2
|
import { ErrorCategory, ErrorDomain, MastraError } from "@mastra/core/error";
|
|
3
|
-
import { AgentsStorage, BackgroundTasksStorage, BlobStore, DatasetsStorage, ExperimentsStorage, KnowledgeConflictError, KnowledgeNotFoundError, KnowledgeStorage, MCPClientsStorage, MCPServersStorage, MastraCompositeStore, MemoryStorage, NotificationsStorage, ObservabilityStorage, PromptBlocksStorage, SchedulesStorage, ScorerDefinitionsStorage, ScoresStorage, SkillsStorage, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_BACKGROUND_TASKS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_KNOWLEDGE_ACTIVITY, TABLE_KNOWLEDGE_MENTIONS, TABLE_KNOWLEDGE_NODES, TABLE_KNOWLEDGE_RECORDS, TABLE_KNOWLEDGE_SEMANTIC_OUTBOX, TABLE_MCP_CLIENTS, TABLE_MCP_CLIENT_VERSIONS, TABLE_MCP_SERVERS, TABLE_MCP_SERVER_VERSIONS, TABLE_MESSAGES, TABLE_NOTIFICATIONS, TABLE_PROMPT_BLOCKS, TABLE_PROMPT_BLOCK_VERSIONS, TABLE_RESOURCES, TABLE_SCHEDULES, TABLE_SCHEDULE_TRIGGERS, TABLE_SCORERS, TABLE_SCORER_DEFINITIONS, TABLE_SCORER_DEFINITION_VERSIONS, TABLE_SKILLS, TABLE_SKILL_BLOBS, TABLE_SKILL_VERSIONS, TABLE_SPANS, TABLE_THREADS, TABLE_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, TraceStatus, WorkflowDefinitionsStorage, WorkflowsStorage, WorkspacesStorage, calculatePagination, canonicalizeKnowledgeScope, createKnowledgeUlid, createStorageErrorId, createVectorErrorId, ensureDate, hasErrorCode, isKnowledgeScopeVisible, knowledgeScopeKey, knowledgeSemanticDocumentId, knowledgeSemanticIdempotencyKey, listTracesArgsSchema, normalizePerPage, normalizeScheduleTarget, parseDuration, parseKnowledgeNodeCursor, parseKnowledgeWikilinks, resolveListOrderBy, safelyParseJSON, storageMessageMatchesMetadataFilter, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
|
|
3
|
+
import { AgentsStorage, BackgroundTasksStorage, BlobStore, DatasetsStorage, ExperimentsStorage, KnowledgeConflictError, KnowledgeNotFoundError, KnowledgeStorage, MCPClientsStorage, MCPServersStorage, MastraCompositeStore, MemoryStorage, NotificationsStorage, ObservabilityStorage, PromptBlocksStorage, RunFenceConflictError, SchedulesStorage, ScorerDefinitionsStorage, ScoresStorage, SkillsStorage, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_BACKGROUND_TASKS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_KNOWLEDGE_ACTIVITY, TABLE_KNOWLEDGE_MENTIONS, TABLE_KNOWLEDGE_NODES, TABLE_KNOWLEDGE_RECORDS, TABLE_KNOWLEDGE_SEMANTIC_OUTBOX, TABLE_MCP_CLIENTS, TABLE_MCP_CLIENT_VERSIONS, TABLE_MCP_SERVERS, TABLE_MCP_SERVER_VERSIONS, TABLE_MEMORY_RUN_FENCES, TABLE_MESSAGES, TABLE_NOTIFICATIONS, TABLE_PROMPT_BLOCKS, TABLE_PROMPT_BLOCK_VERSIONS, TABLE_RESOURCES, TABLE_SCHEDULES, TABLE_SCHEDULE_TRIGGERS, TABLE_SCORERS, TABLE_SCORER_DEFINITIONS, TABLE_SCORER_DEFINITION_VERSIONS, TABLE_SKILLS, TABLE_SKILL_BLOBS, TABLE_SKILL_VERSIONS, TABLE_SPANS, TABLE_THREADS, TABLE_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_RUN_OWNERS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, TraceStatus, WorkflowDefinitionsStorage, WorkflowsStorage, WorkspacesStorage, calculatePagination, canonicalizeKnowledgeScope, createKnowledgeUlid, createStorageErrorId, createVectorErrorId, ensureDate, hasErrorCode, isKnowledgeScopeVisible, isRunFenceConflictError, knowledgeScopeKey, knowledgeSemanticDocumentId, knowledgeSemanticIdempotencyKey, listTracesArgsSchema, normalizePerPage, normalizeScheduleTarget, parseDuration, parseKnowledgeNodeCursor, parseKnowledgeWikilinks, resolveListOrderBy, resolveRunFence, safelyParseJSON, storageMessageMatchesMetadataFilter, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
|
|
4
4
|
import { MastraVector, validateUpsertInput, validateVectorValues } from "@mastra/core/vector";
|
|
5
5
|
import { MongoBulkWriteError, MongoClient, ObjectId } from "mongodb";
|
|
6
6
|
import { BaseFilterTranslator } from "@mastra/core/vector/filter";
|
|
@@ -9,7 +9,7 @@ import { MessageList } from "@mastra/core/agent";
|
|
|
9
9
|
import { saveScorePayloadSchema } from "@mastra/core/evals";
|
|
10
10
|
import { skillSnapshotFieldValuesEqual } from "@mastra/core/storage/domains/skills";
|
|
11
11
|
//#region package.json
|
|
12
|
-
var version = "1.23.1
|
|
12
|
+
var version = "1.23.1";
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region src/vector/filter.ts
|
|
15
15
|
/**
|
|
@@ -1618,20 +1618,29 @@ var MongoDBConnector = class MongoDBConnector {
|
|
|
1618
1618
|
return (await this.getConnection()).listCollections({ name: collectionName }).hasNext();
|
|
1619
1619
|
}
|
|
1620
1620
|
/**
|
|
1621
|
-
*
|
|
1622
|
-
*
|
|
1623
|
-
*
|
|
1621
|
+
* Whether the deployment supports multi-document transactions (replica set
|
|
1622
|
+
* or sharded cluster). Standalone servers and custom connector handlers
|
|
1623
|
+
* don't. The answer is cached once a probe completes; a probe that fails
|
|
1624
|
+
* rejects and is retried on the next call, so the answer never changes.
|
|
1624
1625
|
*/
|
|
1625
|
-
async
|
|
1626
|
+
async probeTransactions() {
|
|
1626
1627
|
if (this.#supportsTransactions !== void 0) return this.#supportsTransactions;
|
|
1627
1628
|
if (!this.#client) {
|
|
1628
1629
|
this.#supportsTransactions = false;
|
|
1629
1630
|
return false;
|
|
1630
1631
|
}
|
|
1632
|
+
const hello = await (await this.getConnection()).admin().command({ hello: 1 });
|
|
1633
|
+
this.#supportsTransactions = Boolean(hello.setName) || hello.msg === "isdbgrid";
|
|
1634
|
+
return this.#supportsTransactions;
|
|
1635
|
+
}
|
|
1636
|
+
/**
|
|
1637
|
+
* Like {@link probeTransactions}, but a failed probe reads as no
|
|
1638
|
+
* transaction support (and is retried on the next call), so a momentary
|
|
1639
|
+
* outage degrades writes to non-atomic instead of failing them.
|
|
1640
|
+
*/
|
|
1641
|
+
async supportsTransactions() {
|
|
1631
1642
|
try {
|
|
1632
|
-
|
|
1633
|
-
this.#supportsTransactions = Boolean(hello.setName) || hello.msg === "isdbgrid";
|
|
1634
|
-
return this.#supportsTransactions;
|
|
1643
|
+
return await this.probeTransactions();
|
|
1635
1644
|
} catch {
|
|
1636
1645
|
return false;
|
|
1637
1646
|
}
|
|
@@ -2278,14 +2287,19 @@ function cutoffFor(policy, encoding, now = Date.now()) {
|
|
|
2278
2287
|
* Delete up to `limit` documents whose anchor field is older than `cutoff`.
|
|
2279
2288
|
*
|
|
2280
2289
|
* `deleteMany` has no limit clause, so each batch first collects `_id`s with a
|
|
2281
|
-
* bounded `find`, then deletes
|
|
2282
|
-
*
|
|
2290
|
+
* bounded `find`, then deletes that set — the Mongo equivalent of the SQL
|
|
2291
|
+
* stores' `DELETE ... WHERE rowid/ctid IN (SELECT ... LIMIT n)` pattern. The
|
|
2292
|
+
* delete re-checks the anchor, so a document updated after the `find` (a
|
|
2293
|
+
* renewed run ownership, a touched snapshot) survives.
|
|
2283
2294
|
*/
|
|
2284
2295
|
async function pruneCollectionBatch({ connector, table, column, cutoff, limit }) {
|
|
2285
2296
|
const collection = await connector.getCollection(table);
|
|
2286
2297
|
const docs = await collection.find({ [column]: { $lt: cutoff } }).project({ _id: 1 }).limit(limit).toArray();
|
|
2287
2298
|
if (docs.length === 0) return 0;
|
|
2288
|
-
return (await collection.deleteMany({
|
|
2299
|
+
return (await collection.deleteMany({
|
|
2300
|
+
_id: { $in: docs.map((doc) => doc._id) },
|
|
2301
|
+
[column]: { $lt: cutoff }
|
|
2302
|
+
})).deletedCount;
|
|
2289
2303
|
}
|
|
2290
2304
|
/**
|
|
2291
2305
|
* Run the bounded/cancellable batched-delete loop for a single logical target,
|
|
@@ -6560,6 +6574,40 @@ function isAppendOnlySince(storedObservations, snapshotObservations) {
|
|
|
6560
6574
|
return stored === snapshot || stored.startsWith(snapshot);
|
|
6561
6575
|
}
|
|
6562
6576
|
//#endregion
|
|
6577
|
+
//#region src/storage/domains/run-fencing.ts
|
|
6578
|
+
async function getRunClaims(connector, name) {
|
|
6579
|
+
return await connector.getCollection(name);
|
|
6580
|
+
}
|
|
6581
|
+
/** Duplicate-key error: an upsert lost the race to insert the document. */
|
|
6582
|
+
function isDuplicateKeyError(error) {
|
|
6583
|
+
return error?.code === 11e3;
|
|
6584
|
+
}
|
|
6585
|
+
/**
|
|
6586
|
+
* Runs a write, fenced when `check` is set. A fenced write runs in a
|
|
6587
|
+
* transaction that first updates the run's claim document, matching it on
|
|
6588
|
+
* the fence. A fence that is no longer current matches nothing, and the write
|
|
6589
|
+
* throws `RunFenceConflictError` before anything is written. A claim that
|
|
6590
|
+
* changes while the transaction runs makes the update conflict, so the
|
|
6591
|
+
* transaction retries and sees the new claim; a claim attempted after the
|
|
6592
|
+
* update waits until the transaction commits. Updating the claim document,
|
|
6593
|
+
* rather than reading it, is what makes concurrent claims conflict.
|
|
6594
|
+
*
|
|
6595
|
+
* Fencing is declared only on deployments that support transactions.
|
|
6596
|
+
*/
|
|
6597
|
+
async function withRunFence(connector, check, write) {
|
|
6598
|
+
if (!check) return write();
|
|
6599
|
+
const { claims, fence, operation } = check;
|
|
6600
|
+
const collection = await getRunClaims(connector, claims);
|
|
6601
|
+
return connector.withTransaction(async (session) => {
|
|
6602
|
+
if ((await collection.updateOne({
|
|
6603
|
+
_id: fence.runId,
|
|
6604
|
+
generation: fence.generation,
|
|
6605
|
+
ownerId: fence.ownerId
|
|
6606
|
+
}, { $inc: { fencedWrites: 1 } }, { session })).matchedCount === 0) throw new RunFenceConflictError(fence, operation);
|
|
6607
|
+
return write(session);
|
|
6608
|
+
});
|
|
6609
|
+
}
|
|
6610
|
+
//#endregion
|
|
6563
6611
|
//#region src/storage/domains/memory/index.ts
|
|
6564
6612
|
/**
|
|
6565
6613
|
* Local constant for the observational memory table name.
|
|
@@ -6629,9 +6677,6 @@ function buildSuccessorDocument(old, pending) {
|
|
|
6629
6677
|
function matchStoredFields(doc, fields) {
|
|
6630
6678
|
return Object.fromEntries(fields.map((f) => [f, doc[f] === void 0 ? { $exists: false } : doc[f]]));
|
|
6631
6679
|
}
|
|
6632
|
-
function isDuplicateKeyError(error) {
|
|
6633
|
-
return error?.code === 11e3;
|
|
6634
|
-
}
|
|
6635
6680
|
function omNotFound(operation, id) {
|
|
6636
6681
|
return new MastraError({
|
|
6637
6682
|
id: createStorageErrorId("MONGODB", operation, "NOT_FOUND"),
|
|
@@ -6671,11 +6716,14 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
6671
6716
|
TABLE_THREADS,
|
|
6672
6717
|
TABLE_MESSAGES,
|
|
6673
6718
|
TABLE_RESOURCES,
|
|
6674
|
-
OM_TABLE
|
|
6719
|
+
OM_TABLE,
|
|
6720
|
+
TABLE_MEMORY_RUN_FENCES
|
|
6675
6721
|
];
|
|
6676
6722
|
/**
|
|
6677
6723
|
* Retention-eligible collections. The observational-memory collection is
|
|
6678
6724
|
* excluded: it has no timestamp anchor to age on. All anchors are BSON dates.
|
|
6725
|
+
* A run fence's `retiredAt` stays null until the execution holding it
|
|
6726
|
+
* settles, and null is never pruned.
|
|
6679
6727
|
*/
|
|
6680
6728
|
static retentionTables = {
|
|
6681
6729
|
messages: {
|
|
@@ -6692,6 +6740,11 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
6692
6740
|
table: TABLE_THREADS,
|
|
6693
6741
|
column: "createdAt",
|
|
6694
6742
|
indexed: true
|
|
6743
|
+
},
|
|
6744
|
+
runFences: {
|
|
6745
|
+
table: TABLE_MEMORY_RUN_FENCES,
|
|
6746
|
+
column: "retiredAt",
|
|
6747
|
+
indexed: true
|
|
6695
6748
|
}
|
|
6696
6749
|
};
|
|
6697
6750
|
constructor(config) {
|
|
@@ -6710,11 +6763,76 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
6710
6763
|
await this.#maintainSupersededBy();
|
|
6711
6764
|
}
|
|
6712
6765
|
/**
|
|
6766
|
+
* Fenced writes need multi-document transactions, so only replica sets and
|
|
6767
|
+
* sharded clusters fence. Rejects while the deployment can't be probed.
|
|
6768
|
+
*/
|
|
6769
|
+
supportsRunFencing() {
|
|
6770
|
+
return this.#connector.probeTransactions();
|
|
6771
|
+
}
|
|
6772
|
+
async raiseRunFence(fence) {
|
|
6773
|
+
try {
|
|
6774
|
+
const fences = await getRunClaims(this.#connector, TABLE_MEMORY_RUN_FENCES);
|
|
6775
|
+
for (;;) {
|
|
6776
|
+
try {
|
|
6777
|
+
await fences.updateOne({
|
|
6778
|
+
_id: fence.runId,
|
|
6779
|
+
$or: [{ generation: { $lt: fence.generation } }, {
|
|
6780
|
+
generation: fence.generation,
|
|
6781
|
+
ownerId: fence.ownerId
|
|
6782
|
+
}]
|
|
6783
|
+
}, { $set: {
|
|
6784
|
+
generation: fence.generation,
|
|
6785
|
+
ownerId: fence.ownerId,
|
|
6786
|
+
retiredAt: null
|
|
6787
|
+
} }, { upsert: true });
|
|
6788
|
+
return true;
|
|
6789
|
+
} catch (error) {
|
|
6790
|
+
if (!isDuplicateKeyError(error)) throw error;
|
|
6791
|
+
}
|
|
6792
|
+
const current = await fences.findOne({ _id: fence.runId });
|
|
6793
|
+
if (current && current.generation >= fence.generation) return current.generation === fence.generation && current.ownerId === fence.ownerId;
|
|
6794
|
+
}
|
|
6795
|
+
} catch (error) {
|
|
6796
|
+
throw new MastraError({
|
|
6797
|
+
id: createStorageErrorId("MONGODB", "RAISE_RUN_FENCE", "FAILED"),
|
|
6798
|
+
domain: ErrorDomain.STORAGE,
|
|
6799
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
6800
|
+
details: { runId: fence.runId }
|
|
6801
|
+
}, error);
|
|
6802
|
+
}
|
|
6803
|
+
}
|
|
6804
|
+
async retireRunFence(fence) {
|
|
6805
|
+
try {
|
|
6806
|
+
return (await (await getRunClaims(this.#connector, TABLE_MEMORY_RUN_FENCES)).updateOne({
|
|
6807
|
+
_id: fence.runId,
|
|
6808
|
+
generation: fence.generation,
|
|
6809
|
+
ownerId: fence.ownerId
|
|
6810
|
+
}, [{ $set: { retiredAt: "$$NOW" } }])).matchedCount > 0;
|
|
6811
|
+
} catch (error) {
|
|
6812
|
+
throw new MastraError({
|
|
6813
|
+
id: createStorageErrorId("MONGODB", "RETIRE_RUN_FENCE", "FAILED"),
|
|
6814
|
+
domain: ErrorDomain.STORAGE,
|
|
6815
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
6816
|
+
details: { runId: fence.runId }
|
|
6817
|
+
}, error);
|
|
6818
|
+
}
|
|
6819
|
+
}
|
|
6820
|
+
/** The check a write must pass: its own fence, otherwise the one in scope. */
|
|
6821
|
+
#runFenceCheck(fence, operation) {
|
|
6822
|
+
const resolved = resolveRunFence(this, fence);
|
|
6823
|
+
return resolved && {
|
|
6824
|
+
claims: TABLE_MEMORY_RUN_FENCES,
|
|
6825
|
+
fence: resolved,
|
|
6826
|
+
operation
|
|
6827
|
+
};
|
|
6828
|
+
}
|
|
6829
|
+
/**
|
|
6713
6830
|
* Delete memory rows older than each table's `maxAge`, batched. Order is
|
|
6714
6831
|
* messages → resources → threads so child rows never outlive the delete of
|
|
6715
|
-
* their thread
|
|
6716
|
-
*
|
|
6717
|
-
*
|
|
6832
|
+
* their thread; run fences are independent and go last. Like
|
|
6833
|
+
* `deleteThread()`, this does not sweep vector-store embeddings —
|
|
6834
|
+
* semantic-recall vectors live in a separate vector store the memory domain
|
|
6835
|
+
* cannot reach; cleaning those up is the operator's concern.
|
|
6718
6836
|
*/
|
|
6719
6837
|
async prune(policies, options) {
|
|
6720
6838
|
const targets = resolveTargets({
|
|
@@ -6723,7 +6841,8 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
6723
6841
|
order: [
|
|
6724
6842
|
"messages",
|
|
6725
6843
|
"resources",
|
|
6726
|
-
"threads"
|
|
6844
|
+
"threads",
|
|
6845
|
+
"runFences"
|
|
6727
6846
|
]
|
|
6728
6847
|
});
|
|
6729
6848
|
return runPrune({
|
|
@@ -6829,17 +6948,19 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
6829
6948
|
}
|
|
6830
6949
|
}
|
|
6831
6950
|
async dangerouslyClearAll() {
|
|
6832
|
-
const [threadsCollection, messagesCollection, resourcesCollection, omCollection] = await Promise.all([
|
|
6951
|
+
const [threadsCollection, messagesCollection, resourcesCollection, omCollection, fencesCollection] = await Promise.all([
|
|
6833
6952
|
this.getCollection(TABLE_THREADS),
|
|
6834
6953
|
this.getCollection(TABLE_MESSAGES),
|
|
6835
6954
|
this.getCollection(TABLE_RESOURCES),
|
|
6836
|
-
this.getCollection(OM_TABLE)
|
|
6955
|
+
this.getCollection(OM_TABLE),
|
|
6956
|
+
this.getCollection(TABLE_MEMORY_RUN_FENCES)
|
|
6837
6957
|
]);
|
|
6838
6958
|
await Promise.all([
|
|
6839
6959
|
threadsCollection.deleteMany({}),
|
|
6840
6960
|
messagesCollection.deleteMany({}),
|
|
6841
6961
|
resourcesCollection.deleteMany({}),
|
|
6842
|
-
omCollection.deleteMany({})
|
|
6962
|
+
omCollection.deleteMany({}),
|
|
6963
|
+
fencesCollection.deleteMany({})
|
|
6843
6964
|
]);
|
|
6844
6965
|
}
|
|
6845
6966
|
parseRow(row) {
|
|
@@ -7195,7 +7316,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7195
7316
|
throw mastraError;
|
|
7196
7317
|
}
|
|
7197
7318
|
}
|
|
7198
|
-
async saveMessages({ messages }) {
|
|
7319
|
+
async saveMessages({ messages, fence }) {
|
|
7199
7320
|
if (messages.length === 0) return { messages: [] };
|
|
7200
7321
|
try {
|
|
7201
7322
|
if (!messages[0]?.threadId) throw new Error("Thread ID is required");
|
|
@@ -7223,12 +7344,16 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7223
7344
|
});
|
|
7224
7345
|
const allThreadIds = new Set(messages.map((m) => m.threadId));
|
|
7225
7346
|
const now = /* @__PURE__ */ new Date();
|
|
7226
|
-
|
|
7347
|
+
const writeMessages = async (session) => {
|
|
7227
7348
|
await collection.bulkWrite(messagesToInsert, { session });
|
|
7228
7349
|
for (const tid of allThreadIds) await threadsCollection.updateOne({ id: tid }, { $set: { updatedAt: now } }, { session });
|
|
7229
|
-
}
|
|
7350
|
+
};
|
|
7351
|
+
const check = this.#runFenceCheck(fence, "saveMessages");
|
|
7352
|
+
if (check) await withRunFence(this.#connector, check, writeMessages);
|
|
7353
|
+
else await this.#connector.withTransaction(writeMessages);
|
|
7230
7354
|
return { messages: new MessageList().add(messages, "memory").get.all.db() };
|
|
7231
7355
|
} catch (error) {
|
|
7356
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
7232
7357
|
throw new MastraError({
|
|
7233
7358
|
id: createStorageErrorId("MONGODB", "SAVE_MESSAGES", "FAILED"),
|
|
7234
7359
|
domain: ErrorDomain.STORAGE,
|
|
@@ -7236,49 +7361,51 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7236
7361
|
}, error);
|
|
7237
7362
|
}
|
|
7238
7363
|
}
|
|
7239
|
-
async updateMessages({ messages }) {
|
|
7364
|
+
async updateMessages({ messages, fence }) {
|
|
7240
7365
|
if (messages.length === 0) return [];
|
|
7241
7366
|
const messageIds = messages.map((m) => m.id);
|
|
7242
7367
|
const collection = await this.getCollection(TABLE_MESSAGES);
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
const
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
...existingMessage.content.metadata
|
|
7262
|
-
|
|
7263
|
-
|
|
7264
|
-
|
|
7265
|
-
|
|
7266
|
-
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
const
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7368
|
+
return withRunFence(this.#connector, this.#runFenceCheck(fence, "updateMessages"), async (session) => {
|
|
7369
|
+
const existingMessagesParsed = (await collection.find({ id: { $in: messageIds } }, { session }).toArray()).map((msg) => this.parseRow(msg));
|
|
7370
|
+
if (existingMessagesParsed.length === 0) return [];
|
|
7371
|
+
const threadIdsToUpdate = /* @__PURE__ */ new Set();
|
|
7372
|
+
const bulkOps = [];
|
|
7373
|
+
for (const existingMessage of existingMessagesParsed) {
|
|
7374
|
+
const updatePayload = messages.find((m) => m.id === existingMessage.id);
|
|
7375
|
+
if (!updatePayload) continue;
|
|
7376
|
+
const { id, ...fieldsToUpdate } = updatePayload;
|
|
7377
|
+
if (Object.keys(fieldsToUpdate).length === 0) continue;
|
|
7378
|
+
threadIdsToUpdate.add(existingMessage.threadId);
|
|
7379
|
+
if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) threadIdsToUpdate.add(updatePayload.threadId);
|
|
7380
|
+
const updateDoc = {};
|
|
7381
|
+
const updatableFields = { ...fieldsToUpdate };
|
|
7382
|
+
if (updatableFields.content) {
|
|
7383
|
+
const newContent = {
|
|
7384
|
+
...existingMessage.content,
|
|
7385
|
+
...updatableFields.content,
|
|
7386
|
+
...existingMessage.content?.metadata && updatableFields.content.metadata ? { metadata: {
|
|
7387
|
+
...existingMessage.content.metadata,
|
|
7388
|
+
...updatableFields.content.metadata
|
|
7389
|
+
} } : {}
|
|
7390
|
+
};
|
|
7391
|
+
updateDoc.content = JSON.stringify(newContent);
|
|
7392
|
+
delete updatableFields.content;
|
|
7393
|
+
}
|
|
7394
|
+
for (const key in updatableFields) if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
|
|
7395
|
+
const dbKey = key === "threadId" ? "thread_id" : key;
|
|
7396
|
+
let value = updatableFields[key];
|
|
7397
|
+
if (typeof value === "object" && value !== null) value = JSON.stringify(value);
|
|
7398
|
+
updateDoc[dbKey] = value;
|
|
7399
|
+
}
|
|
7400
|
+
if (Object.keys(updateDoc).length > 0) bulkOps.push({ updateOne: {
|
|
7401
|
+
filter: { id },
|
|
7402
|
+
update: { $set: updateDoc }
|
|
7403
|
+
} });
|
|
7273
7404
|
}
|
|
7274
|
-
if (
|
|
7275
|
-
|
|
7276
|
-
|
|
7277
|
-
|
|
7278
|
-
}
|
|
7279
|
-
if (bulkOps.length > 0) await collection.bulkWrite(bulkOps);
|
|
7280
|
-
if (threadIdsToUpdate.size > 0) await (await this.getCollection(TABLE_THREADS)).updateMany({ id: { $in: Array.from(threadIdsToUpdate) } }, { $set: { updatedAt: /* @__PURE__ */ new Date() } });
|
|
7281
|
-
return (await collection.find({ id: { $in: messageIds } }).toArray()).map((row) => this.parseRow(row));
|
|
7405
|
+
if (bulkOps.length > 0) await collection.bulkWrite(bulkOps, { session });
|
|
7406
|
+
if (threadIdsToUpdate.size > 0) await (await this.getCollection(TABLE_THREADS)).updateMany({ id: { $in: Array.from(threadIdsToUpdate) } }, { $set: { updatedAt: /* @__PURE__ */ new Date() } }, { session });
|
|
7407
|
+
return (await collection.find({ id: { $in: messageIds } }, { session }).toArray()).map((row) => this.parseRow(row));
|
|
7408
|
+
});
|
|
7282
7409
|
}
|
|
7283
7410
|
async getResourceById({ resourceId }) {
|
|
7284
7411
|
try {
|
|
@@ -7313,9 +7440,11 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7313
7440
|
}, error);
|
|
7314
7441
|
}
|
|
7315
7442
|
}
|
|
7316
|
-
async updateResource({ resourceId, workingMemory, metadata }) {
|
|
7443
|
+
async updateResource({ resourceId, workingMemory, metadata, fence }) {
|
|
7317
7444
|
try {
|
|
7445
|
+
const check = this.#runFenceCheck(fence, "updateResource");
|
|
7318
7446
|
const existingResource = await this.getResourceById({ resourceId });
|
|
7447
|
+
const collection = await this.getCollection(TABLE_RESOURCES);
|
|
7319
7448
|
if (!existingResource) {
|
|
7320
7449
|
const newResource = {
|
|
7321
7450
|
id: resourceId,
|
|
@@ -7324,7 +7453,11 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7324
7453
|
createdAt: /* @__PURE__ */ new Date(),
|
|
7325
7454
|
updatedAt: /* @__PURE__ */ new Date()
|
|
7326
7455
|
};
|
|
7327
|
-
|
|
7456
|
+
await withRunFence(this.#connector, check, (session) => collection.updateOne({ id: resourceId }, { $set: { ...newResource } }, {
|
|
7457
|
+
upsert: true,
|
|
7458
|
+
session
|
|
7459
|
+
}));
|
|
7460
|
+
return newResource;
|
|
7328
7461
|
}
|
|
7329
7462
|
const updatedResource = {
|
|
7330
7463
|
...existingResource,
|
|
@@ -7335,13 +7468,13 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7335
7468
|
} : existingResource.metadata,
|
|
7336
7469
|
updatedAt: /* @__PURE__ */ new Date()
|
|
7337
7470
|
};
|
|
7338
|
-
const collection = await this.getCollection(TABLE_RESOURCES);
|
|
7339
7471
|
const updateDoc = { updatedAt: updatedResource.updatedAt };
|
|
7340
7472
|
if (workingMemory !== void 0) updateDoc.workingMemory = workingMemory;
|
|
7341
7473
|
if (metadata) updateDoc.metadata = updatedResource.metadata;
|
|
7342
|
-
await collection.updateOne({ id: resourceId }, { $set: updateDoc });
|
|
7474
|
+
await withRunFence(this.#connector, check, (session) => collection.updateOne({ id: resourceId }, { $set: updateDoc }, { session }));
|
|
7343
7475
|
return updatedResource;
|
|
7344
7476
|
} catch (error) {
|
|
7477
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
7345
7478
|
throw new MastraError({
|
|
7346
7479
|
id: createStorageErrorId("MONGODB", "UPDATE_RESOURCE", "FAILED"),
|
|
7347
7480
|
domain: ErrorDomain.STORAGE,
|
|
@@ -7441,14 +7574,19 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7441
7574
|
throw mastraError;
|
|
7442
7575
|
}
|
|
7443
7576
|
}
|
|
7444
|
-
async saveThread({ thread }) {
|
|
7577
|
+
async saveThread({ thread, fence }) {
|
|
7445
7578
|
try {
|
|
7446
|
-
|
|
7579
|
+
const collection = await this.getCollection(TABLE_THREADS);
|
|
7580
|
+
await withRunFence(this.#connector, this.#runFenceCheck(fence, "saveThread"), (session) => collection.updateOne({ id: thread.id }, { $set: {
|
|
7447
7581
|
...thread,
|
|
7448
7582
|
metadata: thread.metadata
|
|
7449
|
-
} }, {
|
|
7583
|
+
} }, {
|
|
7584
|
+
upsert: true,
|
|
7585
|
+
session
|
|
7586
|
+
}));
|
|
7450
7587
|
return thread;
|
|
7451
7588
|
} catch (error) {
|
|
7589
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
7452
7590
|
throw new MastraError({
|
|
7453
7591
|
id: createStorageErrorId("MONGODB", "SAVE_THREAD", "FAILED"),
|
|
7454
7592
|
domain: ErrorDomain.STORAGE,
|
|
@@ -7457,7 +7595,8 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7457
7595
|
}, error);
|
|
7458
7596
|
}
|
|
7459
7597
|
}
|
|
7460
|
-
async updateThread({ id, title, metadata }) {
|
|
7598
|
+
async updateThread({ id, title, metadata, fence }) {
|
|
7599
|
+
const check = this.#runFenceCheck(fence, "updateThread");
|
|
7461
7600
|
const thread = await this.getThreadById({ threadId: id });
|
|
7462
7601
|
if (!thread) throw new MastraError({
|
|
7463
7602
|
id: createStorageErrorId("MONGODB", "UPDATE_THREAD", "NOT_FOUND"),
|
|
@@ -7480,12 +7619,14 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7480
7619
|
updatedAt: now
|
|
7481
7620
|
};
|
|
7482
7621
|
try {
|
|
7483
|
-
|
|
7622
|
+
const collection = await this.getCollection(TABLE_THREADS);
|
|
7623
|
+
await withRunFence(this.#connector, check, (session) => collection.updateOne({ id }, { $set: {
|
|
7484
7624
|
title: updatedThread.title,
|
|
7485
7625
|
metadata: updatedThread.metadata,
|
|
7486
7626
|
updatedAt: now
|
|
7487
|
-
} });
|
|
7627
|
+
} }, { session }));
|
|
7488
7628
|
} catch (error) {
|
|
7629
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
7489
7630
|
throw new MastraError({
|
|
7490
7631
|
id: createStorageErrorId("MONGODB", "UPDATE_THREAD", "FAILED"),
|
|
7491
7632
|
domain: ErrorDomain.STORAGE,
|
|
@@ -7508,16 +7649,19 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7508
7649
|
}, error);
|
|
7509
7650
|
}
|
|
7510
7651
|
}
|
|
7511
|
-
async deleteMessages(messageIds) {
|
|
7652
|
+
async deleteMessages(messageIds, options) {
|
|
7512
7653
|
if (messageIds.length === 0) return;
|
|
7513
7654
|
try {
|
|
7514
7655
|
const messagesCollection = await this.getCollection(TABLE_MESSAGES);
|
|
7515
7656
|
const threadsCollection = await this.getCollection(TABLE_THREADS);
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
|
|
7657
|
+
await withRunFence(this.#connector, this.#runFenceCheck(options?.fence, "deleteMessages"), async (session) => {
|
|
7658
|
+
const messagesToDelete = await messagesCollection.find({ id: { $in: messageIds } }, { session }).toArray();
|
|
7659
|
+
const threadIds = [...new Set(messagesToDelete.map((m) => m.thread_id))];
|
|
7660
|
+
await messagesCollection.deleteMany({ id: { $in: messageIds } }, { session });
|
|
7661
|
+
if (threadIds.length > 0) await threadsCollection.updateMany({ id: { $in: threadIds } }, { $set: { updatedAt: /* @__PURE__ */ new Date() } }, { session });
|
|
7662
|
+
});
|
|
7520
7663
|
} catch (error) {
|
|
7664
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
7521
7665
|
throw new MastraError({
|
|
7522
7666
|
id: createStorageErrorId("MONGODB", "DELETE_MESSAGES", "FAILED"),
|
|
7523
7667
|
domain: ErrorDomain.STORAGE,
|
|
@@ -7884,7 +8028,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7884
8028
|
applied: false,
|
|
7885
8029
|
reason: "conflict"
|
|
7886
8030
|
};
|
|
7887
|
-
if ((await collection.updateOne({
|
|
8031
|
+
if ((await withRunFence(this.#connector, this.#runFenceCheck(void 0, "updateActiveObservations"), (session) => collection.updateOne({
|
|
7888
8032
|
id: input.id,
|
|
7889
8033
|
supersededBy: null,
|
|
7890
8034
|
...matchStoredFields(doc, ["activeObservations", "lastObservedAt"])
|
|
@@ -7898,7 +8042,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7898
8042
|
updatedAt: /* @__PURE__ */ new Date()
|
|
7899
8043
|
},
|
|
7900
8044
|
$inc: { totalTokensObserved: safeTokenCount }
|
|
7901
|
-
})).matchedCount === 1) return { applied: true };
|
|
8045
|
+
}, { session }))).matchedCount === 1) return { applied: true };
|
|
7902
8046
|
}
|
|
7903
8047
|
throw omConflict("UPDATE_ACTIVE_OBSERVATIONS", input.id);
|
|
7904
8048
|
} catch (error) {
|
|
@@ -7914,7 +8058,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7914
8058
|
async createReflectionGeneration(input) {
|
|
7915
8059
|
try {
|
|
7916
8060
|
const { currentRecord } = input;
|
|
7917
|
-
return await this.#rollOver(currentRecord, (doc) => {
|
|
8061
|
+
return await this.#rollOver(currentRecord, "createReflectionGeneration", (doc) => {
|
|
7918
8062
|
const stored = this.parseOMDocument(doc);
|
|
7919
8063
|
const plan = planReflectionGenerationText({
|
|
7920
8064
|
storedObservations: stored.activeObservations,
|
|
@@ -8151,7 +8295,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
8151
8295
|
let hops = 0;
|
|
8152
8296
|
let retries = 0;
|
|
8153
8297
|
for (;;) {
|
|
8154
|
-
if ((await collection.updateOne({
|
|
8298
|
+
if ((await withRunFence(this.#connector, this.#runFenceCheck(void 0, "updateBufferedObservations"), (session) => collection.updateOne({
|
|
8155
8299
|
id: targetId,
|
|
8156
8300
|
supersededBy: null,
|
|
8157
8301
|
"bufferedObservationChunks.cycleId": { $ne: input.chunk.cycleId },
|
|
@@ -8165,7 +8309,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
8165
8309
|
bufferedObservationChunks: { $concatArrays: [{ $ifNull: ["$bufferedObservationChunks", []] }, { $literal: [newChunk] }] },
|
|
8166
8310
|
...lastBufferedAtTime ? { lastBufferedAtTime: { $max: ["$lastBufferedAtTime", { $literal: lastBufferedAtTime }] } } : {},
|
|
8167
8311
|
updatedAt: { $literal: /* @__PURE__ */ new Date() }
|
|
8168
|
-
} }])).matchedCount === 1) return {
|
|
8312
|
+
} }], { session }))).matchedCount === 1) return {
|
|
8169
8313
|
persisted: true,
|
|
8170
8314
|
recordId: targetId
|
|
8171
8315
|
};
|
|
@@ -8188,7 +8332,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
8188
8332
|
recordId: targetId
|
|
8189
8333
|
};
|
|
8190
8334
|
if (stored !== void 0 && stored !== null && !Array.isArray(stored)) {
|
|
8191
|
-
if ((await collection.updateOne({
|
|
8335
|
+
if ((await withRunFence(this.#connector, this.#runFenceCheck(void 0, "updateBufferedObservations"), (session) => collection.updateOne({
|
|
8192
8336
|
id: targetId,
|
|
8193
8337
|
supersededBy: null,
|
|
8194
8338
|
bufferedObservationChunks: stored,
|
|
@@ -8197,7 +8341,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
8197
8341
|
bufferedObservationChunks: [newChunk],
|
|
8198
8342
|
...lastBufferedAtTime ? { lastBufferedAtTime: maxObservationCursor(doc.lastBufferedAtTime, lastBufferedAtTime) } : {},
|
|
8199
8343
|
updatedAt: /* @__PURE__ */ new Date()
|
|
8200
|
-
} })).matchedCount === 1) return {
|
|
8344
|
+
} }, { session }))).matchedCount === 1) return {
|
|
8201
8345
|
persisted: true,
|
|
8202
8346
|
recordId: targetId
|
|
8203
8347
|
};
|
|
@@ -8287,7 +8431,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
8287
8431
|
const newTokenCount = existingTokenCount + activatedTokens;
|
|
8288
8432
|
const existingPending = Number(doc.pendingMessageTokens || 0);
|
|
8289
8433
|
const newPending = Math.max(0, existingPending - activatedMessageTokens);
|
|
8290
|
-
if ((await collection.updateOne({
|
|
8434
|
+
if ((await withRunFence(this.#connector, this.#runFenceCheck(void 0, "swapBufferedToActive"), (session) => collection.updateOne({
|
|
8291
8435
|
id: input.id,
|
|
8292
8436
|
supersededBy: null,
|
|
8293
8437
|
...matchStoredFields(doc, [
|
|
@@ -8304,7 +8448,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
8304
8448
|
bufferedObservationChunks: remainingChunks,
|
|
8305
8449
|
lastObservedAt: maxObservationCursor(doc.lastObservedAt, lastObservedAt),
|
|
8306
8450
|
updatedAt: /* @__PURE__ */ new Date()
|
|
8307
|
-
} })).matchedCount === 0) continue;
|
|
8451
|
+
} }, { session }))).matchedCount === 0) continue;
|
|
8308
8452
|
const latestChunkHints = activatedChunks[activatedChunks.length - 1];
|
|
8309
8453
|
return {
|
|
8310
8454
|
chunksActivated: activatedChunks.length,
|
|
@@ -8353,13 +8497,13 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
8353
8497
|
const newContent = existingContent ? `${existingContent}\n\n${input.reflection}` : input.reflection;
|
|
8354
8498
|
const newTokens = existingTokens + input.tokenCount;
|
|
8355
8499
|
const newInputTokens = existingInputTokens + input.inputTokenCount;
|
|
8356
|
-
if ((await collection.updateOne({ id: input.id }, { $set: {
|
|
8500
|
+
if ((await withRunFence(this.#connector, this.#runFenceCheck(void 0, "updateBufferedReflection"), (session) => collection.updateOne({ id: input.id }, { $set: {
|
|
8357
8501
|
bufferedReflection: newContent,
|
|
8358
8502
|
bufferedReflectionTokens: newTokens,
|
|
8359
8503
|
bufferedReflectionInputTokens: newInputTokens,
|
|
8360
8504
|
reflectedObservationLineCount: input.reflectedObservationLineCount,
|
|
8361
8505
|
updatedAt: /* @__PURE__ */ new Date()
|
|
8362
|
-
} })).matchedCount === 0) throw new MastraError({
|
|
8506
|
+
} }, { session }))).matchedCount === 0) throw new MastraError({
|
|
8363
8507
|
id: createStorageErrorId("MONGODB", "UPDATE_BUFFERED_REFLECTION", "NOT_FOUND"),
|
|
8364
8508
|
text: `Observational memory record not found: ${input.id}`,
|
|
8365
8509
|
domain: ErrorDomain.STORAGE,
|
|
@@ -8379,7 +8523,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
8379
8523
|
async swapBufferedReflectionToActive(input) {
|
|
8380
8524
|
try {
|
|
8381
8525
|
const { currentRecord } = input;
|
|
8382
|
-
return await this.#rollOver(currentRecord, (doc) => {
|
|
8526
|
+
return await this.#rollOver(currentRecord, "swapBufferedReflectionToActive", (doc) => {
|
|
8383
8527
|
const stored = this.parseOMDocument(doc);
|
|
8384
8528
|
const bufferedReflection = stored.bufferedReflection || "";
|
|
8385
8529
|
if (!bufferedReflection) throw new MastraError({
|
|
@@ -8465,7 +8609,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
8465
8609
|
*
|
|
8466
8610
|
* A crash between 2 and 3 is finished by the next head read (`#getHeadDoc`) or startup.
|
|
8467
8611
|
*/
|
|
8468
|
-
async #rollOver(currentRecord, decide) {
|
|
8612
|
+
async #rollOver(currentRecord, operation, decide) {
|
|
8469
8613
|
const collection = await this.getCollection(OM_TABLE);
|
|
8470
8614
|
for (let attempt = 1; attempt <= OM_MAX_CONDITIONAL_ATTEMPTS; attempt++) {
|
|
8471
8615
|
const doc = await collection.findOne({ id: currentRecord.id });
|
|
@@ -8476,7 +8620,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
8476
8620
|
}
|
|
8477
8621
|
const pending = decide(doc);
|
|
8478
8622
|
if (!pending) return this.parseOMDocument(doc);
|
|
8479
|
-
const frozen = await collection.findOneAndUpdate({
|
|
8623
|
+
const frozen = await withRunFence(this.#connector, this.#runFenceCheck(void 0, operation), (session) => collection.findOneAndUpdate({
|
|
8480
8624
|
id: doc.id,
|
|
8481
8625
|
supersededBy: null,
|
|
8482
8626
|
...matchStoredFields(doc, [
|
|
@@ -8488,7 +8632,10 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
8488
8632
|
}, { $set: {
|
|
8489
8633
|
supersededBy: pending.newId,
|
|
8490
8634
|
pendingSuccessor: pending
|
|
8491
|
-
} }, {
|
|
8635
|
+
} }, {
|
|
8636
|
+
returnDocument: "before",
|
|
8637
|
+
session
|
|
8638
|
+
}));
|
|
8492
8639
|
if (!frozen) continue;
|
|
8493
8640
|
return this.parseOMDocument(await this.#completeRollover(collection, frozen, pending));
|
|
8494
8641
|
}
|
|
@@ -11803,33 +11950,58 @@ var MongoDBWorkflowDefinitionsStore = class MongoDBWorkflowDefinitionsStore exte
|
|
|
11803
11950
|
};
|
|
11804
11951
|
//#endregion
|
|
11805
11952
|
//#region src/storage/domains/workflows/index.ts
|
|
11953
|
+
const RUN_OWNER_PROJECTION = {
|
|
11954
|
+
generation: 1,
|
|
11955
|
+
ownerId: 1,
|
|
11956
|
+
leaseExpiresAt: 1,
|
|
11957
|
+
live: { $gt: ["$leaseExpiresAt", "$$NOW"] }
|
|
11958
|
+
};
|
|
11959
|
+
const NOT_LIVE = { $or: [{ leaseExpiresAt: null }, { $expr: { $lte: ["$leaseExpiresAt", "$$NOW"] } }] };
|
|
11960
|
+
function toRunOwnershipRecord(doc) {
|
|
11961
|
+
return {
|
|
11962
|
+
runId: doc._id,
|
|
11963
|
+
generation: Number(doc.generation),
|
|
11964
|
+
ownerId: doc.ownerId,
|
|
11965
|
+
leaseExpiresAt: doc.leaseExpiresAt ?? null,
|
|
11966
|
+
live: Boolean(doc.live)
|
|
11967
|
+
};
|
|
11968
|
+
}
|
|
11806
11969
|
var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends WorkflowsStorage {
|
|
11807
11970
|
#connector;
|
|
11808
11971
|
#skipDefaultIndexes;
|
|
11809
11972
|
#indexes;
|
|
11810
11973
|
/** Collections managed by this domain */
|
|
11811
|
-
static MANAGED_COLLECTIONS = [TABLE_WORKFLOW_SNAPSHOT];
|
|
11974
|
+
static MANAGED_COLLECTIONS = [TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKFLOW_RUN_OWNERS];
|
|
11812
11975
|
/**
|
|
11813
|
-
*
|
|
11814
|
-
* a run is pruned only after its snapshot has not been touched
|
|
11976
|
+
* Both anchors are `updatedAt` (BSON date), so the policies read as
|
|
11977
|
+
* inactivity: a run is pruned only after its snapshot has not been touched
|
|
11978
|
+
* for `maxAge`, and an ownership document only after no claim, renewal or
|
|
11979
|
+
* release has touched it for `maxAge`.
|
|
11815
11980
|
*/
|
|
11816
|
-
static retentionTables = {
|
|
11817
|
-
|
|
11818
|
-
|
|
11819
|
-
|
|
11820
|
-
|
|
11981
|
+
static retentionTables = {
|
|
11982
|
+
workflowSnapshot: {
|
|
11983
|
+
table: TABLE_WORKFLOW_SNAPSHOT,
|
|
11984
|
+
column: "updatedAt",
|
|
11985
|
+
indexed: true
|
|
11986
|
+
},
|
|
11987
|
+
runOwnership: {
|
|
11988
|
+
table: TABLE_WORKFLOW_RUN_OWNERS,
|
|
11989
|
+
column: "updatedAt",
|
|
11990
|
+
indexed: true
|
|
11991
|
+
}
|
|
11992
|
+
};
|
|
11821
11993
|
constructor(config) {
|
|
11822
11994
|
super();
|
|
11823
11995
|
this.#connector = resolveMongoDBConfig(config);
|
|
11824
11996
|
this.#skipDefaultIndexes = config.skipDefaultIndexes;
|
|
11825
11997
|
this.#indexes = config.indexes?.filter((idx) => WorkflowsStorageMongoDB.MANAGED_COLLECTIONS.includes(idx.collection));
|
|
11826
11998
|
}
|
|
11827
|
-
/** Delete workflow snapshots idle for longer than
|
|
11999
|
+
/** Delete workflow snapshots and ownership documents idle for longer than each policy's `maxAge`, batched. */
|
|
11828
12000
|
async prune(policies, options) {
|
|
11829
12001
|
const targets = resolveTargets({
|
|
11830
12002
|
policies,
|
|
11831
12003
|
descriptor: WorkflowsStorageMongoDB.retentionTables,
|
|
11832
|
-
order: ["workflowSnapshot"]
|
|
12004
|
+
order: ["workflowSnapshot", "runOwnership"]
|
|
11833
12005
|
});
|
|
11834
12006
|
return runPrune({
|
|
11835
12007
|
connector: this.#connector,
|
|
@@ -11842,6 +12014,13 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends WorkflowsSto
|
|
|
11842
12014
|
supportsConcurrentUpdates() {
|
|
11843
12015
|
return true;
|
|
11844
12016
|
}
|
|
12017
|
+
/**
|
|
12018
|
+
* Fenced writes need multi-document transactions, so only replica sets and
|
|
12019
|
+
* sharded clusters fence. Rejects while the deployment can't be probed.
|
|
12020
|
+
*/
|
|
12021
|
+
supportsRunFencing() {
|
|
12022
|
+
return this.#connector.probeTransactions();
|
|
12023
|
+
}
|
|
11845
12024
|
async getCollection(name) {
|
|
11846
12025
|
return this.#connector.getCollection(name);
|
|
11847
12026
|
}
|
|
@@ -11849,6 +12028,124 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends WorkflowsSto
|
|
|
11849
12028
|
await this.createDefaultIndexes();
|
|
11850
12029
|
await this.createCustomIndexes();
|
|
11851
12030
|
}
|
|
12031
|
+
#runFenceCheck(fence, runId, operation) {
|
|
12032
|
+
const resolved = resolveRunFence(this, fence, runId);
|
|
12033
|
+
return resolved && {
|
|
12034
|
+
claims: TABLE_WORKFLOW_RUN_OWNERS,
|
|
12035
|
+
fence: resolved,
|
|
12036
|
+
operation
|
|
12037
|
+
};
|
|
12038
|
+
}
|
|
12039
|
+
async #readRunOwner(runId) {
|
|
12040
|
+
const doc = await (await getRunClaims(this.#connector, TABLE_WORKFLOW_RUN_OWNERS)).findOne({ _id: runId }, { projection: RUN_OWNER_PROJECTION });
|
|
12041
|
+
return doc ? toRunOwnershipRecord(doc) : null;
|
|
12042
|
+
}
|
|
12043
|
+
#ownershipError(operation, runId, error) {
|
|
12044
|
+
return new MastraError({
|
|
12045
|
+
id: createStorageErrorId("MONGODB", operation, "FAILED"),
|
|
12046
|
+
domain: ErrorDomain.STORAGE,
|
|
12047
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
12048
|
+
details: { runId }
|
|
12049
|
+
}, error);
|
|
12050
|
+
}
|
|
12051
|
+
async claimRunOwnership({ runId, ownerId, leaseMs, force, expectedGeneration }) {
|
|
12052
|
+
try {
|
|
12053
|
+
const owners = await getRunClaims(this.#connector, TABLE_WORKFLOW_RUN_OWNERS);
|
|
12054
|
+
const claim = (generation) => [{ $set: {
|
|
12055
|
+
generation,
|
|
12056
|
+
ownerId: { $literal: ownerId },
|
|
12057
|
+
leaseExpiresAt: { $add: ["$$NOW", leaseMs] },
|
|
12058
|
+
updatedAt: "$$NOW"
|
|
12059
|
+
} }];
|
|
12060
|
+
if (!expectedGeneration) {
|
|
12061
|
+
try {
|
|
12062
|
+
const created = await owners.findOneAndUpdate({
|
|
12063
|
+
_id: runId,
|
|
12064
|
+
generation: { $exists: false }
|
|
12065
|
+
}, claim({ $max: [1, { $toLong: "$$NOW" }] }), {
|
|
12066
|
+
upsert: true,
|
|
12067
|
+
returnDocument: "after",
|
|
12068
|
+
projection: RUN_OWNER_PROJECTION
|
|
12069
|
+
});
|
|
12070
|
+
if (created) return {
|
|
12071
|
+
acquired: true,
|
|
12072
|
+
record: toRunOwnershipRecord(created)
|
|
12073
|
+
};
|
|
12074
|
+
} catch (error) {
|
|
12075
|
+
if (!isDuplicateKeyError(error)) throw error;
|
|
12076
|
+
}
|
|
12077
|
+
if (expectedGeneration === 0) return {
|
|
12078
|
+
acquired: false,
|
|
12079
|
+
record: await this.#readRunOwner(runId)
|
|
12080
|
+
};
|
|
12081
|
+
}
|
|
12082
|
+
const advanced = await owners.findOneAndUpdate({
|
|
12083
|
+
_id: runId,
|
|
12084
|
+
...expectedGeneration !== void 0 ? { generation: expectedGeneration } : {},
|
|
12085
|
+
...force ? {} : NOT_LIVE
|
|
12086
|
+
}, claim({ $add: ["$generation", 1] }), {
|
|
12087
|
+
returnDocument: "after",
|
|
12088
|
+
projection: RUN_OWNER_PROJECTION
|
|
12089
|
+
});
|
|
12090
|
+
if (advanced) return {
|
|
12091
|
+
acquired: true,
|
|
12092
|
+
record: toRunOwnershipRecord(advanced)
|
|
12093
|
+
};
|
|
12094
|
+
return {
|
|
12095
|
+
acquired: false,
|
|
12096
|
+
record: await this.#readRunOwner(runId)
|
|
12097
|
+
};
|
|
12098
|
+
} catch (error) {
|
|
12099
|
+
throw this.#ownershipError("CLAIM_RUN_OWNERSHIP", runId, error);
|
|
12100
|
+
}
|
|
12101
|
+
}
|
|
12102
|
+
async renewRunOwnership({ leaseMs, ...fence }) {
|
|
12103
|
+
try {
|
|
12104
|
+
const renewed = await (await getRunClaims(this.#connector, TABLE_WORKFLOW_RUN_OWNERS)).findOneAndUpdate({
|
|
12105
|
+
_id: fence.runId,
|
|
12106
|
+
generation: fence.generation,
|
|
12107
|
+
ownerId: fence.ownerId,
|
|
12108
|
+
leaseExpiresAt: { $ne: null }
|
|
12109
|
+
}, [{ $set: {
|
|
12110
|
+
leaseExpiresAt: { $add: ["$$NOW", leaseMs] },
|
|
12111
|
+
updatedAt: "$$NOW"
|
|
12112
|
+
} }], {
|
|
12113
|
+
returnDocument: "after",
|
|
12114
|
+
projection: RUN_OWNER_PROJECTION
|
|
12115
|
+
});
|
|
12116
|
+
if (renewed) return {
|
|
12117
|
+
renewed: true,
|
|
12118
|
+
record: toRunOwnershipRecord(renewed)
|
|
12119
|
+
};
|
|
12120
|
+
return {
|
|
12121
|
+
renewed: false,
|
|
12122
|
+
record: await this.#readRunOwner(fence.runId)
|
|
12123
|
+
};
|
|
12124
|
+
} catch (error) {
|
|
12125
|
+
throw this.#ownershipError("RENEW_RUN_OWNERSHIP", fence.runId, error);
|
|
12126
|
+
}
|
|
12127
|
+
}
|
|
12128
|
+
async releaseRunOwnership(fence) {
|
|
12129
|
+
try {
|
|
12130
|
+
return (await (await getRunClaims(this.#connector, TABLE_WORKFLOW_RUN_OWNERS)).updateOne({
|
|
12131
|
+
_id: fence.runId,
|
|
12132
|
+
generation: fence.generation,
|
|
12133
|
+
ownerId: fence.ownerId
|
|
12134
|
+
}, [{ $set: {
|
|
12135
|
+
leaseExpiresAt: null,
|
|
12136
|
+
updatedAt: "$$NOW"
|
|
12137
|
+
} }])).matchedCount > 0;
|
|
12138
|
+
} catch (error) {
|
|
12139
|
+
throw this.#ownershipError("RELEASE_RUN_OWNERSHIP", fence.runId, error);
|
|
12140
|
+
}
|
|
12141
|
+
}
|
|
12142
|
+
async getRunOwnership({ runId }) {
|
|
12143
|
+
try {
|
|
12144
|
+
return await this.#readRunOwner(runId);
|
|
12145
|
+
} catch (error) {
|
|
12146
|
+
throw this.#ownershipError("GET_RUN_OWNERSHIP", runId, error);
|
|
12147
|
+
}
|
|
12148
|
+
}
|
|
11852
12149
|
/**
|
|
11853
12150
|
* Returns default index definitions for the workflows domain collections.
|
|
11854
12151
|
*/
|
|
@@ -11908,8 +12205,10 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends WorkflowsSto
|
|
|
11908
12205
|
}
|
|
11909
12206
|
async dangerouslyClearAll() {
|
|
11910
12207
|
await (await this.getCollection(TABLE_WORKFLOW_SNAPSHOT)).deleteMany({});
|
|
12208
|
+
await (await this.getCollection(TABLE_WORKFLOW_RUN_OWNERS)).deleteMany({});
|
|
11911
12209
|
}
|
|
11912
|
-
async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state }) {
|
|
12210
|
+
async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state, fence }) {
|
|
12211
|
+
const check = this.#runFenceCheck(fence, runId, "updateWorkflowResults");
|
|
11913
12212
|
try {
|
|
11914
12213
|
const collection = await this.getCollection(TABLE_WORKFLOW_SNAPSHOT);
|
|
11915
12214
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -11929,7 +12228,7 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends WorkflowsSto
|
|
|
11929
12228
|
runId,
|
|
11930
12229
|
requestContext: {}
|
|
11931
12230
|
};
|
|
11932
|
-
const updatedDoc = await collection.findOneAndUpdate({
|
|
12231
|
+
const updatedDoc = await withRunFence(this.#connector, check, (session) => collection.findOneAndUpdate({
|
|
11933
12232
|
workflow_name: workflowName,
|
|
11934
12233
|
run_id: runId
|
|
11935
12234
|
}, [{ $set: {
|
|
@@ -11944,10 +12243,12 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends WorkflowsSto
|
|
|
11944
12243
|
createdAt: { $ifNull: ["$createdAt", now] }
|
|
11945
12244
|
} }], {
|
|
11946
12245
|
upsert: true,
|
|
11947
|
-
returnDocument: "after"
|
|
11948
|
-
|
|
12246
|
+
returnDocument: "after",
|
|
12247
|
+
session
|
|
12248
|
+
}));
|
|
11949
12249
|
return (typeof updatedDoc?.snapshot === "string" ? JSON.parse(updatedDoc.snapshot) : updatedDoc?.snapshot)?.context || {};
|
|
11950
12250
|
} catch (error) {
|
|
12251
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
11951
12252
|
throw new MastraError({
|
|
11952
12253
|
id: createStorageErrorId("MONGODB", "UPDATE_WORKFLOW_RESULTS", "FAILED"),
|
|
11953
12254
|
domain: ErrorDomain.STORAGE,
|
|
@@ -11960,7 +12261,8 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends WorkflowsSto
|
|
|
11960
12261
|
}, error);
|
|
11961
12262
|
}
|
|
11962
12263
|
}
|
|
11963
|
-
async updateWorkflowState({ workflowName, runId, opts }) {
|
|
12264
|
+
async updateWorkflowState({ workflowName, runId, opts, fence }) {
|
|
12265
|
+
const check = this.#runFenceCheck(fence, runId, "updateWorkflowState");
|
|
11964
12266
|
try {
|
|
11965
12267
|
const collection = await this.getCollection(TABLE_WORKFLOW_SNAPSHOT);
|
|
11966
12268
|
const { expectedStatus, ...state } = opts;
|
|
@@ -11969,10 +12271,13 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends WorkflowsSto
|
|
|
11969
12271
|
run_id: runId
|
|
11970
12272
|
};
|
|
11971
12273
|
if (expectedStatus !== void 0) filter["snapshot.status"] = { $in: Array.isArray(expectedStatus) ? expectedStatus : [expectedStatus] };
|
|
11972
|
-
const updatedDoc = await collection.findOneAndUpdate(filter, [{ $set: {
|
|
12274
|
+
const updatedDoc = await withRunFence(this.#connector, check, (session) => collection.findOneAndUpdate(filter, [{ $set: {
|
|
11973
12275
|
snapshot: { $mergeObjects: ["$snapshot", state] },
|
|
11974
12276
|
updatedAt: /* @__PURE__ */ new Date()
|
|
11975
|
-
} }], {
|
|
12277
|
+
} }], {
|
|
12278
|
+
returnDocument: "after",
|
|
12279
|
+
session
|
|
12280
|
+
}));
|
|
11976
12281
|
if (!updatedDoc) return;
|
|
11977
12282
|
const snapshot = typeof updatedDoc.snapshot === "string" ? JSON.parse(updatedDoc.snapshot) : updatedDoc.snapshot;
|
|
11978
12283
|
if (!snapshot?.context) throw new MastraError({
|
|
@@ -11987,7 +12292,7 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends WorkflowsSto
|
|
|
11987
12292
|
});
|
|
11988
12293
|
return snapshot;
|
|
11989
12294
|
} catch (error) {
|
|
11990
|
-
if (error instanceof MastraError) throw error;
|
|
12295
|
+
if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
|
|
11991
12296
|
throw new MastraError({
|
|
11992
12297
|
id: createStorageErrorId("MONGODB", "UPDATE_WORKFLOW_STATE", "FAILED"),
|
|
11993
12298
|
domain: ErrorDomain.STORAGE,
|
|
@@ -11999,10 +12304,12 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends WorkflowsSto
|
|
|
11999
12304
|
}, error);
|
|
12000
12305
|
}
|
|
12001
12306
|
}
|
|
12002
|
-
async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt }) {
|
|
12307
|
+
async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt, fence }) {
|
|
12308
|
+
const check = this.#runFenceCheck(fence, runId, "persistWorkflowSnapshot");
|
|
12003
12309
|
try {
|
|
12004
12310
|
const now = /* @__PURE__ */ new Date();
|
|
12005
|
-
|
|
12311
|
+
const collection = await this.getCollection(TABLE_WORKFLOW_SNAPSHOT);
|
|
12312
|
+
await withRunFence(this.#connector, check, (session) => collection.updateOne({
|
|
12006
12313
|
workflow_name: workflowName,
|
|
12007
12314
|
run_id: runId
|
|
12008
12315
|
}, {
|
|
@@ -12014,8 +12321,12 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends WorkflowsSto
|
|
|
12014
12321
|
updatedAt: updatedAt ?? now
|
|
12015
12322
|
},
|
|
12016
12323
|
$setOnInsert: { createdAt: createdAt ?? now }
|
|
12017
|
-
}, {
|
|
12324
|
+
}, {
|
|
12325
|
+
upsert: true,
|
|
12326
|
+
session
|
|
12327
|
+
}));
|
|
12018
12328
|
} catch (error) {
|
|
12329
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
12019
12330
|
throw new MastraError({
|
|
12020
12331
|
id: createStorageErrorId("MONGODB", "PERSIST_WORKFLOW_SNAPSHOT", "FAILED"),
|
|
12021
12332
|
domain: ErrorDomain.STORAGE,
|
|
@@ -12108,13 +12419,16 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends WorkflowsSto
|
|
|
12108
12419
|
}, error);
|
|
12109
12420
|
}
|
|
12110
12421
|
}
|
|
12111
|
-
async deleteWorkflowRunById({ runId, workflowName }) {
|
|
12422
|
+
async deleteWorkflowRunById({ runId, workflowName, fence }) {
|
|
12423
|
+
const check = this.#runFenceCheck(fence, runId, "deleteWorkflowRunById");
|
|
12112
12424
|
try {
|
|
12113
|
-
|
|
12425
|
+
const collection = await this.getCollection(TABLE_WORKFLOW_SNAPSHOT);
|
|
12426
|
+
await withRunFence(this.#connector, check, (session) => collection.deleteOne({
|
|
12114
12427
|
workflow_name: workflowName,
|
|
12115
12428
|
run_id: runId
|
|
12116
|
-
});
|
|
12429
|
+
}, { session }));
|
|
12117
12430
|
} catch (error) {
|
|
12431
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
12118
12432
|
throw new MastraError({
|
|
12119
12433
|
id: createStorageErrorId("MONGODB", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"),
|
|
12120
12434
|
domain: ErrorDomain.STORAGE,
|