@mastra/dsql 1.5.1 → 1.5.2-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/index.cjs +310 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +311 -38
- package/dist/index.js.map +1 -1
- package/dist/storage/db/run-fencing.d.ts +32 -0
- package/dist/storage/db/run-fencing.d.ts.map +1 -0
- package/dist/storage/domains/memory/index.d.ts +17 -7
- 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 +3 -3
package/dist/index.cjs
CHANGED
|
@@ -1825,6 +1825,49 @@ var AgentsDSQL = class AgentsDSQL extends _mastra_core_storage.AgentsStorage {
|
|
|
1825
1825
|
}
|
|
1826
1826
|
};
|
|
1827
1827
|
//#endregion
|
|
1828
|
+
//#region src/storage/db/run-fencing.ts
|
|
1829
|
+
/**
|
|
1830
|
+
* Epoch milliseconds on the database clock. Aurora DSQL never waits on a row
|
|
1831
|
+
* lock, so the transaction's start time is the time its statements run, and
|
|
1832
|
+
* every retry starts a new transaction with a fresh clock.
|
|
1833
|
+
*/
|
|
1834
|
+
const DB_NOW_MS = `(EXTRACT(EPOCH FROM now()) * 1000)::bigint`;
|
|
1835
|
+
/**
|
|
1836
|
+
* Retry predicate for writes to an ownership or fence row that may not exist
|
|
1837
|
+
* yet. Two first writers both insert the same key; the loser normally fails
|
|
1838
|
+
* at commit with an OCC conflict (40001), but a duplicate-key error (23505)
|
|
1839
|
+
* means the same thing here: the retry sees the row and takes the update path.
|
|
1840
|
+
*/
|
|
1841
|
+
function isRetriableRunFenceWrite(error) {
|
|
1842
|
+
return isRetriableError(error) || getErrorCode(error) === "23505";
|
|
1843
|
+
}
|
|
1844
|
+
/**
|
|
1845
|
+
* The check half of a fenced write; call it first inside the write's
|
|
1846
|
+
* transaction. Aurora DSQL has no blocking row locks: `FOR UPDATE` puts the
|
|
1847
|
+
* claim row in this transaction's write set instead, so a claim that commits
|
|
1848
|
+
* against the row while the write is in flight makes the write fail at commit
|
|
1849
|
+
* with an OCC conflict. The retry then sees the new claim and is rejected.
|
|
1850
|
+
*/
|
|
1851
|
+
async function assertRunFence(t, claimsTable, fence, operation) {
|
|
1852
|
+
const row = await t.oneOrNone(`SELECT generation, "ownerId" FROM ${claimsTable} WHERE "runId" = $1 FOR UPDATE`, [fence.runId]);
|
|
1853
|
+
if (!(0, _mastra_core_storage.matchesRunFence)(row && {
|
|
1854
|
+
generation: Number(row.generation),
|
|
1855
|
+
ownerId: row.ownerId
|
|
1856
|
+
}, fence)) throw new _mastra_core_storage.RunFenceConflictError(fence, operation);
|
|
1857
|
+
}
|
|
1858
|
+
/**
|
|
1859
|
+
* Runs `write` directly when it carries no fence, or in a transaction behind
|
|
1860
|
+
* the fence check when it does. Callers own retries: wrap the call in
|
|
1861
|
+
* `withRetry` so an OCC conflict re-runs the check as well as the write.
|
|
1862
|
+
*/
|
|
1863
|
+
function withRunFence(client, claimsTable, fence, operation, write) {
|
|
1864
|
+
if (!fence) return write(client);
|
|
1865
|
+
return client.tx(async (t) => {
|
|
1866
|
+
await assertRunFence(t, claimsTable, fence, operation);
|
|
1867
|
+
return write(t);
|
|
1868
|
+
});
|
|
1869
|
+
}
|
|
1870
|
+
//#endregion
|
|
1828
1871
|
//#region src/storage/domains/memory/index.ts
|
|
1829
1872
|
/**
|
|
1830
1873
|
* Generate SQL placeholder string for IN clauses.
|
|
@@ -1884,6 +1927,10 @@ var MemoryDSQL = class MemoryDSQL extends _mastra_core_storage.MemoryStorage {
|
|
|
1884
1927
|
schema: _mastra_core_storage.TABLE_SCHEMAS[_mastra_core_storage.TABLE_MESSAGES],
|
|
1885
1928
|
ifNotExists: ["resourceId"]
|
|
1886
1929
|
});
|
|
1930
|
+
await this.#db.createTable({
|
|
1931
|
+
tableName: _mastra_core_storage.TABLE_MEMORY_RUN_FENCES,
|
|
1932
|
+
schema: _mastra_core_storage.RUN_FENCING_TABLE_SCHEMAS[_mastra_core_storage.TABLE_MEMORY_RUN_FENCES]
|
|
1933
|
+
});
|
|
1887
1934
|
await this.createDefaultIndexes();
|
|
1888
1935
|
await this.createCustomIndexes();
|
|
1889
1936
|
}
|
|
@@ -1929,6 +1976,62 @@ var MemoryDSQL = class MemoryDSQL extends _mastra_core_storage.MemoryStorage {
|
|
|
1929
1976
|
await this.#db.clearTable({ tableName: _mastra_core_storage.TABLE_MESSAGES });
|
|
1930
1977
|
await this.#db.clearTable({ tableName: _mastra_core_storage.TABLE_THREADS });
|
|
1931
1978
|
await this.#db.clearTable({ tableName: _mastra_core_storage.TABLE_RESOURCES });
|
|
1979
|
+
await this.#db.clearTable({ tableName: _mastra_core_storage.TABLE_MEMORY_RUN_FENCES });
|
|
1980
|
+
}
|
|
1981
|
+
supportsRunFencing() {
|
|
1982
|
+
return true;
|
|
1983
|
+
}
|
|
1984
|
+
#runFencesTable() {
|
|
1985
|
+
return getTableName({
|
|
1986
|
+
indexName: _mastra_core_storage.TABLE_MEMORY_RUN_FENCES,
|
|
1987
|
+
schemaName: getSchemaName(this.#schema)
|
|
1988
|
+
});
|
|
1989
|
+
}
|
|
1990
|
+
/** Runs `write` behind the run's fence check when it carries a fence. */
|
|
1991
|
+
#fenced(fence, operation, write) {
|
|
1992
|
+
return withRunFence(this.#db.client, this.#runFencesTable(), (0, _mastra_core_storage.resolveRunFence)(this, fence), operation, write);
|
|
1993
|
+
}
|
|
1994
|
+
async raiseRunFence(fence) {
|
|
1995
|
+
try {
|
|
1996
|
+
const { result } = await withRetry(() => this.#db.client.oneOrNone(`INSERT INTO ${this.#runFencesTable()} AS f ("runId", generation, "ownerId")
|
|
1997
|
+
VALUES ($1, $2, $3)
|
|
1998
|
+
ON CONFLICT ("runId") DO UPDATE
|
|
1999
|
+
SET generation = EXCLUDED.generation, "ownerId" = EXCLUDED."ownerId", "retiredAt" = NULL
|
|
2000
|
+
WHERE f.generation < EXCLUDED.generation
|
|
2001
|
+
OR (f.generation = EXCLUDED.generation AND f."ownerId" = EXCLUDED."ownerId")
|
|
2002
|
+
RETURNING "runId"`, [
|
|
2003
|
+
fence.runId,
|
|
2004
|
+
fence.generation,
|
|
2005
|
+
fence.ownerId
|
|
2006
|
+
]), { isRetriable: isRetriableRunFenceWrite });
|
|
2007
|
+
return result !== null;
|
|
2008
|
+
} catch (error) {
|
|
2009
|
+
throw new _mastra_core_error.MastraError({
|
|
2010
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("DSQL", "RAISE_RUN_FENCE", "FAILED"),
|
|
2011
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
2012
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
2013
|
+
details: { runId: fence.runId }
|
|
2014
|
+
}, error);
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
async retireRunFence(fence) {
|
|
2018
|
+
try {
|
|
2019
|
+
const { result } = await withRetry(() => this.#db.client.oneOrNone(`UPDATE ${this.#runFencesTable()} SET "retiredAt" = ${DB_NOW_MS}
|
|
2020
|
+
WHERE "runId" = $1 AND generation = $2 AND "ownerId" = $3
|
|
2021
|
+
RETURNING "runId"`, [
|
|
2022
|
+
fence.runId,
|
|
2023
|
+
fence.generation,
|
|
2024
|
+
fence.ownerId
|
|
2025
|
+
]), { isRetriable: isRetriableRunFenceWrite });
|
|
2026
|
+
return result !== null;
|
|
2027
|
+
} catch (error) {
|
|
2028
|
+
throw new _mastra_core_error.MastraError({
|
|
2029
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("DSQL", "RETIRE_RUN_FENCE", "FAILED"),
|
|
2030
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
2031
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
2032
|
+
details: { runId: fence.runId }
|
|
2033
|
+
}, error);
|
|
2034
|
+
}
|
|
1932
2035
|
}
|
|
1933
2036
|
/**
|
|
1934
2037
|
* Normalizes message row from database by applying createdAtZ fallback
|
|
@@ -2065,7 +2168,7 @@ var MemoryDSQL = class MemoryDSQL extends _mastra_core_storage.MemoryStorage {
|
|
|
2065
2168
|
throw mastraError;
|
|
2066
2169
|
}
|
|
2067
2170
|
}
|
|
2068
|
-
async saveThread({ thread }) {
|
|
2171
|
+
async saveThread({ thread, fence }) {
|
|
2069
2172
|
const tableName = getTableName({
|
|
2070
2173
|
indexName: _mastra_core_storage.TABLE_THREADS,
|
|
2071
2174
|
schemaName: getSchemaName(this.#schema)
|
|
@@ -2073,7 +2176,7 @@ var MemoryDSQL = class MemoryDSQL extends _mastra_core_storage.MemoryStorage {
|
|
|
2073
2176
|
const createdAt = toUtcISOString(thread.createdAt);
|
|
2074
2177
|
const updatedAt = toUtcISOString(thread.updatedAt);
|
|
2075
2178
|
await withRetry(async () => {
|
|
2076
|
-
await this.#
|
|
2179
|
+
await this.#fenced(fence, "saveThread", (q) => q.none(`INSERT INTO ${tableName} (
|
|
2077
2180
|
id,
|
|
2078
2181
|
"resourceId",
|
|
2079
2182
|
title,
|
|
@@ -2099,10 +2202,11 @@ var MemoryDSQL = class MemoryDSQL extends _mastra_core_storage.MemoryStorage {
|
|
|
2099
2202
|
createdAt,
|
|
2100
2203
|
updatedAt,
|
|
2101
2204
|
updatedAt
|
|
2102
|
-
]);
|
|
2205
|
+
]));
|
|
2103
2206
|
}, { onRetry: (error, attempt, delay) => {
|
|
2104
2207
|
this.logger?.warn?.(`saveThread retry ${attempt} for ${thread.id} after ${delay}ms: ${error.message}`);
|
|
2105
2208
|
} }).catch((error) => {
|
|
2209
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
2106
2210
|
throw new _mastra_core_error.MastraError({
|
|
2107
2211
|
id: (0, _mastra_core_storage.createStorageErrorId)("DSQL", "SAVE_THREAD", "FAILED"),
|
|
2108
2212
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -2112,13 +2216,13 @@ var MemoryDSQL = class MemoryDSQL extends _mastra_core_storage.MemoryStorage {
|
|
|
2112
2216
|
});
|
|
2113
2217
|
return thread;
|
|
2114
2218
|
}
|
|
2115
|
-
async updateThread({ id, title, metadata }) {
|
|
2219
|
+
async updateThread({ id, title, metadata, fence }) {
|
|
2116
2220
|
const threadTableName = getTableName({
|
|
2117
2221
|
indexName: _mastra_core_storage.TABLE_THREADS,
|
|
2118
2222
|
schemaName: getSchemaName(this.#schema)
|
|
2119
2223
|
});
|
|
2120
|
-
const { result } = await withRetry(async () => {
|
|
2121
|
-
const existingThread = await
|
|
2224
|
+
const { result } = await withRetry(() => this.#fenced(fence, "updateThread", async (q) => {
|
|
2225
|
+
const existingThread = await q.oneOrNone(`SELECT metadata FROM ${threadTableName} WHERE id = $1`, [id]);
|
|
2122
2226
|
if (!existingThread) throw new _mastra_core_error.MastraError({
|
|
2123
2227
|
id: (0, _mastra_core_storage.createStorageErrorId)("DSQL", "UPDATE_THREAD", "NOT_FOUND"),
|
|
2124
2228
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -2130,11 +2234,11 @@ var MemoryDSQL = class MemoryDSQL extends _mastra_core_storage.MemoryStorage {
|
|
|
2130
2234
|
}
|
|
2131
2235
|
});
|
|
2132
2236
|
const mergedMetadata = {
|
|
2133
|
-
...existingThread.metadata,
|
|
2237
|
+
...typeof existingThread.metadata === "string" ? JSON.parse(existingThread.metadata) : existingThread.metadata,
|
|
2134
2238
|
...metadata
|
|
2135
2239
|
};
|
|
2136
2240
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2137
|
-
|
|
2241
|
+
return q.one(`UPDATE ${threadTableName}
|
|
2138
2242
|
SET
|
|
2139
2243
|
title = COALESCE($1, title),
|
|
2140
2244
|
metadata = $2,
|
|
@@ -2149,15 +2253,14 @@ var MemoryDSQL = class MemoryDSQL extends _mastra_core_storage.MemoryStorage {
|
|
|
2149
2253
|
now,
|
|
2150
2254
|
id
|
|
2151
2255
|
]);
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
}, { onRetry: (error, attempt, delay) => {
|
|
2256
|
+
}).then((thread) => ({
|
|
2257
|
+
id: thread.id,
|
|
2258
|
+
resourceId: thread.resourceId,
|
|
2259
|
+
title: thread.title,
|
|
2260
|
+
metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
|
|
2261
|
+
createdAt: thread.createdAtZ || thread.createdAt,
|
|
2262
|
+
updatedAt: thread.updatedAtZ || thread.updatedAt
|
|
2263
|
+
})), { onRetry: (error, attempt, delay) => {
|
|
2161
2264
|
this.logger?.warn?.(`updateThread retry ${attempt} for ${id} after ${delay}ms: ${error.message}`);
|
|
2162
2265
|
} }).catch((error) => {
|
|
2163
2266
|
if (error instanceof _mastra_core_error.MastraError) throw error;
|
|
@@ -2482,7 +2585,7 @@ var MemoryDSQL = class MemoryDSQL extends _mastra_core_storage.MemoryStorage {
|
|
|
2482
2585
|
throw mastraError;
|
|
2483
2586
|
}
|
|
2484
2587
|
}
|
|
2485
|
-
async saveMessages({ messages }) {
|
|
2588
|
+
async saveMessages({ messages, fence: explicitFence }) {
|
|
2486
2589
|
if (messages.length === 0) return { messages: [] };
|
|
2487
2590
|
const threadId = messages[0]?.threadId;
|
|
2488
2591
|
if (!threadId) throw new _mastra_core_error.MastraError({
|
|
@@ -2506,8 +2609,10 @@ var MemoryDSQL = class MemoryDSQL extends _mastra_core_storage.MemoryStorage {
|
|
|
2506
2609
|
indexName: _mastra_core_storage.TABLE_THREADS,
|
|
2507
2610
|
schemaName: getSchemaName(this.#schema)
|
|
2508
2611
|
});
|
|
2612
|
+
const fence = (0, _mastra_core_storage.resolveRunFence)(this, explicitFence);
|
|
2509
2613
|
await withRetry(async () => {
|
|
2510
2614
|
await this.#db.client.tx(async (t) => {
|
|
2615
|
+
if (fence) await assertRunFence(t, this.#runFencesTable(), fence, "saveMessages");
|
|
2511
2616
|
const messageInserts = messages.map((message) => {
|
|
2512
2617
|
if (!message.threadId) throw new Error(`Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`);
|
|
2513
2618
|
if (!message.resourceId) throw new Error(`Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`);
|
|
@@ -2546,6 +2651,7 @@ var MemoryDSQL = class MemoryDSQL extends _mastra_core_storage.MemoryStorage {
|
|
|
2546
2651
|
}, { onRetry: (error, attempt, delay) => {
|
|
2547
2652
|
this.logger?.warn?.(`saveMessages retry ${attempt} for thread ${threadId} after ${delay}ms: ${error.message}`);
|
|
2548
2653
|
} }).catch((error) => {
|
|
2654
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
2549
2655
|
throw new _mastra_core_error.MastraError({
|
|
2550
2656
|
id: (0, _mastra_core_storage.createStorageErrorId)("DSQL", "SAVE_MESSAGES", "FAILED"),
|
|
2551
2657
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -2566,8 +2672,9 @@ var MemoryDSQL = class MemoryDSQL extends _mastra_core_storage.MemoryStorage {
|
|
|
2566
2672
|
});
|
|
2567
2673
|
return { messages: new _mastra_core_agent.MessageList().add(messagesWithParsedContent, "memory").get.all.db() };
|
|
2568
2674
|
}
|
|
2569
|
-
async updateMessages({ messages }) {
|
|
2675
|
+
async updateMessages({ messages, fence: explicitFence }) {
|
|
2570
2676
|
if (messages.length === 0) return [];
|
|
2677
|
+
const fence = (0, _mastra_core_storage.resolveRunFence)(this, explicitFence);
|
|
2571
2678
|
const messageIds = messages.map((m) => m.id);
|
|
2572
2679
|
const selectQuery = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId" FROM ${getTableName({
|
|
2573
2680
|
indexName: _mastra_core_storage.TABLE_MESSAGES,
|
|
@@ -2584,6 +2691,7 @@ var MemoryDSQL = class MemoryDSQL extends _mastra_core_storage.MemoryStorage {
|
|
|
2584
2691
|
const threadIdsToUpdate = /* @__PURE__ */ new Set();
|
|
2585
2692
|
await withRetry(async () => {
|
|
2586
2693
|
await this.#db.client.tx(async (t) => {
|
|
2694
|
+
if (fence) await assertRunFence(t, this.#runFencesTable(), fence, "updateMessages");
|
|
2587
2695
|
const queries = [];
|
|
2588
2696
|
const columnMapping = { threadId: "thread_id" };
|
|
2589
2697
|
for (const existingMessage of existingMessages) {
|
|
@@ -2636,6 +2744,7 @@ var MemoryDSQL = class MemoryDSQL extends _mastra_core_storage.MemoryStorage {
|
|
|
2636
2744
|
}, { onRetry: (error, attempt, delay) => {
|
|
2637
2745
|
this.logger?.warn?.(`updateMessages retry ${attempt} for ${messageIds.length} messages after ${delay}ms: ${error.message}`);
|
|
2638
2746
|
} }).catch((error) => {
|
|
2747
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
2639
2748
|
throw new _mastra_core_error.MastraError({
|
|
2640
2749
|
id: (0, _mastra_core_storage.createStorageErrorId)("DSQL", "UPDATE_MESSAGES", "FAILED"),
|
|
2641
2750
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -2654,8 +2763,9 @@ var MemoryDSQL = class MemoryDSQL extends _mastra_core_storage.MemoryStorage {
|
|
|
2654
2763
|
return message;
|
|
2655
2764
|
});
|
|
2656
2765
|
}
|
|
2657
|
-
async deleteMessages(messageIds) {
|
|
2766
|
+
async deleteMessages(messageIds, options) {
|
|
2658
2767
|
if (!messageIds || messageIds.length === 0) return;
|
|
2768
|
+
const fence = (0, _mastra_core_storage.resolveRunFence)(this, options?.fence);
|
|
2659
2769
|
const messageTableName = getTableName({
|
|
2660
2770
|
indexName: _mastra_core_storage.TABLE_MESSAGES,
|
|
2661
2771
|
schemaName: getSchemaName(this.#schema)
|
|
@@ -2666,6 +2776,7 @@ var MemoryDSQL = class MemoryDSQL extends _mastra_core_storage.MemoryStorage {
|
|
|
2666
2776
|
});
|
|
2667
2777
|
await withRetry(async () => {
|
|
2668
2778
|
await this.#db.client.tx(async (t) => {
|
|
2779
|
+
if (fence) await assertRunFence(t, this.#runFencesTable(), fence, "deleteMessages");
|
|
2669
2780
|
const placeholders = messageIds.map((_, idx) => `$${idx + 1}`).join(",");
|
|
2670
2781
|
const threadIds = (await t.manyOrNone(`SELECT DISTINCT thread_id FROM ${messageTableName} WHERE id IN (${placeholders})`, messageIds))?.map((msg) => msg.thread_id).filter(Boolean) || [];
|
|
2671
2782
|
await t.none(`DELETE FROM ${messageTableName} WHERE id IN (${placeholders})`, messageIds);
|
|
@@ -2677,6 +2788,7 @@ var MemoryDSQL = class MemoryDSQL extends _mastra_core_storage.MemoryStorage {
|
|
|
2677
2788
|
}, { onRetry: (error, attempt, delay) => {
|
|
2678
2789
|
this.logger?.warn?.(`deleteMessages retry ${attempt} for ${messageIds.length} messages after ${delay}ms: ${error.message}`);
|
|
2679
2790
|
} }).catch((error) => {
|
|
2791
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
2680
2792
|
throw new _mastra_core_error.MastraError({
|
|
2681
2793
|
id: (0, _mastra_core_storage.createStorageErrorId)("DSQL", "DELETE_MESSAGES", "FAILED"),
|
|
2682
2794
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -2721,23 +2833,42 @@ var MemoryDSQL = class MemoryDSQL extends _mastra_core_storage.MemoryStorage {
|
|
|
2721
2833
|
});
|
|
2722
2834
|
return resource;
|
|
2723
2835
|
}
|
|
2724
|
-
async updateResource({ resourceId, workingMemory, metadata }) {
|
|
2836
|
+
async updateResource({ resourceId, workingMemory, metadata, fence }) {
|
|
2725
2837
|
const tableName = getTableName({
|
|
2726
2838
|
indexName: _mastra_core_storage.TABLE_RESOURCES,
|
|
2727
2839
|
schemaName: getSchemaName(this.#schema)
|
|
2728
2840
|
});
|
|
2729
|
-
const { result } = await withRetry(async () => {
|
|
2730
|
-
const
|
|
2731
|
-
if (!
|
|
2841
|
+
const { result } = await withRetry(() => this.#fenced(fence, "updateResource", async (q) => {
|
|
2842
|
+
const row = await q.oneOrNone(`SELECT * FROM ${tableName} WHERE id = $1`, [resourceId]);
|
|
2843
|
+
if (!row) {
|
|
2844
|
+
const now = /* @__PURE__ */ new Date();
|
|
2732
2845
|
const newResource = {
|
|
2733
2846
|
id: resourceId,
|
|
2734
2847
|
workingMemory,
|
|
2735
2848
|
metadata: metadata || {},
|
|
2736
|
-
createdAt:
|
|
2737
|
-
updatedAt:
|
|
2849
|
+
createdAt: now,
|
|
2850
|
+
updatedAt: now
|
|
2738
2851
|
};
|
|
2739
|
-
|
|
2852
|
+
const nowIso = now.toISOString();
|
|
2853
|
+
await q.none(`INSERT INTO ${tableName} (id, "workingMemory", metadata, "createdAt", "createdAtZ", "updatedAt", "updatedAtZ")
|
|
2854
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)`, [
|
|
2855
|
+
resourceId,
|
|
2856
|
+
workingMemory ?? null,
|
|
2857
|
+
JSON.stringify(newResource.metadata),
|
|
2858
|
+
nowIso,
|
|
2859
|
+
nowIso,
|
|
2860
|
+
nowIso,
|
|
2861
|
+
nowIso
|
|
2862
|
+
]);
|
|
2863
|
+
return newResource;
|
|
2740
2864
|
}
|
|
2865
|
+
const existingResource = {
|
|
2866
|
+
id: row.id,
|
|
2867
|
+
createdAt: row.createdAtZ || row.createdAt,
|
|
2868
|
+
updatedAt: row.updatedAtZ || row.updatedAt,
|
|
2869
|
+
workingMemory: row.workingMemory,
|
|
2870
|
+
metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata
|
|
2871
|
+
};
|
|
2741
2872
|
const updatedResource = {
|
|
2742
2873
|
...existingResource,
|
|
2743
2874
|
workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
|
|
@@ -2767,9 +2898,9 @@ var MemoryDSQL = class MemoryDSQL extends _mastra_core_storage.MemoryStorage {
|
|
|
2767
2898
|
values.push(updatedResource.updatedAt.toISOString());
|
|
2768
2899
|
paramIndex++;
|
|
2769
2900
|
values.push(resourceId);
|
|
2770
|
-
await
|
|
2901
|
+
await q.none(`UPDATE ${tableName} SET ${updates.join(", ")} WHERE id = $${paramIndex}`, values);
|
|
2771
2902
|
return updatedResource;
|
|
2772
|
-
}, { onRetry: (error, attempt, delay) => {
|
|
2903
|
+
}), { onRetry: (error, attempt, delay) => {
|
|
2773
2904
|
this.logger?.warn?.(`updateResource retry ${attempt} for ${resourceId} after ${delay}ms: ${error.message}`);
|
|
2774
2905
|
} }).catch((error) => {
|
|
2775
2906
|
if (error instanceof _mastra_core_error.MastraError) throw error;
|
|
@@ -3719,6 +3850,17 @@ var ScoresDSQL = class ScoresDSQL extends _mastra_core_storage.ScoresStorage {
|
|
|
3719
3850
|
};
|
|
3720
3851
|
//#endregion
|
|
3721
3852
|
//#region src/storage/domains/workflows/index.ts
|
|
3853
|
+
const RUN_OWNER_COLUMNS = `generation, "ownerId", "leaseExpiresAt", ${DB_NOW_MS} AS "nowMs"`;
|
|
3854
|
+
function toRunOwnershipRecord(runId, row) {
|
|
3855
|
+
const leaseExpiresAt = row.leaseExpiresAt === null ? null : Number(row.leaseExpiresAt);
|
|
3856
|
+
return {
|
|
3857
|
+
runId,
|
|
3858
|
+
generation: Number(row.generation),
|
|
3859
|
+
ownerId: row.ownerId,
|
|
3860
|
+
leaseExpiresAt: leaseExpiresAt === null ? null : new Date(leaseExpiresAt),
|
|
3861
|
+
live: leaseExpiresAt !== null && leaseExpiresAt > Number(row.nowMs)
|
|
3862
|
+
};
|
|
3863
|
+
}
|
|
3722
3864
|
function parseWorkflowRun(row) {
|
|
3723
3865
|
let parsedSnapshot = row.snapshot;
|
|
3724
3866
|
if (typeof parsedSnapshot === "string") try {
|
|
@@ -3780,6 +3922,10 @@ var WorkflowsDSQL = class WorkflowsDSQL extends _mastra_core_storage.WorkflowsSt
|
|
|
3780
3922
|
schema: _mastra_core_storage.TABLE_SCHEMAS[_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT],
|
|
3781
3923
|
ifNotExists: ["resourceId"]
|
|
3782
3924
|
});
|
|
3925
|
+
await this.#db.createTable({
|
|
3926
|
+
tableName: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS,
|
|
3927
|
+
schema: _mastra_core_storage.RUN_FENCING_TABLE_SCHEMAS[_mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS]
|
|
3928
|
+
});
|
|
3783
3929
|
await this.createDefaultIndexes();
|
|
3784
3930
|
await this.createCustomIndexes();
|
|
3785
3931
|
}
|
|
@@ -3796,11 +3942,130 @@ var WorkflowsDSQL = class WorkflowsDSQL extends _mastra_core_storage.WorkflowsSt
|
|
|
3796
3942
|
}
|
|
3797
3943
|
async dangerouslyClearAll() {
|
|
3798
3944
|
await this.#db.clearTable({ tableName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT });
|
|
3945
|
+
await this.#db.clearTable({ tableName: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS });
|
|
3799
3946
|
}
|
|
3800
|
-
|
|
3947
|
+
supportsRunFencing() {
|
|
3948
|
+
return true;
|
|
3949
|
+
}
|
|
3950
|
+
#runOwnersTable() {
|
|
3951
|
+
return getTableName({
|
|
3952
|
+
indexName: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS,
|
|
3953
|
+
schemaName: getSchemaName(this.#schema)
|
|
3954
|
+
});
|
|
3955
|
+
}
|
|
3956
|
+
async #readRunOwner(q, runId) {
|
|
3957
|
+
const row = await q.oneOrNone(`SELECT ${RUN_OWNER_COLUMNS} FROM ${this.#runOwnersTable()} WHERE "runId" = $1`, [runId]);
|
|
3958
|
+
return row ? toRunOwnershipRecord(runId, row) : null;
|
|
3959
|
+
}
|
|
3960
|
+
#ownershipError(operation, runId, error) {
|
|
3961
|
+
return new _mastra_core_error.MastraError({
|
|
3962
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("DSQL", operation, "FAILED"),
|
|
3963
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
3964
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
3965
|
+
details: { runId }
|
|
3966
|
+
}, error);
|
|
3967
|
+
}
|
|
3968
|
+
async claimRunOwnership({ runId, ownerId, leaseMs, force, expectedGeneration }) {
|
|
3969
|
+
const table = this.#runOwnersTable();
|
|
3970
|
+
try {
|
|
3971
|
+
const { result } = await withRetry(() => this.#db.client.tx(async (t) => {
|
|
3972
|
+
if (!await t.oneOrNone(`SELECT 1 FROM ${table} WHERE "runId" = $1 FOR UPDATE`, [runId])) {
|
|
3973
|
+
if (expectedGeneration !== void 0 && expectedGeneration !== 0) return {
|
|
3974
|
+
acquired: false,
|
|
3975
|
+
record: null
|
|
3976
|
+
};
|
|
3977
|
+
return {
|
|
3978
|
+
acquired: true,
|
|
3979
|
+
record: toRunOwnershipRecord(runId, await t.one(`INSERT INTO ${table} ("runId", generation, "ownerId", "leaseExpiresAt", "updatedAt")
|
|
3980
|
+
VALUES ($1, ${DB_NOW_MS}, $2, ${DB_NOW_MS} + $3, ${DB_NOW_MS})
|
|
3981
|
+
RETURNING ${RUN_OWNER_COLUMNS}`, [
|
|
3982
|
+
runId,
|
|
3983
|
+
ownerId,
|
|
3984
|
+
leaseMs
|
|
3985
|
+
]))
|
|
3986
|
+
};
|
|
3987
|
+
}
|
|
3988
|
+
const claimed = await t.oneOrNone(`UPDATE ${table}
|
|
3989
|
+
SET generation = generation + 1, "ownerId" = $2, "leaseExpiresAt" = ${DB_NOW_MS} + $3,
|
|
3990
|
+
"updatedAt" = ${DB_NOW_MS}
|
|
3991
|
+
WHERE "runId" = $1
|
|
3992
|
+
AND ($4::bigint IS NULL OR generation = $4)
|
|
3993
|
+
AND ($5::boolean OR "leaseExpiresAt" IS NULL OR "leaseExpiresAt" <= ${DB_NOW_MS})
|
|
3994
|
+
RETURNING ${RUN_OWNER_COLUMNS}`, [
|
|
3995
|
+
runId,
|
|
3996
|
+
ownerId,
|
|
3997
|
+
leaseMs,
|
|
3998
|
+
expectedGeneration ?? null,
|
|
3999
|
+
force === true
|
|
4000
|
+
]);
|
|
4001
|
+
if (claimed) return {
|
|
4002
|
+
acquired: true,
|
|
4003
|
+
record: toRunOwnershipRecord(runId, claimed)
|
|
4004
|
+
};
|
|
4005
|
+
return {
|
|
4006
|
+
acquired: false,
|
|
4007
|
+
record: await this.#readRunOwner(t, runId)
|
|
4008
|
+
};
|
|
4009
|
+
}), { isRetriable: isRetriableRunFenceWrite });
|
|
4010
|
+
return result;
|
|
4011
|
+
} catch (error) {
|
|
4012
|
+
throw this.#ownershipError("CLAIM_RUN_OWNERSHIP", runId, error);
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
4015
|
+
async renewRunOwnership({ leaseMs, ...fence }) {
|
|
4016
|
+
try {
|
|
4017
|
+
const { result } = await withRetry(async () => {
|
|
4018
|
+
const renewed = await this.#db.client.oneOrNone(`UPDATE ${this.#runOwnersTable()}
|
|
4019
|
+
SET "leaseExpiresAt" = ${DB_NOW_MS} + $4, "updatedAt" = ${DB_NOW_MS}
|
|
4020
|
+
WHERE "runId" = $1 AND generation = $2 AND "ownerId" = $3 AND "leaseExpiresAt" IS NOT NULL
|
|
4021
|
+
RETURNING ${RUN_OWNER_COLUMNS}`, [
|
|
4022
|
+
fence.runId,
|
|
4023
|
+
fence.generation,
|
|
4024
|
+
fence.ownerId,
|
|
4025
|
+
leaseMs
|
|
4026
|
+
]);
|
|
4027
|
+
if (renewed) return {
|
|
4028
|
+
renewed: true,
|
|
4029
|
+
record: toRunOwnershipRecord(fence.runId, renewed)
|
|
4030
|
+
};
|
|
4031
|
+
return {
|
|
4032
|
+
renewed: false,
|
|
4033
|
+
record: await this.#readRunOwner(this.#db.client, fence.runId)
|
|
4034
|
+
};
|
|
4035
|
+
});
|
|
4036
|
+
return result;
|
|
4037
|
+
} catch (error) {
|
|
4038
|
+
throw this.#ownershipError("RENEW_RUN_OWNERSHIP", fence.runId, error);
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
async releaseRunOwnership(fence) {
|
|
4042
|
+
try {
|
|
4043
|
+
const { result } = await withRetry(() => this.#db.client.oneOrNone(`UPDATE ${this.#runOwnersTable()}
|
|
4044
|
+
SET "leaseExpiresAt" = NULL, "updatedAt" = ${DB_NOW_MS}
|
|
4045
|
+
WHERE "runId" = $1 AND generation = $2 AND "ownerId" = $3
|
|
4046
|
+
RETURNING "runId"`, [
|
|
4047
|
+
fence.runId,
|
|
4048
|
+
fence.generation,
|
|
4049
|
+
fence.ownerId
|
|
4050
|
+
]));
|
|
4051
|
+
return result !== null;
|
|
4052
|
+
} catch (error) {
|
|
4053
|
+
throw this.#ownershipError("RELEASE_RUN_OWNERSHIP", fence.runId, error);
|
|
4054
|
+
}
|
|
4055
|
+
}
|
|
4056
|
+
async getRunOwnership({ runId }) {
|
|
4057
|
+
try {
|
|
4058
|
+
return await this.#readRunOwner(this.#db.client, runId);
|
|
4059
|
+
} catch (error) {
|
|
4060
|
+
throw this.#ownershipError("GET_RUN_OWNERSHIP", runId, error);
|
|
4061
|
+
}
|
|
4062
|
+
}
|
|
4063
|
+
async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state, fence: explicitFence }) {
|
|
4064
|
+
const fence = (0, _mastra_core_storage.resolveRunFence)(this, explicitFence, runId);
|
|
3801
4065
|
try {
|
|
3802
4066
|
const { result: context } = await withRetry(async () => {
|
|
3803
4067
|
return this.#db.client.tx(async (t) => {
|
|
4068
|
+
if (fence) await assertRunFence(t, this.#runOwnersTable(), fence, "updateWorkflowResults");
|
|
3804
4069
|
const tableName = getTableName({
|
|
3805
4070
|
indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
|
|
3806
4071
|
schemaName: getSchemaName(this.#schema)
|
|
@@ -3849,6 +4114,7 @@ var WorkflowsDSQL = class WorkflowsDSQL extends _mastra_core_storage.WorkflowsSt
|
|
|
3849
4114
|
} });
|
|
3850
4115
|
return context;
|
|
3851
4116
|
} catch (error) {
|
|
4117
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
3852
4118
|
throw new _mastra_core_error.MastraError({
|
|
3853
4119
|
id: (0, _mastra_core_storage.createStorageErrorId)("DSQL", "UPDATE_WORKFLOW_RESULTS", "FAILED"),
|
|
3854
4120
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -3861,10 +4127,12 @@ var WorkflowsDSQL = class WorkflowsDSQL extends _mastra_core_storage.WorkflowsSt
|
|
|
3861
4127
|
}, error);
|
|
3862
4128
|
}
|
|
3863
4129
|
}
|
|
3864
|
-
async updateWorkflowState({ workflowName, runId, opts }) {
|
|
4130
|
+
async updateWorkflowState({ workflowName, runId, opts, fence: explicitFence }) {
|
|
4131
|
+
const fence = (0, _mastra_core_storage.resolveRunFence)(this, explicitFence, runId);
|
|
3865
4132
|
try {
|
|
3866
4133
|
const { result } = await withRetry(async () => {
|
|
3867
4134
|
return this.#db.client.tx(async (t) => {
|
|
4135
|
+
if (fence) await assertRunFence(t, this.#runOwnersTable(), fence, "updateWorkflowState");
|
|
3868
4136
|
const tableName = getTableName({
|
|
3869
4137
|
indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
|
|
3870
4138
|
schemaName: getSchemaName(this.#schema)
|
|
@@ -3893,6 +4161,7 @@ var WorkflowsDSQL = class WorkflowsDSQL extends _mastra_core_storage.WorkflowsSt
|
|
|
3893
4161
|
} });
|
|
3894
4162
|
return result;
|
|
3895
4163
|
} catch (error) {
|
|
4164
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
3896
4165
|
throw new _mastra_core_error.MastraError({
|
|
3897
4166
|
id: (0, _mastra_core_storage.createStorageErrorId)("DSQL", "UPDATE_WORKFLOW_STATE", "FAILED"),
|
|
3898
4167
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -3904,12 +4173,13 @@ var WorkflowsDSQL = class WorkflowsDSQL extends _mastra_core_storage.WorkflowsSt
|
|
|
3904
4173
|
}, error);
|
|
3905
4174
|
}
|
|
3906
4175
|
}
|
|
3907
|
-
async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt }) {
|
|
4176
|
+
async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt, fence }) {
|
|
3908
4177
|
try {
|
|
3909
4178
|
const now = /* @__PURE__ */ new Date();
|
|
3910
4179
|
const createdAtValue = createdAt ? createdAt : now;
|
|
3911
4180
|
const updatedAtValue = updatedAt ? updatedAt : now;
|
|
3912
|
-
|
|
4181
|
+
const resolvedFence = (0, _mastra_core_storage.resolveRunFence)(this, fence, runId);
|
|
4182
|
+
await withRetry(() => withRunFence(this.#db.client, this.#runOwnersTable(), resolvedFence, "persistWorkflowSnapshot", (q) => q.none(`INSERT INTO ${getTableName({
|
|
3913
4183
|
indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
|
|
3914
4184
|
schemaName: getSchemaName(this.#schema)
|
|
3915
4185
|
})} (workflow_name, run_id, "resourceId", snapshot, "createdAt", "updatedAt")
|
|
@@ -3922,8 +4192,9 @@ var WorkflowsDSQL = class WorkflowsDSQL extends _mastra_core_storage.WorkflowsSt
|
|
|
3922
4192
|
JSON.stringify(snapshot),
|
|
3923
4193
|
createdAtValue.toISOString(),
|
|
3924
4194
|
updatedAtValue.toISOString()
|
|
3925
|
-
]);
|
|
4195
|
+
])));
|
|
3926
4196
|
} catch (error) {
|
|
4197
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
3927
4198
|
throw new _mastra_core_error.MastraError({
|
|
3928
4199
|
id: (0, _mastra_core_storage.createStorageErrorId)("DSQL", "PERSIST_WORKFLOW_SNAPSHOT", "FAILED"),
|
|
3929
4200
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -3989,13 +4260,15 @@ var WorkflowsDSQL = class WorkflowsDSQL extends _mastra_core_storage.WorkflowsSt
|
|
|
3989
4260
|
}, error);
|
|
3990
4261
|
}
|
|
3991
4262
|
}
|
|
3992
|
-
async deleteWorkflowRunById({ runId, workflowName }) {
|
|
4263
|
+
async deleteWorkflowRunById({ runId, workflowName, fence }) {
|
|
3993
4264
|
try {
|
|
3994
|
-
|
|
4265
|
+
const resolvedFence = (0, _mastra_core_storage.resolveRunFence)(this, fence, runId);
|
|
4266
|
+
await withRetry(() => withRunFence(this.#db.client, this.#runOwnersTable(), resolvedFence, "deleteWorkflowRunById", (q) => q.none(`DELETE FROM ${getTableName({
|
|
3995
4267
|
indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
|
|
3996
4268
|
schemaName: getSchemaName(this.#schema)
|
|
3997
|
-
})} WHERE run_id = $1 AND workflow_name = $2`, [runId, workflowName]);
|
|
4269
|
+
})} WHERE run_id = $1 AND workflow_name = $2`, [runId, workflowName])));
|
|
3998
4270
|
} catch (error) {
|
|
4271
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
3999
4272
|
throw new _mastra_core_error.MastraError({
|
|
4000
4273
|
id: (0, _mastra_core_storage.createStorageErrorId)("DSQL", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"),
|
|
4001
4274
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|