@mastra/mysql 0.13.1-alpha.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/index.cjs +506 -108
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +507 -109
- 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 +15 -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 +6 -6
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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
5
|
import { createHash } from "crypto";
|
|
@@ -6108,9 +6108,81 @@ function isAppendOnlySince(storedObservations, snapshotObservations) {
|
|
|
6108
6108
|
return stored === snapshot || stored.startsWith(snapshot);
|
|
6109
6109
|
}
|
|
6110
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
|
|
6111
6179
|
//#region src/storage/domains/memory/index.ts
|
|
6112
6180
|
const OM_TABLE = "mastra_observational_memory";
|
|
6113
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");
|
|
6114
6186
|
/** Canonical head order: newest generation, then earliest createdAt, then lowest id. */
|
|
6115
6187
|
const OM_HEAD_ORDER = `${quoteIdentifier("generationCount", "column name")} DESC, ${quoteIdentifier("createdAt", "column name")} ASC, ${quoteIdentifier("id", "column name")} ASC`;
|
|
6116
6188
|
/** Max `supersededBy` hops when redirecting a write aimed at a retired record. */
|
|
@@ -6217,6 +6289,69 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
6217
6289
|
this.#skipDefaultIndexes = skipDefaultIndexes;
|
|
6218
6290
|
this.#indexes = indexes?.filter((idx) => MemoryMySQL.MANAGED_TABLES.includes(idx.table));
|
|
6219
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
|
+
}
|
|
6220
6355
|
async init() {
|
|
6221
6356
|
await this.operations.createTable({
|
|
6222
6357
|
tableName: TABLE_THREADS,
|
|
@@ -6230,6 +6365,10 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
6230
6365
|
tableName: TABLE_RESOURCES,
|
|
6231
6366
|
schema: TABLE_SCHEMAS[TABLE_RESOURCES]
|
|
6232
6367
|
});
|
|
6368
|
+
await this.operations.createTable({
|
|
6369
|
+
tableName: TABLE_MEMORY_RUN_FENCES,
|
|
6370
|
+
schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_MEMORY_RUN_FENCES]
|
|
6371
|
+
});
|
|
6233
6372
|
const omSchema = withObservationalMemoryLifecycleColumns(OBSERVATIONAL_MEMORY_TABLE_SCHEMA?.[OM_TABLE]);
|
|
6234
6373
|
if (omSchema) {
|
|
6235
6374
|
await this.operations.createTable({
|
|
@@ -6297,6 +6436,10 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
6297
6436
|
tableName,
|
|
6298
6437
|
schema: TABLE_SCHEMAS[tableName]
|
|
6299
6438
|
}));
|
|
6439
|
+
statements.push(generateTableSQL({
|
|
6440
|
+
tableName: TABLE_MEMORY_RUN_FENCES,
|
|
6441
|
+
schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_MEMORY_RUN_FENCES]
|
|
6442
|
+
}));
|
|
6300
6443
|
for (const idx of MemoryMySQL.getDefaultIndexDefs()) statements.push(generateIndexSQL(idx));
|
|
6301
6444
|
return statements;
|
|
6302
6445
|
}
|
|
@@ -6315,6 +6458,7 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
6315
6458
|
await this.pool.execute(`DELETE FROM ${formatTableName(TABLE_MESSAGES)}`);
|
|
6316
6459
|
await this.pool.execute(`DELETE FROM ${formatTableName(TABLE_THREADS)}`);
|
|
6317
6460
|
await this.pool.execute(`DELETE FROM ${formatTableName(TABLE_RESOURCES)}`);
|
|
6461
|
+
await this.pool.execute(`DELETE FROM ${RUN_FENCES_TABLE}`);
|
|
6318
6462
|
try {
|
|
6319
6463
|
await this.pool.execute(`DELETE FROM ${OM_TABLE_QUOTED}`);
|
|
6320
6464
|
} catch (err) {
|
|
@@ -6495,8 +6639,8 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
6495
6639
|
}
|
|
6496
6640
|
return includeMessages;
|
|
6497
6641
|
}
|
|
6498
|
-
async upsertThread(thread) {
|
|
6499
|
-
|
|
6642
|
+
async upsertThread(q, thread) {
|
|
6643
|
+
const statement = prepareStatement({
|
|
6500
6644
|
tableName: TABLE_THREADS,
|
|
6501
6645
|
record: {
|
|
6502
6646
|
id: thread.id,
|
|
@@ -6507,18 +6651,22 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
6507
6651
|
updatedAt: thread.updatedAt
|
|
6508
6652
|
}
|
|
6509
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;
|
|
6510
6666
|
}
|
|
6511
6667
|
async getThreadById({ threadId, resourceId }) {
|
|
6512
6668
|
try {
|
|
6513
|
-
|
|
6514
|
-
const params = [threadId];
|
|
6515
|
-
if (resourceId !== void 0) {
|
|
6516
|
-
sql += ` AND ${quoteIdentifier("resourceId", "column name")} = ?`;
|
|
6517
|
-
params.push(resourceId);
|
|
6518
|
-
}
|
|
6519
|
-
const [rows] = await this.pool.execute(sql, params);
|
|
6520
|
-
const row = rows[0];
|
|
6521
|
-
return row ? this.mapThread(row) : null;
|
|
6669
|
+
return await this.#readThread(this.pool, threadId, resourceId);
|
|
6522
6670
|
} catch (error) {
|
|
6523
6671
|
throw new MastraError({
|
|
6524
6672
|
id: "MYSQL_MEMORY_GET_THREAD_BY_ID_FAILED",
|
|
@@ -6671,11 +6819,12 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
6671
6819
|
} : void 0
|
|
6672
6820
|
});
|
|
6673
6821
|
}
|
|
6674
|
-
async saveThread({ thread }) {
|
|
6822
|
+
async saveThread({ thread, fence }) {
|
|
6675
6823
|
try {
|
|
6676
|
-
await this.upsertThread(thread);
|
|
6824
|
+
await this.#fenced(fence, "saveThread", (q) => this.upsertThread(q, thread));
|
|
6677
6825
|
return thread;
|
|
6678
6826
|
} catch (error) {
|
|
6827
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
6679
6828
|
throw new MastraError({
|
|
6680
6829
|
id: "MYSQL_MEMORY_SAVE_THREAD_FAILED",
|
|
6681
6830
|
domain: ErrorDomain.STORAGE,
|
|
@@ -6684,36 +6833,39 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
6684
6833
|
}, error);
|
|
6685
6834
|
}
|
|
6686
6835
|
}
|
|
6687
|
-
async updateThread({ id, title, metadata }) {
|
|
6836
|
+
async updateThread({ id, title, metadata, fence }) {
|
|
6688
6837
|
try {
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
|
|
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,
|
|
6706
6864
|
title: title ?? existing.title,
|
|
6707
|
-
metadata:
|
|
6865
|
+
metadata: mergedMetadata,
|
|
6708
6866
|
updatedAt
|
|
6709
|
-
}
|
|
6867
|
+
};
|
|
6710
6868
|
});
|
|
6711
|
-
return {
|
|
6712
|
-
...existing,
|
|
6713
|
-
title: title ?? existing.title,
|
|
6714
|
-
metadata: mergedMetadata,
|
|
6715
|
-
updatedAt
|
|
6716
|
-
};
|
|
6717
6869
|
} catch (error) {
|
|
6718
6870
|
if (error instanceof MastraError) throw error;
|
|
6719
6871
|
throw new MastraError({
|
|
@@ -6794,6 +6946,7 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
6794
6946
|
const insertedIds = [];
|
|
6795
6947
|
try {
|
|
6796
6948
|
await connection.beginTransaction();
|
|
6949
|
+
await this.#assertFence(connection, args.fence, "saveMessages");
|
|
6797
6950
|
for (const message of messages) {
|
|
6798
6951
|
if (!message.threadId) throw new MastraError({
|
|
6799
6952
|
id: createStorageErrorId("MYSQL", "SAVE_MESSAGES", "INVALID_INPUT"),
|
|
@@ -6887,18 +7040,29 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
6887
7040
|
if (update.threadId && update.threadId !== current.threadId && current.threadId) affectedThreads.set(current.threadId, now);
|
|
6888
7041
|
}
|
|
6889
7042
|
if (!updates.length) return existing;
|
|
6890
|
-
await this.
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
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
|
+
}
|
|
6898
7061
|
});
|
|
6899
7062
|
const { messages: updated } = await this.listMessagesById({ messageIds: messages.map((m) => m.id) });
|
|
6900
7063
|
return updated;
|
|
6901
7064
|
} catch (error) {
|
|
7065
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
6902
7066
|
throw new MastraError({
|
|
6903
7067
|
id: "MYSQL_MEMORY_UPDATE_MESSAGES_FAILED",
|
|
6904
7068
|
domain: ErrorDomain.STORAGE,
|
|
@@ -6906,19 +7070,22 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
6906
7070
|
}, error);
|
|
6907
7071
|
}
|
|
6908
7072
|
}
|
|
6909
|
-
async deleteMessages(messageIds) {
|
|
7073
|
+
async deleteMessages(messageIds, options) {
|
|
6910
7074
|
if (!messageIds.length) return;
|
|
6911
7075
|
try {
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
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
|
+
});
|
|
6921
7087
|
} catch (error) {
|
|
7088
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
6922
7089
|
throw new MastraError({
|
|
6923
7090
|
id: "MYSQL_MEMORY_DELETE_MESSAGES_FAILED",
|
|
6924
7091
|
domain: ErrorDomain.STORAGE,
|
|
@@ -7317,19 +7484,23 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
7317
7484
|
}, error);
|
|
7318
7485
|
}
|
|
7319
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
|
+
}
|
|
7320
7501
|
async saveResource({ resource }) {
|
|
7321
7502
|
try {
|
|
7322
|
-
|
|
7323
|
-
await this.operations.insert({
|
|
7324
|
-
tableName: TABLE_RESOURCES,
|
|
7325
|
-
record: {
|
|
7326
|
-
id: resource.id,
|
|
7327
|
-
workingMemory: resource.workingMemory ?? null,
|
|
7328
|
-
metadata: metadataValue,
|
|
7329
|
-
createdAt: resource.createdAt ?? /* @__PURE__ */ new Date(),
|
|
7330
|
-
updatedAt: resource.updatedAt ?? /* @__PURE__ */ new Date()
|
|
7331
|
-
}
|
|
7332
|
-
});
|
|
7503
|
+
await this.#insertResource(this.pool, resource);
|
|
7333
7504
|
return resource;
|
|
7334
7505
|
} catch (error) {
|
|
7335
7506
|
throw new MastraError({
|
|
@@ -7340,7 +7511,7 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
7340
7511
|
}, error);
|
|
7341
7512
|
}
|
|
7342
7513
|
}
|
|
7343
|
-
async updateResource({ resourceId, workingMemory, metadata }) {
|
|
7514
|
+
async updateResource({ resourceId, workingMemory, metadata, fence }) {
|
|
7344
7515
|
try {
|
|
7345
7516
|
const existing = await this.getResourceById({ resourceId });
|
|
7346
7517
|
if (!existing) {
|
|
@@ -7351,22 +7522,24 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
7351
7522
|
createdAt: /* @__PURE__ */ new Date(),
|
|
7352
7523
|
updatedAt: /* @__PURE__ */ new Date()
|
|
7353
7524
|
};
|
|
7354
|
-
|
|
7525
|
+
await this.#fenced(fence, "updateResource", (q) => this.#insertResource(q, newResource));
|
|
7526
|
+
return newResource;
|
|
7355
7527
|
}
|
|
7356
7528
|
const mergedMetadata = metadata !== void 0 ? {
|
|
7357
7529
|
...existing.metadata ?? {},
|
|
7358
7530
|
...metadata ?? {}
|
|
7359
7531
|
} : existing.metadata;
|
|
7360
7532
|
const metadataValue = metadata !== void 0 ? metadata === null ? null : JSON.stringify(mergedMetadata ?? {}) : void 0;
|
|
7361
|
-
|
|
7533
|
+
const statement = prepareUpdateStatement({
|
|
7362
7534
|
tableName: TABLE_RESOURCES,
|
|
7363
7535
|
keys: { id: resourceId },
|
|
7364
|
-
|
|
7536
|
+
updates: {
|
|
7365
7537
|
...workingMemory !== void 0 ? { workingMemory } : {},
|
|
7366
7538
|
...metadataValue !== void 0 ? { metadata: metadataValue } : {},
|
|
7367
7539
|
updatedAt: /* @__PURE__ */ new Date()
|
|
7368
7540
|
}
|
|
7369
7541
|
});
|
|
7542
|
+
await this.#fenced(fence, "updateResource", (q) => q.execute(statement.sql, statement.args));
|
|
7370
7543
|
const updated = await this.getResourceById({ resourceId });
|
|
7371
7544
|
if (!updated) throw new MastraError({
|
|
7372
7545
|
id: createStorageErrorId("MYSQL", "UPDATE_RESOURCE", "NOT_FOUND_AFTER_UPDATE"),
|
|
@@ -7571,7 +7744,7 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
7571
7744
|
input.id
|
|
7572
7745
|
]);
|
|
7573
7746
|
return { applied: true };
|
|
7574
|
-
});
|
|
7747
|
+
}, "commitActiveObservations");
|
|
7575
7748
|
} catch (error) {
|
|
7576
7749
|
rethrowOrWrapOM(error, input.id, "UPDATE_ACTIVE_OBSERVATIONS");
|
|
7577
7750
|
}
|
|
@@ -7594,11 +7767,95 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
7594
7767
|
});
|
|
7595
7768
|
if (!plan) return stored;
|
|
7596
7769
|
return this.rollOverOMRow(connection, stored.id, plan.observations, plan.tokenCount, input.newRecordId, false);
|
|
7597
|
-
});
|
|
7770
|
+
}, "createReflectionGeneration");
|
|
7598
7771
|
} catch (error) {
|
|
7599
7772
|
rethrowOrWrapOM(error, input.currentRecord.id, "CREATE_REFLECTION_GENERATION", { currentRecordId: input.currentRecord.id });
|
|
7600
7773
|
}
|
|
7601
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
|
+
}
|
|
7602
7859
|
async setReflectingFlag(id, isReflecting) {
|
|
7603
7860
|
await this.updateOMFlag(id, "isReflecting", isReflecting, "SET_REFLECTING_FLAG");
|
|
7604
7861
|
}
|
|
@@ -7704,7 +7961,7 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
7704
7961
|
persisted: true,
|
|
7705
7962
|
recordId: target.id
|
|
7706
7963
|
};
|
|
7707
|
-
});
|
|
7964
|
+
}, "appendBufferedObservations");
|
|
7708
7965
|
} catch (error) {
|
|
7709
7966
|
rethrowOrWrapOM(error, input.id, "UPDATE_BUFFERED_OBSERVATIONS");
|
|
7710
7967
|
}
|
|
@@ -7816,7 +8073,7 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
7816
8073
|
suggestedContinuation: latestChunkHints?.suggestedContinuation ?? void 0,
|
|
7817
8074
|
currentTask: latestChunkHints?.currentTask ?? void 0
|
|
7818
8075
|
};
|
|
7819
|
-
});
|
|
8076
|
+
}, "swapBufferedToActive");
|
|
7820
8077
|
} catch (error) {
|
|
7821
8078
|
rethrowOrWrapOM(error, input.id, "SWAP_BUFFERED_TO_ACTIVE");
|
|
7822
8079
|
}
|
|
@@ -7825,7 +8082,7 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
7825
8082
|
try {
|
|
7826
8083
|
const nowSql = transformToSqlValue(/* @__PURE__ */ new Date());
|
|
7827
8084
|
const br = omCol("bufferedReflection");
|
|
7828
|
-
const [result] = await this.
|
|
8085
|
+
const [result] = await this.#fenced(void 0, "updateBufferedReflection", (q) => q.execute(`UPDATE ${OM_TABLE_QUOTED} SET
|
|
7829
8086
|
${br} = CASE
|
|
7830
8087
|
WHEN ${br} IS NOT NULL AND ${br} != ''
|
|
7831
8088
|
THEN CONCAT(${br}, CHAR(10), CHAR(10), ?)
|
|
@@ -7843,7 +8100,7 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
7843
8100
|
input.reflectedObservationLineCount,
|
|
7844
8101
|
nowSql,
|
|
7845
8102
|
input.id
|
|
7846
|
-
]);
|
|
8103
|
+
]));
|
|
7847
8104
|
if (result.affectedRows === 0) throwOMNotFound(input.id, "UPDATE_BUFFERED_REFLECTION");
|
|
7848
8105
|
} catch (error) {
|
|
7849
8106
|
rethrowOrWrapOM(error, input.id, "UPDATE_BUFFERED_REFLECTION");
|
|
@@ -7871,7 +8128,7 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
7871
8128
|
const newObservations = unreflectedContent ? `${bufferedReflection}\n\n${unreflectedContent}` : bufferedReflection;
|
|
7872
8129
|
const tokenCount = input.tokenCount + Math.max(0, (stored.observationTokenCount ?? 0) - (currentRecord.observationTokenCount ?? 0));
|
|
7873
8130
|
return this.rollOverOMRow(connection, stored.id, newObservations, tokenCount, input.newRecordId, true);
|
|
7874
|
-
});
|
|
8131
|
+
}, "swapBufferedReflectionToActive");
|
|
7875
8132
|
} catch (error) {
|
|
7876
8133
|
rethrowOrWrapOM(error, input.currentRecord.id, "SWAP_BUFFERED_REFLECTION_TO_ACTIVE");
|
|
7877
8134
|
}
|
|
@@ -7882,11 +8139,12 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
7882
8139
|
* only expected lock conflict is a wait; a deadlock or lock-wait timeout (e.g. against the
|
|
7883
8140
|
* startup backfill's scan) is retried a bounded number of times.
|
|
7884
8141
|
*/
|
|
7885
|
-
async withOMTransaction(fn) {
|
|
8142
|
+
async withOMTransaction(fn, fencedOperation) {
|
|
7886
8143
|
for (let attempt = 1;; attempt++) {
|
|
7887
8144
|
const connection = await this.pool.getConnection();
|
|
7888
8145
|
try {
|
|
7889
8146
|
await connection.beginTransaction();
|
|
8147
|
+
if (fencedOperation) await this.#assertFence(connection, void 0, fencedOperation);
|
|
7890
8148
|
const result = await fn(connection);
|
|
7891
8149
|
await connection.commit();
|
|
7892
8150
|
return result;
|
|
@@ -11063,6 +11321,26 @@ function parseSnapshot(snapshot) {
|
|
|
11063
11321
|
}
|
|
11064
11322
|
return snapshot;
|
|
11065
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
|
+
}
|
|
11066
11344
|
function mapWorkflowRow(row) {
|
|
11067
11345
|
return {
|
|
11068
11346
|
workflowName: row.workflow_name,
|
|
@@ -11095,6 +11373,9 @@ var WorkflowsMySQL = class WorkflowsMySQL extends WorkflowsStorage {
|
|
|
11095
11373
|
tableName: TABLE_WORKFLOW_SNAPSHOT,
|
|
11096
11374
|
schema: TABLE_SCHEMAS[TABLE_WORKFLOW_SNAPSHOT],
|
|
11097
11375
|
compositePrimaryKey: ["workflow_name", "run_id"]
|
|
11376
|
+
}), generateTableSQL({
|
|
11377
|
+
tableName: TABLE_WORKFLOW_RUN_OWNERS,
|
|
11378
|
+
schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_WORKFLOW_RUN_OWNERS]
|
|
11098
11379
|
})];
|
|
11099
11380
|
}
|
|
11100
11381
|
constructor({ operations, pool, skipDefaultIndexes, indexes }) {
|
|
@@ -11138,16 +11419,125 @@ var WorkflowsMySQL = class WorkflowsMySQL extends WorkflowsStorage {
|
|
|
11138
11419
|
schema,
|
|
11139
11420
|
ifNotExists: ["resourceId"]
|
|
11140
11421
|
});
|
|
11422
|
+
await this.operations.createTable({
|
|
11423
|
+
tableName: TABLE_WORKFLOW_RUN_OWNERS,
|
|
11424
|
+
schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_WORKFLOW_RUN_OWNERS]
|
|
11425
|
+
});
|
|
11141
11426
|
await this.createDefaultIndexes();
|
|
11142
11427
|
await this.createCustomIndexes();
|
|
11143
11428
|
}
|
|
11144
11429
|
async dangerouslyClearAll() {
|
|
11145
11430
|
await this.pool.execute(`DELETE FROM ${formatTableName(TABLE_WORKFLOW_SNAPSHOT)}`);
|
|
11431
|
+
await this.pool.execute(`DELETE FROM ${RUN_OWNERS_TABLE}`);
|
|
11432
|
+
}
|
|
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
|
+
}
|
|
11146
11489
|
}
|
|
11147
|
-
async
|
|
11490
|
+
async renewRunOwnership({ leaseMs, ...fence }) {
|
|
11148
11491
|
try {
|
|
11149
|
-
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
|
+
});
|
|
11150
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;
|
|
11151
11541
|
throw new MastraError({
|
|
11152
11542
|
id: "MYSQL_WORKFLOWS_DELETE_RUN_BY_ID_FAILED",
|
|
11153
11543
|
domain: ErrorDomain.STORAGE,
|
|
@@ -11159,10 +11549,12 @@ var WorkflowsMySQL = class WorkflowsMySQL extends WorkflowsStorage {
|
|
|
11159
11549
|
}, error);
|
|
11160
11550
|
}
|
|
11161
11551
|
}
|
|
11162
|
-
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);
|
|
11163
11554
|
const connection = await this.pool.getConnection();
|
|
11164
11555
|
try {
|
|
11165
11556
|
await connection.beginTransaction();
|
|
11557
|
+
if (fence) await assertRunFence(connection, RUN_OWNERS_TABLE, fence, "updateWorkflowResults");
|
|
11166
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]);
|
|
11167
11559
|
if (!Array.isArray(rows) || rows.length === 0) {
|
|
11168
11560
|
await connection.rollback();
|
|
@@ -11213,10 +11605,12 @@ var WorkflowsMySQL = class WorkflowsMySQL extends WorkflowsStorage {
|
|
|
11213
11605
|
connection.release();
|
|
11214
11606
|
}
|
|
11215
11607
|
}
|
|
11216
|
-
async updateWorkflowState({ workflowName, runId, opts }) {
|
|
11608
|
+
async updateWorkflowState({ workflowName, runId, opts, fence: explicitFence }) {
|
|
11609
|
+
const fence = resolveRunFence(this, explicitFence, runId);
|
|
11217
11610
|
const connection = await this.pool.getConnection();
|
|
11218
11611
|
try {
|
|
11219
11612
|
await connection.beginTransaction();
|
|
11613
|
+
if (fence) await assertRunFence(connection, RUN_OWNERS_TABLE, fence, "updateWorkflowState");
|
|
11220
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]);
|
|
11221
11615
|
if (!Array.isArray(rows) || rows.length === 0) {
|
|
11222
11616
|
await connection.rollback();
|
|
@@ -11242,6 +11636,7 @@ var WorkflowsMySQL = class WorkflowsMySQL extends WorkflowsStorage {
|
|
|
11242
11636
|
return updatedSnapshot;
|
|
11243
11637
|
} catch (error) {
|
|
11244
11638
|
await connection.rollback();
|
|
11639
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
11245
11640
|
throw new MastraError({
|
|
11246
11641
|
id: "MYSQL_WORKFLOWS_UPDATE_STATE_FAILED",
|
|
11247
11642
|
domain: ErrorDomain.STORAGE,
|
|
@@ -11255,35 +11650,38 @@ var WorkflowsMySQL = class WorkflowsMySQL extends WorkflowsStorage {
|
|
|
11255
11650
|
connection.release();
|
|
11256
11651
|
}
|
|
11257
11652
|
}
|
|
11258
|
-
async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt }) {
|
|
11653
|
+
async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt, fence }) {
|
|
11259
11654
|
const now = /* @__PURE__ */ new Date();
|
|
11260
11655
|
const createdAtValue = createdAt ?? now;
|
|
11261
11656
|
const updatedAtValue = updatedAt ?? now;
|
|
11657
|
+
const tableName = formatTableName(TABLE_WORKFLOW_SNAPSHOT);
|
|
11262
11658
|
try {
|
|
11263
|
-
|
|
11264
|
-
|
|
11265
|
-
|
|
11266
|
-
|
|
11267
|
-
|
|
11268
|
-
|
|
11269
|
-
|
|
11270
|
-
|
|
11271
|
-
|
|
11272
|
-
|
|
11273
|
-
|
|
11274
|
-
|
|
11275
|
-
|
|
11276
|
-
|
|
11277
|
-
|
|
11278
|
-
|
|
11279
|
-
|
|
11280
|
-
|
|
11281
|
-
|
|
11282
|
-
|
|
11283
|
-
|
|
11284
|
-
|
|
11285
|
-
|
|
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
|
+
});
|
|
11286
11683
|
} catch (error) {
|
|
11684
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
11287
11685
|
throw new MastraError({
|
|
11288
11686
|
id: "MYSQL_WORKFLOWS_PERSIST_SNAPSHOT_FAILED",
|
|
11289
11687
|
domain: ErrorDomain.STORAGE,
|