@mastra/mysql 0.13.0 → 0.13.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/index.cjs +1045 -449
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1046 -450
- package/dist/index.js.map +1 -1
- package/dist/storage/db/run-fencing.d.ts +34 -0
- package/dist/storage/db/run-fencing.d.ts.map +1 -0
- package/dist/storage/domains/memory/index.d.ts +51 -5
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts +16 -5
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { ErrorCategory, ErrorDomain, MastraError } from "@mastra/core/error";
|
|
2
|
-
import { AGENTS_SCHEMA, AGENT_VERSIONS_SCHEMA, AgentsStorage, BackgroundTasksStorage, BlobStore, ChannelsStorage, DATASETS_SCHEMA, DATASET_ITEMS_SCHEMA, DATASET_VERSIONS_SCHEMA, DatasetsStorage, EXPERIMENTS_SCHEMA, EXPERIMENT_RESULTS_SCHEMA, ExperimentsStorage, FAVORITES_SCHEMA, FavoritesStorage, KNOWLEDGE_ACTIVITY_SCHEMA, KNOWLEDGE_MENTIONS_SCHEMA, KNOWLEDGE_NODES_SCHEMA, KNOWLEDGE_RECORDS_SCHEMA, KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA, KnowledgeConflictError, KnowledgeNotFoundError, KnowledgeStorage, MCPClientsStorage, MCPServersStorage, MCP_CLIENTS_SCHEMA, MCP_CLIENT_VERSIONS_SCHEMA, MCP_SERVERS_SCHEMA, MCP_SERVER_VERSIONS_SCHEMA, MastraCompositeStore, MemoryStorage, OBSERVATIONAL_MEMORY_TABLE_SCHEMA, ObservabilityStorage, PROMPT_BLOCKS_SCHEMA, PROMPT_BLOCK_VERSIONS_SCHEMA, PromptBlocksStorage, SCORERS_SCHEMA, SCORER_DEFINITIONS_SCHEMA, SCORER_DEFINITION_VERSIONS_SCHEMA, SKILLS_SCHEMA, SKILL_BLOBS_SCHEMA, SKILL_VERSIONS_SCHEMA, SPAN_SCHEMA, SchedulesStorage, ScorerDefinitionsStorage, ScoresStorage, SkillsStorage, StoreOperations, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_BACKGROUND_TASKS, TABLE_CHANNEL_CONFIG, TABLE_CHANNEL_INSTALLATIONS, TABLE_CONFIGS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_FAVORITES, 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_PROMPT_BLOCKS, TABLE_PROMPT_BLOCK_VERSIONS, TABLE_RESOURCES, TABLE_SCHEDULES, TABLE_SCHEDULE_TRIGGERS, TABLE_SCHEMAS, TABLE_SCORERS, TABLE_SCORER_DEFINITIONS, TABLE_SCORER_DEFINITION_VERSIONS, TABLE_SKILLS, TABLE_SKILL_BLOBS, TABLE_SKILL_VERSIONS, TABLE_SPANS, TABLE_THREADS, TABLE_TOOL_PROVIDER_CONNECTIONS, TABLE_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, TOOL_PROVIDER_CONNECTIONS_SCHEMA, ToolProviderConnectionsStorage, TraceStatus, WORKFLOW_DEFINITIONS_SCHEMA, WORKSPACES_SCHEMA, WORKSPACE_VERSIONS_SCHEMA, WorkflowDefinitionsStorage, WorkflowsStorage, WorkspacesStorage, calculatePagination, canonicalizeKnowledgeScope, createKnowledgeUlid, createStorageErrorId, executeRetentionPrune, hasErrorCode, isKnowledgeScopeVisible, knowledgeScopeKey, knowledgeSemanticDocumentId, knowledgeSemanticIdempotencyKey, listTracesArgsSchema, matchesExpectedWorkflowStatus, normalizePerPage, normalizeScheduleTarget, parseKnowledgeNodeCursor, parseKnowledgeWikilinks, resolveListOrderBy, resolveRetentionTargets, retentionCutoffMs, toTraceSpans, validateStorageMetadataFilter } from "@mastra/core/storage";
|
|
2
|
+
import { AGENTS_SCHEMA, AGENT_VERSIONS_SCHEMA, AgentsStorage, BackgroundTasksStorage, BlobStore, ChannelsStorage, DATASETS_SCHEMA, DATASET_ITEMS_SCHEMA, DATASET_VERSIONS_SCHEMA, DatasetsStorage, EXPERIMENTS_SCHEMA, EXPERIMENT_RESULTS_SCHEMA, ExperimentsStorage, FAVORITES_SCHEMA, FavoritesStorage, KNOWLEDGE_ACTIVITY_SCHEMA, KNOWLEDGE_MENTIONS_SCHEMA, KNOWLEDGE_NODES_SCHEMA, KNOWLEDGE_RECORDS_SCHEMA, KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA, KnowledgeConflictError, KnowledgeNotFoundError, KnowledgeStorage, MCPClientsStorage, MCPServersStorage, MCP_CLIENTS_SCHEMA, MCP_CLIENT_VERSIONS_SCHEMA, MCP_SERVERS_SCHEMA, MCP_SERVER_VERSIONS_SCHEMA, MastraCompositeStore, MemoryStorage, OBSERVATIONAL_MEMORY_TABLE_SCHEMA, ObservabilityStorage, PROMPT_BLOCKS_SCHEMA, PROMPT_BLOCK_VERSIONS_SCHEMA, PromptBlocksStorage, RUN_FENCING_TABLE_SCHEMAS, RunFenceConflictError, SCORERS_SCHEMA, SCORER_DEFINITIONS_SCHEMA, SCORER_DEFINITION_VERSIONS_SCHEMA, SKILLS_SCHEMA, SKILL_BLOBS_SCHEMA, SKILL_VERSIONS_SCHEMA, SPAN_SCHEMA, SchedulesStorage, ScorerDefinitionsStorage, ScoresStorage, SkillsStorage, StoreOperations, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_BACKGROUND_TASKS, TABLE_CHANNEL_CONFIG, TABLE_CHANNEL_INSTALLATIONS, TABLE_CONFIGS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_FAVORITES, 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_PROMPT_BLOCKS, TABLE_PROMPT_BLOCK_VERSIONS, TABLE_RESOURCES, TABLE_SCHEDULES, TABLE_SCHEDULE_TRIGGERS, TABLE_SCHEMAS, TABLE_SCORERS, TABLE_SCORER_DEFINITIONS, TABLE_SCORER_DEFINITION_VERSIONS, TABLE_SKILLS, TABLE_SKILL_BLOBS, TABLE_SKILL_VERSIONS, TABLE_SPANS, TABLE_THREADS, TABLE_TOOL_PROVIDER_CONNECTIONS, TABLE_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_RUN_OWNERS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, TOOL_PROVIDER_CONNECTIONS_SCHEMA, ToolProviderConnectionsStorage, TraceStatus, WORKFLOW_DEFINITIONS_SCHEMA, WORKSPACES_SCHEMA, WORKSPACE_VERSIONS_SCHEMA, WorkflowDefinitionsStorage, WorkflowsStorage, WorkspacesStorage, calculatePagination, canonicalizeKnowledgeScope, createKnowledgeUlid, createStorageErrorId, executeRetentionPrune, hasErrorCode, isKnowledgeScopeVisible, isRunFenceConflictError, knowledgeScopeKey, knowledgeSemanticDocumentId, knowledgeSemanticIdempotencyKey, listTracesArgsSchema, matchesExpectedWorkflowStatus, matchesRunFence, normalizePerPage, normalizeScheduleTarget, parseKnowledgeNodeCursor, parseKnowledgeWikilinks, resolveListOrderBy, resolveRetentionTargets, resolveRunFence, retentionCutoffMs, toTraceSpans, validateStorageMetadataFilter } from "@mastra/core/storage";
|
|
3
3
|
import { createPool } from "mysql2/promise";
|
|
4
4
|
import { parseSqlIdentifier } from "@mastra/core/utils";
|
|
5
|
+
import { createHash } from "crypto";
|
|
5
6
|
import { MessageList } from "@mastra/core/agent";
|
|
6
7
|
import { skillSnapshotFieldValuesEqual } from "@mastra/core/storage/domains/skills";
|
|
7
8
|
//#region src/storage/db/schema-snapshot.ts
|
|
@@ -6021,9 +6022,180 @@ var MCPServersMySQL = class MCPServersMySQL extends MCPServersStorage {
|
|
|
6021
6022
|
}
|
|
6022
6023
|
};
|
|
6023
6024
|
//#endregion
|
|
6025
|
+
//#region ../../packages/_internal-core/dist/memory/index.js
|
|
6026
|
+
/**
|
|
6027
|
+
* Shared rules for observational memory lifecycle writes. Every storage adapter applies
|
|
6028
|
+
* these inside its own atomic section (lock, transaction, or conditional update), so the
|
|
6029
|
+
* decisions are identical across adapters.
|
|
6030
|
+
*/
|
|
6031
|
+
/**
|
|
6032
|
+
* Observational memory columns that lifecycle writes depend on. Storage adapters merge these
|
|
6033
|
+
* into the table schema they get from `@mastra/core`, so the columns exist even when the
|
|
6034
|
+
* installed core predates them.
|
|
6035
|
+
*/
|
|
6036
|
+
const OBSERVATIONAL_MEMORY_LIFECYCLE_COLUMNS = { supersededBy: {
|
|
6037
|
+
type: "text",
|
|
6038
|
+
nullable: true
|
|
6039
|
+
} };
|
|
6040
|
+
/** `schema` plus {@link OBSERVATIONAL_MEMORY_LIFECYCLE_COLUMNS}; `undefined` when `schema` is. */
|
|
6041
|
+
function withObservationalMemoryLifecycleColumns(schema) {
|
|
6042
|
+
return schema ? {
|
|
6043
|
+
...schema,
|
|
6044
|
+
...OBSERVATIONAL_MEMORY_LIFECYCLE_COLUMNS
|
|
6045
|
+
} : void 0;
|
|
6046
|
+
}
|
|
6047
|
+
/**
|
|
6048
|
+
* A buffered chunk stores `lastObservedAt = max message time + 1ms`. It is wholly covered
|
|
6049
|
+
* (every message it observed is already at or before the cursor) iff
|
|
6050
|
+
* `cursor >= chunk.lastObservedAt - 1ms`.
|
|
6051
|
+
*/
|
|
6052
|
+
function isBufferedChunkCoveredByCursor(chunkLastObservedAt, cursor) {
|
|
6053
|
+
if (!cursor) return false;
|
|
6054
|
+
return new Date(cursor).getTime() >= new Date(chunkLastObservedAt).getTime() - 1;
|
|
6055
|
+
}
|
|
6056
|
+
/** The later of two cursors; `undefined` only when both are absent. */
|
|
6057
|
+
function maxObservationCursor(a, b) {
|
|
6058
|
+
if (!a) return b ? new Date(b) : void 0;
|
|
6059
|
+
if (!b) return new Date(a);
|
|
6060
|
+
return new Date(Math.max(new Date(a).getTime(), new Date(b).getTime()));
|
|
6061
|
+
}
|
|
6062
|
+
/**
|
|
6063
|
+
* Decide the new generation's text when a reflection built from `snapshot` commits against
|
|
6064
|
+
* the stored record.
|
|
6065
|
+
*
|
|
6066
|
+
* - Stored text equals the snapshot: the reflection replaces it.
|
|
6067
|
+
* - Stored text extends the snapshot (activation only appends): the appended tail is kept
|
|
6068
|
+
* after the reflection, and its tokens are added.
|
|
6069
|
+
* - Anything else (a non-append rewrite): `null` — the reflection must not be applied.
|
|
6070
|
+
*/
|
|
6071
|
+
function planReflectionGenerationText(input) {
|
|
6072
|
+
const stored = input.storedObservations ?? "";
|
|
6073
|
+
const snapshot = input.snapshotObservations ?? "";
|
|
6074
|
+
if (stored === snapshot) return {
|
|
6075
|
+
mode: "equal",
|
|
6076
|
+
observations: input.reflection,
|
|
6077
|
+
tokenCount: input.tokenCount
|
|
6078
|
+
};
|
|
6079
|
+
if (!stored.startsWith(snapshot)) return null;
|
|
6080
|
+
const tail = stored.slice(snapshot.length).trimStart();
|
|
6081
|
+
if (!tail.trim()) return {
|
|
6082
|
+
mode: "equal",
|
|
6083
|
+
observations: input.reflection,
|
|
6084
|
+
tokenCount: input.tokenCount
|
|
6085
|
+
};
|
|
6086
|
+
return {
|
|
6087
|
+
mode: "append",
|
|
6088
|
+
observations: input.reflection ? `${input.reflection}\n\n${tail}` : tail,
|
|
6089
|
+
tokenCount: input.tokenCount + Math.max(0, (input.storedObservationTokenCount ?? 0) - (input.snapshotObservationTokenCount ?? 0))
|
|
6090
|
+
};
|
|
6091
|
+
}
|
|
6092
|
+
/**
|
|
6093
|
+
* Backs `updateActiveObservations` in adapters that implement `commitActiveObservations`.
|
|
6094
|
+
* Callers of `updateActiveObservations` (which resolves `void`) can't see a commit result, so a
|
|
6095
|
+
* write that was not applied throws instead of resolving as if it had committed.
|
|
6096
|
+
*/
|
|
6097
|
+
function assertActiveObservationsApplied(result, id) {
|
|
6098
|
+
if (result.applied) return;
|
|
6099
|
+
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`);
|
|
6100
|
+
}
|
|
6101
|
+
/**
|
|
6102
|
+
* Whether the stored text can still accept a reflection built from `snapshot`
|
|
6103
|
+
* (equal, or only appended to since).
|
|
6104
|
+
*/
|
|
6105
|
+
function isAppendOnlySince(storedObservations, snapshotObservations) {
|
|
6106
|
+
const stored = storedObservations ?? "";
|
|
6107
|
+
const snapshot = snapshotObservations ?? "";
|
|
6108
|
+
return stored === snapshot || stored.startsWith(snapshot);
|
|
6109
|
+
}
|
|
6110
|
+
//#endregion
|
|
6111
|
+
//#region src/storage/db/run-fencing.ts
|
|
6112
|
+
/**
|
|
6113
|
+
* Epoch milliseconds on the database clock. `UTC_TIMESTAMP` is the time the
|
|
6114
|
+
* statement started and doesn't depend on the session time zone. Every lease
|
|
6115
|
+
* comparison runs in a statement that already holds the run's owner row, so
|
|
6116
|
+
* no lock wait sits between that start time and the comparison.
|
|
6117
|
+
*/
|
|
6118
|
+
const DB_NOW_MS = `(TIMESTAMPDIFF(MICROSECOND, '1970-01-01 00:00:00', UTC_TIMESTAMP(3)) DIV 1000)`;
|
|
6119
|
+
const ER_DUP_ENTRY = 1062;
|
|
6120
|
+
const ER_LOCK_DEADLOCK = 1213;
|
|
6121
|
+
const MAX_CLAIM_ATTEMPTS = 5;
|
|
6122
|
+
/**
|
|
6123
|
+
* The check half of a fenced write; call it first inside the write's
|
|
6124
|
+
* transaction. The shared lock holds the claim row until commit, so a claim
|
|
6125
|
+
* that updates the row waits for the write to finish, and every later write
|
|
6126
|
+
* sees the new claim.
|
|
6127
|
+
*/
|
|
6128
|
+
async function assertRunFence(q, claimsTable, fence, operation) {
|
|
6129
|
+
const [rows] = await q.execute(`SELECT generation, ${quoteIdentifier("ownerId", "column name")} AS ownerId FROM ${claimsTable}
|
|
6130
|
+
WHERE ${quoteIdentifier("runId", "column name")} = ? LOCK IN SHARE MODE`, [fence.runId]);
|
|
6131
|
+
const row = rows[0];
|
|
6132
|
+
if (!matchesRunFence(row ? {
|
|
6133
|
+
generation: Number(row.generation),
|
|
6134
|
+
ownerId: String(row.ownerId)
|
|
6135
|
+
} : null, fence)) throw new RunFenceConflictError(fence, operation);
|
|
6136
|
+
}
|
|
6137
|
+
/** Runs `fn` in a transaction on its own pooled connection. */
|
|
6138
|
+
async function inTransaction(pool, fn) {
|
|
6139
|
+
const connection = await pool.getConnection();
|
|
6140
|
+
try {
|
|
6141
|
+
await connection.beginTransaction();
|
|
6142
|
+
const result = await fn(connection);
|
|
6143
|
+
await connection.commit();
|
|
6144
|
+
return result;
|
|
6145
|
+
} catch (error) {
|
|
6146
|
+
await connection.rollback().catch(() => {});
|
|
6147
|
+
throw error;
|
|
6148
|
+
} finally {
|
|
6149
|
+
connection.release();
|
|
6150
|
+
}
|
|
6151
|
+
}
|
|
6152
|
+
/**
|
|
6153
|
+
* Runs `write` directly when it carries no fence, or in a transaction behind
|
|
6154
|
+
* the fence check when it does.
|
|
6155
|
+
*/
|
|
6156
|
+
function withRunFence(pool, claimsTable, fence, operation, write) {
|
|
6157
|
+
if (!fence) return write(pool);
|
|
6158
|
+
return inTransaction(pool, async (connection) => {
|
|
6159
|
+
await assertRunFence(connection, claimsTable, fence, operation);
|
|
6160
|
+
return write(connection);
|
|
6161
|
+
});
|
|
6162
|
+
}
|
|
6163
|
+
/**
|
|
6164
|
+
* Runs a transaction that creates or updates a run's claim row, retrying when
|
|
6165
|
+
* two first claims race to insert it. Under REPEATABLE READ both lock the gap
|
|
6166
|
+
* where the row would go and one is chosen as a deadlock victim; under READ
|
|
6167
|
+
* COMMITTED the later insert hits the duplicate key. On retry the row exists
|
|
6168
|
+
* and the claim serializes behind its row lock.
|
|
6169
|
+
*/
|
|
6170
|
+
async function claimTransaction(pool, fn) {
|
|
6171
|
+
for (let attempt = 1;; attempt++) try {
|
|
6172
|
+
return await inTransaction(pool, fn);
|
|
6173
|
+
} catch (error) {
|
|
6174
|
+
const errno = error.errno;
|
|
6175
|
+
if (attempt >= MAX_CLAIM_ATTEMPTS || errno !== ER_DUP_ENTRY && errno !== ER_LOCK_DEADLOCK) throw error;
|
|
6176
|
+
}
|
|
6177
|
+
}
|
|
6178
|
+
//#endregion
|
|
6024
6179
|
//#region src/storage/domains/memory/index.ts
|
|
6025
6180
|
const OM_TABLE = "mastra_observational_memory";
|
|
6026
6181
|
const OM_TABLE_QUOTED = quoteIdentifier(OM_TABLE, "table name");
|
|
6182
|
+
const RUN_FENCES_TABLE = formatTableName(TABLE_MEMORY_RUN_FENCES);
|
|
6183
|
+
const RUN_ID$1 = quoteIdentifier("runId", "column name");
|
|
6184
|
+
const OWNER_ID$1 = quoteIdentifier("ownerId", "column name");
|
|
6185
|
+
const RETIRED_AT = quoteIdentifier("retiredAt", "column name");
|
|
6186
|
+
/** Canonical head order: newest generation, then earliest createdAt, then lowest id. */
|
|
6187
|
+
const OM_HEAD_ORDER = `${quoteIdentifier("generationCount", "column name")} DESC, ${quoteIdentifier("createdAt", "column name")} ASC, ${quoteIdentifier("id", "column name")} ASC`;
|
|
6188
|
+
/** Max `supersededBy` hops when redirecting a write aimed at a retired record. */
|
|
6189
|
+
const OM_MAX_HEAD_HOPS = 3;
|
|
6190
|
+
/** Attempts for an OM transaction that hits a deadlock or lock-wait timeout. */
|
|
6191
|
+
const OM_MAX_TRANSACTION_ATTEMPTS = 3;
|
|
6192
|
+
/** How long initialization waits for the per-key `GET_LOCK` before failing. */
|
|
6193
|
+
const OM_INIT_LOCK_TIMEOUT_SECONDS = 30;
|
|
6194
|
+
/** ER_LOCK_DEADLOCK (1213) or ER_LOCK_WAIT_TIMEOUT (1205): the transaction was rolled back and can be retried. */
|
|
6195
|
+
function isRetryableLockError(error) {
|
|
6196
|
+
const errno = error?.errno;
|
|
6197
|
+
return errno === 1213 || errno === 1205;
|
|
6198
|
+
}
|
|
6027
6199
|
function emitValidationError(message) {
|
|
6028
6200
|
return new MastraError({
|
|
6029
6201
|
id: "MYSQL_MEMORY_VALIDATION_ERROR",
|
|
@@ -6117,6 +6289,69 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
6117
6289
|
this.#skipDefaultIndexes = skipDefaultIndexes;
|
|
6118
6290
|
this.#indexes = indexes?.filter((idx) => MemoryMySQL.MANAGED_TABLES.includes(idx.table));
|
|
6119
6291
|
}
|
|
6292
|
+
supportsRunFencing() {
|
|
6293
|
+
return true;
|
|
6294
|
+
}
|
|
6295
|
+
async raiseRunFence(fence) {
|
|
6296
|
+
try {
|
|
6297
|
+
return await claimTransaction(this.pool, async (connection) => {
|
|
6298
|
+
const [rows] = await connection.execute(`SELECT generation, ${OWNER_ID$1} AS ownerId FROM ${RUN_FENCES_TABLE} WHERE ${RUN_ID$1} = ? FOR UPDATE`, [fence.runId]);
|
|
6299
|
+
const row = rows[0];
|
|
6300
|
+
if (!row) {
|
|
6301
|
+
await connection.execute(`INSERT INTO ${RUN_FENCES_TABLE} (${RUN_ID$1}, generation, ${OWNER_ID$1}) VALUES (?, ?, ?)`, [
|
|
6302
|
+
fence.runId,
|
|
6303
|
+
fence.generation,
|
|
6304
|
+
fence.ownerId
|
|
6305
|
+
]);
|
|
6306
|
+
return true;
|
|
6307
|
+
}
|
|
6308
|
+
const generation = Number(row.generation);
|
|
6309
|
+
const raises = generation < fence.generation;
|
|
6310
|
+
const reaffirms = generation === fence.generation && String(row.ownerId) === fence.ownerId;
|
|
6311
|
+
if (!raises && !reaffirms) return false;
|
|
6312
|
+
await connection.execute(`UPDATE ${RUN_FENCES_TABLE} SET generation = ?, ${OWNER_ID$1} = ?, ${RETIRED_AT} = NULL WHERE ${RUN_ID$1} = ?`, [
|
|
6313
|
+
fence.generation,
|
|
6314
|
+
fence.ownerId,
|
|
6315
|
+
fence.runId
|
|
6316
|
+
]);
|
|
6317
|
+
return true;
|
|
6318
|
+
});
|
|
6319
|
+
} catch (error) {
|
|
6320
|
+
throw new MastraError({
|
|
6321
|
+
id: createStorageErrorId("MYSQL", "RAISE_RUN_FENCE", "FAILED"),
|
|
6322
|
+
domain: ErrorDomain.STORAGE,
|
|
6323
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
6324
|
+
details: { runId: fence.runId }
|
|
6325
|
+
}, error);
|
|
6326
|
+
}
|
|
6327
|
+
}
|
|
6328
|
+
async retireRunFence(fence) {
|
|
6329
|
+
try {
|
|
6330
|
+
return await inTransaction(this.pool, async (connection) => {
|
|
6331
|
+
const [rows] = await connection.execute(`SELECT generation, ${OWNER_ID$1} AS ownerId FROM ${RUN_FENCES_TABLE} WHERE ${RUN_ID$1} = ? FOR UPDATE`, [fence.runId]);
|
|
6332
|
+
const row = rows[0];
|
|
6333
|
+
if (!row || Number(row.generation) !== fence.generation || String(row.ownerId) !== fence.ownerId) return false;
|
|
6334
|
+
await connection.execute(`UPDATE ${RUN_FENCES_TABLE} SET ${RETIRED_AT} = ${DB_NOW_MS} WHERE ${RUN_ID$1} = ?`, [fence.runId]);
|
|
6335
|
+
return true;
|
|
6336
|
+
});
|
|
6337
|
+
} catch (error) {
|
|
6338
|
+
throw new MastraError({
|
|
6339
|
+
id: createStorageErrorId("MYSQL", "RETIRE_RUN_FENCE", "FAILED"),
|
|
6340
|
+
domain: ErrorDomain.STORAGE,
|
|
6341
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
6342
|
+
details: { runId: fence.runId }
|
|
6343
|
+
}, error);
|
|
6344
|
+
}
|
|
6345
|
+
}
|
|
6346
|
+
/** Runs `write` behind its own fence, otherwise the one in scope for this store. */
|
|
6347
|
+
#fenced(fence, operation, write) {
|
|
6348
|
+
return withRunFence(this.pool, RUN_FENCES_TABLE, resolveRunFence(this, fence), operation, write);
|
|
6349
|
+
}
|
|
6350
|
+
/** The fence check for a write that already runs in its own transaction. */
|
|
6351
|
+
async #assertFence(connection, fence, operation) {
|
|
6352
|
+
const resolved = resolveRunFence(this, fence);
|
|
6353
|
+
if (resolved) await assertRunFence(connection, RUN_FENCES_TABLE, resolved, operation);
|
|
6354
|
+
}
|
|
6120
6355
|
async init() {
|
|
6121
6356
|
await this.operations.createTable({
|
|
6122
6357
|
tableName: TABLE_THREADS,
|
|
@@ -6130,7 +6365,11 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
6130
6365
|
tableName: TABLE_RESOURCES,
|
|
6131
6366
|
schema: TABLE_SCHEMAS[TABLE_RESOURCES]
|
|
6132
6367
|
});
|
|
6133
|
-
|
|
6368
|
+
await this.operations.createTable({
|
|
6369
|
+
tableName: TABLE_MEMORY_RUN_FENCES,
|
|
6370
|
+
schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_MEMORY_RUN_FENCES]
|
|
6371
|
+
});
|
|
6372
|
+
const omSchema = withObservationalMemoryLifecycleColumns(OBSERVATIONAL_MEMORY_TABLE_SCHEMA?.[OM_TABLE]);
|
|
6134
6373
|
if (omSchema) {
|
|
6135
6374
|
await this.operations.createTable({
|
|
6136
6375
|
tableName: OM_TABLE,
|
|
@@ -6153,7 +6392,8 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
6153
6392
|
"isBufferingObservation",
|
|
6154
6393
|
"isBufferingReflection",
|
|
6155
6394
|
"lastBufferedAtTokens",
|
|
6156
|
-
"lastBufferedAtTime"
|
|
6395
|
+
"lastBufferedAtTime",
|
|
6396
|
+
"supersededBy"
|
|
6157
6397
|
]
|
|
6158
6398
|
});
|
|
6159
6399
|
}
|
|
@@ -6171,6 +6411,7 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
6171
6411
|
if (err?.errno !== 1061) throw err;
|
|
6172
6412
|
}
|
|
6173
6413
|
}
|
|
6414
|
+
if (omSchema) await this.backfillSupersededBy();
|
|
6174
6415
|
await this.createDefaultIndexes();
|
|
6175
6416
|
await this.createCustomIndexes();
|
|
6176
6417
|
}
|
|
@@ -6195,6 +6436,10 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
6195
6436
|
tableName,
|
|
6196
6437
|
schema: TABLE_SCHEMAS[tableName]
|
|
6197
6438
|
}));
|
|
6439
|
+
statements.push(generateTableSQL({
|
|
6440
|
+
tableName: TABLE_MEMORY_RUN_FENCES,
|
|
6441
|
+
schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_MEMORY_RUN_FENCES]
|
|
6442
|
+
}));
|
|
6198
6443
|
for (const idx of MemoryMySQL.getDefaultIndexDefs()) statements.push(generateIndexSQL(idx));
|
|
6199
6444
|
return statements;
|
|
6200
6445
|
}
|
|
@@ -6213,6 +6458,7 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
6213
6458
|
await this.pool.execute(`DELETE FROM ${formatTableName(TABLE_MESSAGES)}`);
|
|
6214
6459
|
await this.pool.execute(`DELETE FROM ${formatTableName(TABLE_THREADS)}`);
|
|
6215
6460
|
await this.pool.execute(`DELETE FROM ${formatTableName(TABLE_RESOURCES)}`);
|
|
6461
|
+
await this.pool.execute(`DELETE FROM ${RUN_FENCES_TABLE}`);
|
|
6216
6462
|
try {
|
|
6217
6463
|
await this.pool.execute(`DELETE FROM ${OM_TABLE_QUOTED}`);
|
|
6218
6464
|
} catch (err) {
|
|
@@ -6393,8 +6639,8 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
6393
6639
|
}
|
|
6394
6640
|
return includeMessages;
|
|
6395
6641
|
}
|
|
6396
|
-
async upsertThread(thread) {
|
|
6397
|
-
|
|
6642
|
+
async upsertThread(q, thread) {
|
|
6643
|
+
const statement = prepareStatement({
|
|
6398
6644
|
tableName: TABLE_THREADS,
|
|
6399
6645
|
record: {
|
|
6400
6646
|
id: thread.id,
|
|
@@ -6405,18 +6651,22 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
6405
6651
|
updatedAt: thread.updatedAt
|
|
6406
6652
|
}
|
|
6407
6653
|
});
|
|
6654
|
+
await q.execute(statement.sql, statement.args);
|
|
6655
|
+
}
|
|
6656
|
+
async #readThread(q, threadId, resourceId) {
|
|
6657
|
+
let sql = `SELECT * FROM ${formatTableName(TABLE_THREADS)} WHERE ${quoteIdentifier("id", "column name")} = ?`;
|
|
6658
|
+
const params = [threadId];
|
|
6659
|
+
if (resourceId !== void 0) {
|
|
6660
|
+
sql += ` AND ${quoteIdentifier("resourceId", "column name")} = ?`;
|
|
6661
|
+
params.push(resourceId);
|
|
6662
|
+
}
|
|
6663
|
+
const [rows] = await q.execute(sql, params);
|
|
6664
|
+
const row = rows[0];
|
|
6665
|
+
return row ? this.mapThread(row) : null;
|
|
6408
6666
|
}
|
|
6409
6667
|
async getThreadById({ threadId, resourceId }) {
|
|
6410
6668
|
try {
|
|
6411
|
-
|
|
6412
|
-
const params = [threadId];
|
|
6413
|
-
if (resourceId !== void 0) {
|
|
6414
|
-
sql += ` AND ${quoteIdentifier("resourceId", "column name")} = ?`;
|
|
6415
|
-
params.push(resourceId);
|
|
6416
|
-
}
|
|
6417
|
-
const [rows] = await this.pool.execute(sql, params);
|
|
6418
|
-
const row = rows[0];
|
|
6419
|
-
return row ? this.mapThread(row) : null;
|
|
6669
|
+
return await this.#readThread(this.pool, threadId, resourceId);
|
|
6420
6670
|
} catch (error) {
|
|
6421
6671
|
throw new MastraError({
|
|
6422
6672
|
id: "MYSQL_MEMORY_GET_THREAD_BY_ID_FAILED",
|
|
@@ -6569,11 +6819,12 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
6569
6819
|
} : void 0
|
|
6570
6820
|
});
|
|
6571
6821
|
}
|
|
6572
|
-
async saveThread({ thread }) {
|
|
6822
|
+
async saveThread({ thread, fence }) {
|
|
6573
6823
|
try {
|
|
6574
|
-
await this.upsertThread(thread);
|
|
6824
|
+
await this.#fenced(fence, "saveThread", (q) => this.upsertThread(q, thread));
|
|
6575
6825
|
return thread;
|
|
6576
6826
|
} catch (error) {
|
|
6827
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
6577
6828
|
throw new MastraError({
|
|
6578
6829
|
id: "MYSQL_MEMORY_SAVE_THREAD_FAILED",
|
|
6579
6830
|
domain: ErrorDomain.STORAGE,
|
|
@@ -6582,36 +6833,39 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
6582
6833
|
}, error);
|
|
6583
6834
|
}
|
|
6584
6835
|
}
|
|
6585
|
-
async updateThread({ id, title, metadata }) {
|
|
6836
|
+
async updateThread({ id, title, metadata, fence }) {
|
|
6586
6837
|
try {
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6838
|
+
return await this.#fenced(fence, "updateThread", async (q) => {
|
|
6839
|
+
const existing = await this.#readThread(q, id);
|
|
6840
|
+
if (!existing) throw new MastraError({
|
|
6841
|
+
id: createStorageErrorId("MYSQL", "UPDATE_THREAD", "NOT_FOUND"),
|
|
6842
|
+
domain: ErrorDomain.STORAGE,
|
|
6843
|
+
category: ErrorCategory.USER,
|
|
6844
|
+
text: `Thread ${id} not found`,
|
|
6845
|
+
details: { threadId: id }
|
|
6846
|
+
});
|
|
6847
|
+
const mergedMetadata = {
|
|
6848
|
+
...existing.metadata ?? {},
|
|
6849
|
+
...metadata ?? {}
|
|
6850
|
+
};
|
|
6851
|
+
const updatedAt = /* @__PURE__ */ new Date();
|
|
6852
|
+
const statement = prepareUpdateStatement({
|
|
6853
|
+
tableName: TABLE_THREADS,
|
|
6854
|
+
keys: { id },
|
|
6855
|
+
updates: {
|
|
6856
|
+
title: title ?? existing.title,
|
|
6857
|
+
metadata: JSON.stringify(mergedMetadata),
|
|
6858
|
+
updatedAt
|
|
6859
|
+
}
|
|
6860
|
+
});
|
|
6861
|
+
await q.execute(statement.sql, statement.args);
|
|
6862
|
+
return {
|
|
6863
|
+
...existing,
|
|
6604
6864
|
title: title ?? existing.title,
|
|
6605
|
-
metadata:
|
|
6865
|
+
metadata: mergedMetadata,
|
|
6606
6866
|
updatedAt
|
|
6607
|
-
}
|
|
6867
|
+
};
|
|
6608
6868
|
});
|
|
6609
|
-
return {
|
|
6610
|
-
...existing,
|
|
6611
|
-
title: title ?? existing.title,
|
|
6612
|
-
metadata: mergedMetadata,
|
|
6613
|
-
updatedAt
|
|
6614
|
-
};
|
|
6615
6869
|
} catch (error) {
|
|
6616
6870
|
if (error instanceof MastraError) throw error;
|
|
6617
6871
|
throw new MastraError({
|
|
@@ -6692,6 +6946,7 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
6692
6946
|
const insertedIds = [];
|
|
6693
6947
|
try {
|
|
6694
6948
|
await connection.beginTransaction();
|
|
6949
|
+
await this.#assertFence(connection, args.fence, "saveMessages");
|
|
6695
6950
|
for (const message of messages) {
|
|
6696
6951
|
if (!message.threadId) throw new MastraError({
|
|
6697
6952
|
id: createStorageErrorId("MYSQL", "SAVE_MESSAGES", "INVALID_INPUT"),
|
|
@@ -6785,18 +7040,29 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
6785
7040
|
if (update.threadId && update.threadId !== current.threadId && current.threadId) affectedThreads.set(current.threadId, now);
|
|
6786
7041
|
}
|
|
6787
7042
|
if (!updates.length) return existing;
|
|
6788
|
-
await this.
|
|
6789
|
-
|
|
6790
|
-
|
|
6791
|
-
|
|
6792
|
-
|
|
6793
|
-
|
|
6794
|
-
|
|
6795
|
-
|
|
7043
|
+
await inTransaction(this.pool, async (connection) => {
|
|
7044
|
+
await this.#assertFence(connection, args.fence, "updateMessages");
|
|
7045
|
+
for (const { keys, data } of updates) {
|
|
7046
|
+
const statement = prepareUpdateStatement({
|
|
7047
|
+
tableName: TABLE_MESSAGES,
|
|
7048
|
+
updates: data,
|
|
7049
|
+
keys
|
|
7050
|
+
});
|
|
7051
|
+
await connection.execute(statement.sql, statement.args);
|
|
7052
|
+
}
|
|
7053
|
+
for (const [threadId, timestamp] of affectedThreads.entries()) {
|
|
7054
|
+
const statement = prepareUpdateStatement({
|
|
7055
|
+
tableName: TABLE_THREADS,
|
|
7056
|
+
updates: { updatedAt: timestamp },
|
|
7057
|
+
keys: { id: threadId }
|
|
7058
|
+
});
|
|
7059
|
+
await connection.execute(statement.sql, statement.args);
|
|
7060
|
+
}
|
|
6796
7061
|
});
|
|
6797
7062
|
const { messages: updated } = await this.listMessagesById({ messageIds: messages.map((m) => m.id) });
|
|
6798
7063
|
return updated;
|
|
6799
7064
|
} catch (error) {
|
|
7065
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
6800
7066
|
throw new MastraError({
|
|
6801
7067
|
id: "MYSQL_MEMORY_UPDATE_MESSAGES_FAILED",
|
|
6802
7068
|
domain: ErrorDomain.STORAGE,
|
|
@@ -6804,19 +7070,22 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
6804
7070
|
}, error);
|
|
6805
7071
|
}
|
|
6806
7072
|
}
|
|
6807
|
-
async deleteMessages(messageIds) {
|
|
7073
|
+
async deleteMessages(messageIds, options) {
|
|
6808
7074
|
if (!messageIds.length) return;
|
|
6809
7075
|
try {
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
7076
|
+
await this.#fenced(options?.fence, "deleteMessages", async (q) => {
|
|
7077
|
+
const placeholders = messageIds.map(() => "?").join(", ");
|
|
7078
|
+
const [rows] = await q.execute(`SELECT DISTINCT thread_id FROM ${formatTableName(TABLE_MESSAGES)} WHERE id IN (${placeholders})`, messageIds);
|
|
7079
|
+
const threadIds = rows.map((row) => row.thread_id).filter((threadId) => Boolean(threadId));
|
|
7080
|
+
await q.execute(`DELETE FROM ${formatTableName(TABLE_MESSAGES)} WHERE id IN (${placeholders})`, messageIds);
|
|
7081
|
+
if (threadIds.length) {
|
|
7082
|
+
const threadPlaceholders = threadIds.map(() => "?").join(", ");
|
|
7083
|
+
const timestamp = transformToSqlValue(/* @__PURE__ */ new Date());
|
|
7084
|
+
await q.execute(`UPDATE ${formatTableName(TABLE_THREADS)} SET ${quoteIdentifier("updatedAt", "column name")} = ? WHERE id IN (${threadPlaceholders})`, [timestamp, ...threadIds]);
|
|
7085
|
+
}
|
|
7086
|
+
});
|
|
6819
7087
|
} catch (error) {
|
|
7088
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
6820
7089
|
throw new MastraError({
|
|
6821
7090
|
id: "MYSQL_MEMORY_DELETE_MESSAGES_FAILED",
|
|
6822
7091
|
domain: ErrorDomain.STORAGE,
|
|
@@ -7215,19 +7484,23 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
7215
7484
|
}, error);
|
|
7216
7485
|
}
|
|
7217
7486
|
}
|
|
7487
|
+
async #insertResource(q, resource) {
|
|
7488
|
+
const metadataValue = resource.metadata === void 0 || resource.metadata === null ? null : JSON.stringify(resource.metadata);
|
|
7489
|
+
const statement = prepareStatement({
|
|
7490
|
+
tableName: TABLE_RESOURCES,
|
|
7491
|
+
record: {
|
|
7492
|
+
id: resource.id,
|
|
7493
|
+
workingMemory: resource.workingMemory ?? null,
|
|
7494
|
+
metadata: metadataValue,
|
|
7495
|
+
createdAt: resource.createdAt ?? /* @__PURE__ */ new Date(),
|
|
7496
|
+
updatedAt: resource.updatedAt ?? /* @__PURE__ */ new Date()
|
|
7497
|
+
}
|
|
7498
|
+
});
|
|
7499
|
+
await q.execute(statement.sql, statement.args);
|
|
7500
|
+
}
|
|
7218
7501
|
async saveResource({ resource }) {
|
|
7219
7502
|
try {
|
|
7220
|
-
|
|
7221
|
-
await this.operations.insert({
|
|
7222
|
-
tableName: TABLE_RESOURCES,
|
|
7223
|
-
record: {
|
|
7224
|
-
id: resource.id,
|
|
7225
|
-
workingMemory: resource.workingMemory ?? null,
|
|
7226
|
-
metadata: metadataValue,
|
|
7227
|
-
createdAt: resource.createdAt ?? /* @__PURE__ */ new Date(),
|
|
7228
|
-
updatedAt: resource.updatedAt ?? /* @__PURE__ */ new Date()
|
|
7229
|
-
}
|
|
7230
|
-
});
|
|
7503
|
+
await this.#insertResource(this.pool, resource);
|
|
7231
7504
|
return resource;
|
|
7232
7505
|
} catch (error) {
|
|
7233
7506
|
throw new MastraError({
|
|
@@ -7238,7 +7511,7 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
7238
7511
|
}, error);
|
|
7239
7512
|
}
|
|
7240
7513
|
}
|
|
7241
|
-
async updateResource({ resourceId, workingMemory, metadata }) {
|
|
7514
|
+
async updateResource({ resourceId, workingMemory, metadata, fence }) {
|
|
7242
7515
|
try {
|
|
7243
7516
|
const existing = await this.getResourceById({ resourceId });
|
|
7244
7517
|
if (!existing) {
|
|
@@ -7249,22 +7522,24 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
7249
7522
|
createdAt: /* @__PURE__ */ new Date(),
|
|
7250
7523
|
updatedAt: /* @__PURE__ */ new Date()
|
|
7251
7524
|
};
|
|
7252
|
-
|
|
7525
|
+
await this.#fenced(fence, "updateResource", (q) => this.#insertResource(q, newResource));
|
|
7526
|
+
return newResource;
|
|
7253
7527
|
}
|
|
7254
7528
|
const mergedMetadata = metadata !== void 0 ? {
|
|
7255
7529
|
...existing.metadata ?? {},
|
|
7256
7530
|
...metadata ?? {}
|
|
7257
7531
|
} : existing.metadata;
|
|
7258
7532
|
const metadataValue = metadata !== void 0 ? metadata === null ? null : JSON.stringify(mergedMetadata ?? {}) : void 0;
|
|
7259
|
-
|
|
7533
|
+
const statement = prepareUpdateStatement({
|
|
7260
7534
|
tableName: TABLE_RESOURCES,
|
|
7261
7535
|
keys: { id: resourceId },
|
|
7262
|
-
|
|
7536
|
+
updates: {
|
|
7263
7537
|
...workingMemory !== void 0 ? { workingMemory } : {},
|
|
7264
7538
|
...metadataValue !== void 0 ? { metadata: metadataValue } : {},
|
|
7265
7539
|
updatedAt: /* @__PURE__ */ new Date()
|
|
7266
7540
|
}
|
|
7267
7541
|
});
|
|
7542
|
+
await this.#fenced(fence, "updateResource", (q) => q.execute(statement.sql, statement.args));
|
|
7268
7543
|
const updated = await this.getResourceById({ resourceId });
|
|
7269
7544
|
if (!updated) throw new MastraError({
|
|
7270
7545
|
id: createStorageErrorId("MYSQL", "UPDATE_RESOURCE", "NOT_FOUND_AFTER_UPDATE"),
|
|
@@ -7287,7 +7562,7 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
7287
7562
|
async getObservationalMemory(threadId, resourceId) {
|
|
7288
7563
|
try {
|
|
7289
7564
|
const lookupKey = this.getOMKey(threadId, resourceId);
|
|
7290
|
-
const [rows] = await this.pool.execute(`SELECT * FROM ${OM_TABLE_QUOTED} WHERE ${omCol("lookupKey")} = ? ORDER BY ${
|
|
7565
|
+
const [rows] = await this.pool.execute(`SELECT * FROM ${OM_TABLE_QUOTED} WHERE ${omCol("lookupKey")} = ? ORDER BY ${OM_HEAD_ORDER} LIMIT 1`, [lookupKey]);
|
|
7291
7566
|
if (!rows || rows.length === 0) return null;
|
|
7292
7567
|
return this.parseOMRow(rows[0]);
|
|
7293
7568
|
} catch (error) {
|
|
@@ -7349,32 +7624,11 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
7349
7624
|
}
|
|
7350
7625
|
async initializeObservationalMemory(input) {
|
|
7351
7626
|
try {
|
|
7627
|
+
const lookupKey = this.getOMKey(input.threadId, input.resourceId);
|
|
7352
7628
|
const id = globalThis.crypto.randomUUID();
|
|
7353
7629
|
const now = /* @__PURE__ */ new Date();
|
|
7354
|
-
const
|
|
7355
|
-
|
|
7356
|
-
id,
|
|
7357
|
-
scope: input.scope,
|
|
7358
|
-
threadId: input.threadId,
|
|
7359
|
-
resourceId: input.resourceId,
|
|
7360
|
-
createdAt: now,
|
|
7361
|
-
updatedAt: now,
|
|
7362
|
-
lastObservedAt: void 0,
|
|
7363
|
-
originType: "initial",
|
|
7364
|
-
generationCount: 0,
|
|
7365
|
-
activeObservations: "",
|
|
7366
|
-
totalTokensObserved: 0,
|
|
7367
|
-
observationTokenCount: 0,
|
|
7368
|
-
pendingMessageTokens: 0,
|
|
7369
|
-
isReflecting: false,
|
|
7370
|
-
isObserving: false,
|
|
7371
|
-
isBufferingObservation: false,
|
|
7372
|
-
isBufferingReflection: false,
|
|
7373
|
-
lastBufferedAtTokens: 0,
|
|
7374
|
-
lastBufferedAtTime: null,
|
|
7375
|
-
config: input.config,
|
|
7376
|
-
observedTimezone: input.observedTimezone
|
|
7377
|
-
};
|
|
7630
|
+
const existing = await this.getObservationalMemory(input.threadId, input.resourceId);
|
|
7631
|
+
if (existing) return existing;
|
|
7378
7632
|
const nowSql = transformToSqlValue(now);
|
|
7379
7633
|
const cols = [
|
|
7380
7634
|
"id",
|
|
@@ -7400,36 +7654,53 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
7400
7654
|
"lastBufferedAtTime",
|
|
7401
7655
|
"observedTimezone",
|
|
7402
7656
|
"createdAt",
|
|
7403
|
-
"updatedAt"
|
|
7657
|
+
"updatedAt",
|
|
7658
|
+
"supersededBy"
|
|
7404
7659
|
].map(omCol).join(", ");
|
|
7405
|
-
const placeholders = Array.from({ length:
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
|
|
7410
|
-
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
|
|
7660
|
+
const placeholders = Array.from({ length: 25 }, () => "?").join(", ");
|
|
7661
|
+
const lockName = `mastra_om_init:${createHash("sha256").update(lookupKey).digest("hex").slice(0, 40)}`;
|
|
7662
|
+
const connection = await this.pool.getConnection();
|
|
7663
|
+
try {
|
|
7664
|
+
const [lockRows] = await connection.query("SELECT GET_LOCK(?, ?) AS acquired", [lockName, OM_INIT_LOCK_TIMEOUT_SECONDS]);
|
|
7665
|
+
if (Number(lockRows[0]?.acquired) !== 1) throw new Error(`Timed out waiting for the observational memory initialization lock for ${lookupKey}`);
|
|
7666
|
+
try {
|
|
7667
|
+
const [liveRows] = await connection.query(`SELECT 1 FROM ${OM_TABLE_QUOTED} WHERE ${omCol("lookupKey")} = ? AND ${omCol("supersededBy")} IS NULL LIMIT 1`, [lookupKey]);
|
|
7668
|
+
if (liveRows.length === 0) await connection.execute(`INSERT INTO ${OM_TABLE_QUOTED} (${cols}) VALUES (${placeholders})`, [
|
|
7669
|
+
id,
|
|
7670
|
+
lookupKey,
|
|
7671
|
+
input.scope,
|
|
7672
|
+
input.resourceId,
|
|
7673
|
+
input.threadId || null,
|
|
7674
|
+
"",
|
|
7675
|
+
null,
|
|
7676
|
+
"initial",
|
|
7677
|
+
JSON.stringify(input.config),
|
|
7678
|
+
0,
|
|
7679
|
+
null,
|
|
7680
|
+
null,
|
|
7681
|
+
0,
|
|
7682
|
+
0,
|
|
7683
|
+
0,
|
|
7684
|
+
false,
|
|
7685
|
+
false,
|
|
7686
|
+
false,
|
|
7687
|
+
false,
|
|
7688
|
+
0,
|
|
7689
|
+
null,
|
|
7690
|
+
input.observedTimezone || null,
|
|
7691
|
+
nowSql,
|
|
7692
|
+
nowSql,
|
|
7693
|
+
null
|
|
7694
|
+
]);
|
|
7695
|
+
} finally {
|
|
7696
|
+
await connection.query("SELECT RELEASE_LOCK(?)", [lockName]);
|
|
7697
|
+
}
|
|
7698
|
+
} finally {
|
|
7699
|
+
connection.release();
|
|
7700
|
+
}
|
|
7701
|
+
const head = await this.getObservationalMemory(input.threadId, input.resourceId);
|
|
7702
|
+
if (!head) throwOMNotFound(id, "INITIALIZE_OBSERVATIONAL_MEMORY");
|
|
7703
|
+
return head;
|
|
7433
7704
|
} catch (error) {
|
|
7434
7705
|
rethrowOrWrapOM(error, "", "INITIALIZE_OBSERVATIONAL_MEMORY", {
|
|
7435
7706
|
threadId: input.threadId,
|
|
@@ -7438,119 +7709,153 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
7438
7709
|
}
|
|
7439
7710
|
}
|
|
7440
7711
|
async updateActiveObservations(input) {
|
|
7712
|
+
assertActiveObservationsApplied(await this.commitActiveObservations(input), input.id);
|
|
7713
|
+
}
|
|
7714
|
+
async commitActiveObservations(input) {
|
|
7441
7715
|
try {
|
|
7442
|
-
const now = /* @__PURE__ */ new Date();
|
|
7443
7716
|
const observedMessageIdsJson = input.observedMessageIds ? JSON.stringify(input.observedMessageIds) : null;
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
input.
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7717
|
+
return await this.withOMTransaction(async (connection) => {
|
|
7718
|
+
const row = await this.lockOMRow(connection, input.id);
|
|
7719
|
+
if (!row) throwOMNotFound(input.id, "UPDATE_ACTIVE_OBSERVATIONS");
|
|
7720
|
+
if (row.supersededBy) return {
|
|
7721
|
+
applied: false,
|
|
7722
|
+
reason: "retired"
|
|
7723
|
+
};
|
|
7724
|
+
if (input.expectedActiveObservations !== void 0 && input.expectedActiveObservations !== (row.activeObservations || "")) return {
|
|
7725
|
+
applied: false,
|
|
7726
|
+
reason: "conflict"
|
|
7727
|
+
};
|
|
7728
|
+
const lastObservedAt = maxObservationCursor(parseDateTime(row.lastObservedAt), input.lastObservedAt);
|
|
7729
|
+
await connection.execute(`UPDATE ${OM_TABLE_QUOTED} SET
|
|
7730
|
+
${omCol("activeObservations")} = ?,
|
|
7731
|
+
${omCol("lastObservedAt")} = ?,
|
|
7732
|
+
${omCol("pendingMessageTokens")} = 0,
|
|
7733
|
+
${omCol("observationTokenCount")} = ?,
|
|
7734
|
+
${omCol("totalTokensObserved")} = ${omCol("totalTokensObserved")} + ?,
|
|
7735
|
+
${omCol("observedMessageIds")} = ?,
|
|
7736
|
+
${omCol("updatedAt")} = ?
|
|
7737
|
+
WHERE ${omCol("id")} = ?`, [
|
|
7738
|
+
input.observations,
|
|
7739
|
+
transformToSqlValue(lastObservedAt),
|
|
7740
|
+
input.tokenCount,
|
|
7741
|
+
input.tokenCount,
|
|
7742
|
+
observedMessageIdsJson,
|
|
7743
|
+
transformToSqlValue(/* @__PURE__ */ new Date()),
|
|
7744
|
+
input.id
|
|
7745
|
+
]);
|
|
7746
|
+
return { applied: true };
|
|
7747
|
+
}, "commitActiveObservations");
|
|
7462
7748
|
} catch (error) {
|
|
7463
7749
|
rethrowOrWrapOM(error, input.id, "UPDATE_ACTIVE_OBSERVATIONS");
|
|
7464
7750
|
}
|
|
7465
7751
|
}
|
|
7466
7752
|
async createReflectionGeneration(input) {
|
|
7467
7753
|
try {
|
|
7468
|
-
const
|
|
7469
|
-
|
|
7470
|
-
|
|
7471
|
-
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
isReflecting: false,
|
|
7486
|
-
isObserving: false,
|
|
7487
|
-
isBufferingObservation: false,
|
|
7488
|
-
isBufferingReflection: false,
|
|
7489
|
-
lastBufferedAtTokens: 0,
|
|
7490
|
-
lastBufferedAtTime: null,
|
|
7491
|
-
config: input.currentRecord.config,
|
|
7492
|
-
metadata: input.currentRecord.metadata,
|
|
7493
|
-
observedTimezone: input.currentRecord.observedTimezone
|
|
7494
|
-
};
|
|
7495
|
-
const nowSql = transformToSqlValue(now);
|
|
7496
|
-
const cols = [
|
|
7497
|
-
"id",
|
|
7498
|
-
"lookupKey",
|
|
7499
|
-
"scope",
|
|
7500
|
-
"resourceId",
|
|
7501
|
-
"threadId",
|
|
7502
|
-
"activeObservations",
|
|
7503
|
-
"activeObservationsPendingUpdate",
|
|
7504
|
-
"originType",
|
|
7505
|
-
"config",
|
|
7506
|
-
"generationCount",
|
|
7507
|
-
"lastObservedAt",
|
|
7508
|
-
"lastReflectionAt",
|
|
7509
|
-
"pendingMessageTokens",
|
|
7510
|
-
"totalTokensObserved",
|
|
7511
|
-
"observationTokenCount",
|
|
7512
|
-
"isObserving",
|
|
7513
|
-
"isReflecting",
|
|
7514
|
-
"isBufferingObservation",
|
|
7515
|
-
"isBufferingReflection",
|
|
7516
|
-
"lastBufferedAtTokens",
|
|
7517
|
-
"lastBufferedAtTime",
|
|
7518
|
-
"observedTimezone",
|
|
7519
|
-
"createdAt",
|
|
7520
|
-
"updatedAt"
|
|
7521
|
-
].map(omCol).join(", ");
|
|
7522
|
-
const placeholders = Array.from({ length: 24 }, () => "?").join(", ");
|
|
7523
|
-
await this.pool.execute(`INSERT INTO ${OM_TABLE_QUOTED} (${cols}) VALUES (${placeholders})`, [
|
|
7524
|
-
id,
|
|
7525
|
-
lookupKey,
|
|
7526
|
-
record.scope,
|
|
7527
|
-
record.resourceId,
|
|
7528
|
-
record.threadId || null,
|
|
7529
|
-
input.reflection,
|
|
7530
|
-
null,
|
|
7531
|
-
"reflection",
|
|
7532
|
-
JSON.stringify(record.config),
|
|
7533
|
-
input.currentRecord.generationCount + 1,
|
|
7534
|
-
record.lastObservedAt ? transformToSqlValue(record.lastObservedAt) : null,
|
|
7535
|
-
nowSql,
|
|
7536
|
-
record.pendingMessageTokens,
|
|
7537
|
-
record.totalTokensObserved,
|
|
7538
|
-
record.observationTokenCount,
|
|
7539
|
-
false,
|
|
7540
|
-
false,
|
|
7541
|
-
false,
|
|
7542
|
-
false,
|
|
7543
|
-
0,
|
|
7544
|
-
null,
|
|
7545
|
-
record.observedTimezone || null,
|
|
7546
|
-
nowSql,
|
|
7547
|
-
nowSql
|
|
7548
|
-
]);
|
|
7549
|
-
return record;
|
|
7754
|
+
const { currentRecord } = input;
|
|
7755
|
+
return await this.withOMTransaction(async (connection) => {
|
|
7756
|
+
const row = await this.lockOMRow(connection, currentRecord.id);
|
|
7757
|
+
if (!row) return currentRecord;
|
|
7758
|
+
if (row.supersededBy) return this.parseOMRow(await this.resolveLiveOMRow(connection, row));
|
|
7759
|
+
const stored = this.parseOMRow(row);
|
|
7760
|
+
const plan = planReflectionGenerationText({
|
|
7761
|
+
storedObservations: stored.activeObservations,
|
|
7762
|
+
storedObservationTokenCount: stored.observationTokenCount,
|
|
7763
|
+
snapshotObservations: currentRecord.activeObservations,
|
|
7764
|
+
snapshotObservationTokenCount: currentRecord.observationTokenCount,
|
|
7765
|
+
reflection: input.reflection,
|
|
7766
|
+
tokenCount: input.tokenCount
|
|
7767
|
+
});
|
|
7768
|
+
if (!plan) return stored;
|
|
7769
|
+
return this.rollOverOMRow(connection, stored.id, plan.observations, plan.tokenCount, input.newRecordId, false);
|
|
7770
|
+
}, "createReflectionGeneration");
|
|
7550
7771
|
} catch (error) {
|
|
7551
7772
|
rethrowOrWrapOM(error, input.currentRecord.id, "CREATE_REFLECTION_GENERATION", { currentRecordId: input.currentRecord.id });
|
|
7552
7773
|
}
|
|
7553
7774
|
}
|
|
7775
|
+
async #insertReflectionGeneration(q, input) {
|
|
7776
|
+
const id = globalThis.crypto.randomUUID();
|
|
7777
|
+
const now = /* @__PURE__ */ new Date();
|
|
7778
|
+
const lookupKey = this.getOMKey(input.currentRecord.threadId, input.currentRecord.resourceId);
|
|
7779
|
+
const record = {
|
|
7780
|
+
id,
|
|
7781
|
+
scope: input.currentRecord.scope,
|
|
7782
|
+
threadId: input.currentRecord.threadId,
|
|
7783
|
+
resourceId: input.currentRecord.resourceId,
|
|
7784
|
+
createdAt: now,
|
|
7785
|
+
updatedAt: now,
|
|
7786
|
+
lastObservedAt: input.currentRecord.lastObservedAt,
|
|
7787
|
+
originType: "reflection",
|
|
7788
|
+
generationCount: input.currentRecord.generationCount + 1,
|
|
7789
|
+
activeObservations: input.reflection,
|
|
7790
|
+
totalTokensObserved: input.currentRecord.totalTokensObserved,
|
|
7791
|
+
observationTokenCount: input.tokenCount,
|
|
7792
|
+
pendingMessageTokens: 0,
|
|
7793
|
+
isReflecting: false,
|
|
7794
|
+
isObserving: false,
|
|
7795
|
+
isBufferingObservation: false,
|
|
7796
|
+
isBufferingReflection: false,
|
|
7797
|
+
lastBufferedAtTokens: 0,
|
|
7798
|
+
lastBufferedAtTime: null,
|
|
7799
|
+
config: input.currentRecord.config,
|
|
7800
|
+
metadata: input.currentRecord.metadata,
|
|
7801
|
+
observedTimezone: input.currentRecord.observedTimezone
|
|
7802
|
+
};
|
|
7803
|
+
const nowSql = transformToSqlValue(now);
|
|
7804
|
+
const cols = [
|
|
7805
|
+
"id",
|
|
7806
|
+
"lookupKey",
|
|
7807
|
+
"scope",
|
|
7808
|
+
"resourceId",
|
|
7809
|
+
"threadId",
|
|
7810
|
+
"activeObservations",
|
|
7811
|
+
"activeObservationsPendingUpdate",
|
|
7812
|
+
"originType",
|
|
7813
|
+
"config",
|
|
7814
|
+
"generationCount",
|
|
7815
|
+
"lastObservedAt",
|
|
7816
|
+
"lastReflectionAt",
|
|
7817
|
+
"pendingMessageTokens",
|
|
7818
|
+
"totalTokensObserved",
|
|
7819
|
+
"observationTokenCount",
|
|
7820
|
+
"isObserving",
|
|
7821
|
+
"isReflecting",
|
|
7822
|
+
"isBufferingObservation",
|
|
7823
|
+
"isBufferingReflection",
|
|
7824
|
+
"lastBufferedAtTokens",
|
|
7825
|
+
"lastBufferedAtTime",
|
|
7826
|
+
"observedTimezone",
|
|
7827
|
+
"createdAt",
|
|
7828
|
+
"updatedAt"
|
|
7829
|
+
].map(omCol).join(", ");
|
|
7830
|
+
const placeholders = Array.from({ length: 24 }, () => "?").join(", ");
|
|
7831
|
+
await q.execute(`INSERT INTO ${OM_TABLE_QUOTED} (${cols}) VALUES (${placeholders})`, [
|
|
7832
|
+
id,
|
|
7833
|
+
lookupKey,
|
|
7834
|
+
record.scope,
|
|
7835
|
+
record.resourceId,
|
|
7836
|
+
record.threadId || null,
|
|
7837
|
+
input.reflection,
|
|
7838
|
+
null,
|
|
7839
|
+
"reflection",
|
|
7840
|
+
JSON.stringify(record.config),
|
|
7841
|
+
input.currentRecord.generationCount + 1,
|
|
7842
|
+
record.lastObservedAt ? transformToSqlValue(record.lastObservedAt) : null,
|
|
7843
|
+
nowSql,
|
|
7844
|
+
record.pendingMessageTokens,
|
|
7845
|
+
record.totalTokensObserved,
|
|
7846
|
+
record.observationTokenCount,
|
|
7847
|
+
false,
|
|
7848
|
+
false,
|
|
7849
|
+
false,
|
|
7850
|
+
false,
|
|
7851
|
+
0,
|
|
7852
|
+
null,
|
|
7853
|
+
record.observedTimezone || null,
|
|
7854
|
+
nowSql,
|
|
7855
|
+
nowSql
|
|
7856
|
+
]);
|
|
7857
|
+
return record;
|
|
7858
|
+
}
|
|
7554
7859
|
async setReflectingFlag(id, isReflecting) {
|
|
7555
7860
|
await this.updateOMFlag(id, "isReflecting", isReflecting, "SET_REFLECTING_FLAG");
|
|
7556
7861
|
}
|
|
@@ -7559,20 +7864,22 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
7559
7864
|
}
|
|
7560
7865
|
async setBufferingObservationFlag(id, isBuffering, lastBufferedAtTokens) {
|
|
7561
7866
|
try {
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
|
|
7566
|
-
|
|
7567
|
-
|
|
7568
|
-
|
|
7569
|
-
|
|
7570
|
-
|
|
7571
|
-
|
|
7572
|
-
|
|
7573
|
-
|
|
7574
|
-
|
|
7575
|
-
|
|
7867
|
+
await this.withOMTransaction(async (connection) => {
|
|
7868
|
+
const row = await this.lockOMRow(connection, id);
|
|
7869
|
+
if (!row) throwOMNotFound(id, "SET_BUFFERING_OBSERVATION_FLAG");
|
|
7870
|
+
const target = await this.resolveLiveOMRow(connection, row);
|
|
7871
|
+
const sets = [
|
|
7872
|
+
`${omCol("isBufferingObservation")} = ?`,
|
|
7873
|
+
...lastBufferedAtTokens !== void 0 ? [`${omCol("lastBufferedAtTokens")} = ?`] : [],
|
|
7874
|
+
`${omCol("updatedAt")} = ?`
|
|
7875
|
+
].join(", ");
|
|
7876
|
+
await connection.execute(`UPDATE ${OM_TABLE_QUOTED} SET ${sets} WHERE ${omCol("id")} = ?`, [
|
|
7877
|
+
isBuffering,
|
|
7878
|
+
...lastBufferedAtTokens !== void 0 ? [lastBufferedAtTokens] : [],
|
|
7879
|
+
transformToSqlValue(/* @__PURE__ */ new Date()),
|
|
7880
|
+
target.id
|
|
7881
|
+
]);
|
|
7882
|
+
});
|
|
7576
7883
|
} catch (error) {
|
|
7577
7884
|
rethrowOrWrapOM(error, id, "SET_BUFFERING_OBSERVATION_FLAG");
|
|
7578
7885
|
}
|
|
@@ -7593,83 +7900,78 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
7593
7900
|
}
|
|
7594
7901
|
async setPendingMessageTokens(id, tokenCount) {
|
|
7595
7902
|
try {
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
|
|
7903
|
+
await this.withOMTransaction(async (connection) => {
|
|
7904
|
+
const row = await this.lockOMRow(connection, id);
|
|
7905
|
+
if (!row) throwOMNotFound(id, "SET_PENDING_MESSAGE_TOKENS");
|
|
7906
|
+
const target = await this.resolveLiveOMRow(connection, row);
|
|
7907
|
+
await connection.execute(`UPDATE ${OM_TABLE_QUOTED} SET
|
|
7908
|
+
${omCol("pendingMessageTokens")} = ?,
|
|
7909
|
+
${omCol("updatedAt")} = ?
|
|
7910
|
+
WHERE ${omCol("id")} = ?`, [
|
|
7911
|
+
tokenCount,
|
|
7912
|
+
transformToSqlValue(/* @__PURE__ */ new Date()),
|
|
7913
|
+
target.id
|
|
7914
|
+
]);
|
|
7915
|
+
});
|
|
7605
7916
|
} catch (error) {
|
|
7606
7917
|
rethrowOrWrapOM(error, id, "SET_PENDING_MESSAGE_TOKENS");
|
|
7607
7918
|
}
|
|
7608
7919
|
}
|
|
7609
7920
|
async updateBufferedObservations(input) {
|
|
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
|
-
|
|
7921
|
+
await this.appendBufferedObservations(input);
|
|
7922
|
+
}
|
|
7923
|
+
async appendBufferedObservations(input) {
|
|
7924
|
+
try {
|
|
7925
|
+
const newChunk = {
|
|
7926
|
+
id: `ombuf-${globalThis.crypto.randomUUID()}`,
|
|
7927
|
+
cycleId: input.chunk.cycleId,
|
|
7928
|
+
observations: input.chunk.observations,
|
|
7929
|
+
tokenCount: input.chunk.tokenCount,
|
|
7930
|
+
messageIds: input.chunk.messageIds,
|
|
7931
|
+
messageTokens: input.chunk.messageTokens,
|
|
7932
|
+
lastObservedAt: input.chunk.lastObservedAt,
|
|
7933
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
7934
|
+
suggestedContinuation: input.chunk.suggestedContinuation,
|
|
7935
|
+
currentTask: input.chunk.currentTask,
|
|
7936
|
+
threadTitle: input.chunk.threadTitle,
|
|
7937
|
+
extractedValues: input.chunk.extractedValues,
|
|
7938
|
+
extractionFailures: input.chunk.extractionFailures
|
|
7939
|
+
};
|
|
7940
|
+
return await this.withOMTransaction(async (connection) => {
|
|
7941
|
+
const row = await this.lockOMRow(connection, input.id);
|
|
7942
|
+
if (!row) throwOMNotFound(input.id, "UPDATE_BUFFERED_OBSERVATIONS");
|
|
7943
|
+
const target = await this.resolveLiveOMRow(connection, row);
|
|
7944
|
+
const existingChunks = parseBufferedChunks(target.bufferedObservationChunks);
|
|
7945
|
+
if (existingChunks.some((existing) => existing.cycleId === input.chunk.cycleId) || isBufferedChunkCoveredByCursor(input.chunk.lastObservedAt, parseDateTime(target.lastObservedAt))) return {
|
|
7946
|
+
persisted: false,
|
|
7947
|
+
recordId: target.id
|
|
7636
7948
|
};
|
|
7637
|
-
const
|
|
7638
|
-
|
|
7639
|
-
const [result] = await connection.execute(`UPDATE ${OM_TABLE_QUOTED} SET
|
|
7949
|
+
const lastBufferedAtTime = maxObservationCursor(parseDateTime(target.lastBufferedAtTime), input.lastBufferedAtTime ?? void 0);
|
|
7950
|
+
await connection.execute(`UPDATE ${OM_TABLE_QUOTED} SET
|
|
7640
7951
|
${omCol("bufferedObservationChunks")} = ?,
|
|
7641
|
-
${omCol("lastBufferedAtTime")} =
|
|
7952
|
+
${omCol("lastBufferedAtTime")} = ?,
|
|
7642
7953
|
${omCol("updatedAt")} = ?
|
|
7643
7954
|
WHERE ${omCol("id")} = ?`, [
|
|
7644
|
-
JSON.stringify(
|
|
7645
|
-
lastBufferedAtTime,
|
|
7646
|
-
|
|
7647
|
-
|
|
7955
|
+
JSON.stringify([...existingChunks, newChunk]),
|
|
7956
|
+
transformToSqlValue(lastBufferedAtTime),
|
|
7957
|
+
transformToSqlValue(/* @__PURE__ */ new Date()),
|
|
7958
|
+
target.id
|
|
7648
7959
|
]);
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
} finally {
|
|
7655
|
-
connection.release();
|
|
7656
|
-
}
|
|
7960
|
+
return {
|
|
7961
|
+
persisted: true,
|
|
7962
|
+
recordId: target.id
|
|
7963
|
+
};
|
|
7964
|
+
}, "appendBufferedObservations");
|
|
7657
7965
|
} catch (error) {
|
|
7658
7966
|
rethrowOrWrapOM(error, input.id, "UPDATE_BUFFERED_OBSERVATIONS");
|
|
7659
7967
|
}
|
|
7660
7968
|
}
|
|
7661
7969
|
async swapBufferedToActive(input) {
|
|
7662
|
-
const connection = await this.pool.getConnection();
|
|
7663
7970
|
try {
|
|
7664
|
-
await
|
|
7665
|
-
|
|
7666
|
-
|
|
7667
|
-
|
|
7668
|
-
const row = currentRows[0];
|
|
7669
|
-
const chunks = parseBufferedChunks(row.bufferedObservationChunks);
|
|
7670
|
-
if (chunks.length === 0) {
|
|
7671
|
-
await connection.commit();
|
|
7672
|
-
return {
|
|
7971
|
+
return await this.withOMTransaction(async (connection) => {
|
|
7972
|
+
const row = await this.lockOMRow(connection, input.id);
|
|
7973
|
+
if (!row) throwOMNotFound(input.id, "SWAP_BUFFERED_TO_ACTIVE");
|
|
7974
|
+
const emptyResult = {
|
|
7673
7975
|
chunksActivated: 0,
|
|
7674
7976
|
messageTokensActivated: 0,
|
|
7675
7977
|
observationTokensActivated: 0,
|
|
@@ -7677,102 +7979,110 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
7677
7979
|
activatedCycleIds: [],
|
|
7678
7980
|
activatedMessageIds: []
|
|
7679
7981
|
};
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
7690
|
-
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7982
|
+
if (row.supersededBy) return {
|
|
7983
|
+
...emptyResult,
|
|
7984
|
+
retired: true
|
|
7985
|
+
};
|
|
7986
|
+
const persistedChunks = parseBufferedChunks(row.bufferedObservationChunks);
|
|
7987
|
+
const refreshedWeights = new Map((Array.isArray(input.bufferedChunks) ? input.bufferedChunks : []).map((c) => [c.id, c.messageTokens]));
|
|
7988
|
+
const chunks = persistedChunks.map((c) => refreshedWeights.has(c.id) ? {
|
|
7989
|
+
...c,
|
|
7990
|
+
messageTokens: refreshedWeights.get(c.id)
|
|
7991
|
+
} : c);
|
|
7992
|
+
if (chunks.length === 0) return emptyResult;
|
|
7993
|
+
const retentionFloor = input.messageTokensThreshold * (1 - input.activationRatio);
|
|
7994
|
+
const targetMessageTokens = Math.max(0, input.currentPendingTokens - retentionFloor);
|
|
7995
|
+
let cumulativeMessageTokens = 0;
|
|
7996
|
+
let bestOverBoundary = 0;
|
|
7997
|
+
let bestOverTokens = 0;
|
|
7998
|
+
let bestUnderBoundary = 0;
|
|
7999
|
+
let bestUnderTokens = 0;
|
|
8000
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
8001
|
+
cumulativeMessageTokens += chunks[i].messageTokens ?? 0;
|
|
8002
|
+
const boundary = i + 1;
|
|
8003
|
+
if (cumulativeMessageTokens >= targetMessageTokens) {
|
|
8004
|
+
if (bestOverBoundary === 0 || cumulativeMessageTokens < bestOverTokens) {
|
|
8005
|
+
bestOverBoundary = boundary;
|
|
8006
|
+
bestOverTokens = cumulativeMessageTokens;
|
|
8007
|
+
}
|
|
8008
|
+
} else if (cumulativeMessageTokens > bestUnderTokens) {
|
|
8009
|
+
bestUnderBoundary = boundary;
|
|
8010
|
+
bestUnderTokens = cumulativeMessageTokens;
|
|
7695
8011
|
}
|
|
7696
|
-
}
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
|
|
7704
|
-
|
|
7705
|
-
|
|
7706
|
-
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
|
|
7722
|
-
|
|
7723
|
-
|
|
7724
|
-
|
|
7725
|
-
|
|
7726
|
-
|
|
7727
|
-
|
|
7728
|
-
|
|
7729
|
-
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
${omCol("
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
|
|
7745
|
-
|
|
7746
|
-
|
|
7747
|
-
|
|
7748
|
-
|
|
7749
|
-
|
|
7750
|
-
|
|
7751
|
-
|
|
7752
|
-
|
|
7753
|
-
|
|
7754
|
-
|
|
7755
|
-
|
|
7756
|
-
|
|
7757
|
-
|
|
7758
|
-
|
|
7759
|
-
|
|
7760
|
-
|
|
7761
|
-
suggestedContinuation: latestChunkHints?.suggestedContinuation ?? void 0,
|
|
7762
|
-
currentTask: latestChunkHints?.currentTask ?? void 0
|
|
7763
|
-
};
|
|
8012
|
+
}
|
|
8013
|
+
const maxOvershoot = retentionFloor * .95;
|
|
8014
|
+
const overshoot = bestOverTokens - targetMessageTokens;
|
|
8015
|
+
const remainingAfterOver = input.currentPendingTokens - bestOverTokens;
|
|
8016
|
+
const remainingAfterUnder = input.currentPendingTokens - bestUnderTokens;
|
|
8017
|
+
const minRemaining = Math.min(1e3, retentionFloor);
|
|
8018
|
+
let chunksToActivate;
|
|
8019
|
+
if (input.forceMaxActivation && bestOverBoundary > 0 && remainingAfterOver >= minRemaining) chunksToActivate = bestOverBoundary;
|
|
8020
|
+
else if (bestOverBoundary > 0 && overshoot <= maxOvershoot && remainingAfterOver >= minRemaining) chunksToActivate = bestOverBoundary;
|
|
8021
|
+
else if (bestUnderBoundary > 0 && remainingAfterUnder >= minRemaining) chunksToActivate = bestUnderBoundary;
|
|
8022
|
+
else if (bestOverBoundary > 0) chunksToActivate = bestOverBoundary;
|
|
8023
|
+
else chunksToActivate = 1;
|
|
8024
|
+
const activatedChunks = chunks.slice(0, chunksToActivate);
|
|
8025
|
+
const remainingChunks = persistedChunks.slice(chunksToActivate);
|
|
8026
|
+
const activatedContent = activatedChunks.map((c) => c.observations).join("\n\n");
|
|
8027
|
+
const activatedTokens = activatedChunks.reduce((sum, c) => sum + c.tokenCount, 0);
|
|
8028
|
+
const activatedMessageTokens = activatedChunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0);
|
|
8029
|
+
const activatedMessageCount = activatedChunks.reduce((sum, c) => sum + c.messageIds.length, 0);
|
|
8030
|
+
const activatedCycleIds = activatedChunks.map((c) => c.cycleId).filter((cid) => !!cid);
|
|
8031
|
+
const activatedMessageIds = activatedChunks.flatMap((c) => c.messageIds ?? []);
|
|
8032
|
+
const latestChunk = activatedChunks[activatedChunks.length - 1];
|
|
8033
|
+
const derivedLastObservedAt = input.lastObservedAt ?? (latestChunk?.lastObservedAt ? new Date(latestChunk.lastObservedAt) : /* @__PURE__ */ new Date());
|
|
8034
|
+
const lastObservedAt = maxObservationCursor(parseDateTime(row.lastObservedAt), derivedLastObservedAt);
|
|
8035
|
+
const existingActive = row.activeObservations || "";
|
|
8036
|
+
const existingTokenCount = Number(row.observationTokenCount || 0);
|
|
8037
|
+
const newActive = existingActive ? `${existingActive}\n\n${activatedContent}` : activatedContent;
|
|
8038
|
+
const newTokenCount = existingTokenCount + activatedTokens;
|
|
8039
|
+
const existingPending = Number(row.pendingMessageTokens || 0);
|
|
8040
|
+
const newPending = Math.max(0, existingPending - activatedMessageTokens);
|
|
8041
|
+
await connection.execute(`UPDATE ${OM_TABLE_QUOTED} SET
|
|
8042
|
+
${omCol("activeObservations")} = ?,
|
|
8043
|
+
${omCol("observationTokenCount")} = ?,
|
|
8044
|
+
${omCol("pendingMessageTokens")} = ?,
|
|
8045
|
+
${omCol("bufferedObservationChunks")} = ?,
|
|
8046
|
+
${omCol("lastObservedAt")} = ?,
|
|
8047
|
+
${omCol("updatedAt")} = ?
|
|
8048
|
+
WHERE ${omCol("id")} = ?`, [
|
|
8049
|
+
newActive,
|
|
8050
|
+
newTokenCount,
|
|
8051
|
+
newPending,
|
|
8052
|
+
remainingChunks.length > 0 ? JSON.stringify(remainingChunks) : null,
|
|
8053
|
+
transformToSqlValue(lastObservedAt),
|
|
8054
|
+
transformToSqlValue(/* @__PURE__ */ new Date()),
|
|
8055
|
+
input.id
|
|
8056
|
+
]);
|
|
8057
|
+
const latestChunkHints = activatedChunks[activatedChunks.length - 1];
|
|
8058
|
+
return {
|
|
8059
|
+
chunksActivated: activatedChunks.length,
|
|
8060
|
+
messageTokensActivated: activatedMessageTokens,
|
|
8061
|
+
observationTokensActivated: activatedTokens,
|
|
8062
|
+
messagesActivated: activatedMessageCount,
|
|
8063
|
+
activatedCycleIds,
|
|
8064
|
+
activatedMessageIds,
|
|
8065
|
+
observations: activatedContent,
|
|
8066
|
+
perChunk: activatedChunks.map((c) => ({
|
|
8067
|
+
cycleId: c.cycleId ?? "",
|
|
8068
|
+
messageTokens: c.messageTokens ?? 0,
|
|
8069
|
+
observationTokens: c.tokenCount,
|
|
8070
|
+
messageCount: c.messageIds.length,
|
|
8071
|
+
observations: c.observations
|
|
8072
|
+
})),
|
|
8073
|
+
suggestedContinuation: latestChunkHints?.suggestedContinuation ?? void 0,
|
|
8074
|
+
currentTask: latestChunkHints?.currentTask ?? void 0
|
|
8075
|
+
};
|
|
8076
|
+
}, "swapBufferedToActive");
|
|
7764
8077
|
} catch (error) {
|
|
7765
|
-
await connection.rollback();
|
|
7766
8078
|
rethrowOrWrapOM(error, input.id, "SWAP_BUFFERED_TO_ACTIVE");
|
|
7767
|
-
} finally {
|
|
7768
|
-
connection.release();
|
|
7769
8079
|
}
|
|
7770
8080
|
}
|
|
7771
8081
|
async updateBufferedReflection(input) {
|
|
7772
8082
|
try {
|
|
7773
8083
|
const nowSql = transformToSqlValue(/* @__PURE__ */ new Date());
|
|
7774
8084
|
const br = omCol("bufferedReflection");
|
|
7775
|
-
const [result] = await this.
|
|
8085
|
+
const [result] = await this.#fenced(void 0, "updateBufferedReflection", (q) => q.execute(`UPDATE ${OM_TABLE_QUOTED} SET
|
|
7776
8086
|
${br} = CASE
|
|
7777
8087
|
WHEN ${br} IS NOT NULL AND ${br} != ''
|
|
7778
8088
|
THEN CONCAT(${br}, CHAR(10), CHAR(10), ?)
|
|
@@ -7790,7 +8100,7 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
7790
8100
|
input.reflectedObservationLineCount,
|
|
7791
8101
|
nowSql,
|
|
7792
8102
|
input.id
|
|
7793
|
-
]);
|
|
8103
|
+
]));
|
|
7794
8104
|
if (result.affectedRows === 0) throwOMNotFound(input.id, "UPDATE_BUFFERED_REFLECTION");
|
|
7795
8105
|
} catch (error) {
|
|
7796
8106
|
rethrowOrWrapOM(error, input.id, "UPDATE_BUFFERED_REFLECTION");
|
|
@@ -7798,38 +8108,183 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
7798
8108
|
}
|
|
7799
8109
|
async swapBufferedReflectionToActive(input) {
|
|
7800
8110
|
try {
|
|
7801
|
-
const
|
|
7802
|
-
|
|
7803
|
-
|
|
7804
|
-
|
|
7805
|
-
|
|
7806
|
-
|
|
7807
|
-
|
|
7808
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
|
|
7819
|
-
|
|
7820
|
-
|
|
7821
|
-
|
|
7822
|
-
${omCol("bufferedReflection")} = NULL,
|
|
7823
|
-
${omCol("bufferedReflectionTokens")} = NULL,
|
|
7824
|
-
${omCol("bufferedReflectionInputTokens")} = NULL,
|
|
7825
|
-
${omCol("reflectedObservationLineCount")} = NULL,
|
|
7826
|
-
${omCol("updatedAt")} = ?
|
|
7827
|
-
WHERE ${omCol("id")} = ?`, [nowSql, input.currentRecord.id]);
|
|
7828
|
-
return newRecord;
|
|
8111
|
+
const { currentRecord } = input;
|
|
8112
|
+
return await this.withOMTransaction(async (connection) => {
|
|
8113
|
+
const row = await this.lockOMRow(connection, currentRecord.id);
|
|
8114
|
+
if (!row) return currentRecord;
|
|
8115
|
+
if (row.supersededBy) return this.parseOMRow(await this.resolveLiveOMRow(connection, row));
|
|
8116
|
+
const stored = this.parseOMRow(row);
|
|
8117
|
+
const bufferedReflection = stored.bufferedReflection || "";
|
|
8118
|
+
if (!bufferedReflection) throw new MastraError({
|
|
8119
|
+
id: createStorageErrorId("MYSQL", "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "NO_CONTENT"),
|
|
8120
|
+
text: "No buffered reflection to swap",
|
|
8121
|
+
domain: ErrorDomain.STORAGE,
|
|
8122
|
+
category: ErrorCategory.USER,
|
|
8123
|
+
details: { id: currentRecord.id }
|
|
8124
|
+
});
|
|
8125
|
+
if (!isAppendOnlySince(stored.activeObservations, currentRecord.activeObservations)) return stored;
|
|
8126
|
+
const reflectedLineCount = stored.reflectedObservationLineCount ?? 0;
|
|
8127
|
+
const unreflectedContent = (stored.activeObservations || "").split("\n").slice(reflectedLineCount).join("\n").trim();
|
|
8128
|
+
const newObservations = unreflectedContent ? `${bufferedReflection}\n\n${unreflectedContent}` : bufferedReflection;
|
|
8129
|
+
const tokenCount = input.tokenCount + Math.max(0, (stored.observationTokenCount ?? 0) - (currentRecord.observationTokenCount ?? 0));
|
|
8130
|
+
return this.rollOverOMRow(connection, stored.id, newObservations, tokenCount, input.newRecordId, true);
|
|
8131
|
+
}, "swapBufferedReflectionToActive");
|
|
7829
8132
|
} catch (error) {
|
|
7830
8133
|
rethrowOrWrapOM(error, input.currentRecord.id, "SWAP_BUFFERED_REFLECTION_TO_ACTIVE");
|
|
7831
8134
|
}
|
|
7832
8135
|
}
|
|
8136
|
+
/**
|
|
8137
|
+
* Runs `fn` in one transaction on a dedicated connection. Every OM lifecycle write locks its
|
|
8138
|
+
* target row by primary key (`SELECT … FOR UPDATE`, a record lock — never a range), so the
|
|
8139
|
+
* only expected lock conflict is a wait; a deadlock or lock-wait timeout (e.g. against the
|
|
8140
|
+
* startup backfill's scan) is retried a bounded number of times.
|
|
8141
|
+
*/
|
|
8142
|
+
async withOMTransaction(fn, fencedOperation) {
|
|
8143
|
+
for (let attempt = 1;; attempt++) {
|
|
8144
|
+
const connection = await this.pool.getConnection();
|
|
8145
|
+
try {
|
|
8146
|
+
await connection.beginTransaction();
|
|
8147
|
+
if (fencedOperation) await this.#assertFence(connection, void 0, fencedOperation);
|
|
8148
|
+
const result = await fn(connection);
|
|
8149
|
+
await connection.commit();
|
|
8150
|
+
return result;
|
|
8151
|
+
} catch (error) {
|
|
8152
|
+
await connection.rollback().catch(() => {});
|
|
8153
|
+
if (attempt < OM_MAX_TRANSACTION_ATTEMPTS && isRetryableLockError(error)) continue;
|
|
8154
|
+
throw error;
|
|
8155
|
+
} finally {
|
|
8156
|
+
connection.release();
|
|
8157
|
+
}
|
|
8158
|
+
}
|
|
8159
|
+
}
|
|
8160
|
+
/** Row-locks one OM record by primary key for the rest of the transaction. */
|
|
8161
|
+
async lockOMRow(connection, id) {
|
|
8162
|
+
const [rows] = await connection.execute(`SELECT * FROM ${OM_TABLE_QUOTED} WHERE ${omCol("id")} = ? FOR UPDATE`, [id]);
|
|
8163
|
+
return rows?.[0] ?? null;
|
|
8164
|
+
}
|
|
8165
|
+
/**
|
|
8166
|
+
* The live, row-locked record a lifecycle write aimed at `row` lands on: `row` itself while
|
|
8167
|
+
* live, otherwise the head. The head is found with a plain (non-locking, so no range locks)
|
|
8168
|
+
* canonical head read, then locked by primary key. Under REPEATABLE READ that read can predate
|
|
8169
|
+
* a rollover committed after this transaction started; the locking read sees the latest
|
|
8170
|
+
* committed row, so a head retired since is followed through `supersededBy` (bounded).
|
|
8171
|
+
*/
|
|
8172
|
+
async resolveLiveOMRow(connection, row) {
|
|
8173
|
+
if (!row.supersededBy) return row;
|
|
8174
|
+
const [heads] = await connection.execute(`SELECT id FROM ${OM_TABLE_QUOTED} WHERE ${omCol("lookupKey")} = ? ORDER BY ${OM_HEAD_ORDER} LIMIT 1`, [row.lookupKey]);
|
|
8175
|
+
const headId = heads?.[0]?.id ?? row.supersededBy;
|
|
8176
|
+
let current = await this.lockOMRow(connection, headId) ?? row;
|
|
8177
|
+
for (let hop = 0; current.supersededBy; hop++) {
|
|
8178
|
+
const next = hop < OM_MAX_HEAD_HOPS ? await this.lockOMRow(connection, current.supersededBy) : null;
|
|
8179
|
+
if (!next) throw new MastraError({
|
|
8180
|
+
id: createStorageErrorId("MYSQL", "RESOLVE_OBSERVATIONAL_MEMORY_HEAD", "FAILED"),
|
|
8181
|
+
text: `Observational memory record ${row.id} is superseded but no live head was found`,
|
|
8182
|
+
domain: ErrorDomain.STORAGE,
|
|
8183
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
8184
|
+
details: { id: row.id }
|
|
8185
|
+
});
|
|
8186
|
+
current = next;
|
|
8187
|
+
}
|
|
8188
|
+
return current;
|
|
8189
|
+
}
|
|
8190
|
+
/**
|
|
8191
|
+
* Create the next generation from the stored (live, row-locked) record and retire the stored
|
|
8192
|
+
* record in the same transaction. Carried columns are copied in SQL from the stored row, so
|
|
8193
|
+
* nothing round-trips through a client-side date conversion. Buffered chunks move to the new
|
|
8194
|
+
* generation; the cursor, buffering markers, flags, and counters carry over; buffered
|
|
8195
|
+
* reflection state does not (and is cleared on the retired row after a buffered swap).
|
|
8196
|
+
*/
|
|
8197
|
+
async rollOverOMRow(connection, storedId, observations, tokenCount, newRecordId, clearBufferedReflection) {
|
|
8198
|
+
const id = newRecordId ?? globalThis.crypto.randomUUID();
|
|
8199
|
+
const nowSql = transformToSqlValue(/* @__PURE__ */ new Date());
|
|
8200
|
+
const c = omCol;
|
|
8201
|
+
await connection.execute(`INSERT INTO ${OM_TABLE_QUOTED} (
|
|
8202
|
+
${c("id")}, ${c("lookupKey")}, ${c("scope")}, ${c("resourceId")}, ${c("threadId")},
|
|
8203
|
+
${c("activeObservations")}, ${c("activeObservationsPendingUpdate")},
|
|
8204
|
+
${c("originType")}, ${c("config")}, ${c("generationCount")}, ${c("lastObservedAt")}, ${c("lastReflectionAt")},
|
|
8205
|
+
${c("pendingMessageTokens")}, ${c("totalTokensObserved")}, ${c("observationTokenCount")}, ${c("bufferedObservationChunks")},
|
|
8206
|
+
${c("isObserving")}, ${c("isReflecting")}, ${c("isBufferingObservation")}, ${c("isBufferingReflection")},
|
|
8207
|
+
${c("lastBufferedAtTokens")}, ${c("lastBufferedAtTime")},
|
|
8208
|
+
${c("observedTimezone")}, ${c("metadata")}, ${c("supersededBy")}, ${c("createdAt")}, ${c("updatedAt")}
|
|
8209
|
+
)
|
|
8210
|
+
SELECT
|
|
8211
|
+
?, ${c("lookupKey")}, ${c("scope")}, ${c("resourceId")}, ${c("threadId")},
|
|
8212
|
+
?, NULL,
|
|
8213
|
+
'reflection', ${c("config")}, ${c("generationCount")} + 1, ${c("lastObservedAt")}, ?,
|
|
8214
|
+
${c("pendingMessageTokens")}, ${c("totalTokensObserved")}, ?, ${c("bufferedObservationChunks")},
|
|
8215
|
+
false, false, ${c("isBufferingObservation")}, false,
|
|
8216
|
+
${c("lastBufferedAtTokens")}, ${c("lastBufferedAtTime")},
|
|
8217
|
+
${c("observedTimezone")}, ${c("metadata")}, NULL, ?, ?
|
|
8218
|
+
FROM ${OM_TABLE_QUOTED} WHERE ${c("id")} = ?`, [
|
|
8219
|
+
id,
|
|
8220
|
+
observations,
|
|
8221
|
+
nowSql,
|
|
8222
|
+
Math.round(tokenCount),
|
|
8223
|
+
nowSql,
|
|
8224
|
+
nowSql,
|
|
8225
|
+
storedId
|
|
8226
|
+
]);
|
|
8227
|
+
const [retired] = await connection.execute(`UPDATE ${OM_TABLE_QUOTED} SET
|
|
8228
|
+
${c("supersededBy")} = ?,
|
|
8229
|
+
${c("bufferedObservationChunks")} = NULL,
|
|
8230
|
+
${clearBufferedReflection ? `${c("bufferedReflection")} = NULL, ${c("bufferedReflectionTokens")} = NULL,
|
|
8231
|
+
${c("bufferedReflectionInputTokens")} = NULL, ${c("reflectedObservationLineCount")} = NULL,` : ""}
|
|
8232
|
+
${c("updatedAt")} = ?
|
|
8233
|
+
WHERE ${c("id")} = ? AND ${c("supersededBy")} IS NULL`, [
|
|
8234
|
+
id,
|
|
8235
|
+
nowSql,
|
|
8236
|
+
storedId
|
|
8237
|
+
]);
|
|
8238
|
+
if (retired.affectedRows !== 1) throw new MastraError({
|
|
8239
|
+
id: createStorageErrorId("MYSQL", "CREATE_REFLECTION_GENERATION", "RETIRE_FAILED"),
|
|
8240
|
+
text: `Failed to retire observational memory record ${storedId}`,
|
|
8241
|
+
domain: ErrorDomain.STORAGE,
|
|
8242
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
8243
|
+
details: { id: storedId }
|
|
8244
|
+
});
|
|
8245
|
+
const inserted = await this.lockOMRow(connection, id);
|
|
8246
|
+
if (!inserted) throwOMNotFound(id, "CREATE_REFLECTION_GENERATION");
|
|
8247
|
+
return this.parseOMRow(inserted);
|
|
8248
|
+
}
|
|
8249
|
+
/**
|
|
8250
|
+
* Retire every live row that sorts after its key's canonical head, marking it superseded by
|
|
8251
|
+
* the head. Rows retired by older adapter versions (which never set `supersededBy`) become
|
|
8252
|
+
* frozen. Only keys with more than one live row are touched; each update embeds the head's
|
|
8253
|
+
* ordering key, so a row created by a concurrent rollover (a newer generation) never matches.
|
|
8254
|
+
* Runs at READ COMMITTED so the scan takes no gap locks.
|
|
8255
|
+
*/
|
|
8256
|
+
async backfillSupersededBy() {
|
|
8257
|
+
const [keys] = await this.pool.execute(`SELECT ${omCol("lookupKey")} AS lookupKey FROM ${OM_TABLE_QUOTED}
|
|
8258
|
+
WHERE ${omCol("supersededBy")} IS NULL GROUP BY ${omCol("lookupKey")} HAVING COUNT(*) > 1`);
|
|
8259
|
+
for (const { lookupKey } of keys ?? []) for (let attempt = 1;; attempt++) {
|
|
8260
|
+
const connection = await this.pool.getConnection();
|
|
8261
|
+
try {
|
|
8262
|
+
await connection.query("SET TRANSACTION ISOLATION LEVEL READ COMMITTED");
|
|
8263
|
+
await connection.beginTransaction();
|
|
8264
|
+
await connection.execute(`UPDATE ${OM_TABLE_QUOTED} AS r
|
|
8265
|
+
JOIN (
|
|
8266
|
+
SELECT ${omCol("id")} AS id, ${omCol("generationCount")} AS generationCount, ${omCol("createdAt")} AS createdAt
|
|
8267
|
+
FROM ${OM_TABLE_QUOTED} WHERE ${omCol("lookupKey")} = ? ORDER BY ${OM_HEAD_ORDER} LIMIT 1
|
|
8268
|
+
) AS h
|
|
8269
|
+
SET r.${omCol("supersededBy")} = h.id
|
|
8270
|
+
WHERE r.${omCol("lookupKey")} = ?
|
|
8271
|
+
AND r.${omCol("supersededBy")} IS NULL
|
|
8272
|
+
AND (
|
|
8273
|
+
r.${omCol("generationCount")} < h.generationCount
|
|
8274
|
+
OR (r.${omCol("generationCount")} = h.generationCount AND r.${omCol("createdAt")} > h.createdAt)
|
|
8275
|
+
OR (r.${omCol("generationCount")} = h.generationCount AND r.${omCol("createdAt")} = h.createdAt AND r.${omCol("id")} > h.id)
|
|
8276
|
+
)`, [lookupKey, lookupKey]);
|
|
8277
|
+
await connection.commit();
|
|
8278
|
+
break;
|
|
8279
|
+
} catch (error) {
|
|
8280
|
+
await connection.rollback().catch(() => {});
|
|
8281
|
+
if (attempt < OM_MAX_TRANSACTION_ATTEMPTS && isRetryableLockError(error)) continue;
|
|
8282
|
+
throw error;
|
|
8283
|
+
} finally {
|
|
8284
|
+
connection.release();
|
|
8285
|
+
}
|
|
8286
|
+
}
|
|
8287
|
+
}
|
|
7833
8288
|
async updateOMFlag(id, column, value, operation) {
|
|
7834
8289
|
try {
|
|
7835
8290
|
const [result] = await this.pool.execute(`UPDATE ${OM_TABLE_QUOTED} SET ${omCol(column)} = ?, ${omCol("updatedAt")} = ? WHERE ${omCol("id")} = ?`, [
|
|
@@ -7877,7 +8332,8 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
7877
8332
|
config: parseJSONColumn(row.config) ?? {},
|
|
7878
8333
|
metadata: parseJSONColumn(row.metadata),
|
|
7879
8334
|
observedMessageIds: parseJSONColumn(row.observedMessageIds),
|
|
7880
|
-
observedTimezone: row.observedTimezone || void 0
|
|
8335
|
+
observedTimezone: row.observedTimezone || void 0,
|
|
8336
|
+
supersededBy: row.supersededBy ?? null
|
|
7881
8337
|
};
|
|
7882
8338
|
}
|
|
7883
8339
|
};
|
|
@@ -10865,6 +11321,26 @@ function parseSnapshot(snapshot) {
|
|
|
10865
11321
|
}
|
|
10866
11322
|
return snapshot;
|
|
10867
11323
|
}
|
|
11324
|
+
const RUN_OWNERS_TABLE = formatTableName(TABLE_WORKFLOW_RUN_OWNERS);
|
|
11325
|
+
const RUN_ID = quoteIdentifier("runId", "column name");
|
|
11326
|
+
const OWNER_ID = quoteIdentifier("ownerId", "column name");
|
|
11327
|
+
const LEASE_EXPIRES_AT = quoteIdentifier("leaseExpiresAt", "column name");
|
|
11328
|
+
const UPDATED_AT = quoteIdentifier("updatedAt", "column name");
|
|
11329
|
+
const RUN_OWNER_COLUMNS = `generation, ${OWNER_ID} AS ownerId, ${LEASE_EXPIRES_AT} AS leaseExpiresAt, ${DB_NOW_MS} AS nowMs`;
|
|
11330
|
+
function toRunOwnershipRecord(runId, row) {
|
|
11331
|
+
const leaseExpiresAt = row.leaseExpiresAt === null ? null : Number(row.leaseExpiresAt);
|
|
11332
|
+
return {
|
|
11333
|
+
runId,
|
|
11334
|
+
generation: Number(row.generation),
|
|
11335
|
+
ownerId: String(row.ownerId),
|
|
11336
|
+
leaseExpiresAt: leaseExpiresAt === null ? null : new Date(leaseExpiresAt),
|
|
11337
|
+
live: leaseExpiresAt !== null && leaseExpiresAt > Number(row.nowMs)
|
|
11338
|
+
};
|
|
11339
|
+
}
|
|
11340
|
+
async function readRunOwner(q, runId) {
|
|
11341
|
+
const [rows] = await q.execute(`SELECT ${RUN_OWNER_COLUMNS} FROM ${RUN_OWNERS_TABLE} WHERE ${RUN_ID} = ?`, [runId]);
|
|
11342
|
+
return rows[0] ? toRunOwnershipRecord(runId, rows[0]) : null;
|
|
11343
|
+
}
|
|
10868
11344
|
function mapWorkflowRow(row) {
|
|
10869
11345
|
return {
|
|
10870
11346
|
workflowName: row.workflow_name,
|
|
@@ -10897,6 +11373,9 @@ var WorkflowsMySQL = class WorkflowsMySQL extends WorkflowsStorage {
|
|
|
10897
11373
|
tableName: TABLE_WORKFLOW_SNAPSHOT,
|
|
10898
11374
|
schema: TABLE_SCHEMAS[TABLE_WORKFLOW_SNAPSHOT],
|
|
10899
11375
|
compositePrimaryKey: ["workflow_name", "run_id"]
|
|
11376
|
+
}), generateTableSQL({
|
|
11377
|
+
tableName: TABLE_WORKFLOW_RUN_OWNERS,
|
|
11378
|
+
schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_WORKFLOW_RUN_OWNERS]
|
|
10900
11379
|
})];
|
|
10901
11380
|
}
|
|
10902
11381
|
constructor({ operations, pool, skipDefaultIndexes, indexes }) {
|
|
@@ -10940,16 +11419,125 @@ var WorkflowsMySQL = class WorkflowsMySQL extends WorkflowsStorage {
|
|
|
10940
11419
|
schema,
|
|
10941
11420
|
ifNotExists: ["resourceId"]
|
|
10942
11421
|
});
|
|
11422
|
+
await this.operations.createTable({
|
|
11423
|
+
tableName: TABLE_WORKFLOW_RUN_OWNERS,
|
|
11424
|
+
schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_WORKFLOW_RUN_OWNERS]
|
|
11425
|
+
});
|
|
10943
11426
|
await this.createDefaultIndexes();
|
|
10944
11427
|
await this.createCustomIndexes();
|
|
10945
11428
|
}
|
|
10946
11429
|
async dangerouslyClearAll() {
|
|
10947
11430
|
await this.pool.execute(`DELETE FROM ${formatTableName(TABLE_WORKFLOW_SNAPSHOT)}`);
|
|
11431
|
+
await this.pool.execute(`DELETE FROM ${RUN_OWNERS_TABLE}`);
|
|
10948
11432
|
}
|
|
10949
|
-
|
|
11433
|
+
supportsRunFencing() {
|
|
11434
|
+
return true;
|
|
11435
|
+
}
|
|
11436
|
+
#ownershipError(operation, runId, error) {
|
|
11437
|
+
return new MastraError({
|
|
11438
|
+
id: createStorageErrorId("MYSQL", operation, "FAILED"),
|
|
11439
|
+
domain: ErrorDomain.STORAGE,
|
|
11440
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
11441
|
+
details: { runId }
|
|
11442
|
+
}, error);
|
|
11443
|
+
}
|
|
11444
|
+
async claimRunOwnership({ runId, ownerId, leaseMs, force, expectedGeneration }) {
|
|
11445
|
+
try {
|
|
11446
|
+
return await claimTransaction(this.pool, async (connection) => {
|
|
11447
|
+
const [existing] = await connection.execute(`SELECT generation FROM ${RUN_OWNERS_TABLE} WHERE ${RUN_ID} = ? FOR UPDATE`, [runId]);
|
|
11448
|
+
if (existing.length === 0) {
|
|
11449
|
+
if (expectedGeneration !== void 0 && expectedGeneration !== 0) return {
|
|
11450
|
+
acquired: false,
|
|
11451
|
+
record: null
|
|
11452
|
+
};
|
|
11453
|
+
await connection.execute(`INSERT INTO ${RUN_OWNERS_TABLE} (${RUN_ID}, generation, ${OWNER_ID}, ${LEASE_EXPIRES_AT}, ${UPDATED_AT})
|
|
11454
|
+
VALUES (?, GREATEST(1, ${DB_NOW_MS}), ?, ${DB_NOW_MS} + ?, ${DB_NOW_MS})`, [
|
|
11455
|
+
runId,
|
|
11456
|
+
ownerId,
|
|
11457
|
+
leaseMs
|
|
11458
|
+
]);
|
|
11459
|
+
return {
|
|
11460
|
+
acquired: true,
|
|
11461
|
+
record: await readRunOwner(connection, runId)
|
|
11462
|
+
};
|
|
11463
|
+
}
|
|
11464
|
+
await connection.execute(`UPDATE ${RUN_OWNERS_TABLE}
|
|
11465
|
+
SET generation = generation + 1, ${OWNER_ID} = ?, ${LEASE_EXPIRES_AT} = ${DB_NOW_MS} + ?,
|
|
11466
|
+
${UPDATED_AT} = ${DB_NOW_MS}
|
|
11467
|
+
WHERE ${RUN_ID} = ?
|
|
11468
|
+
AND (? IS NULL OR generation = ?)
|
|
11469
|
+
AND (? OR ${LEASE_EXPIRES_AT} IS NULL OR ${LEASE_EXPIRES_AT} <= ${DB_NOW_MS})`, [
|
|
11470
|
+
ownerId,
|
|
11471
|
+
leaseMs,
|
|
11472
|
+
runId,
|
|
11473
|
+
expectedGeneration ?? null,
|
|
11474
|
+
expectedGeneration ?? null,
|
|
11475
|
+
force === true
|
|
11476
|
+
]);
|
|
11477
|
+
const record = await readRunOwner(connection, runId);
|
|
11478
|
+
return record && record.generation === Number(existing[0].generation) + 1 ? {
|
|
11479
|
+
acquired: true,
|
|
11480
|
+
record
|
|
11481
|
+
} : {
|
|
11482
|
+
acquired: false,
|
|
11483
|
+
record
|
|
11484
|
+
};
|
|
11485
|
+
});
|
|
11486
|
+
} catch (error) {
|
|
11487
|
+
throw this.#ownershipError("CLAIM_RUN_OWNERSHIP", runId, error);
|
|
11488
|
+
}
|
|
11489
|
+
}
|
|
11490
|
+
async renewRunOwnership({ leaseMs, ...fence }) {
|
|
10950
11491
|
try {
|
|
10951
|
-
await this.pool
|
|
11492
|
+
return await inTransaction(this.pool, async (connection) => {
|
|
11493
|
+
await connection.execute(`UPDATE ${RUN_OWNERS_TABLE}
|
|
11494
|
+
SET ${LEASE_EXPIRES_AT} = ${DB_NOW_MS} + ?, ${UPDATED_AT} = ${DB_NOW_MS}
|
|
11495
|
+
WHERE ${RUN_ID} = ? AND generation = ? AND ${OWNER_ID} = ? AND ${LEASE_EXPIRES_AT} IS NOT NULL`, [
|
|
11496
|
+
leaseMs,
|
|
11497
|
+
fence.runId,
|
|
11498
|
+
fence.generation,
|
|
11499
|
+
fence.ownerId
|
|
11500
|
+
]);
|
|
11501
|
+
const record = await readRunOwner(connection, fence.runId);
|
|
11502
|
+
return record && matchesRunFence(record, fence) && record.leaseExpiresAt !== null ? {
|
|
11503
|
+
renewed: true,
|
|
11504
|
+
record
|
|
11505
|
+
} : {
|
|
11506
|
+
renewed: false,
|
|
11507
|
+
record
|
|
11508
|
+
};
|
|
11509
|
+
});
|
|
10952
11510
|
} catch (error) {
|
|
11511
|
+
throw this.#ownershipError("RENEW_RUN_OWNERSHIP", fence.runId, error);
|
|
11512
|
+
}
|
|
11513
|
+
}
|
|
11514
|
+
async releaseRunOwnership(fence) {
|
|
11515
|
+
try {
|
|
11516
|
+
return await inTransaction(this.pool, async (connection) => {
|
|
11517
|
+
await connection.execute(`UPDATE ${RUN_OWNERS_TABLE} SET ${LEASE_EXPIRES_AT} = NULL, ${UPDATED_AT} = ${DB_NOW_MS}
|
|
11518
|
+
WHERE ${RUN_ID} = ? AND generation = ? AND ${OWNER_ID} = ?`, [
|
|
11519
|
+
fence.runId,
|
|
11520
|
+
fence.generation,
|
|
11521
|
+
fence.ownerId
|
|
11522
|
+
]);
|
|
11523
|
+
return matchesRunFence(await readRunOwner(connection, fence.runId), fence);
|
|
11524
|
+
});
|
|
11525
|
+
} catch (error) {
|
|
11526
|
+
throw this.#ownershipError("RELEASE_RUN_OWNERSHIP", fence.runId, error);
|
|
11527
|
+
}
|
|
11528
|
+
}
|
|
11529
|
+
async getRunOwnership({ runId }) {
|
|
11530
|
+
try {
|
|
11531
|
+
return await readRunOwner(this.pool, runId);
|
|
11532
|
+
} catch (error) {
|
|
11533
|
+
throw this.#ownershipError("GET_RUN_OWNERSHIP", runId, error);
|
|
11534
|
+
}
|
|
11535
|
+
}
|
|
11536
|
+
async deleteWorkflowRunById({ runId, workflowName, fence }) {
|
|
11537
|
+
try {
|
|
11538
|
+
await withRunFence(this.pool, RUN_OWNERS_TABLE, resolveRunFence(this, fence, runId), "deleteWorkflowRunById", (q) => q.execute(`DELETE FROM ${formatTableName(TABLE_WORKFLOW_SNAPSHOT)} WHERE ${quoteIdentifier("workflow_name", "column name")} = ? AND ${quoteIdentifier("run_id", "column name")} = ?`, [workflowName, runId]));
|
|
11539
|
+
} catch (error) {
|
|
11540
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
10953
11541
|
throw new MastraError({
|
|
10954
11542
|
id: "MYSQL_WORKFLOWS_DELETE_RUN_BY_ID_FAILED",
|
|
10955
11543
|
domain: ErrorDomain.STORAGE,
|
|
@@ -10961,10 +11549,12 @@ var WorkflowsMySQL = class WorkflowsMySQL extends WorkflowsStorage {
|
|
|
10961
11549
|
}, error);
|
|
10962
11550
|
}
|
|
10963
11551
|
}
|
|
10964
|
-
async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state }) {
|
|
11552
|
+
async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state, fence: explicitFence }) {
|
|
11553
|
+
const fence = resolveRunFence(this, explicitFence, runId);
|
|
10965
11554
|
const connection = await this.pool.getConnection();
|
|
10966
11555
|
try {
|
|
10967
11556
|
await connection.beginTransaction();
|
|
11557
|
+
if (fence) await assertRunFence(connection, RUN_OWNERS_TABLE, fence, "updateWorkflowResults");
|
|
10968
11558
|
const [rows] = await connection.execute(`SELECT snapshot FROM ${formatTableName(TABLE_WORKFLOW_SNAPSHOT)} WHERE ${quoteIdentifier("workflow_name", "column name")} = ? AND ${quoteIdentifier("run_id", "column name")} = ? FOR UPDATE`, [workflowName, runId]);
|
|
10969
11559
|
if (!Array.isArray(rows) || rows.length === 0) {
|
|
10970
11560
|
await connection.rollback();
|
|
@@ -11015,10 +11605,12 @@ var WorkflowsMySQL = class WorkflowsMySQL extends WorkflowsStorage {
|
|
|
11015
11605
|
connection.release();
|
|
11016
11606
|
}
|
|
11017
11607
|
}
|
|
11018
|
-
async updateWorkflowState({ workflowName, runId, opts }) {
|
|
11608
|
+
async updateWorkflowState({ workflowName, runId, opts, fence: explicitFence }) {
|
|
11609
|
+
const fence = resolveRunFence(this, explicitFence, runId);
|
|
11019
11610
|
const connection = await this.pool.getConnection();
|
|
11020
11611
|
try {
|
|
11021
11612
|
await connection.beginTransaction();
|
|
11613
|
+
if (fence) await assertRunFence(connection, RUN_OWNERS_TABLE, fence, "updateWorkflowState");
|
|
11022
11614
|
const [rows] = await connection.execute(`SELECT snapshot FROM ${formatTableName(TABLE_WORKFLOW_SNAPSHOT)} WHERE ${quoteIdentifier("workflow_name", "column name")} = ? AND ${quoteIdentifier("run_id", "column name")} = ? FOR UPDATE`, [workflowName, runId]);
|
|
11023
11615
|
if (!Array.isArray(rows) || rows.length === 0) {
|
|
11024
11616
|
await connection.rollback();
|
|
@@ -11044,6 +11636,7 @@ var WorkflowsMySQL = class WorkflowsMySQL extends WorkflowsStorage {
|
|
|
11044
11636
|
return updatedSnapshot;
|
|
11045
11637
|
} catch (error) {
|
|
11046
11638
|
await connection.rollback();
|
|
11639
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
11047
11640
|
throw new MastraError({
|
|
11048
11641
|
id: "MYSQL_WORKFLOWS_UPDATE_STATE_FAILED",
|
|
11049
11642
|
domain: ErrorDomain.STORAGE,
|
|
@@ -11057,35 +11650,38 @@ var WorkflowsMySQL = class WorkflowsMySQL extends WorkflowsStorage {
|
|
|
11057
11650
|
connection.release();
|
|
11058
11651
|
}
|
|
11059
11652
|
}
|
|
11060
|
-
async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt }) {
|
|
11653
|
+
async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt, fence }) {
|
|
11061
11654
|
const now = /* @__PURE__ */ new Date();
|
|
11062
11655
|
const createdAtValue = createdAt ?? now;
|
|
11063
11656
|
const updatedAtValue = updatedAt ?? now;
|
|
11657
|
+
const tableName = formatTableName(TABLE_WORKFLOW_SNAPSHOT);
|
|
11064
11658
|
try {
|
|
11065
|
-
|
|
11066
|
-
|
|
11067
|
-
|
|
11068
|
-
|
|
11069
|
-
|
|
11070
|
-
|
|
11071
|
-
|
|
11072
|
-
|
|
11073
|
-
|
|
11074
|
-
|
|
11075
|
-
|
|
11076
|
-
|
|
11077
|
-
|
|
11078
|
-
|
|
11079
|
-
|
|
11080
|
-
|
|
11081
|
-
|
|
11082
|
-
|
|
11083
|
-
|
|
11084
|
-
|
|
11085
|
-
|
|
11086
|
-
|
|
11087
|
-
|
|
11659
|
+
await withRunFence(this.pool, RUN_OWNERS_TABLE, resolveRunFence(this, fence, runId), "persistWorkflowSnapshot", async (q) => {
|
|
11660
|
+
const [updateResult] = await q.execute(`UPDATE ${tableName}
|
|
11661
|
+
SET ${quoteIdentifier("resourceId", "column name")} = ?,
|
|
11662
|
+
${quoteIdentifier("snapshot", "column name")} = ?,
|
|
11663
|
+
${quoteIdentifier("updatedAt", "column name")} = ?
|
|
11664
|
+
WHERE ${quoteIdentifier("workflow_name", "column name")} = ? AND ${quoteIdentifier("run_id", "column name")} = ?`, [
|
|
11665
|
+
resourceId ?? null,
|
|
11666
|
+
JSON.stringify(snapshot),
|
|
11667
|
+
transformToSqlValue(updatedAtValue),
|
|
11668
|
+
workflowName,
|
|
11669
|
+
runId
|
|
11670
|
+
]);
|
|
11671
|
+
if (updateResult.affectedRows > 0) return;
|
|
11672
|
+
await q.execute(`INSERT INTO ${tableName} (${quoteIdentifier("workflow_name", "column name")}, ${quoteIdentifier("run_id", "column name")}, ${quoteIdentifier("resourceId", "column name")}, ${quoteIdentifier("snapshot", "column name")}, ${quoteIdentifier("createdAt", "column name")}, ${quoteIdentifier("updatedAt", "column name")})
|
|
11673
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
11674
|
+
ON DUPLICATE KEY UPDATE ${quoteIdentifier("resourceId", "column name")} = VALUES(${quoteIdentifier("resourceId", "column name")}), ${quoteIdentifier("snapshot", "column name")} = VALUES(${quoteIdentifier("snapshot", "column name")}), ${quoteIdentifier("updatedAt", "column name")} = VALUES(${quoteIdentifier("updatedAt", "column name")})`, [
|
|
11675
|
+
workflowName,
|
|
11676
|
+
runId,
|
|
11677
|
+
resourceId ?? null,
|
|
11678
|
+
JSON.stringify(snapshot),
|
|
11679
|
+
transformToSqlValue(createdAtValue),
|
|
11680
|
+
transformToSqlValue(updatedAtValue)
|
|
11681
|
+
]);
|
|
11682
|
+
});
|
|
11088
11683
|
} catch (error) {
|
|
11684
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
11089
11685
|
throw new MastraError({
|
|
11090
11686
|
id: "MYSQL_WORKFLOWS_PERSIST_SNAPSHOT_FAILED",
|
|
11091
11687
|
domain: ErrorDomain.STORAGE,
|