@mastra/mongodb 1.23.0 → 1.23.1-alpha.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/_types/@internal_core/dist/memory/index.d.ts +102 -0
- package/dist/_types/@internal_core/package.json +1 -0
- 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 +1033 -393
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1034 -394
- 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 +31 -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 +6 -5
package/dist/index.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
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";
|
|
7
|
+
import { createHash } from "crypto";
|
|
7
8
|
import { MessageList } from "@mastra/core/agent";
|
|
8
9
|
import { saveScorePayloadSchema } from "@mastra/core/evals";
|
|
9
10
|
import { skillSnapshotFieldValuesEqual } from "@mastra/core/storage/domains/skills";
|
|
10
11
|
//#region package.json
|
|
11
|
-
var version = "1.23.
|
|
12
|
+
var version = "1.23.1-alpha.1";
|
|
12
13
|
//#endregion
|
|
13
14
|
//#region src/vector/filter.ts
|
|
14
15
|
/**
|
|
@@ -1617,20 +1618,29 @@ var MongoDBConnector = class MongoDBConnector {
|
|
|
1617
1618
|
return (await this.getConnection()).listCollections({ name: collectionName }).hasNext();
|
|
1618
1619
|
}
|
|
1619
1620
|
/**
|
|
1620
|
-
*
|
|
1621
|
-
*
|
|
1622
|
-
*
|
|
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.
|
|
1623
1625
|
*/
|
|
1624
|
-
async
|
|
1626
|
+
async probeTransactions() {
|
|
1625
1627
|
if (this.#supportsTransactions !== void 0) return this.#supportsTransactions;
|
|
1626
1628
|
if (!this.#client) {
|
|
1627
1629
|
this.#supportsTransactions = false;
|
|
1628
1630
|
return false;
|
|
1629
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() {
|
|
1630
1642
|
try {
|
|
1631
|
-
|
|
1632
|
-
this.#supportsTransactions = Boolean(hello.setName) || hello.msg === "isdbgrid";
|
|
1633
|
-
return this.#supportsTransactions;
|
|
1643
|
+
return await this.probeTransactions();
|
|
1634
1644
|
} catch {
|
|
1635
1645
|
return false;
|
|
1636
1646
|
}
|
|
@@ -2277,14 +2287,19 @@ function cutoffFor(policy, encoding, now = Date.now()) {
|
|
|
2277
2287
|
* Delete up to `limit` documents whose anchor field is older than `cutoff`.
|
|
2278
2288
|
*
|
|
2279
2289
|
* `deleteMany` has no limit clause, so each batch first collects `_id`s with a
|
|
2280
|
-
* bounded `find`, then deletes
|
|
2281
|
-
*
|
|
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.
|
|
2282
2294
|
*/
|
|
2283
2295
|
async function pruneCollectionBatch({ connector, table, column, cutoff, limit }) {
|
|
2284
2296
|
const collection = await connector.getCollection(table);
|
|
2285
2297
|
const docs = await collection.find({ [column]: { $lt: cutoff } }).project({ _id: 1 }).limit(limit).toArray();
|
|
2286
2298
|
if (docs.length === 0) return 0;
|
|
2287
|
-
return (await collection.deleteMany({
|
|
2299
|
+
return (await collection.deleteMany({
|
|
2300
|
+
_id: { $in: docs.map((doc) => doc._id) },
|
|
2301
|
+
[column]: { $lt: cutoff }
|
|
2302
|
+
})).deletedCount;
|
|
2288
2303
|
}
|
|
2289
2304
|
/**
|
|
2290
2305
|
* Run the bounded/cancellable batched-delete loop for a single logical target,
|
|
@@ -6494,6 +6509,105 @@ var MongoDBMCPServersStorage = class MongoDBMCPServersStorage extends MCPServers
|
|
|
6494
6509
|
}
|
|
6495
6510
|
};
|
|
6496
6511
|
//#endregion
|
|
6512
|
+
//#region ../../packages/_internal-core/dist/memory/index.js
|
|
6513
|
+
/**
|
|
6514
|
+
* A buffered chunk stores `lastObservedAt = max message time + 1ms`. It is wholly covered
|
|
6515
|
+
* (every message it observed is already at or before the cursor) iff
|
|
6516
|
+
* `cursor >= chunk.lastObservedAt - 1ms`.
|
|
6517
|
+
*/
|
|
6518
|
+
function isBufferedChunkCoveredByCursor(chunkLastObservedAt, cursor) {
|
|
6519
|
+
if (!cursor) return false;
|
|
6520
|
+
return new Date(cursor).getTime() >= new Date(chunkLastObservedAt).getTime() - 1;
|
|
6521
|
+
}
|
|
6522
|
+
/** The later of two cursors; `undefined` only when both are absent. */
|
|
6523
|
+
function maxObservationCursor(a, b) {
|
|
6524
|
+
if (!a) return b ? new Date(b) : void 0;
|
|
6525
|
+
if (!b) return new Date(a);
|
|
6526
|
+
return new Date(Math.max(new Date(a).getTime(), new Date(b).getTime()));
|
|
6527
|
+
}
|
|
6528
|
+
/**
|
|
6529
|
+
* Decide the new generation's text when a reflection built from `snapshot` commits against
|
|
6530
|
+
* the stored record.
|
|
6531
|
+
*
|
|
6532
|
+
* - Stored text equals the snapshot: the reflection replaces it.
|
|
6533
|
+
* - Stored text extends the snapshot (activation only appends): the appended tail is kept
|
|
6534
|
+
* after the reflection, and its tokens are added.
|
|
6535
|
+
* - Anything else (a non-append rewrite): `null` — the reflection must not be applied.
|
|
6536
|
+
*/
|
|
6537
|
+
function planReflectionGenerationText(input) {
|
|
6538
|
+
const stored = input.storedObservations ?? "";
|
|
6539
|
+
const snapshot = input.snapshotObservations ?? "";
|
|
6540
|
+
if (stored === snapshot) return {
|
|
6541
|
+
mode: "equal",
|
|
6542
|
+
observations: input.reflection,
|
|
6543
|
+
tokenCount: input.tokenCount
|
|
6544
|
+
};
|
|
6545
|
+
if (!stored.startsWith(snapshot)) return null;
|
|
6546
|
+
const tail = stored.slice(snapshot.length).trimStart();
|
|
6547
|
+
if (!tail.trim()) return {
|
|
6548
|
+
mode: "equal",
|
|
6549
|
+
observations: input.reflection,
|
|
6550
|
+
tokenCount: input.tokenCount
|
|
6551
|
+
};
|
|
6552
|
+
return {
|
|
6553
|
+
mode: "append",
|
|
6554
|
+
observations: input.reflection ? `${input.reflection}\n\n${tail}` : tail,
|
|
6555
|
+
tokenCount: input.tokenCount + Math.max(0, (input.storedObservationTokenCount ?? 0) - (input.snapshotObservationTokenCount ?? 0))
|
|
6556
|
+
};
|
|
6557
|
+
}
|
|
6558
|
+
/**
|
|
6559
|
+
* Backs `updateActiveObservations` in adapters that implement `commitActiveObservations`.
|
|
6560
|
+
* Callers of `updateActiveObservations` (which resolves `void`) can't see a commit result, so a
|
|
6561
|
+
* write that was not applied throws instead of resolving as if it had committed.
|
|
6562
|
+
*/
|
|
6563
|
+
function assertActiveObservationsApplied(result, id) {
|
|
6564
|
+
if (result.applied) return;
|
|
6565
|
+
throw new Error(result.reason === "retired" ? `Observational memory record ${id} was superseded by a newer generation; observations were not written` : `Observational memory record ${id} changed since the observations were composed; observations were not written`);
|
|
6566
|
+
}
|
|
6567
|
+
/**
|
|
6568
|
+
* Whether the stored text can still accept a reflection built from `snapshot`
|
|
6569
|
+
* (equal, or only appended to since).
|
|
6570
|
+
*/
|
|
6571
|
+
function isAppendOnlySince(storedObservations, snapshotObservations) {
|
|
6572
|
+
const stored = storedObservations ?? "";
|
|
6573
|
+
const snapshot = snapshotObservations ?? "";
|
|
6574
|
+
return stored === snapshot || stored.startsWith(snapshot);
|
|
6575
|
+
}
|
|
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
|
|
6497
6611
|
//#region src/storage/domains/memory/index.ts
|
|
6498
6612
|
/**
|
|
6499
6613
|
* Local constant for the observational memory table name.
|
|
@@ -6501,6 +6615,95 @@ var MongoDBMCPServersStorage = class MongoDBMCPServersStorage extends MCPServers
|
|
|
6501
6615
|
* versions that don't export TABLE_OBSERVATIONAL_MEMORY.
|
|
6502
6616
|
*/
|
|
6503
6617
|
const OM_TABLE = "mastra_observational_memory";
|
|
6618
|
+
/** Canonical head order: newest generation, then earliest createdAt, then lowest id. */
|
|
6619
|
+
const OM_HEAD_SORT = {
|
|
6620
|
+
generationCount: -1,
|
|
6621
|
+
createdAt: 1,
|
|
6622
|
+
id: 1
|
|
6623
|
+
};
|
|
6624
|
+
/** Max redirects when a write aimed at a retired record follows the head. */
|
|
6625
|
+
const OM_MAX_HEAD_HOPS = 3;
|
|
6626
|
+
/** Max read-decide-conditional-update rounds before giving up on a contended record. */
|
|
6627
|
+
const OM_MAX_CONDITIONAL_ATTEMPTS = 3;
|
|
6628
|
+
/** Document `_id` of a key's generation-0 record; see `initializeObservationalMemory`. */
|
|
6629
|
+
function getGeneration0DocumentId(lookupKey) {
|
|
6630
|
+
return `om0_${createHash("sha256").update(lookupKey).digest("hex").slice(0, 32)}`;
|
|
6631
|
+
}
|
|
6632
|
+
/**
|
|
6633
|
+
* The successor document of a fenced (frozen) document: a pure function of the old document's
|
|
6634
|
+
* final state and the rollover inputs, so concurrent roll-forwards insert identical documents.
|
|
6635
|
+
* Buffered chunks, the cursor, buffering markers, flags, and counters carry over; buffered
|
|
6636
|
+
* reflection state does not.
|
|
6637
|
+
*/
|
|
6638
|
+
function buildSuccessorDocument(old, pending) {
|
|
6639
|
+
let activeObservations = pending.reflection;
|
|
6640
|
+
let observationTokenCount = pending.tokenCount;
|
|
6641
|
+
if (pending.mode === "append") {
|
|
6642
|
+
const tail = String(old.activeObservations ?? "").slice(pending.snapTextLength).trimStart();
|
|
6643
|
+
activeObservations = pending.reflection ? `${pending.reflection}\n\n${tail}` : tail;
|
|
6644
|
+
observationTokenCount = pending.tokenCount + Math.max(0, Number(old.observationTokenCount || 0) - pending.snapObservationTokenCount);
|
|
6645
|
+
}
|
|
6646
|
+
return {
|
|
6647
|
+
id: pending.newId,
|
|
6648
|
+
lookupKey: old.lookupKey,
|
|
6649
|
+
scope: old.scope,
|
|
6650
|
+
resourceId: old.resourceId,
|
|
6651
|
+
threadId: old.threadId ?? null,
|
|
6652
|
+
activeObservations,
|
|
6653
|
+
activeObservationsPendingUpdate: null,
|
|
6654
|
+
originType: "reflection",
|
|
6655
|
+
config: old.config ?? null,
|
|
6656
|
+
generationCount: Number(old.generationCount || 0) + 1,
|
|
6657
|
+
lastObservedAt: old.lastObservedAt ?? null,
|
|
6658
|
+
lastReflectionAt: pending.createdAt,
|
|
6659
|
+
pendingMessageTokens: old.pendingMessageTokens ?? 0,
|
|
6660
|
+
totalTokensObserved: old.totalTokensObserved ?? 0,
|
|
6661
|
+
observationTokenCount,
|
|
6662
|
+
bufferedObservationChunks: Array.isArray(old.bufferedObservationChunks) ? old.bufferedObservationChunks : [],
|
|
6663
|
+
isObserving: false,
|
|
6664
|
+
isReflecting: false,
|
|
6665
|
+
isBufferingObservation: Boolean(old.isBufferingObservation),
|
|
6666
|
+
isBufferingReflection: false,
|
|
6667
|
+
lastBufferedAtTokens: old.lastBufferedAtTokens ?? 0,
|
|
6668
|
+
lastBufferedAtTime: old.lastBufferedAtTime ?? null,
|
|
6669
|
+
observedTimezone: old.observedTimezone ?? null,
|
|
6670
|
+
metadata: old.metadata ?? null,
|
|
6671
|
+
supersededBy: null,
|
|
6672
|
+
createdAt: pending.createdAt,
|
|
6673
|
+
updatedAt: pending.createdAt
|
|
6674
|
+
};
|
|
6675
|
+
}
|
|
6676
|
+
/** Equality filters for `fields` as stored in `doc` (`$exists: false` for absent fields). */
|
|
6677
|
+
function matchStoredFields(doc, fields) {
|
|
6678
|
+
return Object.fromEntries(fields.map((f) => [f, doc[f] === void 0 ? { $exists: false } : doc[f]]));
|
|
6679
|
+
}
|
|
6680
|
+
function omNotFound(operation, id) {
|
|
6681
|
+
return new MastraError({
|
|
6682
|
+
id: createStorageErrorId("MONGODB", operation, "NOT_FOUND"),
|
|
6683
|
+
text: `Observational memory record not found: ${id}`,
|
|
6684
|
+
domain: ErrorDomain.STORAGE,
|
|
6685
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
6686
|
+
details: { id }
|
|
6687
|
+
});
|
|
6688
|
+
}
|
|
6689
|
+
function omConflict(operation, id) {
|
|
6690
|
+
return new MastraError({
|
|
6691
|
+
id: createStorageErrorId("MONGODB", operation, "CONFLICT"),
|
|
6692
|
+
text: `Observational memory record ${id} kept changing during ${operation}; giving up after ${OM_MAX_CONDITIONAL_ATTEMPTS} attempts`,
|
|
6693
|
+
domain: ErrorDomain.STORAGE,
|
|
6694
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
6695
|
+
details: { id }
|
|
6696
|
+
});
|
|
6697
|
+
}
|
|
6698
|
+
function omNoLiveHead(id) {
|
|
6699
|
+
return new MastraError({
|
|
6700
|
+
id: createStorageErrorId("MONGODB", "RESOLVE_OBSERVATIONAL_MEMORY_HEAD", "FAILED"),
|
|
6701
|
+
text: `Observational memory record ${id} is superseded but no live head was found`,
|
|
6702
|
+
domain: ErrorDomain.STORAGE,
|
|
6703
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
6704
|
+
details: { id }
|
|
6705
|
+
});
|
|
6706
|
+
}
|
|
6504
6707
|
var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
6505
6708
|
supportsPartialThreadUpdate = true;
|
|
6506
6709
|
supportsObservationalMemory = true;
|
|
@@ -6513,11 +6716,14 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
6513
6716
|
TABLE_THREADS,
|
|
6514
6717
|
TABLE_MESSAGES,
|
|
6515
6718
|
TABLE_RESOURCES,
|
|
6516
|
-
OM_TABLE
|
|
6719
|
+
OM_TABLE,
|
|
6720
|
+
TABLE_MEMORY_RUN_FENCES
|
|
6517
6721
|
];
|
|
6518
6722
|
/**
|
|
6519
6723
|
* Retention-eligible collections. The observational-memory collection is
|
|
6520
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.
|
|
6521
6727
|
*/
|
|
6522
6728
|
static retentionTables = {
|
|
6523
6729
|
messages: {
|
|
@@ -6534,6 +6740,11 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
6534
6740
|
table: TABLE_THREADS,
|
|
6535
6741
|
column: "createdAt",
|
|
6536
6742
|
indexed: true
|
|
6743
|
+
},
|
|
6744
|
+
runFences: {
|
|
6745
|
+
table: TABLE_MEMORY_RUN_FENCES,
|
|
6746
|
+
column: "retiredAt",
|
|
6747
|
+
indexed: true
|
|
6537
6748
|
}
|
|
6538
6749
|
};
|
|
6539
6750
|
constructor(config) {
|
|
@@ -6548,13 +6759,80 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
6548
6759
|
async init() {
|
|
6549
6760
|
await this.createDefaultIndexes();
|
|
6550
6761
|
await this.createCustomIndexes();
|
|
6762
|
+
await this.#warnIfOMIdIndexMissing();
|
|
6763
|
+
await this.#maintainSupersededBy();
|
|
6764
|
+
}
|
|
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
|
+
};
|
|
6551
6828
|
}
|
|
6552
6829
|
/**
|
|
6553
6830
|
* Delete memory rows older than each table's `maxAge`, batched. Order is
|
|
6554
6831
|
* messages → resources → threads so child rows never outlive the delete of
|
|
6555
|
-
* their thread
|
|
6556
|
-
*
|
|
6557
|
-
*
|
|
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.
|
|
6558
6836
|
*/
|
|
6559
6837
|
async prune(policies, options) {
|
|
6560
6838
|
const targets = resolveTargets({
|
|
@@ -6563,7 +6841,8 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
6563
6841
|
order: [
|
|
6564
6842
|
"messages",
|
|
6565
6843
|
"resources",
|
|
6566
|
-
"threads"
|
|
6844
|
+
"threads",
|
|
6845
|
+
"runFences"
|
|
6567
6846
|
]
|
|
6568
6847
|
});
|
|
6569
6848
|
return runPrune({
|
|
@@ -6669,17 +6948,19 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
6669
6948
|
}
|
|
6670
6949
|
}
|
|
6671
6950
|
async dangerouslyClearAll() {
|
|
6672
|
-
const [threadsCollection, messagesCollection, resourcesCollection, omCollection] = await Promise.all([
|
|
6951
|
+
const [threadsCollection, messagesCollection, resourcesCollection, omCollection, fencesCollection] = await Promise.all([
|
|
6673
6952
|
this.getCollection(TABLE_THREADS),
|
|
6674
6953
|
this.getCollection(TABLE_MESSAGES),
|
|
6675
6954
|
this.getCollection(TABLE_RESOURCES),
|
|
6676
|
-
this.getCollection(OM_TABLE)
|
|
6955
|
+
this.getCollection(OM_TABLE),
|
|
6956
|
+
this.getCollection(TABLE_MEMORY_RUN_FENCES)
|
|
6677
6957
|
]);
|
|
6678
6958
|
await Promise.all([
|
|
6679
6959
|
threadsCollection.deleteMany({}),
|
|
6680
6960
|
messagesCollection.deleteMany({}),
|
|
6681
6961
|
resourcesCollection.deleteMany({}),
|
|
6682
|
-
omCollection.deleteMany({})
|
|
6962
|
+
omCollection.deleteMany({}),
|
|
6963
|
+
fencesCollection.deleteMany({})
|
|
6683
6964
|
]);
|
|
6684
6965
|
}
|
|
6685
6966
|
parseRow(row) {
|
|
@@ -7035,7 +7316,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7035
7316
|
throw mastraError;
|
|
7036
7317
|
}
|
|
7037
7318
|
}
|
|
7038
|
-
async saveMessages({ messages }) {
|
|
7319
|
+
async saveMessages({ messages, fence }) {
|
|
7039
7320
|
if (messages.length === 0) return { messages: [] };
|
|
7040
7321
|
try {
|
|
7041
7322
|
if (!messages[0]?.threadId) throw new Error("Thread ID is required");
|
|
@@ -7063,12 +7344,16 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7063
7344
|
});
|
|
7064
7345
|
const allThreadIds = new Set(messages.map((m) => m.threadId));
|
|
7065
7346
|
const now = /* @__PURE__ */ new Date();
|
|
7066
|
-
|
|
7347
|
+
const writeMessages = async (session) => {
|
|
7067
7348
|
await collection.bulkWrite(messagesToInsert, { session });
|
|
7068
7349
|
for (const tid of allThreadIds) await threadsCollection.updateOne({ id: tid }, { $set: { updatedAt: now } }, { session });
|
|
7069
|
-
}
|
|
7350
|
+
};
|
|
7351
|
+
const check = this.#runFenceCheck(fence, "saveMessages");
|
|
7352
|
+
if (check) await withRunFence(this.#connector, check, writeMessages);
|
|
7353
|
+
else await this.#connector.withTransaction(writeMessages);
|
|
7070
7354
|
return { messages: new MessageList().add(messages, "memory").get.all.db() };
|
|
7071
7355
|
} catch (error) {
|
|
7356
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
7072
7357
|
throw new MastraError({
|
|
7073
7358
|
id: createStorageErrorId("MONGODB", "SAVE_MESSAGES", "FAILED"),
|
|
7074
7359
|
domain: ErrorDomain.STORAGE,
|
|
@@ -7076,49 +7361,51 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7076
7361
|
}, error);
|
|
7077
7362
|
}
|
|
7078
7363
|
}
|
|
7079
|
-
async updateMessages({ messages }) {
|
|
7364
|
+
async updateMessages({ messages, fence }) {
|
|
7080
7365
|
if (messages.length === 0) return [];
|
|
7081
7366
|
const messageIds = messages.map((m) => m.id);
|
|
7082
7367
|
const collection = await this.getCollection(TABLE_MESSAGES);
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
|
|
7088
|
-
const
|
|
7089
|
-
|
|
7090
|
-
|
|
7091
|
-
|
|
7092
|
-
|
|
7093
|
-
|
|
7094
|
-
|
|
7095
|
-
|
|
7096
|
-
|
|
7097
|
-
|
|
7098
|
-
|
|
7099
|
-
|
|
7100
|
-
|
|
7101
|
-
...existingMessage.content.metadata
|
|
7102
|
-
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
const
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
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
|
+
} });
|
|
7113
7404
|
}
|
|
7114
|
-
if (
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
|
|
7118
|
-
}
|
|
7119
|
-
if (bulkOps.length > 0) await collection.bulkWrite(bulkOps);
|
|
7120
|
-
if (threadIdsToUpdate.size > 0) await (await this.getCollection(TABLE_THREADS)).updateMany({ id: { $in: Array.from(threadIdsToUpdate) } }, { $set: { updatedAt: /* @__PURE__ */ new Date() } });
|
|
7121
|
-
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
|
+
});
|
|
7122
7409
|
}
|
|
7123
7410
|
async getResourceById({ resourceId }) {
|
|
7124
7411
|
try {
|
|
@@ -7153,9 +7440,11 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7153
7440
|
}, error);
|
|
7154
7441
|
}
|
|
7155
7442
|
}
|
|
7156
|
-
async updateResource({ resourceId, workingMemory, metadata }) {
|
|
7443
|
+
async updateResource({ resourceId, workingMemory, metadata, fence }) {
|
|
7157
7444
|
try {
|
|
7445
|
+
const check = this.#runFenceCheck(fence, "updateResource");
|
|
7158
7446
|
const existingResource = await this.getResourceById({ resourceId });
|
|
7447
|
+
const collection = await this.getCollection(TABLE_RESOURCES);
|
|
7159
7448
|
if (!existingResource) {
|
|
7160
7449
|
const newResource = {
|
|
7161
7450
|
id: resourceId,
|
|
@@ -7164,7 +7453,11 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7164
7453
|
createdAt: /* @__PURE__ */ new Date(),
|
|
7165
7454
|
updatedAt: /* @__PURE__ */ new Date()
|
|
7166
7455
|
};
|
|
7167
|
-
|
|
7456
|
+
await withRunFence(this.#connector, check, (session) => collection.updateOne({ id: resourceId }, { $set: { ...newResource } }, {
|
|
7457
|
+
upsert: true,
|
|
7458
|
+
session
|
|
7459
|
+
}));
|
|
7460
|
+
return newResource;
|
|
7168
7461
|
}
|
|
7169
7462
|
const updatedResource = {
|
|
7170
7463
|
...existingResource,
|
|
@@ -7175,13 +7468,13 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7175
7468
|
} : existingResource.metadata,
|
|
7176
7469
|
updatedAt: /* @__PURE__ */ new Date()
|
|
7177
7470
|
};
|
|
7178
|
-
const collection = await this.getCollection(TABLE_RESOURCES);
|
|
7179
7471
|
const updateDoc = { updatedAt: updatedResource.updatedAt };
|
|
7180
7472
|
if (workingMemory !== void 0) updateDoc.workingMemory = workingMemory;
|
|
7181
7473
|
if (metadata) updateDoc.metadata = updatedResource.metadata;
|
|
7182
|
-
await collection.updateOne({ id: resourceId }, { $set: updateDoc });
|
|
7474
|
+
await withRunFence(this.#connector, check, (session) => collection.updateOne({ id: resourceId }, { $set: updateDoc }, { session }));
|
|
7183
7475
|
return updatedResource;
|
|
7184
7476
|
} catch (error) {
|
|
7477
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
7185
7478
|
throw new MastraError({
|
|
7186
7479
|
id: createStorageErrorId("MONGODB", "UPDATE_RESOURCE", "FAILED"),
|
|
7187
7480
|
domain: ErrorDomain.STORAGE,
|
|
@@ -7281,14 +7574,19 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7281
7574
|
throw mastraError;
|
|
7282
7575
|
}
|
|
7283
7576
|
}
|
|
7284
|
-
async saveThread({ thread }) {
|
|
7577
|
+
async saveThread({ thread, fence }) {
|
|
7285
7578
|
try {
|
|
7286
|
-
|
|
7579
|
+
const collection = await this.getCollection(TABLE_THREADS);
|
|
7580
|
+
await withRunFence(this.#connector, this.#runFenceCheck(fence, "saveThread"), (session) => collection.updateOne({ id: thread.id }, { $set: {
|
|
7287
7581
|
...thread,
|
|
7288
7582
|
metadata: thread.metadata
|
|
7289
|
-
} }, {
|
|
7583
|
+
} }, {
|
|
7584
|
+
upsert: true,
|
|
7585
|
+
session
|
|
7586
|
+
}));
|
|
7290
7587
|
return thread;
|
|
7291
7588
|
} catch (error) {
|
|
7589
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
7292
7590
|
throw new MastraError({
|
|
7293
7591
|
id: createStorageErrorId("MONGODB", "SAVE_THREAD", "FAILED"),
|
|
7294
7592
|
domain: ErrorDomain.STORAGE,
|
|
@@ -7297,7 +7595,8 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7297
7595
|
}, error);
|
|
7298
7596
|
}
|
|
7299
7597
|
}
|
|
7300
|
-
async updateThread({ id, title, metadata }) {
|
|
7598
|
+
async updateThread({ id, title, metadata, fence }) {
|
|
7599
|
+
const check = this.#runFenceCheck(fence, "updateThread");
|
|
7301
7600
|
const thread = await this.getThreadById({ threadId: id });
|
|
7302
7601
|
if (!thread) throw new MastraError({
|
|
7303
7602
|
id: createStorageErrorId("MONGODB", "UPDATE_THREAD", "NOT_FOUND"),
|
|
@@ -7320,12 +7619,14 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7320
7619
|
updatedAt: now
|
|
7321
7620
|
};
|
|
7322
7621
|
try {
|
|
7323
|
-
|
|
7622
|
+
const collection = await this.getCollection(TABLE_THREADS);
|
|
7623
|
+
await withRunFence(this.#connector, check, (session) => collection.updateOne({ id }, { $set: {
|
|
7324
7624
|
title: updatedThread.title,
|
|
7325
7625
|
metadata: updatedThread.metadata,
|
|
7326
7626
|
updatedAt: now
|
|
7327
|
-
} });
|
|
7627
|
+
} }, { session }));
|
|
7328
7628
|
} catch (error) {
|
|
7629
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
7329
7630
|
throw new MastraError({
|
|
7330
7631
|
id: createStorageErrorId("MONGODB", "UPDATE_THREAD", "FAILED"),
|
|
7331
7632
|
domain: ErrorDomain.STORAGE,
|
|
@@ -7348,16 +7649,19 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7348
7649
|
}, error);
|
|
7349
7650
|
}
|
|
7350
7651
|
}
|
|
7351
|
-
async deleteMessages(messageIds) {
|
|
7652
|
+
async deleteMessages(messageIds, options) {
|
|
7352
7653
|
if (messageIds.length === 0) return;
|
|
7353
7654
|
try {
|
|
7354
7655
|
const messagesCollection = await this.getCollection(TABLE_MESSAGES);
|
|
7355
7656
|
const threadsCollection = await this.getCollection(TABLE_THREADS);
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
|
|
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
|
+
});
|
|
7360
7663
|
} catch (error) {
|
|
7664
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
7361
7665
|
throw new MastraError({
|
|
7362
7666
|
id: createStorageErrorId("MONGODB", "DELETE_MESSAGES", "FAILED"),
|
|
7363
7667
|
domain: ErrorDomain.STORAGE,
|
|
@@ -7525,15 +7829,15 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7525
7829
|
config: doc.config || {},
|
|
7526
7830
|
metadata: doc.metadata || void 0,
|
|
7527
7831
|
observedMessageIds: doc.observedMessageIds || void 0,
|
|
7528
|
-
observedTimezone: doc.observedTimezone || void 0
|
|
7832
|
+
observedTimezone: doc.observedTimezone || void 0,
|
|
7833
|
+
supersededBy: doc.supersededBy ?? null
|
|
7529
7834
|
};
|
|
7530
7835
|
}
|
|
7531
7836
|
async getObservationalMemory(threadId, resourceId) {
|
|
7532
7837
|
try {
|
|
7533
|
-
const
|
|
7534
|
-
const doc = await (
|
|
7535
|
-
|
|
7536
|
-
return this.parseOMDocument(doc);
|
|
7838
|
+
const collection = await this.getCollection(OM_TABLE);
|
|
7839
|
+
const doc = await this.#getHeadDoc(collection, this.getOMKey(threadId, resourceId));
|
|
7840
|
+
return doc ? this.parseOMDocument(doc) : null;
|
|
7537
7841
|
} catch (error) {
|
|
7538
7842
|
throw new MastraError({
|
|
7539
7843
|
id: createStorageErrorId("MONGODB", "GET_OBSERVATIONAL_MEMORY", "FAILED"),
|
|
@@ -7596,60 +7900,55 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7596
7900
|
}
|
|
7597
7901
|
async initializeObservationalMemory(input) {
|
|
7598
7902
|
try {
|
|
7903
|
+
const lookupKey = this.getOMKey(input.threadId, input.resourceId);
|
|
7904
|
+
const collection = await this.getCollection(OM_TABLE);
|
|
7905
|
+
const existing = await this.#getHeadDoc(collection, lookupKey);
|
|
7906
|
+
if (existing) return this.parseOMDocument(existing);
|
|
7599
7907
|
const id = globalThis.crypto.randomUUID();
|
|
7600
7908
|
const now = /* @__PURE__ */ new Date();
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
|
|
7605
|
-
|
|
7606
|
-
|
|
7607
|
-
|
|
7608
|
-
|
|
7609
|
-
|
|
7610
|
-
|
|
7611
|
-
|
|
7612
|
-
|
|
7613
|
-
|
|
7614
|
-
|
|
7615
|
-
|
|
7616
|
-
|
|
7617
|
-
|
|
7618
|
-
|
|
7619
|
-
|
|
7620
|
-
|
|
7621
|
-
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
7633
|
-
|
|
7634
|
-
|
|
7635
|
-
|
|
7636
|
-
|
|
7637
|
-
|
|
7638
|
-
|
|
7639
|
-
|
|
7640
|
-
observationTokenCount: 0,
|
|
7641
|
-
isObserving: false,
|
|
7642
|
-
isReflecting: false,
|
|
7643
|
-
isBufferingObservation: false,
|
|
7644
|
-
isBufferingReflection: false,
|
|
7645
|
-
lastBufferedAtTokens: 0,
|
|
7646
|
-
lastBufferedAtTime: null,
|
|
7647
|
-
observedTimezone: input.observedTimezone || null,
|
|
7648
|
-
createdAt: now,
|
|
7649
|
-
updatedAt: now
|
|
7909
|
+
try {
|
|
7910
|
+
await collection.insertOne({
|
|
7911
|
+
_id: getGeneration0DocumentId(lookupKey),
|
|
7912
|
+
id,
|
|
7913
|
+
lookupKey,
|
|
7914
|
+
scope: input.scope,
|
|
7915
|
+
resourceId: input.resourceId,
|
|
7916
|
+
threadId: input.threadId || null,
|
|
7917
|
+
activeObservations: "",
|
|
7918
|
+
activeObservationsPendingUpdate: null,
|
|
7919
|
+
originType: "initial",
|
|
7920
|
+
config: input.config,
|
|
7921
|
+
generationCount: 0,
|
|
7922
|
+
lastObservedAt: null,
|
|
7923
|
+
lastReflectionAt: null,
|
|
7924
|
+
pendingMessageTokens: 0,
|
|
7925
|
+
totalTokensObserved: 0,
|
|
7926
|
+
observationTokenCount: 0,
|
|
7927
|
+
isObserving: false,
|
|
7928
|
+
isReflecting: false,
|
|
7929
|
+
isBufferingObservation: false,
|
|
7930
|
+
isBufferingReflection: false,
|
|
7931
|
+
lastBufferedAtTokens: 0,
|
|
7932
|
+
lastBufferedAtTime: null,
|
|
7933
|
+
observedTimezone: input.observedTimezone || null,
|
|
7934
|
+
supersededBy: null,
|
|
7935
|
+
createdAt: now,
|
|
7936
|
+
updatedAt: now
|
|
7937
|
+
});
|
|
7938
|
+
} catch (error) {
|
|
7939
|
+
if (!isDuplicateKeyError(error)) throw error;
|
|
7940
|
+
}
|
|
7941
|
+
const head = await this.#getHeadDoc(collection, lookupKey);
|
|
7942
|
+
if (!head) throw new MastraError({
|
|
7943
|
+
id: createStorageErrorId("MONGODB", "INITIALIZE_OBSERVATIONAL_MEMORY", "NOT_FOUND"),
|
|
7944
|
+
text: `Observational memory record not found after initialization: ${id}`,
|
|
7945
|
+
domain: ErrorDomain.STORAGE,
|
|
7946
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
7947
|
+
details: { id }
|
|
7650
7948
|
});
|
|
7651
|
-
return
|
|
7949
|
+
return this.parseOMDocument(head);
|
|
7652
7950
|
} catch (error) {
|
|
7951
|
+
if (error instanceof MastraError) throw error;
|
|
7653
7952
|
throw new MastraError({
|
|
7654
7953
|
id: createStorageErrorId("MONGODB", "INITIALIZE_OBSERVATIONAL_MEMORY", "FAILED"),
|
|
7655
7954
|
domain: ErrorDomain.STORAGE,
|
|
@@ -7694,6 +7993,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7694
7993
|
lastBufferedAtTime: record.lastBufferedAtTime || null,
|
|
7695
7994
|
observedTimezone: record.observedTimezone || null,
|
|
7696
7995
|
metadata: record.metadata || null,
|
|
7996
|
+
supersededBy: record.supersededBy ?? null,
|
|
7697
7997
|
createdAt: record.createdAt,
|
|
7698
7998
|
updatedAt: record.updatedAt
|
|
7699
7999
|
});
|
|
@@ -7711,28 +8011,40 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7711
8011
|
}
|
|
7712
8012
|
}
|
|
7713
8013
|
async updateActiveObservations(input) {
|
|
8014
|
+
assertActiveObservationsApplied(await this.commitActiveObservations(input), input.id);
|
|
8015
|
+
}
|
|
8016
|
+
async commitActiveObservations(input) {
|
|
7714
8017
|
try {
|
|
7715
|
-
const now = /* @__PURE__ */ new Date();
|
|
7716
8018
|
const collection = await this.getCollection(OM_TABLE);
|
|
7717
8019
|
const safeTokenCount = Number.isFinite(input.tokenCount) && input.tokenCount >= 0 ? input.tokenCount : 0;
|
|
7718
|
-
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
|
|
7722
|
-
|
|
7723
|
-
|
|
7724
|
-
|
|
7725
|
-
|
|
7726
|
-
|
|
7727
|
-
|
|
7728
|
-
|
|
7729
|
-
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
8020
|
+
for (let attempt = 1; attempt <= OM_MAX_CONDITIONAL_ATTEMPTS; attempt++) {
|
|
8021
|
+
const doc = await collection.findOne({ id: input.id });
|
|
8022
|
+
if (!doc) throw omNotFound("UPDATE_ACTIVE_OBSERVATIONS", input.id);
|
|
8023
|
+
if (doc.supersededBy) return {
|
|
8024
|
+
applied: false,
|
|
8025
|
+
reason: "retired"
|
|
8026
|
+
};
|
|
8027
|
+
if (input.expectedActiveObservations !== void 0 && input.expectedActiveObservations !== (doc.activeObservations || "")) return {
|
|
8028
|
+
applied: false,
|
|
8029
|
+
reason: "conflict"
|
|
8030
|
+
};
|
|
8031
|
+
if ((await withRunFence(this.#connector, this.#runFenceCheck(void 0, "updateActiveObservations"), (session) => collection.updateOne({
|
|
8032
|
+
id: input.id,
|
|
8033
|
+
supersededBy: null,
|
|
8034
|
+
...matchStoredFields(doc, ["activeObservations", "lastObservedAt"])
|
|
8035
|
+
}, {
|
|
8036
|
+
$set: {
|
|
8037
|
+
activeObservations: input.observations,
|
|
8038
|
+
lastObservedAt: maxObservationCursor(doc.lastObservedAt, input.lastObservedAt),
|
|
8039
|
+
pendingMessageTokens: 0,
|
|
8040
|
+
observationTokenCount: safeTokenCount,
|
|
8041
|
+
observedMessageIds: input.observedMessageIds ?? null,
|
|
8042
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
8043
|
+
},
|
|
8044
|
+
$inc: { totalTokensObserved: safeTokenCount }
|
|
8045
|
+
}, { session }))).matchedCount === 1) return { applied: true };
|
|
8046
|
+
}
|
|
8047
|
+
throw omConflict("UPDATE_ACTIVE_OBSERVATIONS", input.id);
|
|
7736
8048
|
} catch (error) {
|
|
7737
8049
|
if (error instanceof MastraError) throw error;
|
|
7738
8050
|
throw new MastraError({
|
|
@@ -7745,62 +8057,31 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7745
8057
|
}
|
|
7746
8058
|
async createReflectionGeneration(input) {
|
|
7747
8059
|
try {
|
|
7748
|
-
const
|
|
7749
|
-
|
|
7750
|
-
|
|
7751
|
-
|
|
7752
|
-
|
|
7753
|
-
|
|
7754
|
-
|
|
7755
|
-
|
|
7756
|
-
|
|
7757
|
-
|
|
7758
|
-
|
|
7759
|
-
|
|
7760
|
-
|
|
7761
|
-
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
lastBufferedAtTime: null,
|
|
7771
|
-
config: input.currentRecord.config,
|
|
7772
|
-
metadata: input.currentRecord.metadata,
|
|
7773
|
-
observedTimezone: input.currentRecord.observedTimezone
|
|
7774
|
-
};
|
|
7775
|
-
await (await this.getCollection(OM_TABLE)).insertOne({
|
|
7776
|
-
id,
|
|
7777
|
-
lookupKey,
|
|
7778
|
-
scope: record.scope,
|
|
7779
|
-
resourceId: record.resourceId,
|
|
7780
|
-
threadId: record.threadId || null,
|
|
7781
|
-
activeObservations: input.reflection,
|
|
7782
|
-
activeObservationsPendingUpdate: null,
|
|
7783
|
-
originType: "reflection",
|
|
7784
|
-
config: record.config,
|
|
7785
|
-
generationCount: input.currentRecord.generationCount + 1,
|
|
7786
|
-
lastObservedAt: record.lastObservedAt || null,
|
|
7787
|
-
lastReflectionAt: now,
|
|
7788
|
-
pendingMessageTokens: record.pendingMessageTokens,
|
|
7789
|
-
totalTokensObserved: record.totalTokensObserved,
|
|
7790
|
-
observationTokenCount: record.observationTokenCount,
|
|
7791
|
-
isObserving: false,
|
|
7792
|
-
isReflecting: false,
|
|
7793
|
-
isBufferingObservation: false,
|
|
7794
|
-
isBufferingReflection: false,
|
|
7795
|
-
lastBufferedAtTokens: 0,
|
|
7796
|
-
lastBufferedAtTime: null,
|
|
7797
|
-
observedTimezone: record.observedTimezone || null,
|
|
7798
|
-
createdAt: now,
|
|
7799
|
-
updatedAt: now,
|
|
7800
|
-
metadata: record.metadata || null
|
|
8060
|
+
const { currentRecord } = input;
|
|
8061
|
+
return await this.#rollOver(currentRecord, "createReflectionGeneration", (doc) => {
|
|
8062
|
+
const stored = this.parseOMDocument(doc);
|
|
8063
|
+
const plan = planReflectionGenerationText({
|
|
8064
|
+
storedObservations: stored.activeObservations,
|
|
8065
|
+
storedObservationTokenCount: stored.observationTokenCount,
|
|
8066
|
+
snapshotObservations: currentRecord.activeObservations,
|
|
8067
|
+
snapshotObservationTokenCount: currentRecord.observationTokenCount,
|
|
8068
|
+
reflection: input.reflection,
|
|
8069
|
+
tokenCount: input.tokenCount
|
|
8070
|
+
});
|
|
8071
|
+
if (!plan) return null;
|
|
8072
|
+
return {
|
|
8073
|
+
newId: input.newRecordId ?? globalThis.crypto.randomUUID(),
|
|
8074
|
+
mode: plan.mode,
|
|
8075
|
+
reflection: input.reflection,
|
|
8076
|
+
tokenCount: input.tokenCount,
|
|
8077
|
+
snapTextLength: (currentRecord.activeObservations ?? "").length,
|
|
8078
|
+
snapObservationTokenCount: currentRecord.observationTokenCount ?? 0,
|
|
8079
|
+
clearBufferedReflection: false,
|
|
8080
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
8081
|
+
};
|
|
7801
8082
|
});
|
|
7802
|
-
return record;
|
|
7803
8083
|
} catch (error) {
|
|
8084
|
+
if (error instanceof MastraError) throw error;
|
|
7804
8085
|
throw new MastraError({
|
|
7805
8086
|
id: createStorageErrorId("MONGODB", "CREATE_REFLECTION_GENERATION", "FAILED"),
|
|
7806
8087
|
domain: ErrorDomain.STORAGE,
|
|
@@ -7867,23 +8148,12 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7867
8148
|
}
|
|
7868
8149
|
async setBufferingObservationFlag(id, isBuffering, lastBufferedAtTokens) {
|
|
7869
8150
|
try {
|
|
7870
|
-
const collection = await this.getCollection(OM_TABLE);
|
|
7871
8151
|
const updateDoc = {
|
|
7872
8152
|
isBufferingObservation: isBuffering,
|
|
7873
8153
|
updatedAt: /* @__PURE__ */ new Date()
|
|
7874
8154
|
};
|
|
7875
8155
|
if (lastBufferedAtTokens !== void 0) updateDoc.lastBufferedAtTokens = lastBufferedAtTokens;
|
|
7876
|
-
|
|
7877
|
-
id: createStorageErrorId("MONGODB", "SET_BUFFERING_OBSERVATION_FLAG", "NOT_FOUND"),
|
|
7878
|
-
text: `Observational memory record not found: ${id}`,
|
|
7879
|
-
domain: ErrorDomain.STORAGE,
|
|
7880
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
7881
|
-
details: {
|
|
7882
|
-
id,
|
|
7883
|
-
isBuffering,
|
|
7884
|
-
lastBufferedAtTokens: lastBufferedAtTokens ?? null
|
|
7885
|
-
}
|
|
7886
|
-
});
|
|
8156
|
+
await this.#updateLiveDoc(id, "SET_BUFFERING_OBSERVATION_FLAG", { $set: updateDoc });
|
|
7887
8157
|
} catch (error) {
|
|
7888
8158
|
if (error instanceof MastraError) throw error;
|
|
7889
8159
|
throw new MastraError({
|
|
@@ -7954,19 +8224,10 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
7954
8224
|
}
|
|
7955
8225
|
});
|
|
7956
8226
|
try {
|
|
7957
|
-
|
|
8227
|
+
await this.#updateLiveDoc(id, "SET_PENDING_MESSAGE_TOKENS", { $set: {
|
|
7958
8228
|
pendingMessageTokens: tokenCount,
|
|
7959
8229
|
updatedAt: /* @__PURE__ */ new Date()
|
|
7960
|
-
} })
|
|
7961
|
-
id: createStorageErrorId("MONGODB", "SET_PENDING_MESSAGE_TOKENS", "NOT_FOUND"),
|
|
7962
|
-
text: `Observational memory record not found: ${id}`,
|
|
7963
|
-
domain: ErrorDomain.STORAGE,
|
|
7964
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
7965
|
-
details: {
|
|
7966
|
-
id,
|
|
7967
|
-
tokenCount
|
|
7968
|
-
}
|
|
7969
|
-
});
|
|
8230
|
+
} });
|
|
7970
8231
|
} catch (error) {
|
|
7971
8232
|
if (error instanceof MastraError) throw error;
|
|
7972
8233
|
throw new MastraError({
|
|
@@ -8008,6 +8269,9 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
8008
8269
|
}
|
|
8009
8270
|
}
|
|
8010
8271
|
async updateBufferedObservations(input) {
|
|
8272
|
+
await this.appendBufferedObservations(input);
|
|
8273
|
+
}
|
|
8274
|
+
async appendBufferedObservations(input) {
|
|
8011
8275
|
try {
|
|
8012
8276
|
const collection = await this.getCollection(OM_TABLE);
|
|
8013
8277
|
const newChunk = {
|
|
@@ -8025,18 +8289,65 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
8025
8289
|
extractedValues: input.chunk.extractedValues,
|
|
8026
8290
|
extractionFailures: input.chunk.extractionFailures
|
|
8027
8291
|
};
|
|
8028
|
-
const
|
|
8029
|
-
|
|
8030
|
-
|
|
8031
|
-
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
|
|
8292
|
+
const notCovered = { $or: [{ lastObservedAt: null }, { lastObservedAt: { $lt: /* @__PURE__ */ new Date(new Date(input.chunk.lastObservedAt).getTime() - 1) } }] };
|
|
8293
|
+
const lastBufferedAtTime = input.lastBufferedAtTime ? new Date(input.lastBufferedAtTime) : void 0;
|
|
8294
|
+
let targetId = input.id;
|
|
8295
|
+
let hops = 0;
|
|
8296
|
+
let retries = 0;
|
|
8297
|
+
for (;;) {
|
|
8298
|
+
if ((await withRunFence(this.#connector, this.#runFenceCheck(void 0, "updateBufferedObservations"), (session) => collection.updateOne({
|
|
8299
|
+
id: targetId,
|
|
8300
|
+
supersededBy: null,
|
|
8301
|
+
"bufferedObservationChunks.cycleId": { $ne: input.chunk.cycleId },
|
|
8302
|
+
...notCovered,
|
|
8303
|
+
$and: [{ $or: [
|
|
8304
|
+
{ bufferedObservationChunks: { $exists: false } },
|
|
8305
|
+
{ bufferedObservationChunks: null },
|
|
8306
|
+
{ bufferedObservationChunks: { $type: "array" } }
|
|
8307
|
+
] }]
|
|
8308
|
+
}, [{ $set: {
|
|
8309
|
+
bufferedObservationChunks: { $concatArrays: [{ $ifNull: ["$bufferedObservationChunks", []] }, { $literal: [newChunk] }] },
|
|
8310
|
+
...lastBufferedAtTime ? { lastBufferedAtTime: { $max: ["$lastBufferedAtTime", { $literal: lastBufferedAtTime }] } } : {},
|
|
8311
|
+
updatedAt: { $literal: /* @__PURE__ */ new Date() }
|
|
8312
|
+
} }], { session }))).matchedCount === 1) return {
|
|
8313
|
+
persisted: true,
|
|
8314
|
+
recordId: targetId
|
|
8315
|
+
};
|
|
8316
|
+
const doc = await collection.findOne({ id: targetId });
|
|
8317
|
+
if (!doc) throw omNotFound("UPDATE_BUFFERED_OBSERVATIONS", targetId);
|
|
8318
|
+
if (doc.supersededBy) {
|
|
8319
|
+
const head = hops < OM_MAX_HEAD_HOPS ? await this.#getHeadDoc(collection, doc.lookupKey) : null;
|
|
8320
|
+
if (!head || head.supersededBy || head.id === targetId) throw omNoLiveHead(input.id);
|
|
8321
|
+
targetId = head.id;
|
|
8322
|
+
hops++;
|
|
8323
|
+
continue;
|
|
8324
|
+
}
|
|
8325
|
+
const stored = doc.bufferedObservationChunks;
|
|
8326
|
+
if (Array.isArray(stored) && stored.some((c) => c?.cycleId === input.chunk.cycleId)) return {
|
|
8327
|
+
persisted: false,
|
|
8328
|
+
recordId: targetId
|
|
8329
|
+
};
|
|
8330
|
+
if (isBufferedChunkCoveredByCursor(input.chunk.lastObservedAt, doc.lastObservedAt)) return {
|
|
8331
|
+
persisted: false,
|
|
8332
|
+
recordId: targetId
|
|
8333
|
+
};
|
|
8334
|
+
if (stored !== void 0 && stored !== null && !Array.isArray(stored)) {
|
|
8335
|
+
if ((await withRunFence(this.#connector, this.#runFenceCheck(void 0, "updateBufferedObservations"), (session) => collection.updateOne({
|
|
8336
|
+
id: targetId,
|
|
8337
|
+
supersededBy: null,
|
|
8338
|
+
bufferedObservationChunks: stored,
|
|
8339
|
+
...notCovered
|
|
8340
|
+
}, { $set: {
|
|
8341
|
+
bufferedObservationChunks: [newChunk],
|
|
8342
|
+
...lastBufferedAtTime ? { lastBufferedAtTime: maxObservationCursor(doc.lastBufferedAtTime, lastBufferedAtTime) } : {},
|
|
8343
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
8344
|
+
} }, { session }))).matchedCount === 1) return {
|
|
8345
|
+
persisted: true,
|
|
8346
|
+
recordId: targetId
|
|
8347
|
+
};
|
|
8348
|
+
}
|
|
8349
|
+
if (++retries >= OM_MAX_CONDITIONAL_ATTEMPTS) throw omConflict("UPDATE_BUFFERED_OBSERVATIONS", targetId);
|
|
8350
|
+
}
|
|
8040
8351
|
} catch (error) {
|
|
8041
8352
|
if (error instanceof MastraError) throw error;
|
|
8042
8353
|
throw new MastraError({
|
|
@@ -8050,16 +8361,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
8050
8361
|
async swapBufferedToActive(input) {
|
|
8051
8362
|
try {
|
|
8052
8363
|
const collection = await this.getCollection(OM_TABLE);
|
|
8053
|
-
const
|
|
8054
|
-
if (!doc) throw new MastraError({
|
|
8055
|
-
id: createStorageErrorId("MONGODB", "SWAP_BUFFERED_TO_ACTIVE", "NOT_FOUND"),
|
|
8056
|
-
text: `Observational memory record not found: ${input.id}`,
|
|
8057
|
-
domain: ErrorDomain.STORAGE,
|
|
8058
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
8059
|
-
details: { id: input.id }
|
|
8060
|
-
});
|
|
8061
|
-
const chunks = Array.isArray(doc.bufferedObservationChunks) ? doc.bufferedObservationChunks : [];
|
|
8062
|
-
if (chunks.length === 0) return {
|
|
8364
|
+
const emptyResult = {
|
|
8063
8365
|
chunksActivated: 0,
|
|
8064
8366
|
messageTokensActivated: 0,
|
|
8065
8367
|
observationTokensActivated: 0,
|
|
@@ -8067,95 +8369,107 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
8067
8369
|
activatedCycleIds: [],
|
|
8068
8370
|
activatedMessageIds: []
|
|
8069
8371
|
};
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
|
|
8079
|
-
const
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8372
|
+
for (let attempt = 1; attempt <= OM_MAX_CONDITIONAL_ATTEMPTS; attempt++) {
|
|
8373
|
+
const doc = await collection.findOne({ id: input.id });
|
|
8374
|
+
if (!doc) throw omNotFound("SWAP_BUFFERED_TO_ACTIVE", input.id);
|
|
8375
|
+
if (doc.supersededBy) return {
|
|
8376
|
+
...emptyResult,
|
|
8377
|
+
retired: true
|
|
8378
|
+
};
|
|
8379
|
+
const persistedChunks = Array.isArray(doc.bufferedObservationChunks) ? doc.bufferedObservationChunks : [];
|
|
8380
|
+
const refreshedWeights = new Map((Array.isArray(input.bufferedChunks) ? input.bufferedChunks : []).map((c) => [c.id, c.messageTokens]));
|
|
8381
|
+
const chunks = persistedChunks.map((c) => refreshedWeights.has(c.id) ? {
|
|
8382
|
+
...c,
|
|
8383
|
+
messageTokens: refreshedWeights.get(c.id)
|
|
8384
|
+
} : c);
|
|
8385
|
+
if (chunks.length === 0) return emptyResult;
|
|
8386
|
+
const retentionFloor = input.messageTokensThreshold * (1 - input.activationRatio);
|
|
8387
|
+
const targetMessageTokens = Math.max(0, input.currentPendingTokens - retentionFloor);
|
|
8388
|
+
let cumulativeMessageTokens = 0;
|
|
8389
|
+
let bestOverBoundary = 0;
|
|
8390
|
+
let bestOverTokens = 0;
|
|
8391
|
+
let bestUnderBoundary = 0;
|
|
8392
|
+
let bestUnderTokens = 0;
|
|
8393
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
8394
|
+
cumulativeMessageTokens += chunks[i].messageTokens ?? 0;
|
|
8395
|
+
const boundary = i + 1;
|
|
8396
|
+
if (cumulativeMessageTokens >= targetMessageTokens) {
|
|
8397
|
+
if (bestOverBoundary === 0 || cumulativeMessageTokens < bestOverTokens) {
|
|
8398
|
+
bestOverBoundary = boundary;
|
|
8399
|
+
bestOverTokens = cumulativeMessageTokens;
|
|
8400
|
+
}
|
|
8401
|
+
} else if (cumulativeMessageTokens > bestUnderTokens) {
|
|
8402
|
+
bestUnderBoundary = boundary;
|
|
8403
|
+
bestUnderTokens = cumulativeMessageTokens;
|
|
8084
8404
|
}
|
|
8085
|
-
} else if (cumulativeMessageTokens > bestUnderTokens) {
|
|
8086
|
-
bestUnderBoundary = boundary;
|
|
8087
|
-
bestUnderTokens = cumulativeMessageTokens;
|
|
8088
8405
|
}
|
|
8406
|
+
const maxOvershoot = retentionFloor * .95;
|
|
8407
|
+
const overshoot = bestOverTokens - targetMessageTokens;
|
|
8408
|
+
const remainingAfterOver = input.currentPendingTokens - bestOverTokens;
|
|
8409
|
+
const remainingAfterUnder = input.currentPendingTokens - bestUnderTokens;
|
|
8410
|
+
const minRemaining = Math.min(1e3, retentionFloor);
|
|
8411
|
+
let chunksToActivate;
|
|
8412
|
+
if (input.forceMaxActivation && bestOverBoundary > 0 && remainingAfterOver >= minRemaining) chunksToActivate = bestOverBoundary;
|
|
8413
|
+
else if (bestOverBoundary > 0 && overshoot <= maxOvershoot && remainingAfterOver >= minRemaining) chunksToActivate = bestOverBoundary;
|
|
8414
|
+
else if (bestUnderBoundary > 0 && remainingAfterUnder >= minRemaining) chunksToActivate = bestUnderBoundary;
|
|
8415
|
+
else if (bestOverBoundary > 0) chunksToActivate = bestOverBoundary;
|
|
8416
|
+
else chunksToActivate = 1;
|
|
8417
|
+
const activatedChunks = chunks.slice(0, chunksToActivate);
|
|
8418
|
+
const remainingChunks = persistedChunks.slice(chunksToActivate);
|
|
8419
|
+
const activatedContent = activatedChunks.map((c) => c.observations).join("\n\n");
|
|
8420
|
+
const activatedTokens = activatedChunks.reduce((sum, c) => sum + c.tokenCount, 0);
|
|
8421
|
+
const activatedMessageTokens = activatedChunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0);
|
|
8422
|
+
const activatedMessageCount = activatedChunks.reduce((sum, c) => sum + c.messageIds.length, 0);
|
|
8423
|
+
const activatedCycleIds = activatedChunks.map((c) => c.cycleId).filter((id) => !!id);
|
|
8424
|
+
const activatedMessageIds = activatedChunks.flatMap((c) => c.messageIds ?? []);
|
|
8425
|
+
const latestChunk = activatedChunks[activatedChunks.length - 1];
|
|
8426
|
+
const lastObservedAt = input.lastObservedAt ?? (latestChunk?.lastObservedAt ? new Date(latestChunk.lastObservedAt) : /* @__PURE__ */ new Date());
|
|
8427
|
+
const existingActive = doc.activeObservations || "";
|
|
8428
|
+
const existingTokenCount = Number(doc.observationTokenCount || 0);
|
|
8429
|
+
const boundary = `\n\n--- message boundary (${lastObservedAt.toISOString()}) ---\n\n`;
|
|
8430
|
+
const newActive = existingActive ? `${existingActive}${boundary}${activatedContent}` : activatedContent;
|
|
8431
|
+
const newTokenCount = existingTokenCount + activatedTokens;
|
|
8432
|
+
const existingPending = Number(doc.pendingMessageTokens || 0);
|
|
8433
|
+
const newPending = Math.max(0, existingPending - activatedMessageTokens);
|
|
8434
|
+
if ((await withRunFence(this.#connector, this.#runFenceCheck(void 0, "swapBufferedToActive"), (session) => collection.updateOne({
|
|
8435
|
+
id: input.id,
|
|
8436
|
+
supersededBy: null,
|
|
8437
|
+
...matchStoredFields(doc, [
|
|
8438
|
+
"bufferedObservationChunks",
|
|
8439
|
+
"activeObservations",
|
|
8440
|
+
"observationTokenCount",
|
|
8441
|
+
"pendingMessageTokens",
|
|
8442
|
+
"lastObservedAt"
|
|
8443
|
+
])
|
|
8444
|
+
}, { $set: {
|
|
8445
|
+
activeObservations: newActive,
|
|
8446
|
+
observationTokenCount: newTokenCount,
|
|
8447
|
+
pendingMessageTokens: newPending,
|
|
8448
|
+
bufferedObservationChunks: remainingChunks,
|
|
8449
|
+
lastObservedAt: maxObservationCursor(doc.lastObservedAt, lastObservedAt),
|
|
8450
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
8451
|
+
} }, { session }))).matchedCount === 0) continue;
|
|
8452
|
+
const latestChunkHints = activatedChunks[activatedChunks.length - 1];
|
|
8453
|
+
return {
|
|
8454
|
+
chunksActivated: activatedChunks.length,
|
|
8455
|
+
messageTokensActivated: activatedMessageTokens,
|
|
8456
|
+
observationTokensActivated: activatedTokens,
|
|
8457
|
+
messagesActivated: activatedMessageCount,
|
|
8458
|
+
activatedCycleIds,
|
|
8459
|
+
activatedMessageIds,
|
|
8460
|
+
observations: activatedContent,
|
|
8461
|
+
perChunk: activatedChunks.map((c) => ({
|
|
8462
|
+
cycleId: c.cycleId ?? "",
|
|
8463
|
+
messageTokens: c.messageTokens ?? 0,
|
|
8464
|
+
observationTokens: c.tokenCount,
|
|
8465
|
+
messageCount: c.messageIds.length,
|
|
8466
|
+
observations: c.observations
|
|
8467
|
+
})),
|
|
8468
|
+
suggestedContinuation: latestChunkHints?.suggestedContinuation ?? void 0,
|
|
8469
|
+
currentTask: latestChunkHints?.currentTask ?? void 0
|
|
8470
|
+
};
|
|
8089
8471
|
}
|
|
8090
|
-
|
|
8091
|
-
const overshoot = bestOverTokens - targetMessageTokens;
|
|
8092
|
-
const remainingAfterOver = input.currentPendingTokens - bestOverTokens;
|
|
8093
|
-
const remainingAfterUnder = input.currentPendingTokens - bestUnderTokens;
|
|
8094
|
-
const minRemaining = Math.min(1e3, retentionFloor);
|
|
8095
|
-
let chunksToActivate;
|
|
8096
|
-
if (input.forceMaxActivation && bestOverBoundary > 0 && remainingAfterOver >= minRemaining) chunksToActivate = bestOverBoundary;
|
|
8097
|
-
else if (bestOverBoundary > 0 && overshoot <= maxOvershoot && remainingAfterOver >= minRemaining) chunksToActivate = bestOverBoundary;
|
|
8098
|
-
else if (bestUnderBoundary > 0 && remainingAfterUnder >= minRemaining) chunksToActivate = bestUnderBoundary;
|
|
8099
|
-
else if (bestOverBoundary > 0) chunksToActivate = bestOverBoundary;
|
|
8100
|
-
else chunksToActivate = 1;
|
|
8101
|
-
const activatedChunks = chunks.slice(0, chunksToActivate);
|
|
8102
|
-
const remainingChunks = chunks.slice(chunksToActivate);
|
|
8103
|
-
const activatedContent = activatedChunks.map((c) => c.observations).join("\n\n");
|
|
8104
|
-
const activatedTokens = activatedChunks.reduce((sum, c) => sum + c.tokenCount, 0);
|
|
8105
|
-
const activatedMessageTokens = activatedChunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0);
|
|
8106
|
-
const activatedMessageCount = activatedChunks.reduce((sum, c) => sum + c.messageIds.length, 0);
|
|
8107
|
-
const activatedCycleIds = activatedChunks.map((c) => c.cycleId).filter((id) => !!id);
|
|
8108
|
-
const activatedMessageIds = activatedChunks.flatMap((c) => c.messageIds ?? []);
|
|
8109
|
-
const latestChunk = activatedChunks[activatedChunks.length - 1];
|
|
8110
|
-
const lastObservedAt = input.lastObservedAt ?? (latestChunk?.lastObservedAt ? new Date(latestChunk.lastObservedAt) : /* @__PURE__ */ new Date());
|
|
8111
|
-
const existingActive = doc.activeObservations || "";
|
|
8112
|
-
const existingTokenCount = Number(doc.observationTokenCount || 0);
|
|
8113
|
-
const boundary = `\n\n--- message boundary (${lastObservedAt.toISOString()}) ---\n\n`;
|
|
8114
|
-
const newActive = existingActive ? `${existingActive}${boundary}${activatedContent}` : activatedContent;
|
|
8115
|
-
const newTokenCount = existingTokenCount + activatedTokens;
|
|
8116
|
-
const existingPending = Number(doc.pendingMessageTokens || 0);
|
|
8117
|
-
const newPending = Math.max(0, existingPending - activatedMessageTokens);
|
|
8118
|
-
if ((await collection.updateOne({
|
|
8119
|
-
id: input.id,
|
|
8120
|
-
bufferedObservationChunks: {
|
|
8121
|
-
$exists: true,
|
|
8122
|
-
$ne: null,
|
|
8123
|
-
$not: { $size: 0 }
|
|
8124
|
-
}
|
|
8125
|
-
}, { $set: {
|
|
8126
|
-
activeObservations: newActive,
|
|
8127
|
-
observationTokenCount: newTokenCount,
|
|
8128
|
-
pendingMessageTokens: newPending,
|
|
8129
|
-
bufferedObservationChunks: remainingChunks,
|
|
8130
|
-
lastObservedAt,
|
|
8131
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
8132
|
-
} })).modifiedCount === 0) return {
|
|
8133
|
-
chunksActivated: 0,
|
|
8134
|
-
messageTokensActivated: 0,
|
|
8135
|
-
observationTokensActivated: 0,
|
|
8136
|
-
messagesActivated: 0,
|
|
8137
|
-
activatedCycleIds: [],
|
|
8138
|
-
activatedMessageIds: []
|
|
8139
|
-
};
|
|
8140
|
-
const latestChunkHints = activatedChunks[activatedChunks.length - 1];
|
|
8141
|
-
return {
|
|
8142
|
-
chunksActivated: activatedChunks.length,
|
|
8143
|
-
messageTokensActivated: activatedMessageTokens,
|
|
8144
|
-
observationTokensActivated: activatedTokens,
|
|
8145
|
-
messagesActivated: activatedMessageCount,
|
|
8146
|
-
activatedCycleIds,
|
|
8147
|
-
activatedMessageIds,
|
|
8148
|
-
observations: activatedContent,
|
|
8149
|
-
perChunk: activatedChunks.map((c) => ({
|
|
8150
|
-
cycleId: c.cycleId ?? "",
|
|
8151
|
-
messageTokens: c.messageTokens ?? 0,
|
|
8152
|
-
observationTokens: c.tokenCount,
|
|
8153
|
-
messageCount: c.messageIds.length,
|
|
8154
|
-
observations: c.observations
|
|
8155
|
-
})),
|
|
8156
|
-
suggestedContinuation: latestChunkHints?.suggestedContinuation ?? void 0,
|
|
8157
|
-
currentTask: latestChunkHints?.currentTask ?? void 0
|
|
8158
|
-
};
|
|
8472
|
+
throw omConflict("SWAP_BUFFERED_TO_ACTIVE", input.id);
|
|
8159
8473
|
} catch (error) {
|
|
8160
8474
|
if (error instanceof MastraError) throw error;
|
|
8161
8475
|
throw new MastraError({
|
|
@@ -8183,13 +8497,13 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
8183
8497
|
const newContent = existingContent ? `${existingContent}\n\n${input.reflection}` : input.reflection;
|
|
8184
8498
|
const newTokens = existingTokens + input.tokenCount;
|
|
8185
8499
|
const newInputTokens = existingInputTokens + input.inputTokenCount;
|
|
8186
|
-
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: {
|
|
8187
8501
|
bufferedReflection: newContent,
|
|
8188
8502
|
bufferedReflectionTokens: newTokens,
|
|
8189
8503
|
bufferedReflectionInputTokens: newInputTokens,
|
|
8190
8504
|
reflectedObservationLineCount: input.reflectedObservationLineCount,
|
|
8191
8505
|
updatedAt: /* @__PURE__ */ new Date()
|
|
8192
|
-
} })).matchedCount === 0) throw new MastraError({
|
|
8506
|
+
} }, { session }))).matchedCount === 0) throw new MastraError({
|
|
8193
8507
|
id: createStorageErrorId("MONGODB", "UPDATE_BUFFERED_REFLECTION", "NOT_FOUND"),
|
|
8194
8508
|
text: `Observational memory record not found: ${input.id}`,
|
|
8195
8509
|
domain: ErrorDomain.STORAGE,
|
|
@@ -8208,39 +8522,33 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
8208
8522
|
}
|
|
8209
8523
|
async swapBufferedReflectionToActive(input) {
|
|
8210
8524
|
try {
|
|
8211
|
-
const
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
|
|
8217
|
-
|
|
8218
|
-
|
|
8219
|
-
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8525
|
+
const { currentRecord } = input;
|
|
8526
|
+
return await this.#rollOver(currentRecord, "swapBufferedReflectionToActive", (doc) => {
|
|
8527
|
+
const stored = this.parseOMDocument(doc);
|
|
8528
|
+
const bufferedReflection = stored.bufferedReflection || "";
|
|
8529
|
+
if (!bufferedReflection) throw new MastraError({
|
|
8530
|
+
id: createStorageErrorId("MONGODB", "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "NO_CONTENT"),
|
|
8531
|
+
text: "No buffered reflection to swap",
|
|
8532
|
+
domain: ErrorDomain.STORAGE,
|
|
8533
|
+
category: ErrorCategory.USER,
|
|
8534
|
+
details: { id: currentRecord.id }
|
|
8535
|
+
});
|
|
8536
|
+
if (!isAppendOnlySince(stored.activeObservations, currentRecord.activeObservations)) return null;
|
|
8537
|
+
const reflectedLineCount = stored.reflectedObservationLineCount ?? 0;
|
|
8538
|
+
const unreflectedContent = (stored.activeObservations || "").split("\n").slice(reflectedLineCount).join("\n").trim();
|
|
8539
|
+
const newObservations = unreflectedContent ? `${bufferedReflection}\n\n${unreflectedContent}` : bufferedReflection;
|
|
8540
|
+
const tokenCount = input.tokenCount + Math.max(0, (stored.observationTokenCount ?? 0) - (currentRecord.observationTokenCount ?? 0));
|
|
8541
|
+
return {
|
|
8542
|
+
newId: input.newRecordId ?? globalThis.crypto.randomUUID(),
|
|
8543
|
+
mode: "equal",
|
|
8544
|
+
reflection: newObservations,
|
|
8545
|
+
tokenCount,
|
|
8546
|
+
snapTextLength: (stored.activeObservations ?? "").length,
|
|
8547
|
+
snapObservationTokenCount: stored.observationTokenCount ?? 0,
|
|
8548
|
+
clearBufferedReflection: true,
|
|
8549
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
8550
|
+
};
|
|
8235
8551
|
});
|
|
8236
|
-
await collection.updateOne({ id: input.currentRecord.id }, { $set: {
|
|
8237
|
-
bufferedReflection: null,
|
|
8238
|
-
bufferedReflectionTokens: null,
|
|
8239
|
-
bufferedReflectionInputTokens: null,
|
|
8240
|
-
reflectedObservationLineCount: null,
|
|
8241
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
8242
|
-
} });
|
|
8243
|
-
return newRecord;
|
|
8244
8552
|
} catch (error) {
|
|
8245
8553
|
if (error instanceof MastraError) throw error;
|
|
8246
8554
|
throw new MastraError({
|
|
@@ -8251,6 +8559,171 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends MemoryStorage {
|
|
|
8251
8559
|
}, error);
|
|
8252
8560
|
}
|
|
8253
8561
|
}
|
|
8562
|
+
/**
|
|
8563
|
+
* The head of a lookup key by the canonical order (`generationCount DESC, createdAt ASC, id ASC`).
|
|
8564
|
+
* A head that was fenced for rollover but whose successor insert never ran (crash, or still in
|
|
8565
|
+
* flight in another process) is rolled forward here, so every head read reaches the successor.
|
|
8566
|
+
*/
|
|
8567
|
+
async #getHeadDoc(collection, lookupKey) {
|
|
8568
|
+
for (let hop = 0; hop <= OM_MAX_HEAD_HOPS; hop++) {
|
|
8569
|
+
const head = await collection.findOne({ lookupKey }, { sort: OM_HEAD_SORT });
|
|
8570
|
+
if (!head || !head.supersededBy || !head.pendingSuccessor) return head;
|
|
8571
|
+
await this.#completeRollover(collection, head, head.pendingSuccessor);
|
|
8572
|
+
}
|
|
8573
|
+
throw omNoLiveHead(lookupKey);
|
|
8574
|
+
}
|
|
8575
|
+
/**
|
|
8576
|
+
* A single-document update aimed at a live record. A retired (or concurrently fenced) id is
|
|
8577
|
+
* redirected to the head, at most `OM_MAX_HEAD_HOPS` times.
|
|
8578
|
+
*/
|
|
8579
|
+
async #updateLiveDoc(id, operation, update) {
|
|
8580
|
+
const collection = await this.getCollection(OM_TABLE);
|
|
8581
|
+
let targetId = id;
|
|
8582
|
+
for (let hop = 0; hop <= OM_MAX_HEAD_HOPS; hop++) {
|
|
8583
|
+
if ((await collection.updateOne({
|
|
8584
|
+
id: targetId,
|
|
8585
|
+
supersededBy: null
|
|
8586
|
+
}, update)).matchedCount === 1) return targetId;
|
|
8587
|
+
const doc = await collection.findOne({ id: targetId }, { projection: {
|
|
8588
|
+
lookupKey: 1,
|
|
8589
|
+
supersededBy: 1
|
|
8590
|
+
} });
|
|
8591
|
+
if (!doc) throw omNotFound(operation, targetId);
|
|
8592
|
+
if (!doc.supersededBy) continue;
|
|
8593
|
+
const head = await this.#getHeadDoc(collection, doc.lookupKey);
|
|
8594
|
+
if (!head || head.supersededBy || head.id === targetId) throw omNoLiveHead(id);
|
|
8595
|
+
targetId = head.id;
|
|
8596
|
+
}
|
|
8597
|
+
throw omNoLiveHead(id);
|
|
8598
|
+
}
|
|
8599
|
+
/**
|
|
8600
|
+
* Roll the live record `currentRecord.id` over to a new generation.
|
|
8601
|
+
*
|
|
8602
|
+
* 1. Read the stored document and decide (`decide` returns the successor inputs, or `null`
|
|
8603
|
+
* when the snapshot is stale).
|
|
8604
|
+
* 2. Fence: one conditional update sets `supersededBy` and the small `pendingSuccessor`
|
|
8605
|
+
* payload, matching only if the fields the decision read are unchanged. From here the old
|
|
8606
|
+
* document is frozen — no lifecycle write matches it — so its pre-image is its final state.
|
|
8607
|
+
* 3. Insert the successor built purely from that pre-image, then clear the old document's
|
|
8608
|
+
* chunks (they moved) and `pendingSuccessor`.
|
|
8609
|
+
*
|
|
8610
|
+
* A crash between 2 and 3 is finished by the next head read (`#getHeadDoc`) or startup.
|
|
8611
|
+
*/
|
|
8612
|
+
async #rollOver(currentRecord, operation, decide) {
|
|
8613
|
+
const collection = await this.getCollection(OM_TABLE);
|
|
8614
|
+
for (let attempt = 1; attempt <= OM_MAX_CONDITIONAL_ATTEMPTS; attempt++) {
|
|
8615
|
+
const doc = await collection.findOne({ id: currentRecord.id });
|
|
8616
|
+
if (!doc) return currentRecord;
|
|
8617
|
+
if (doc.supersededBy) {
|
|
8618
|
+
const head = await this.#getHeadDoc(collection, doc.lookupKey);
|
|
8619
|
+
return head ? this.parseOMDocument(head) : currentRecord;
|
|
8620
|
+
}
|
|
8621
|
+
const pending = decide(doc);
|
|
8622
|
+
if (!pending) return this.parseOMDocument(doc);
|
|
8623
|
+
const frozen = await withRunFence(this.#connector, this.#runFenceCheck(void 0, operation), (session) => collection.findOneAndUpdate({
|
|
8624
|
+
id: doc.id,
|
|
8625
|
+
supersededBy: null,
|
|
8626
|
+
...matchStoredFields(doc, [
|
|
8627
|
+
"activeObservations",
|
|
8628
|
+
"observationTokenCount",
|
|
8629
|
+
"bufferedReflection",
|
|
8630
|
+
"reflectedObservationLineCount"
|
|
8631
|
+
])
|
|
8632
|
+
}, { $set: {
|
|
8633
|
+
supersededBy: pending.newId,
|
|
8634
|
+
pendingSuccessor: pending
|
|
8635
|
+
} }, {
|
|
8636
|
+
returnDocument: "before",
|
|
8637
|
+
session
|
|
8638
|
+
}));
|
|
8639
|
+
if (!frozen) continue;
|
|
8640
|
+
return this.parseOMDocument(await this.#completeRollover(collection, frozen, pending));
|
|
8641
|
+
}
|
|
8642
|
+
throw omConflict("CREATE_REFLECTION_GENERATION", currentRecord.id);
|
|
8643
|
+
}
|
|
8644
|
+
/** Insert the successor of a fenced document (idempotent) and clean up the old document. */
|
|
8645
|
+
async #completeRollover(collection, frozen, pending) {
|
|
8646
|
+
try {
|
|
8647
|
+
await collection.insertOne(buildSuccessorDocument(frozen, pending));
|
|
8648
|
+
} catch (error) {
|
|
8649
|
+
if (!isDuplicateKeyError(error)) throw error;
|
|
8650
|
+
}
|
|
8651
|
+
await collection.updateOne({
|
|
8652
|
+
id: frozen.id,
|
|
8653
|
+
supersededBy: pending.newId
|
|
8654
|
+
}, {
|
|
8655
|
+
$set: {
|
|
8656
|
+
bufferedObservationChunks: [],
|
|
8657
|
+
...pending.clearBufferedReflection ? {
|
|
8658
|
+
bufferedReflection: null,
|
|
8659
|
+
bufferedReflectionTokens: null,
|
|
8660
|
+
bufferedReflectionInputTokens: null,
|
|
8661
|
+
reflectedObservationLineCount: null
|
|
8662
|
+
} : {},
|
|
8663
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
8664
|
+
},
|
|
8665
|
+
$unset: { pendingSuccessor: "" }
|
|
8666
|
+
});
|
|
8667
|
+
const successor = await collection.findOne({ id: pending.newId });
|
|
8668
|
+
if (!successor) throw omNotFound("CREATE_REFLECTION_GENERATION", pending.newId);
|
|
8669
|
+
return successor;
|
|
8670
|
+
}
|
|
8671
|
+
/**
|
|
8672
|
+
* Startup maintenance for the `supersededBy` marker:
|
|
8673
|
+
* - finish rollovers interrupted after the fence (insert the successor if missing, clean up);
|
|
8674
|
+
* - for keys with more than one live document (left by older adapter versions, which never set
|
|
8675
|
+
* `supersededBy`), retire every live document that sorts after the canonical head. The head's
|
|
8676
|
+
* ordering key is embedded in the filter, so a document created by a concurrent rollover
|
|
8677
|
+
* (a newer generation) never matches.
|
|
8678
|
+
*/
|
|
8679
|
+
async #maintainSupersededBy() {
|
|
8680
|
+
const collection = await this.getCollection(OM_TABLE);
|
|
8681
|
+
const interrupted = await collection.find({
|
|
8682
|
+
supersededBy: { $nin: [null] },
|
|
8683
|
+
pendingSuccessor: { $exists: true }
|
|
8684
|
+
}).toArray();
|
|
8685
|
+
for (const doc of interrupted) await this.#completeRollover(collection, doc, doc.pendingSuccessor);
|
|
8686
|
+
const keys = await collection.aggregate([
|
|
8687
|
+
{ $match: { supersededBy: null } },
|
|
8688
|
+
{ $group: {
|
|
8689
|
+
_id: "$lookupKey",
|
|
8690
|
+
live: { $sum: 1 }
|
|
8691
|
+
} },
|
|
8692
|
+
{ $match: { live: { $gt: 1 } } }
|
|
8693
|
+
]).toArray();
|
|
8694
|
+
for (const { _id: lookupKey } of keys) {
|
|
8695
|
+
const head = await this.#getHeadDoc(collection, lookupKey);
|
|
8696
|
+
if (!head || head.supersededBy) continue;
|
|
8697
|
+
await collection.updateMany({
|
|
8698
|
+
lookupKey,
|
|
8699
|
+
supersededBy: null,
|
|
8700
|
+
$or: [
|
|
8701
|
+
{ generationCount: { $lt: head.generationCount } },
|
|
8702
|
+
{
|
|
8703
|
+
generationCount: head.generationCount,
|
|
8704
|
+
createdAt: { $gt: head.createdAt }
|
|
8705
|
+
},
|
|
8706
|
+
{
|
|
8707
|
+
generationCount: head.generationCount,
|
|
8708
|
+
createdAt: head.createdAt,
|
|
8709
|
+
id: { $gt: head.id }
|
|
8710
|
+
}
|
|
8711
|
+
]
|
|
8712
|
+
}, { $set: { supersededBy: head.id } });
|
|
8713
|
+
}
|
|
8714
|
+
}
|
|
8715
|
+
/**
|
|
8716
|
+
* Cross-process rollover and initialization rely on the unique `id` index (a duplicate insert
|
|
8717
|
+
* of the same successor or generation-0 id must fail). Warn once if it is missing, e.g. under
|
|
8718
|
+
* `skipDefaultIndexes`.
|
|
8719
|
+
*/
|
|
8720
|
+
async #warnIfOMIdIndexMissing() {
|
|
8721
|
+
try {
|
|
8722
|
+
if (!(await (await this.getCollection(OM_TABLE)).listIndexes().toArray().catch((error) => error?.code === 26 ? [] : Promise.reject(error))).some((index) => index.unique && Object.keys(index.key ?? {}).length === 1 && index.key.id === 1)) this.logger?.warn?.(`MongoDB collection "${OM_TABLE}" has no unique index on { id: 1 }. Observational memory needs it to stay consistent when several processes write the same thread or resource. Create it with db.${OM_TABLE}.createIndex({ id: 1 }, { unique: true }).`);
|
|
8723
|
+
} catch (error) {
|
|
8724
|
+
this.logger?.warn?.(`Could not list indexes on MongoDB collection "${OM_TABLE}":`, error);
|
|
8725
|
+
}
|
|
8726
|
+
}
|
|
8254
8727
|
};
|
|
8255
8728
|
//#endregion
|
|
8256
8729
|
//#region src/storage/domains/notifications/index.ts
|
|
@@ -11477,33 +11950,58 @@ var MongoDBWorkflowDefinitionsStore = class MongoDBWorkflowDefinitionsStore exte
|
|
|
11477
11950
|
};
|
|
11478
11951
|
//#endregion
|
|
11479
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
|
+
}
|
|
11480
11969
|
var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends WorkflowsStorage {
|
|
11481
11970
|
#connector;
|
|
11482
11971
|
#skipDefaultIndexes;
|
|
11483
11972
|
#indexes;
|
|
11484
11973
|
/** Collections managed by this domain */
|
|
11485
|
-
static MANAGED_COLLECTIONS = [TABLE_WORKFLOW_SNAPSHOT];
|
|
11974
|
+
static MANAGED_COLLECTIONS = [TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKFLOW_RUN_OWNERS];
|
|
11486
11975
|
/**
|
|
11487
|
-
*
|
|
11488
|
-
* 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`.
|
|
11489
11980
|
*/
|
|
11490
|
-
static retentionTables = {
|
|
11491
|
-
|
|
11492
|
-
|
|
11493
|
-
|
|
11494
|
-
|
|
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
|
+
};
|
|
11495
11993
|
constructor(config) {
|
|
11496
11994
|
super();
|
|
11497
11995
|
this.#connector = resolveMongoDBConfig(config);
|
|
11498
11996
|
this.#skipDefaultIndexes = config.skipDefaultIndexes;
|
|
11499
11997
|
this.#indexes = config.indexes?.filter((idx) => WorkflowsStorageMongoDB.MANAGED_COLLECTIONS.includes(idx.collection));
|
|
11500
11998
|
}
|
|
11501
|
-
/** Delete workflow snapshots idle for longer than
|
|
11999
|
+
/** Delete workflow snapshots and ownership documents idle for longer than each policy's `maxAge`, batched. */
|
|
11502
12000
|
async prune(policies, options) {
|
|
11503
12001
|
const targets = resolveTargets({
|
|
11504
12002
|
policies,
|
|
11505
12003
|
descriptor: WorkflowsStorageMongoDB.retentionTables,
|
|
11506
|
-
order: ["workflowSnapshot"]
|
|
12004
|
+
order: ["workflowSnapshot", "runOwnership"]
|
|
11507
12005
|
});
|
|
11508
12006
|
return runPrune({
|
|
11509
12007
|
connector: this.#connector,
|
|
@@ -11516,6 +12014,13 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends WorkflowsSto
|
|
|
11516
12014
|
supportsConcurrentUpdates() {
|
|
11517
12015
|
return true;
|
|
11518
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
|
+
}
|
|
11519
12024
|
async getCollection(name) {
|
|
11520
12025
|
return this.#connector.getCollection(name);
|
|
11521
12026
|
}
|
|
@@ -11523,6 +12028,124 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends WorkflowsSto
|
|
|
11523
12028
|
await this.createDefaultIndexes();
|
|
11524
12029
|
await this.createCustomIndexes();
|
|
11525
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
|
+
}
|
|
11526
12149
|
/**
|
|
11527
12150
|
* Returns default index definitions for the workflows domain collections.
|
|
11528
12151
|
*/
|
|
@@ -11582,8 +12205,10 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends WorkflowsSto
|
|
|
11582
12205
|
}
|
|
11583
12206
|
async dangerouslyClearAll() {
|
|
11584
12207
|
await (await this.getCollection(TABLE_WORKFLOW_SNAPSHOT)).deleteMany({});
|
|
12208
|
+
await (await this.getCollection(TABLE_WORKFLOW_RUN_OWNERS)).deleteMany({});
|
|
11585
12209
|
}
|
|
11586
|
-
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");
|
|
11587
12212
|
try {
|
|
11588
12213
|
const collection = await this.getCollection(TABLE_WORKFLOW_SNAPSHOT);
|
|
11589
12214
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -11603,7 +12228,7 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends WorkflowsSto
|
|
|
11603
12228
|
runId,
|
|
11604
12229
|
requestContext: {}
|
|
11605
12230
|
};
|
|
11606
|
-
const updatedDoc = await collection.findOneAndUpdate({
|
|
12231
|
+
const updatedDoc = await withRunFence(this.#connector, check, (session) => collection.findOneAndUpdate({
|
|
11607
12232
|
workflow_name: workflowName,
|
|
11608
12233
|
run_id: runId
|
|
11609
12234
|
}, [{ $set: {
|
|
@@ -11618,10 +12243,12 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends WorkflowsSto
|
|
|
11618
12243
|
createdAt: { $ifNull: ["$createdAt", now] }
|
|
11619
12244
|
} }], {
|
|
11620
12245
|
upsert: true,
|
|
11621
|
-
returnDocument: "after"
|
|
11622
|
-
|
|
12246
|
+
returnDocument: "after",
|
|
12247
|
+
session
|
|
12248
|
+
}));
|
|
11623
12249
|
return (typeof updatedDoc?.snapshot === "string" ? JSON.parse(updatedDoc.snapshot) : updatedDoc?.snapshot)?.context || {};
|
|
11624
12250
|
} catch (error) {
|
|
12251
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
11625
12252
|
throw new MastraError({
|
|
11626
12253
|
id: createStorageErrorId("MONGODB", "UPDATE_WORKFLOW_RESULTS", "FAILED"),
|
|
11627
12254
|
domain: ErrorDomain.STORAGE,
|
|
@@ -11634,7 +12261,8 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends WorkflowsSto
|
|
|
11634
12261
|
}, error);
|
|
11635
12262
|
}
|
|
11636
12263
|
}
|
|
11637
|
-
async updateWorkflowState({ workflowName, runId, opts }) {
|
|
12264
|
+
async updateWorkflowState({ workflowName, runId, opts, fence }) {
|
|
12265
|
+
const check = this.#runFenceCheck(fence, runId, "updateWorkflowState");
|
|
11638
12266
|
try {
|
|
11639
12267
|
const collection = await this.getCollection(TABLE_WORKFLOW_SNAPSHOT);
|
|
11640
12268
|
const { expectedStatus, ...state } = opts;
|
|
@@ -11643,10 +12271,13 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends WorkflowsSto
|
|
|
11643
12271
|
run_id: runId
|
|
11644
12272
|
};
|
|
11645
12273
|
if (expectedStatus !== void 0) filter["snapshot.status"] = { $in: Array.isArray(expectedStatus) ? expectedStatus : [expectedStatus] };
|
|
11646
|
-
const updatedDoc = await collection.findOneAndUpdate(filter, [{ $set: {
|
|
12274
|
+
const updatedDoc = await withRunFence(this.#connector, check, (session) => collection.findOneAndUpdate(filter, [{ $set: {
|
|
11647
12275
|
snapshot: { $mergeObjects: ["$snapshot", state] },
|
|
11648
12276
|
updatedAt: /* @__PURE__ */ new Date()
|
|
11649
|
-
} }], {
|
|
12277
|
+
} }], {
|
|
12278
|
+
returnDocument: "after",
|
|
12279
|
+
session
|
|
12280
|
+
}));
|
|
11650
12281
|
if (!updatedDoc) return;
|
|
11651
12282
|
const snapshot = typeof updatedDoc.snapshot === "string" ? JSON.parse(updatedDoc.snapshot) : updatedDoc.snapshot;
|
|
11652
12283
|
if (!snapshot?.context) throw new MastraError({
|
|
@@ -11661,7 +12292,7 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends WorkflowsSto
|
|
|
11661
12292
|
});
|
|
11662
12293
|
return snapshot;
|
|
11663
12294
|
} catch (error) {
|
|
11664
|
-
if (error instanceof MastraError) throw error;
|
|
12295
|
+
if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
|
|
11665
12296
|
throw new MastraError({
|
|
11666
12297
|
id: createStorageErrorId("MONGODB", "UPDATE_WORKFLOW_STATE", "FAILED"),
|
|
11667
12298
|
domain: ErrorDomain.STORAGE,
|
|
@@ -11673,10 +12304,12 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends WorkflowsSto
|
|
|
11673
12304
|
}, error);
|
|
11674
12305
|
}
|
|
11675
12306
|
}
|
|
11676
|
-
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");
|
|
11677
12309
|
try {
|
|
11678
12310
|
const now = /* @__PURE__ */ new Date();
|
|
11679
|
-
|
|
12311
|
+
const collection = await this.getCollection(TABLE_WORKFLOW_SNAPSHOT);
|
|
12312
|
+
await withRunFence(this.#connector, check, (session) => collection.updateOne({
|
|
11680
12313
|
workflow_name: workflowName,
|
|
11681
12314
|
run_id: runId
|
|
11682
12315
|
}, {
|
|
@@ -11688,8 +12321,12 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends WorkflowsSto
|
|
|
11688
12321
|
updatedAt: updatedAt ?? now
|
|
11689
12322
|
},
|
|
11690
12323
|
$setOnInsert: { createdAt: createdAt ?? now }
|
|
11691
|
-
}, {
|
|
12324
|
+
}, {
|
|
12325
|
+
upsert: true,
|
|
12326
|
+
session
|
|
12327
|
+
}));
|
|
11692
12328
|
} catch (error) {
|
|
12329
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
11693
12330
|
throw new MastraError({
|
|
11694
12331
|
id: createStorageErrorId("MONGODB", "PERSIST_WORKFLOW_SNAPSHOT", "FAILED"),
|
|
11695
12332
|
domain: ErrorDomain.STORAGE,
|
|
@@ -11782,13 +12419,16 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends WorkflowsSto
|
|
|
11782
12419
|
}, error);
|
|
11783
12420
|
}
|
|
11784
12421
|
}
|
|
11785
|
-
async deleteWorkflowRunById({ runId, workflowName }) {
|
|
12422
|
+
async deleteWorkflowRunById({ runId, workflowName, fence }) {
|
|
12423
|
+
const check = this.#runFenceCheck(fence, runId, "deleteWorkflowRunById");
|
|
11786
12424
|
try {
|
|
11787
|
-
|
|
12425
|
+
const collection = await this.getCollection(TABLE_WORKFLOW_SNAPSHOT);
|
|
12426
|
+
await withRunFence(this.#connector, check, (session) => collection.deleteOne({
|
|
11788
12427
|
workflow_name: workflowName,
|
|
11789
12428
|
run_id: runId
|
|
11790
|
-
});
|
|
12429
|
+
}, { session }));
|
|
11791
12430
|
} catch (error) {
|
|
12431
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
11792
12432
|
throw new MastraError({
|
|
11793
12433
|
id: createStorageErrorId("MONGODB", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"),
|
|
11794
12434
|
domain: ErrorDomain.STORAGE,
|