@mastra/mssql 1.10.2 → 1.10.3-alpha.0
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 +315 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +316 -68
- package/dist/index.js.map +1 -1
- package/dist/storage/db/index.d.ts +2 -1
- package/dist/storage/db/index.d.ts.map +1 -1
- package/dist/storage/db/run-fencing.d.ts +38 -0
- package/dist/storage/db/run-fencing.d.ts.map +1 -0
- package/dist/storage/domains/memory/index.d.ts +18 -7
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts +17 -5
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/package.json +4 -4
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, MastraCompositeStore, MemoryStorage, ObservabilityStorage, SPAN_SCHEMA, ScoresStorage, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_BACKGROUND_TASKS, TABLE_MESSAGES, TABLE_RESOURCES, TABLE_SCHEMAS, TABLE_SCORERS, TABLE_SPANS, TABLE_THREADS, TABLE_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_SNAPSHOT, TraceStatus, WORKFLOW_DEFINITIONS_SCHEMA, WorkflowDefinitionsStorage, WorkflowsStorage, calculatePagination, createStorageErrorId, executeRetentionPrune, getDefaultValue, listTracesArgsSchema, matchesExpectedWorkflowStatus, normalizePerPage, resolveRetentionTargets, retentionCutoffMs, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
|
|
2
|
+
import { AGENTS_SCHEMA, AGENT_VERSIONS_SCHEMA, AgentsStorage, BackgroundTasksStorage, MastraCompositeStore, MemoryStorage, ObservabilityStorage, RUN_FENCING_TABLE_SCHEMAS, RunFenceConflictError, SPAN_SCHEMA, ScoresStorage, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_BACKGROUND_TASKS, TABLE_MEMORY_RUN_FENCES, TABLE_MESSAGES, TABLE_RESOURCES, TABLE_SCHEMAS, TABLE_SCORERS, TABLE_SPANS, TABLE_THREADS, TABLE_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_RUN_OWNERS, TABLE_WORKFLOW_SNAPSHOT, TraceStatus, WORKFLOW_DEFINITIONS_SCHEMA, WorkflowDefinitionsStorage, WorkflowsStorage, calculatePagination, createStorageErrorId, executeRetentionPrune, getDefaultValue, isRunFenceConflictError, listTracesArgsSchema, matchesExpectedWorkflowStatus, matchesRunFence, normalizePerPage, resolveRetentionTargets, resolveRunFence, retentionCutoffMs, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
|
|
3
3
|
import sql from "mssql";
|
|
4
4
|
import { parseSqlIdentifier } from "@mastra/core/utils";
|
|
5
5
|
import { MastraBase } from "@mastra/core/base";
|
|
@@ -2103,6 +2103,73 @@ var BackgroundTasksMSSQL = class BackgroundTasksMSSQL extends BackgroundTasksSto
|
|
|
2103
2103
|
}
|
|
2104
2104
|
};
|
|
2105
2105
|
//#endregion
|
|
2106
|
+
//#region src/storage/db/run-fencing.ts
|
|
2107
|
+
/**
|
|
2108
|
+
* Epoch milliseconds on the database clock. SQL Server evaluates
|
|
2109
|
+
* `SYSUTCDATETIME()` once when a statement starts, so a statement that waits
|
|
2110
|
+
* on a lock compares against the time it started. That error only makes an
|
|
2111
|
+
* existing lease look longer and a new lease shorter.
|
|
2112
|
+
*/
|
|
2113
|
+
const DB_NOW_MS = `DATEDIFF_BIG(MILLISECOND, '1970-01-01', SYSUTCDATETIME())`;
|
|
2114
|
+
const ERR_DEADLOCK_VICTIM = 1205;
|
|
2115
|
+
const ERR_UNIQUE_INDEX = 2601;
|
|
2116
|
+
const ERR_PRIMARY_KEY = 2627;
|
|
2117
|
+
const MAX_CLAIM_ATTEMPTS = 5;
|
|
2118
|
+
/**
|
|
2119
|
+
* The check half of a fenced write; call it first inside the write's
|
|
2120
|
+
* transaction. `HOLDLOCK` keeps the shared lock on the claim row until commit,
|
|
2121
|
+
* even on databases with read-committed snapshot on, so a claim that updates
|
|
2122
|
+
* the row waits for the write to finish, and every later write sees the new
|
|
2123
|
+
* claim.
|
|
2124
|
+
*/
|
|
2125
|
+
async function assertRunFence(q, claimsTable, fence, operation) {
|
|
2126
|
+
const row = (await q.request().input("runId", fence.runId).query(`SELECT [generation], [ownerId] FROM ${claimsTable} WITH (HOLDLOCK) WHERE [runId] = @runId`)).recordset[0];
|
|
2127
|
+
if (!matchesRunFence(row ? {
|
|
2128
|
+
generation: Number(row.generation),
|
|
2129
|
+
ownerId: String(row.ownerId)
|
|
2130
|
+
} : null, fence)) throw new RunFenceConflictError(fence, operation);
|
|
2131
|
+
}
|
|
2132
|
+
/** Runs `fn` in a transaction. */
|
|
2133
|
+
async function inTransaction(pool, fn) {
|
|
2134
|
+
const transaction = pool.transaction();
|
|
2135
|
+
await transaction.begin();
|
|
2136
|
+
try {
|
|
2137
|
+
const result = await fn(transaction);
|
|
2138
|
+
await transaction.commit();
|
|
2139
|
+
return result;
|
|
2140
|
+
} catch (error) {
|
|
2141
|
+
await transaction.rollback().catch(() => {});
|
|
2142
|
+
throw error;
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
/**
|
|
2146
|
+
* Runs `write` directly when it carries no fence, or in a transaction behind
|
|
2147
|
+
* the fence check when it does.
|
|
2148
|
+
*/
|
|
2149
|
+
function withRunFence(pool, claimsTable, fence, operation, write) {
|
|
2150
|
+
if (!fence) return write(pool);
|
|
2151
|
+
return inTransaction(pool, async (transaction) => {
|
|
2152
|
+
await assertRunFence(transaction, claimsTable, fence, operation);
|
|
2153
|
+
return write(transaction);
|
|
2154
|
+
});
|
|
2155
|
+
}
|
|
2156
|
+
/**
|
|
2157
|
+
* Runs a transaction that creates or updates a run's claim row, retrying when
|
|
2158
|
+
* two first claims race to insert it. Claims read the row with
|
|
2159
|
+
* `UPDLOCK, HOLDLOCK`, which locks the key range of a missing row, so racing
|
|
2160
|
+
* first claims normally queue; the retry covers a deadlock victim or a
|
|
2161
|
+
* duplicate key if they don't. On retry the row exists and the claim
|
|
2162
|
+
* serializes behind its row lock.
|
|
2163
|
+
*/
|
|
2164
|
+
async function claimTransaction(pool, fn) {
|
|
2165
|
+
for (let attempt = 1;; attempt++) try {
|
|
2166
|
+
return await inTransaction(pool, fn);
|
|
2167
|
+
} catch (error) {
|
|
2168
|
+
const number = error.number;
|
|
2169
|
+
if (attempt >= MAX_CLAIM_ATTEMPTS || !(number === ERR_DEADLOCK_VICTIM || number === ERR_PRIMARY_KEY || number === ERR_UNIQUE_INDEX)) throw error;
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
//#endregion
|
|
2106
2173
|
//#region src/storage/domains/memory/index.ts
|
|
2107
2174
|
function bindMssqlMetadataParams(request, params) {
|
|
2108
2175
|
for (const [paramName, paramValue] of Object.entries(params)) request.input(paramName, paramValue);
|
|
@@ -2192,9 +2259,69 @@ var MemoryMSSQL = class MemoryMSSQL extends MemoryStorage {
|
|
|
2192
2259
|
tableName: TABLE_RESOURCES,
|
|
2193
2260
|
schema: TABLE_SCHEMAS[TABLE_RESOURCES]
|
|
2194
2261
|
});
|
|
2262
|
+
await this.db.createTable({
|
|
2263
|
+
tableName: TABLE_MEMORY_RUN_FENCES,
|
|
2264
|
+
schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_MEMORY_RUN_FENCES]
|
|
2265
|
+
});
|
|
2195
2266
|
await this.createDefaultIndexes();
|
|
2196
2267
|
await this.createCustomIndexes();
|
|
2197
2268
|
}
|
|
2269
|
+
supportsRunFencing() {
|
|
2270
|
+
return true;
|
|
2271
|
+
}
|
|
2272
|
+
get #runFencesTable() {
|
|
2273
|
+
return getTableName({
|
|
2274
|
+
indexName: TABLE_MEMORY_RUN_FENCES,
|
|
2275
|
+
schemaName: getSchemaName(this.schema)
|
|
2276
|
+
});
|
|
2277
|
+
}
|
|
2278
|
+
async raiseRunFence(fence) {
|
|
2279
|
+
const table = this.#runFencesTable;
|
|
2280
|
+
try {
|
|
2281
|
+
return await claimTransaction(this.pool, async (transaction) => {
|
|
2282
|
+
const row = (await transaction.request().input("runId", fence.runId).query(`SELECT [generation], [ownerId] FROM ${table} WITH (UPDLOCK, HOLDLOCK) WHERE [runId] = @runId`)).recordset[0];
|
|
2283
|
+
if (!row) {
|
|
2284
|
+
await transaction.request().input("runId", fence.runId).input("generation", sql.BigInt, fence.generation).input("ownerId", fence.ownerId).query(`INSERT INTO ${table} ([runId], [generation], [ownerId]) VALUES (@runId, @generation, @ownerId)`);
|
|
2285
|
+
return true;
|
|
2286
|
+
}
|
|
2287
|
+
const generation = Number(row.generation);
|
|
2288
|
+
const raises = generation < fence.generation;
|
|
2289
|
+
const reaffirms = generation === fence.generation && String(row.ownerId) === fence.ownerId;
|
|
2290
|
+
if (!raises && !reaffirms) return false;
|
|
2291
|
+
await transaction.request().input("runId", fence.runId).input("generation", sql.BigInt, fence.generation).input("ownerId", fence.ownerId).query(`UPDATE ${table} SET [generation] = @generation, [ownerId] = @ownerId, [retiredAt] = NULL WHERE [runId] = @runId`);
|
|
2292
|
+
return true;
|
|
2293
|
+
});
|
|
2294
|
+
} catch (error) {
|
|
2295
|
+
throw new MastraError({
|
|
2296
|
+
id: createStorageErrorId("MSSQL", "RAISE_RUN_FENCE", "FAILED"),
|
|
2297
|
+
domain: ErrorDomain.STORAGE,
|
|
2298
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2299
|
+
details: { runId: fence.runId }
|
|
2300
|
+
}, error);
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
async retireRunFence(fence) {
|
|
2304
|
+
try {
|
|
2305
|
+
return (await this.pool.request().input("runId", fence.runId).input("generation", sql.BigInt, fence.generation).input("ownerId", fence.ownerId).query(`UPDATE ${this.#runFencesTable} SET [retiredAt] = ${DB_NOW_MS}
|
|
2306
|
+
WHERE [runId] = @runId AND [generation] = @generation AND [ownerId] = @ownerId`)).rowsAffected[0] === 1;
|
|
2307
|
+
} catch (error) {
|
|
2308
|
+
throw new MastraError({
|
|
2309
|
+
id: createStorageErrorId("MSSQL", "RETIRE_RUN_FENCE", "FAILED"),
|
|
2310
|
+
domain: ErrorDomain.STORAGE,
|
|
2311
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2312
|
+
details: { runId: fence.runId }
|
|
2313
|
+
}, error);
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
/** Runs `write` behind its own fence, otherwise the one in scope for this store. */
|
|
2317
|
+
#fenced(fence, operation, write) {
|
|
2318
|
+
return withRunFence(this.pool, this.#runFencesTable, resolveRunFence(this, fence), operation, write);
|
|
2319
|
+
}
|
|
2320
|
+
/** The fence check for a write that already runs in its own transaction. */
|
|
2321
|
+
async #assertFence(transaction, fence, operation) {
|
|
2322
|
+
const resolved = resolveRunFence(this, fence);
|
|
2323
|
+
if (resolved) await assertRunFence(transaction, this.#runFencesTable, resolved, operation);
|
|
2324
|
+
}
|
|
2198
2325
|
/**
|
|
2199
2326
|
* Returns default index definitions for the memory domain tables.
|
|
2200
2327
|
* IMPORTANT: Uses seq_id DESC instead of createdAt DESC for MSSQL due to millisecond accuracy limitations
|
|
@@ -2237,6 +2364,7 @@ var MemoryMSSQL = class MemoryMSSQL extends MemoryStorage {
|
|
|
2237
2364
|
await this.db.clearTable({ tableName: TABLE_MESSAGES });
|
|
2238
2365
|
await this.db.clearTable({ tableName: TABLE_THREADS });
|
|
2239
2366
|
await this.db.clearTable({ tableName: TABLE_RESOURCES });
|
|
2367
|
+
await this.db.clearTable({ tableName: TABLE_MEMORY_RUN_FENCES });
|
|
2240
2368
|
}
|
|
2241
2369
|
async getThreadById({ threadId, resourceId }) {
|
|
2242
2370
|
try {
|
|
@@ -2387,7 +2515,7 @@ var MemoryMSSQL = class MemoryMSSQL extends MemoryStorage {
|
|
|
2387
2515
|
throw mastraError;
|
|
2388
2516
|
}
|
|
2389
2517
|
}
|
|
2390
|
-
async saveThread({ thread }) {
|
|
2518
|
+
async saveThread({ thread, fence }) {
|
|
2391
2519
|
try {
|
|
2392
2520
|
const mergeSql = `MERGE INTO ${getTableName({
|
|
2393
2521
|
indexName: TABLE_THREADS,
|
|
@@ -2404,18 +2532,21 @@ var MemoryMSSQL = class MemoryMSSQL extends MemoryStorage {
|
|
|
2404
2532
|
WHEN NOT MATCHED THEN
|
|
2405
2533
|
INSERT (id, [resourceId], title, metadata, [createdAt], [updatedAt])
|
|
2406
2534
|
VALUES (@id, @resourceId, @title, @metadata, @createdAt, @updatedAt);`;
|
|
2407
|
-
const req = this.pool.request();
|
|
2408
|
-
req.input("id", thread.id);
|
|
2409
|
-
req.input("resourceId", thread.resourceId);
|
|
2410
|
-
req.input("title", thread.title);
|
|
2411
2535
|
const metadata = thread.metadata ? JSON.stringify(thread.metadata) : null;
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2536
|
+
await this.#fenced(fence, "saveThread", (q) => {
|
|
2537
|
+
const req = q.request();
|
|
2538
|
+
req.input("id", thread.id);
|
|
2539
|
+
req.input("resourceId", thread.resourceId);
|
|
2540
|
+
req.input("title", thread.title);
|
|
2541
|
+
if (metadata === null) req.input("metadata", sql.NVarChar, null);
|
|
2542
|
+
else req.input("metadata", metadata);
|
|
2543
|
+
req.input("createdAt", sql.DateTime2, thread.createdAt);
|
|
2544
|
+
req.input("updatedAt", sql.DateTime2, thread.updatedAt);
|
|
2545
|
+
return req.query(mergeSql);
|
|
2546
|
+
});
|
|
2417
2547
|
return thread;
|
|
2418
2548
|
} catch (error) {
|
|
2549
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
2419
2550
|
throw new MastraError({
|
|
2420
2551
|
id: createStorageErrorId("MSSQL", "SAVE_THREAD", "FAILED"),
|
|
2421
2552
|
domain: ErrorDomain.STORAGE,
|
|
@@ -2427,7 +2558,7 @@ var MemoryMSSQL = class MemoryMSSQL extends MemoryStorage {
|
|
|
2427
2558
|
/**
|
|
2428
2559
|
* Updates a thread's title and metadata, merging with existing metadata. Returns the updated thread.
|
|
2429
2560
|
*/
|
|
2430
|
-
async updateThread({ id, title, metadata }) {
|
|
2561
|
+
async updateThread({ id, title, metadata, fence }) {
|
|
2431
2562
|
const existingThread = await this.getThreadById({ threadId: id });
|
|
2432
2563
|
if (!existingThread) throw new MastraError({
|
|
2433
2564
|
id: createStorageErrorId("MSSQL", "UPDATE_THREAD", "NOT_FOUND"),
|
|
@@ -2453,12 +2584,7 @@ var MemoryMSSQL = class MemoryMSSQL extends MemoryStorage {
|
|
|
2453
2584
|
[updatedAt] = @updatedAt
|
|
2454
2585
|
OUTPUT INSERTED.*
|
|
2455
2586
|
WHERE id = @id`;
|
|
2456
|
-
const
|
|
2457
|
-
req.input("id", id);
|
|
2458
|
-
req.input("title", title ?? existingThread.title);
|
|
2459
|
-
req.input("metadata", JSON.stringify(mergedMetadata));
|
|
2460
|
-
req.input("updatedAt", /* @__PURE__ */ new Date());
|
|
2461
|
-
const result = await req.query(sql);
|
|
2587
|
+
const result = await this.#fenced(fence, "updateThread", (q) => q.request().input("id", id).input("title", title ?? existingThread.title).input("metadata", JSON.stringify(mergedMetadata)).input("updatedAt", /* @__PURE__ */ new Date()).query(sql));
|
|
2462
2588
|
let thread = result.recordset && result.recordset[0];
|
|
2463
2589
|
if (thread && "seq_id" in thread) {
|
|
2464
2590
|
const { seq_id, ...rest } = thread;
|
|
@@ -2481,6 +2607,7 @@ var MemoryMSSQL = class MemoryMSSQL extends MemoryStorage {
|
|
|
2481
2607
|
updatedAt: thread.updatedAt
|
|
2482
2608
|
};
|
|
2483
2609
|
} catch (error) {
|
|
2610
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
2484
2611
|
throw new MastraError({
|
|
2485
2612
|
id: createStorageErrorId("MSSQL", "UPDATE_THREAD", "FAILED"),
|
|
2486
2613
|
domain: ErrorDomain.STORAGE,
|
|
@@ -2846,7 +2973,7 @@ var MemoryMSSQL = class MemoryMSSQL extends MemoryStorage {
|
|
|
2846
2973
|
throw mastraError;
|
|
2847
2974
|
}
|
|
2848
2975
|
}
|
|
2849
|
-
async saveMessages({ messages }) {
|
|
2976
|
+
async saveMessages({ messages, fence }) {
|
|
2850
2977
|
if (messages.length === 0) return { messages: [] };
|
|
2851
2978
|
const threadId = messages[0]?.threadId;
|
|
2852
2979
|
if (!threadId) throw new MastraError({
|
|
@@ -2874,6 +3001,7 @@ var MemoryMSSQL = class MemoryMSSQL extends MemoryStorage {
|
|
|
2874
3001
|
const transaction = this.pool.transaction();
|
|
2875
3002
|
await transaction.begin();
|
|
2876
3003
|
try {
|
|
3004
|
+
await this.#assertFence(transaction, fence, "saveMessages");
|
|
2877
3005
|
for (const message of messages) {
|
|
2878
3006
|
if (!message.threadId) throw new Error(`Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`);
|
|
2879
3007
|
if (!message.resourceId) throw new Error(`Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`);
|
|
@@ -2921,6 +3049,7 @@ var MemoryMSSQL = class MemoryMSSQL extends MemoryStorage {
|
|
|
2921
3049
|
});
|
|
2922
3050
|
return { messages: new MessageList().add(messagesWithParsedContent, "memory").get.all.db() };
|
|
2923
3051
|
} catch (error) {
|
|
3052
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
2924
3053
|
throw new MastraError({
|
|
2925
3054
|
id: createStorageErrorId("MSSQL", "SAVE_MESSAGES", "FAILED"),
|
|
2926
3055
|
domain: ErrorDomain.STORAGE,
|
|
@@ -2929,7 +3058,7 @@ var MemoryMSSQL = class MemoryMSSQL extends MemoryStorage {
|
|
|
2929
3058
|
}, error);
|
|
2930
3059
|
}
|
|
2931
3060
|
}
|
|
2932
|
-
async updateMessages({ messages }) {
|
|
3061
|
+
async updateMessages({ messages, fence }) {
|
|
2933
3062
|
if (!messages || messages.length === 0) return [];
|
|
2934
3063
|
const messageIds = messages.map((m) => m.id);
|
|
2935
3064
|
const idParams = messageIds.map((_, i) => `@id${i}`).join(", ");
|
|
@@ -2953,6 +3082,7 @@ var MemoryMSSQL = class MemoryMSSQL extends MemoryStorage {
|
|
|
2953
3082
|
const transaction = this.pool.transaction();
|
|
2954
3083
|
try {
|
|
2955
3084
|
await transaction.begin();
|
|
3085
|
+
await this.#assertFence(transaction, fence, "updateMessages");
|
|
2956
3086
|
for (const existingMessage of existingMessages) {
|
|
2957
3087
|
const updatePayload = messages.find((m) => m.id === existingMessage.id);
|
|
2958
3088
|
if (!updatePayload) continue;
|
|
@@ -3005,6 +3135,7 @@ var MemoryMSSQL = class MemoryMSSQL extends MemoryStorage {
|
|
|
3005
3135
|
await transaction.commit();
|
|
3006
3136
|
} catch (error) {
|
|
3007
3137
|
await transaction.rollback();
|
|
3138
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
3008
3139
|
throw new MastraError({
|
|
3009
3140
|
id: createStorageErrorId("MSSQL", "UPDATE_MESSAGES", "FAILED"),
|
|
3010
3141
|
domain: ErrorDomain.STORAGE,
|
|
@@ -3020,7 +3151,7 @@ var MemoryMSSQL = class MemoryMSSQL extends MemoryStorage {
|
|
|
3020
3151
|
return message;
|
|
3021
3152
|
});
|
|
3022
3153
|
}
|
|
3023
|
-
async deleteMessages(messageIds) {
|
|
3154
|
+
async deleteMessages(messageIds, options) {
|
|
3024
3155
|
if (!messageIds || messageIds.length === 0) return;
|
|
3025
3156
|
try {
|
|
3026
3157
|
const messageTableName = getTableName({
|
|
@@ -3040,6 +3171,7 @@ var MemoryMSSQL = class MemoryMSSQL extends MemoryStorage {
|
|
|
3040
3171
|
const transaction = this.pool.transaction();
|
|
3041
3172
|
await transaction.begin();
|
|
3042
3173
|
try {
|
|
3174
|
+
await this.#assertFence(transaction, options?.fence, "deleteMessages");
|
|
3043
3175
|
const deleteRequest = transaction.request();
|
|
3044
3176
|
messageIds.forEach((id, idx) => {
|
|
3045
3177
|
deleteRequest.input(`p${idx + 1}`, id);
|
|
@@ -3058,6 +3190,7 @@ var MemoryMSSQL = class MemoryMSSQL extends MemoryStorage {
|
|
|
3058
3190
|
throw error;
|
|
3059
3191
|
}
|
|
3060
3192
|
} catch (error) {
|
|
3193
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
3061
3194
|
throw new MastraError({
|
|
3062
3195
|
id: createStorageErrorId("MSSQL", "DELETE_MESSAGES", "FAILED"),
|
|
3063
3196
|
domain: ErrorDomain.STORAGE,
|
|
@@ -3105,7 +3238,7 @@ var MemoryMSSQL = class MemoryMSSQL extends MemoryStorage {
|
|
|
3105
3238
|
});
|
|
3106
3239
|
return resource;
|
|
3107
3240
|
}
|
|
3108
|
-
async updateResource({ resourceId, workingMemory, metadata }) {
|
|
3241
|
+
async updateResource({ resourceId, workingMemory, metadata, fence }) {
|
|
3109
3242
|
try {
|
|
3110
3243
|
const existingResource = await this.getResourceById({ resourceId });
|
|
3111
3244
|
if (!existingResource) {
|
|
@@ -3116,7 +3249,12 @@ var MemoryMSSQL = class MemoryMSSQL extends MemoryStorage {
|
|
|
3116
3249
|
createdAt: /* @__PURE__ */ new Date(),
|
|
3117
3250
|
updatedAt: /* @__PURE__ */ new Date()
|
|
3118
3251
|
};
|
|
3119
|
-
|
|
3252
|
+
await this.#fenced(fence, "updateResource", (q) => this.db.insert({
|
|
3253
|
+
tableName: TABLE_RESOURCES,
|
|
3254
|
+
record: newResource,
|
|
3255
|
+
transaction: q
|
|
3256
|
+
}));
|
|
3257
|
+
return newResource;
|
|
3120
3258
|
}
|
|
3121
3259
|
const updatedResource = {
|
|
3122
3260
|
...existingResource,
|
|
@@ -3131,22 +3269,25 @@ var MemoryMSSQL = class MemoryMSSQL extends MemoryStorage {
|
|
|
3131
3269
|
indexName: TABLE_RESOURCES,
|
|
3132
3270
|
schemaName: getSchemaName(this.schema)
|
|
3133
3271
|
});
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3272
|
+
await this.#fenced(fence, "updateResource", (q) => {
|
|
3273
|
+
const updates = [];
|
|
3274
|
+
const req = q.request();
|
|
3275
|
+
if (workingMemory !== void 0) {
|
|
3276
|
+
updates.push("workingMemory = @workingMemory");
|
|
3277
|
+
req.input("workingMemory", workingMemory);
|
|
3278
|
+
}
|
|
3279
|
+
if (metadata) {
|
|
3280
|
+
updates.push("metadata = @metadata");
|
|
3281
|
+
req.input("metadata", JSON.stringify(updatedResource.metadata));
|
|
3282
|
+
}
|
|
3283
|
+
updates.push("updatedAt = @updatedAt");
|
|
3284
|
+
req.input("updatedAt", updatedResource.updatedAt.toISOString());
|
|
3285
|
+
req.input("id", resourceId);
|
|
3286
|
+
return req.query(`UPDATE ${tableName} SET ${updates.join(", ")} WHERE id = @id`);
|
|
3287
|
+
});
|
|
3148
3288
|
return updatedResource;
|
|
3149
3289
|
} catch (error) {
|
|
3290
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
3150
3291
|
const mastraError = new MastraError({
|
|
3151
3292
|
id: createStorageErrorId("MSSQL", "UPDATE_RESOURCE", "FAILED"),
|
|
3152
3293
|
domain: ErrorDomain.STORAGE,
|
|
@@ -4423,6 +4564,17 @@ var WorkflowDefinitionsMSSQL = class WorkflowDefinitionsMSSQL extends WorkflowDe
|
|
|
4423
4564
|
};
|
|
4424
4565
|
//#endregion
|
|
4425
4566
|
//#region src/storage/domains/workflows/index.ts
|
|
4567
|
+
const RUN_OWNER_COLUMNS = `[generation], [ownerId], [leaseExpiresAt], ${DB_NOW_MS} AS [nowMs]`;
|
|
4568
|
+
function toRunOwnershipRecord(runId, row) {
|
|
4569
|
+
const leaseExpiresAt = row.leaseExpiresAt === null ? null : Number(row.leaseExpiresAt);
|
|
4570
|
+
return {
|
|
4571
|
+
runId,
|
|
4572
|
+
generation: Number(row.generation),
|
|
4573
|
+
ownerId: String(row.ownerId),
|
|
4574
|
+
leaseExpiresAt: leaseExpiresAt === null ? null : new Date(leaseExpiresAt),
|
|
4575
|
+
live: leaseExpiresAt !== null && leaseExpiresAt > Number(row.nowMs)
|
|
4576
|
+
};
|
|
4577
|
+
}
|
|
4426
4578
|
var WorkflowsMSSQL = class WorkflowsMSSQL extends WorkflowsStorage {
|
|
4427
4579
|
pool;
|
|
4428
4580
|
db;
|
|
@@ -4472,6 +4624,10 @@ var WorkflowsMSSQL = class WorkflowsMSSQL extends WorkflowsStorage {
|
|
|
4472
4624
|
tableName: TABLE_WORKFLOW_SNAPSHOT,
|
|
4473
4625
|
schema: TABLE_SCHEMAS[TABLE_WORKFLOW_SNAPSHOT]
|
|
4474
4626
|
});
|
|
4627
|
+
await this.db.createTable({
|
|
4628
|
+
tableName: TABLE_WORKFLOW_RUN_OWNERS,
|
|
4629
|
+
schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_WORKFLOW_RUN_OWNERS]
|
|
4630
|
+
});
|
|
4475
4631
|
await this.createDefaultIndexes();
|
|
4476
4632
|
await this.createCustomIndexes();
|
|
4477
4633
|
}
|
|
@@ -4488,6 +4644,98 @@ var WorkflowsMSSQL = class WorkflowsMSSQL extends WorkflowsStorage {
|
|
|
4488
4644
|
}
|
|
4489
4645
|
async dangerouslyClearAll() {
|
|
4490
4646
|
await this.db.clearTable({ tableName: TABLE_WORKFLOW_SNAPSHOT });
|
|
4647
|
+
await this.db.clearTable({ tableName: TABLE_WORKFLOW_RUN_OWNERS });
|
|
4648
|
+
}
|
|
4649
|
+
get #runOwnersTable() {
|
|
4650
|
+
return getTableName({
|
|
4651
|
+
indexName: TABLE_WORKFLOW_RUN_OWNERS,
|
|
4652
|
+
schemaName: getSchemaName(this.schema)
|
|
4653
|
+
});
|
|
4654
|
+
}
|
|
4655
|
+
async #readRunOwner(q, runId) {
|
|
4656
|
+
const row = (await q.request().input("runId", runId).query(`SELECT ${RUN_OWNER_COLUMNS} FROM ${this.#runOwnersTable} WHERE [runId] = @runId`)).recordset[0];
|
|
4657
|
+
return row ? toRunOwnershipRecord(runId, row) : null;
|
|
4658
|
+
}
|
|
4659
|
+
supportsRunFencing() {
|
|
4660
|
+
return true;
|
|
4661
|
+
}
|
|
4662
|
+
#ownershipError(operation, runId, error) {
|
|
4663
|
+
return new MastraError({
|
|
4664
|
+
id: createStorageErrorId("MSSQL", operation, "FAILED"),
|
|
4665
|
+
domain: ErrorDomain.STORAGE,
|
|
4666
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
4667
|
+
details: { runId }
|
|
4668
|
+
}, error);
|
|
4669
|
+
}
|
|
4670
|
+
async claimRunOwnership({ runId, ownerId, leaseMs, force, expectedGeneration }) {
|
|
4671
|
+
const table = this.#runOwnersTable;
|
|
4672
|
+
try {
|
|
4673
|
+
return await claimTransaction(this.pool, async (transaction) => {
|
|
4674
|
+
if ((await transaction.request().input("runId", runId).query(`SELECT [generation] FROM ${table} WITH (UPDLOCK, HOLDLOCK) WHERE [runId] = @runId`)).recordset.length === 0) {
|
|
4675
|
+
if (expectedGeneration !== void 0 && expectedGeneration !== 0) return {
|
|
4676
|
+
acquired: false,
|
|
4677
|
+
record: null
|
|
4678
|
+
};
|
|
4679
|
+
await transaction.request().input("runId", runId).input("ownerId", ownerId).input("leaseMs", sql.BigInt, leaseMs).query(`INSERT INTO ${table} ([runId], [generation], [ownerId], [leaseExpiresAt], [updatedAt])
|
|
4680
|
+
VALUES (@runId, ${DB_NOW_MS}, @ownerId, ${DB_NOW_MS} + @leaseMs, ${DB_NOW_MS})`);
|
|
4681
|
+
return {
|
|
4682
|
+
acquired: true,
|
|
4683
|
+
record: await this.#readRunOwner(transaction, runId)
|
|
4684
|
+
};
|
|
4685
|
+
}
|
|
4686
|
+
const updated = await transaction.request().input("runId", runId).input("ownerId", ownerId).input("leaseMs", sql.BigInt, leaseMs).input("expectedGeneration", sql.BigInt, expectedGeneration ?? null).input("force", sql.Bit, force === true).query(`UPDATE ${table}
|
|
4687
|
+
SET [generation] = [generation] + 1, [ownerId] = @ownerId, [leaseExpiresAt] = ${DB_NOW_MS} + @leaseMs,
|
|
4688
|
+
[updatedAt] = ${DB_NOW_MS}
|
|
4689
|
+
WHERE [runId] = @runId
|
|
4690
|
+
AND (@expectedGeneration IS NULL OR [generation] = @expectedGeneration)
|
|
4691
|
+
AND (@force = 1 OR [leaseExpiresAt] IS NULL OR [leaseExpiresAt] <= ${DB_NOW_MS})`);
|
|
4692
|
+
const record = await this.#readRunOwner(transaction, runId);
|
|
4693
|
+
return updated.rowsAffected[0] === 1 && record ? {
|
|
4694
|
+
acquired: true,
|
|
4695
|
+
record
|
|
4696
|
+
} : {
|
|
4697
|
+
acquired: false,
|
|
4698
|
+
record
|
|
4699
|
+
};
|
|
4700
|
+
});
|
|
4701
|
+
} catch (error) {
|
|
4702
|
+
throw this.#ownershipError("CLAIM_RUN_OWNERSHIP", runId, error);
|
|
4703
|
+
}
|
|
4704
|
+
}
|
|
4705
|
+
async renewRunOwnership({ leaseMs, ...fence }) {
|
|
4706
|
+
try {
|
|
4707
|
+
return await inTransaction(this.pool, async (transaction) => {
|
|
4708
|
+
const updated = await transaction.request().input("runId", fence.runId).input("generation", sql.BigInt, fence.generation).input("ownerId", fence.ownerId).input("leaseMs", sql.BigInt, leaseMs).query(`UPDATE ${this.#runOwnersTable}
|
|
4709
|
+
SET [leaseExpiresAt] = ${DB_NOW_MS} + @leaseMs, [updatedAt] = ${DB_NOW_MS}
|
|
4710
|
+
WHERE [runId] = @runId AND [generation] = @generation AND [ownerId] = @ownerId
|
|
4711
|
+
AND [leaseExpiresAt] IS NOT NULL`);
|
|
4712
|
+
const record = await this.#readRunOwner(transaction, fence.runId);
|
|
4713
|
+
return updated.rowsAffected[0] === 1 && record ? {
|
|
4714
|
+
renewed: true,
|
|
4715
|
+
record
|
|
4716
|
+
} : {
|
|
4717
|
+
renewed: false,
|
|
4718
|
+
record
|
|
4719
|
+
};
|
|
4720
|
+
});
|
|
4721
|
+
} catch (error) {
|
|
4722
|
+
throw this.#ownershipError("RENEW_RUN_OWNERSHIP", fence.runId, error);
|
|
4723
|
+
}
|
|
4724
|
+
}
|
|
4725
|
+
async releaseRunOwnership(fence) {
|
|
4726
|
+
try {
|
|
4727
|
+
return (await this.pool.request().input("runId", fence.runId).input("generation", sql.BigInt, fence.generation).input("ownerId", fence.ownerId).query(`UPDATE ${this.#runOwnersTable} SET [leaseExpiresAt] = NULL, [updatedAt] = ${DB_NOW_MS}
|
|
4728
|
+
WHERE [runId] = @runId AND [generation] = @generation AND [ownerId] = @ownerId`)).rowsAffected[0] === 1;
|
|
4729
|
+
} catch (error) {
|
|
4730
|
+
throw this.#ownershipError("RELEASE_RUN_OWNERSHIP", fence.runId, error);
|
|
4731
|
+
}
|
|
4732
|
+
}
|
|
4733
|
+
async getRunOwnership({ runId }) {
|
|
4734
|
+
try {
|
|
4735
|
+
return await this.#readRunOwner(this.pool, runId);
|
|
4736
|
+
} catch (error) {
|
|
4737
|
+
throw this.#ownershipError("GET_RUN_OWNERSHIP", runId, error);
|
|
4738
|
+
}
|
|
4491
4739
|
}
|
|
4492
4740
|
parseWorkflowRun(row) {
|
|
4493
4741
|
let parsedSnapshot = row.snapshot;
|
|
@@ -4505,14 +4753,16 @@ var WorkflowsMSSQL = class WorkflowsMSSQL extends WorkflowsStorage {
|
|
|
4505
4753
|
resourceId: row.resourceId
|
|
4506
4754
|
};
|
|
4507
4755
|
}
|
|
4508
|
-
async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state }) {
|
|
4756
|
+
async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state, fence: explicitFence }) {
|
|
4509
4757
|
const table = getTableName({
|
|
4510
4758
|
indexName: TABLE_WORKFLOW_SNAPSHOT,
|
|
4511
4759
|
schemaName: getSchemaName(this.schema)
|
|
4512
4760
|
});
|
|
4761
|
+
const fence = resolveRunFence(this, explicitFence, runId);
|
|
4513
4762
|
const transaction = this.pool.transaction();
|
|
4514
4763
|
try {
|
|
4515
4764
|
await transaction.begin();
|
|
4765
|
+
if (fence) await assertRunFence(transaction, this.#runOwnersTable, fence, "updateWorkflowResults");
|
|
4516
4766
|
const selectRequest = new sql.Request(transaction);
|
|
4517
4767
|
selectRequest.input("workflow_name", workflowName);
|
|
4518
4768
|
selectRequest.input("run_id", runId);
|
|
@@ -4560,6 +4810,7 @@ var WorkflowsMSSQL = class WorkflowsMSSQL extends WorkflowsStorage {
|
|
|
4560
4810
|
try {
|
|
4561
4811
|
await transaction.rollback();
|
|
4562
4812
|
} catch {}
|
|
4813
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
4563
4814
|
throw new MastraError({
|
|
4564
4815
|
id: createStorageErrorId("MSSQL", "UPDATE_WORKFLOW_RESULTS", "FAILED"),
|
|
4565
4816
|
domain: ErrorDomain.STORAGE,
|
|
@@ -4572,14 +4823,16 @@ var WorkflowsMSSQL = class WorkflowsMSSQL extends WorkflowsStorage {
|
|
|
4572
4823
|
}, error);
|
|
4573
4824
|
}
|
|
4574
4825
|
}
|
|
4575
|
-
async updateWorkflowState({ workflowName, runId, opts }) {
|
|
4826
|
+
async updateWorkflowState({ workflowName, runId, opts, fence: explicitFence }) {
|
|
4576
4827
|
const table = getTableName({
|
|
4577
4828
|
indexName: TABLE_WORKFLOW_SNAPSHOT,
|
|
4578
4829
|
schemaName: getSchemaName(this.schema)
|
|
4579
4830
|
});
|
|
4831
|
+
const fence = resolveRunFence(this, explicitFence, runId);
|
|
4580
4832
|
const transaction = this.pool.transaction();
|
|
4581
4833
|
try {
|
|
4582
4834
|
await transaction.begin();
|
|
4835
|
+
if (fence) await assertRunFence(transaction, this.#runOwnersTable, fence, "updateWorkflowState");
|
|
4583
4836
|
const selectRequest = new sql.Request(transaction);
|
|
4584
4837
|
selectRequest.input("workflow_name", workflowName);
|
|
4585
4838
|
selectRequest.input("run_id", runId);
|
|
@@ -4635,31 +4888,34 @@ var WorkflowsMSSQL = class WorkflowsMSSQL extends WorkflowsStorage {
|
|
|
4635
4888
|
}, error);
|
|
4636
4889
|
}
|
|
4637
4890
|
}
|
|
4638
|
-
async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot }) {
|
|
4891
|
+
async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, fence }) {
|
|
4639
4892
|
const table = getTableName({
|
|
4640
4893
|
indexName: TABLE_WORKFLOW_SNAPSHOT,
|
|
4641
4894
|
schemaName: getSchemaName(this.schema)
|
|
4642
4895
|
});
|
|
4643
4896
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4644
4897
|
try {
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4898
|
+
await withRunFence(this.pool, this.#runOwnersTable, resolveRunFence(this, fence, runId), "persistWorkflowSnapshot", async (q) => {
|
|
4899
|
+
const request = q.request();
|
|
4900
|
+
request.input("workflow_name", workflowName);
|
|
4901
|
+
request.input("run_id", runId);
|
|
4902
|
+
request.input("resourceId", resourceId);
|
|
4903
|
+
request.input("snapshot", JSON.stringify(snapshot));
|
|
4904
|
+
request.input("createdAt", sql.DateTime2, new Date(now));
|
|
4905
|
+
request.input("updatedAt", sql.DateTime2, new Date(now));
|
|
4906
|
+
const mergeSql = `MERGE INTO ${table} AS target
|
|
4907
|
+
USING (SELECT @workflow_name AS workflow_name, @run_id AS run_id) AS src
|
|
4908
|
+
ON target.workflow_name = src.workflow_name AND target.run_id = src.run_id
|
|
4909
|
+
WHEN MATCHED THEN UPDATE SET
|
|
4910
|
+
resourceId = @resourceId,
|
|
4911
|
+
snapshot = @snapshot,
|
|
4912
|
+
[updatedAt] = @updatedAt
|
|
4913
|
+
WHEN NOT MATCHED THEN INSERT (workflow_name, run_id, resourceId, snapshot, [createdAt], [updatedAt])
|
|
4914
|
+
VALUES (@workflow_name, @run_id, @resourceId, @snapshot, @createdAt, @updatedAt);`;
|
|
4915
|
+
await request.query(mergeSql);
|
|
4916
|
+
});
|
|
4662
4917
|
} catch (error) {
|
|
4918
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
4663
4919
|
throw new MastraError({
|
|
4664
4920
|
id: createStorageErrorId("MSSQL", "PERSIST_WORKFLOW_SNAPSHOT", "FAILED"),
|
|
4665
4921
|
domain: ErrorDomain.STORAGE,
|
|
@@ -4728,23 +4984,15 @@ var WorkflowsMSSQL = class WorkflowsMSSQL extends WorkflowsStorage {
|
|
|
4728
4984
|
}, error);
|
|
4729
4985
|
}
|
|
4730
4986
|
}
|
|
4731
|
-
async deleteWorkflowRunById({ runId, workflowName }) {
|
|
4987
|
+
async deleteWorkflowRunById({ runId, workflowName, fence }) {
|
|
4732
4988
|
const table = getTableName({
|
|
4733
4989
|
indexName: TABLE_WORKFLOW_SNAPSHOT,
|
|
4734
4990
|
schemaName: getSchemaName(this.schema)
|
|
4735
4991
|
});
|
|
4736
|
-
const transaction = this.pool.transaction();
|
|
4737
4992
|
try {
|
|
4738
|
-
await
|
|
4739
|
-
const deleteRequest = new sql.Request(transaction);
|
|
4740
|
-
deleteRequest.input("workflow_name", workflowName);
|
|
4741
|
-
deleteRequest.input("run_id", runId);
|
|
4742
|
-
await deleteRequest.query(`DELETE FROM ${table} WHERE workflow_name = @workflow_name AND run_id = @run_id`);
|
|
4743
|
-
await transaction.commit();
|
|
4993
|
+
await withRunFence(this.pool, this.#runOwnersTable, resolveRunFence(this, fence, runId), "deleteWorkflowRunById", (q) => q.request().input("workflow_name", workflowName).input("run_id", runId).query(`DELETE FROM ${table} WHERE workflow_name = @workflow_name AND run_id = @run_id`));
|
|
4744
4994
|
} catch (error) {
|
|
4745
|
-
|
|
4746
|
-
await transaction.rollback();
|
|
4747
|
-
} catch {}
|
|
4995
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
4748
4996
|
throw new MastraError({
|
|
4749
4997
|
id: createStorageErrorId("MSSQL", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"),
|
|
4750
4998
|
domain: ErrorDomain.STORAGE,
|