@mastra/cloudflare 1.7.2-alpha.1 → 1.7.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.
@@ -103,11 +103,15 @@ var SqlBuilder = class {
103
103
  * @param values Values to insert
104
104
  * @param conflictColumns Columns to check for conflict (usually PK or UNIQUE)
105
105
  * @param updateMap Object mapping columns to update to their new value (e.g. { name: 'excluded.name' })
106
+ * @param guard Condition the statement writes under: when it is false, neither the insert nor the conflict update happens
106
107
  */
107
- insert(table, columns, values, conflictColumns, updateMap) {
108
+ insert(table, columns, values, conflictColumns, updateMap, guard) {
108
109
  const parsedTableName = (0, _mastra_core_utils.parseSqlIdentifier)(table, "table name");
109
110
  const parsedColumns = columns.map((col) => (0, _mastra_core_utils.parseSqlIdentifier)(col, "column name"));
110
111
  const placeholders = parsedColumns.map(() => "?").join(", ");
112
+ const source = guard ? `SELECT ${placeholders} WHERE ${guard.sql}` : `VALUES (${placeholders})`;
113
+ this.sql = `INSERT INTO ${parsedTableName} (${parsedColumns.join(", ")}) ${source}`;
114
+ this.params.push(...values, ...guard?.params ?? []);
111
115
  if (conflictColumns && updateMap) {
112
116
  const parsedConflictColumns = conflictColumns.map((col) => (0, _mastra_core_utils.parseSqlIdentifier)(col, "column name"));
113
117
  const excludedPattern = /^excluded\.[a-zA-Z_][a-zA-Z0-9_]*$/;
@@ -117,12 +121,8 @@ var SqlBuilder = class {
117
121
  if (!excludedPattern.test(expr) && !coalescePattern.test(expr)) throw new Error(`Invalid update expression for column ${col}: must be 'excluded.<column>' or 'COALESCE(excluded.<column>, <table>.<column>)' pattern`);
118
122
  return `${parsedCol} = ${expr}`;
119
123
  }).join(", ");
120
- this.sql = `INSERT INTO ${parsedTableName} (${parsedColumns.join(", ")}) VALUES (${placeholders}) ON CONFLICT(${parsedConflictColumns.join(", ")}) DO UPDATE SET ${updateClause}`;
121
- this.params.push(...values);
122
- return this;
124
+ this.sql += ` ON CONFLICT(${parsedConflictColumns.join(", ")}) DO UPDATE SET ${updateClause}`;
123
125
  }
124
- this.sql = `INSERT INTO ${parsedTableName} (${parsedColumns.join(", ")}) VALUES (${placeholders})`;
125
- this.params.push(...values);
126
126
  return this;
127
127
  }
128
128
  update(table, columns, values) {
@@ -268,6 +268,67 @@ function parseSelectIdentifier(column) {
268
268
  return column;
269
269
  }
270
270
  //#endregion
271
+ //#region src/do/storage/db/run-fencing.ts
272
+ /**
273
+ * Epoch milliseconds on the database clock. `julianday('now')` rather than
274
+ * `unixepoch('subsec')`, which needs SQLite 3.42.
275
+ */
276
+ const DB_NOW_MS = `CAST(ROUND((julianday('now') - 2440587.5) * 86400000) AS INTEGER)`;
277
+ /**
278
+ * The condition a fenced statement writes under: the check's fence is the
279
+ * run's current claim. A Durable Object serves other requests whenever it
280
+ * awaits I/O, so a fenced write can't check the claim first and write later; each of its statements carries this condition instead, and
281
+ * SQLite evaluates it atomically with the write.
282
+ */
283
+ function runFenceGuard(check) {
284
+ if (!check) return void 0;
285
+ const { claimsTable, fence } = check;
286
+ return {
287
+ sql: `EXISTS (SELECT 1 FROM ${claimsTable} AS run_fence WHERE run_fence.runId = ? AND run_fence.generation = ? AND run_fence.ownerId = ?)`,
288
+ params: [
289
+ fence.runId,
290
+ fence.generation,
291
+ fence.ownerId
292
+ ]
293
+ };
294
+ }
295
+ /** Throws `RunFenceConflictError` unless the check's fence is the run's current claim. */
296
+ async function assertRunFence(db, { claimsTable, fence, operation }) {
297
+ const row = await db.executeQuery({
298
+ sql: `SELECT generation, ownerId FROM ${claimsTable} WHERE runId = ?`,
299
+ params: [fence.runId],
300
+ first: true
301
+ });
302
+ if (!(0, _mastra_core_storage.matchesRunFence)(row ? {
303
+ generation: Number(row.generation),
304
+ ownerId: String(row.ownerId)
305
+ } : null, fence)) throw new _mastra_core_storage.RunFenceConflictError(fence, operation);
306
+ }
307
+ /**
308
+ * Runs one statement of a fenced write. With a check, `statement` must carry
309
+ * `runFenceGuard(check)`. A guarded statement that changes no row either
310
+ * found no target or was refused by its guard; re-reading the claim tells
311
+ * which, and a refusal throws `RunFenceConflictError`.
312
+ *
313
+ * A write made of several statements stops at the first refused one, so a
314
+ * takeover that lands between them leaves the earlier statements applied.
315
+ * None of the refused writer's statements land after the takeover.
316
+ */
317
+ async function executeFenced(db, check, { sql, params }) {
318
+ if (!check) {
319
+ await db.executeQuery({
320
+ sql,
321
+ params
322
+ });
323
+ return;
324
+ }
325
+ const rows = await db.executeQuery({
326
+ sql: `${sql} RETURNING 1 AS written`,
327
+ params
328
+ });
329
+ if (Array.isArray(rows) && rows.length === 0) await assertRunFence(db, check);
330
+ }
331
+ //#endregion
271
332
  //#region src/do/storage/db/index.ts
272
333
  var DODB = class extends _mastra_core_base.MastraBase {
273
334
  sql;
@@ -517,10 +578,12 @@ var DODB = class extends _mastra_core_base.MastraBase {
517
578
  * @param tableName The table to insert into
518
579
  * @param records The records to insert
519
580
  * @param conflictKeys The columns to use for conflict detection (defaults to ['id'])
581
+ * @param check Run fence every upsert must hold; a refused upsert throws and stops the batch
520
582
  */
521
- async batchUpsert({ tableName, records, conflictKeys = ["id"] }) {
583
+ async batchUpsert({ tableName, records, conflictKeys = ["id"], check }) {
522
584
  if (records.length === 0) return;
523
585
  const fullTableName = this.getTableName(tableName);
586
+ const guard = runFenceGuard(check);
524
587
  try {
525
588
  const batchSize = 50;
526
589
  for (let i = 0; i < records.length; i += batchSize) {
@@ -538,17 +601,15 @@ var DODB = class extends _mastra_core_base.MastraBase {
538
601
  if (col !== "createdAt" && !conflictKeys.includes(col)) acc[col] = `excluded.${col}`;
539
602
  return acc;
540
603
  }, {});
541
- const { sql, params } = createSqlBuilder().insert(fullTableName, columns, values, conflictKeys, recordToUpsert).build();
542
- await this.executeQuery({
543
- sql,
544
- params
545
- });
604
+ const query = createSqlBuilder().insert(fullTableName, columns, values, conflictKeys, recordToUpsert, guard);
605
+ await executeFenced(this, check, query.build());
546
606
  }
547
607
  }
548
608
  this.logger.debug(`Processed batch ${Math.floor(i / batchSize) + 1} of ${Math.ceil(records.length / batchSize)}`);
549
609
  }
550
610
  this.logger.debug(`Successfully batch upserted ${records.length} records into ${tableName}`);
551
611
  } catch (error) {
612
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
552
613
  throw new _mastra_core_error.MastraError({
553
614
  id: (0, _mastra_core_storage.createStorageErrorId)("CLOUDFLARE_DO", "BATCH_UPSERT", "FAILED"),
554
615
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -920,11 +981,76 @@ var MemoryStorageDO = class extends _mastra_core_storage.MemoryStorage {
920
981
  schema: _mastra_core_storage.TABLE_SCHEMAS[_mastra_core_storage.TABLE_MESSAGES],
921
982
  ifNotExists: ["resourceId"]
922
983
  });
984
+ await this.#db.createTable({
985
+ tableName: _mastra_core_storage.TABLE_MEMORY_RUN_FENCES,
986
+ schema: _mastra_core_storage.RUN_FENCING_TABLE_SCHEMAS[_mastra_core_storage.TABLE_MEMORY_RUN_FENCES]
987
+ });
923
988
  }
924
989
  async dangerouslyClearAll() {
925
990
  await this.#db.clearTable({ tableName: _mastra_core_storage.TABLE_MESSAGES });
926
991
  await this.#db.clearTable({ tableName: _mastra_core_storage.TABLE_THREADS });
927
992
  await this.#db.clearTable({ tableName: _mastra_core_storage.TABLE_RESOURCES });
993
+ await this.#db.clearTable({ tableName: _mastra_core_storage.TABLE_MEMORY_RUN_FENCES });
994
+ }
995
+ supportsRunFencing() {
996
+ return true;
997
+ }
998
+ get #fencesTable() {
999
+ return this.#db.getTableName(_mastra_core_storage.TABLE_MEMORY_RUN_FENCES);
1000
+ }
1001
+ async raiseRunFence(fence) {
1002
+ const table = this.#fencesTable;
1003
+ try {
1004
+ return (await this.#db.executeQuery({
1005
+ sql: `INSERT INTO ${table} (runId, generation, ownerId) VALUES (?, ?, ?)
1006
+ ON CONFLICT(runId) DO UPDATE SET generation = excluded.generation, ownerId = excluded.ownerId, retiredAt = NULL
1007
+ WHERE excluded.generation > ${table}.generation
1008
+ OR (excluded.generation = ${table}.generation AND excluded.ownerId = ${table}.ownerId)
1009
+ RETURNING runId`,
1010
+ params: [
1011
+ fence.runId,
1012
+ fence.generation,
1013
+ fence.ownerId
1014
+ ]
1015
+ })).length > 0;
1016
+ } catch (error) {
1017
+ throw new _mastra_core_error.MastraError({
1018
+ id: (0, _mastra_core_storage.createStorageErrorId)("CLOUDFLARE_DO", "RAISE_RUN_FENCE", "FAILED"),
1019
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
1020
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
1021
+ details: { runId: fence.runId }
1022
+ }, error);
1023
+ }
1024
+ }
1025
+ async retireRunFence(fence) {
1026
+ try {
1027
+ return (await this.#db.executeQuery({
1028
+ sql: `UPDATE ${this.#fencesTable} SET retiredAt = ${DB_NOW_MS}
1029
+ WHERE runId = ? AND generation = ? AND ownerId = ?
1030
+ RETURNING runId`,
1031
+ params: [
1032
+ fence.runId,
1033
+ fence.generation,
1034
+ fence.ownerId
1035
+ ]
1036
+ })).length > 0;
1037
+ } catch (error) {
1038
+ throw new _mastra_core_error.MastraError({
1039
+ id: (0, _mastra_core_storage.createStorageErrorId)("CLOUDFLARE_DO", "RETIRE_RUN_FENCE", "FAILED"),
1040
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
1041
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
1042
+ details: { runId: fence.runId }
1043
+ }, error);
1044
+ }
1045
+ }
1046
+ /** The check a write must pass: its own fence, otherwise the one in scope. */
1047
+ #runFenceCheck(fence, operation) {
1048
+ const resolved = (0, _mastra_core_storage.resolveRunFence)(this, fence);
1049
+ return resolved && {
1050
+ claimsTable: this.#fencesTable,
1051
+ fence: resolved,
1052
+ operation
1053
+ };
928
1054
  }
929
1055
  async getResourceById({ resourceId }) {
930
1056
  const resource = await this.#db.load({
@@ -953,6 +1079,9 @@ var MemoryStorageDO = class extends _mastra_core_storage.MemoryStorage {
953
1079
  }
954
1080
  }
955
1081
  async saveResource({ resource }) {
1082
+ return this.#insertResource(resource, void 0);
1083
+ }
1084
+ async #insertResource(resource, check) {
956
1085
  const fullTableName = this.#db.getTableName(_mastra_core_storage.TABLE_RESOURCES);
957
1086
  const resourceToSave = {
958
1087
  id: resource.id,
@@ -964,19 +1093,17 @@ var MemoryStorageDO = class extends _mastra_core_storage.MemoryStorage {
964
1093
  const processedRecord = await this.#db.processRecord(resourceToSave);
965
1094
  const columns = Object.keys(processedRecord);
966
1095
  const values = Object.values(processedRecord);
967
- const { sql, params } = createSqlBuilder().insert(fullTableName, columns, values, ["id"], {
1096
+ const query = createSqlBuilder().insert(fullTableName, columns, values, ["id"], {
968
1097
  workingMemory: "excluded.workingMemory",
969
1098
  metadata: "excluded.metadata",
970
1099
  createdAt: "excluded.createdAt",
971
1100
  updatedAt: "excluded.updatedAt"
972
- }).build();
1101
+ }, runFenceGuard(check));
973
1102
  try {
974
- await this.#db.executeQuery({
975
- sql,
976
- params
977
- });
1103
+ await executeFenced(this.#db, check, query.build());
978
1104
  return resource;
979
1105
  } catch (error) {
1106
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
980
1107
  throw new _mastra_core_error.MastraError({
981
1108
  id: (0, _mastra_core_storage.createStorageErrorId)("CLOUDFLARE_DO", "SAVE_RESOURCE", "FAILED"),
982
1109
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -986,7 +1113,8 @@ var MemoryStorageDO = class extends _mastra_core_storage.MemoryStorage {
986
1113
  }, error);
987
1114
  }
988
1115
  }
989
- async updateResource({ resourceId, workingMemory, metadata }) {
1116
+ async updateResource({ resourceId, workingMemory, metadata, fence }) {
1117
+ const check = this.#runFenceCheck(fence, "updateResource");
990
1118
  const existingResource = await this.getResourceById({ resourceId });
991
1119
  if (!existingResource) {
992
1120
  const newResource = {
@@ -996,7 +1124,7 @@ var MemoryStorageDO = class extends _mastra_core_storage.MemoryStorage {
996
1124
  createdAt: /* @__PURE__ */ new Date(),
997
1125
  updatedAt: /* @__PURE__ */ new Date()
998
1126
  };
999
- return this.saveResource({ resource: newResource });
1127
+ return this.#insertResource(newResource, check);
1000
1128
  }
1001
1129
  const updatedAt = /* @__PURE__ */ new Date();
1002
1130
  const updatedResource = {
@@ -1019,14 +1147,14 @@ var MemoryStorageDO = class extends _mastra_core_storage.MemoryStorage {
1019
1147
  JSON.stringify(updatedResource.metadata),
1020
1148
  updatedAt.toISOString()
1021
1149
  ];
1022
- const { sql, params } = createSqlBuilder().update(fullTableName, columns, values).where("id = ?", resourceId).build();
1150
+ const query = createSqlBuilder().update(fullTableName, columns, values).where("id = ?", resourceId);
1151
+ const guard = runFenceGuard(check);
1152
+ if (guard) query.andWhere(guard.sql, ...guard.params);
1023
1153
  try {
1024
- await this.#db.executeQuery({
1025
- sql,
1026
- params
1027
- });
1154
+ await executeFenced(this.#db, check, query.build());
1028
1155
  return updatedResource;
1029
1156
  } catch (error) {
1157
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
1030
1158
  throw new _mastra_core_error.MastraError({
1031
1159
  id: (0, _mastra_core_storage.createStorageErrorId)("CLOUDFLARE_DO", "UPDATE_RESOURCE", "FAILED"),
1032
1160
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -1167,8 +1295,9 @@ var MemoryStorageDO = class extends _mastra_core_storage.MemoryStorage {
1167
1295
  };
1168
1296
  }
1169
1297
  }
1170
- async saveThread({ thread }) {
1298
+ async saveThread({ thread, fence }) {
1171
1299
  const fullTableName = this.#db.getTableName(_mastra_core_storage.TABLE_THREADS);
1300
+ const check = this.#runFenceCheck(fence, "saveThread");
1172
1301
  const threadToSave = {
1173
1302
  id: thread.id,
1174
1303
  resourceId: thread.resourceId,
@@ -1180,20 +1309,18 @@ var MemoryStorageDO = class extends _mastra_core_storage.MemoryStorage {
1180
1309
  const processedRecord = await this.#db.processRecord(threadToSave);
1181
1310
  const columns = Object.keys(processedRecord);
1182
1311
  const values = Object.values(processedRecord);
1183
- const { sql, params } = createSqlBuilder().insert(fullTableName, columns, values, ["id"], {
1312
+ const query = createSqlBuilder().insert(fullTableName, columns, values, ["id"], {
1184
1313
  resourceId: "excluded.resourceId",
1185
1314
  title: "excluded.title",
1186
1315
  metadata: "excluded.metadata",
1187
1316
  createdAt: "excluded.createdAt",
1188
1317
  updatedAt: "excluded.updatedAt"
1189
- }).build();
1318
+ }, runFenceGuard(check));
1190
1319
  try {
1191
- await this.#db.executeQuery({
1192
- sql,
1193
- params
1194
- });
1320
+ await executeFenced(this.#db, check, query.build());
1195
1321
  return thread;
1196
1322
  } catch (error) {
1323
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
1197
1324
  throw new _mastra_core_error.MastraError({
1198
1325
  id: (0, _mastra_core_storage.createStorageErrorId)("CLOUDFLARE_DO", "SAVE_THREAD", "FAILED"),
1199
1326
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -1203,7 +1330,8 @@ var MemoryStorageDO = class extends _mastra_core_storage.MemoryStorage {
1203
1330
  }, error);
1204
1331
  }
1205
1332
  }
1206
- async updateThread({ id, title, metadata }) {
1333
+ async updateThread({ id, title, metadata, fence }) {
1334
+ const check = this.#runFenceCheck(fence, "updateThread");
1207
1335
  const thread = await this.getThreadById({ threadId: id });
1208
1336
  try {
1209
1337
  if (!thread) throw new Error(`Thread ${id} not found`);
@@ -1226,11 +1354,10 @@ var MemoryStorageDO = class extends _mastra_core_storage.MemoryStorage {
1226
1354
  }
1227
1355
  columns.push("updatedAt");
1228
1356
  values.push(updatedAt.toISOString());
1229
- const { sql, params } = createSqlBuilder().update(fullTableName, columns, values).where("id = ?", id).build();
1230
- await this.#db.executeQuery({
1231
- sql,
1232
- params
1233
- });
1357
+ const query = createSqlBuilder().update(fullTableName, columns, values).where("id = ?", id);
1358
+ const guard = runFenceGuard(check);
1359
+ if (guard) query.andWhere(guard.sql, ...guard.params);
1360
+ await executeFenced(this.#db, check, query.build());
1234
1361
  return {
1235
1362
  ...thread,
1236
1363
  title: updatedTitle,
@@ -1238,6 +1365,7 @@ var MemoryStorageDO = class extends _mastra_core_storage.MemoryStorage {
1238
1365
  updatedAt
1239
1366
  };
1240
1367
  } catch (error) {
1368
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
1241
1369
  throw new _mastra_core_error.MastraError({
1242
1370
  id: (0, _mastra_core_storage.createStorageErrorId)("CLOUDFLARE_DO", "UPDATE_THREAD", "FAILED"),
1243
1371
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -1274,6 +1402,7 @@ var MemoryStorageDO = class extends _mastra_core_storage.MemoryStorage {
1274
1402
  async saveMessages(args) {
1275
1403
  const { messages } = args;
1276
1404
  if (messages.length === 0) return { messages: [] };
1405
+ const check = this.#runFenceCheck(args.fence, "saveMessages");
1277
1406
  try {
1278
1407
  const now = /* @__PURE__ */ new Date();
1279
1408
  for (const [i, message] of messages.entries()) {
@@ -1299,16 +1428,24 @@ var MemoryStorageDO = class extends _mastra_core_storage.MemoryStorage {
1299
1428
  resourceId: message.resourceId
1300
1429
  };
1301
1430
  });
1302
- await Promise.all([this.#db.batchUpsert({
1431
+ await this.#db.batchUpsert({
1303
1432
  tableName: _mastra_core_storage.TABLE_MESSAGES,
1304
- records: messagesToInsert
1305
- }), ...uniqueThreadIds.map((tid) => this.#db.executeQuery({
1306
- sql: `UPDATE ${this.#db.getTableName(_mastra_core_storage.TABLE_THREADS)} SET updatedAt = ? WHERE id = ?`,
1307
- params: [now.toISOString(), tid]
1308
- }))]);
1433
+ records: messagesToInsert,
1434
+ check
1435
+ });
1436
+ const guard = runFenceGuard(check);
1437
+ for (const tid of uniqueThreadIds) await executeFenced(this.#db, check, {
1438
+ sql: `UPDATE ${this.#db.getTableName(_mastra_core_storage.TABLE_THREADS)} SET updatedAt = ? WHERE id = ?${guard ? ` AND ${guard.sql}` : ""}`,
1439
+ params: [
1440
+ now.toISOString(),
1441
+ tid,
1442
+ ...guard?.params ?? []
1443
+ ]
1444
+ });
1309
1445
  this.logger.debug(`Saved ${messages.length} messages`);
1310
1446
  return { messages: new _mastra_core_agent.MessageList().add(messages, "memory").get.all.db() };
1311
1447
  } catch (error) {
1448
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
1312
1449
  throw new _mastra_core_error.MastraError({
1313
1450
  id: (0, _mastra_core_storage.createStorageErrorId)("CLOUDFLARE_DO", "SAVE_MESSAGES", "FAILED"),
1314
1451
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -1584,6 +1721,8 @@ var MemoryStorageDO = class extends _mastra_core_storage.MemoryStorage {
1584
1721
  const { messages } = args;
1585
1722
  this.logger.debug("Updating messages", { count: messages.length });
1586
1723
  if (!messages.length) return [];
1724
+ const check = this.#runFenceCheck(args.fence, "updateMessages");
1725
+ const guard = runFenceGuard(check);
1587
1726
  const messageIds = messages.map((m) => m.id);
1588
1727
  const fullTableName = this.#db.getTableName(_mastra_core_storage.TABLE_MESSAGES);
1589
1728
  const threadsTableName = this.#db.getTableName(_mastra_core_storage.TABLE_THREADS);
@@ -1628,7 +1767,7 @@ var MemoryStorageDO = class extends _mastra_core_storage.MemoryStorage {
1628
1767
  }
1629
1768
  for (const msg of updatedMessages) {
1630
1769
  const contentStr = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
1631
- const { sql, params } = createSqlBuilder().update(fullTableName, [
1770
+ const updateQuery = createSqlBuilder().update(fullTableName, [
1632
1771
  "content",
1633
1772
  "role",
1634
1773
  "type"
@@ -1636,19 +1775,22 @@ var MemoryStorageDO = class extends _mastra_core_storage.MemoryStorage {
1636
1775
  contentStr,
1637
1776
  msg.role,
1638
1777
  msg.type
1639
- ]).where("id = ?", msg.id).build();
1640
- await this.#db.executeQuery({
1641
- sql,
1642
- params
1643
- });
1778
+ ]).where("id = ?", msg.id);
1779
+ if (guard) updateQuery.andWhere(guard.sql, ...guard.params);
1780
+ await executeFenced(this.#db, check, updateQuery.build());
1644
1781
  }
1645
1782
  const threadIds = [...new Set(updatedMessages.map((m) => m.threadId))];
1646
- for (const tid of threadIds) await this.#db.executeQuery({
1647
- sql: `UPDATE ${threadsTableName} SET updatedAt = ? WHERE id = ?`,
1648
- params: [now, tid]
1783
+ for (const tid of threadIds) await executeFenced(this.#db, check, {
1784
+ sql: `UPDATE ${threadsTableName} SET updatedAt = ? WHERE id = ?${guard ? ` AND ${guard.sql}` : ""}`,
1785
+ params: [
1786
+ now,
1787
+ tid,
1788
+ ...guard?.params ?? []
1789
+ ]
1649
1790
  });
1650
1791
  return new _mastra_core_agent.MessageList().add(updatedMessages, "memory").get.all.db();
1651
1792
  } catch (error) {
1793
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
1652
1794
  throw new _mastra_core_error.MastraError({
1653
1795
  id: (0, _mastra_core_storage.createStorageErrorId)("CLOUDFLARE_DO", "UPDATE_MESSAGES", "FAILED"),
1654
1796
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -1658,17 +1800,19 @@ var MemoryStorageDO = class extends _mastra_core_storage.MemoryStorage {
1658
1800
  }, error);
1659
1801
  }
1660
1802
  }
1661
- async deleteMessages(messageIds) {
1803
+ async deleteMessages(messageIds, options) {
1662
1804
  if (messageIds.length === 0) return;
1663
1805
  const fullTableName = this.#db.getTableName(_mastra_core_storage.TABLE_MESSAGES);
1806
+ const check = this.#runFenceCheck(options?.fence, "deleteMessages");
1664
1807
  try {
1665
- const sql = `DELETE FROM ${fullTableName} WHERE id IN (${messageIds.map(() => "?").join(",")})`;
1666
- await this.#db.executeQuery({
1667
- sql,
1668
- params: messageIds
1669
- });
1808
+ const placeholders = messageIds.map(() => "?").join(",");
1809
+ const query = createSqlBuilder().delete(fullTableName).where(`id IN (${placeholders})`, ...messageIds);
1810
+ const guard = runFenceGuard(check);
1811
+ if (guard) query.andWhere(guard.sql, ...guard.params);
1812
+ await executeFenced(this.#db, check, query.build());
1670
1813
  this.logger.debug(`Deleted ${messageIds.length} messages`);
1671
1814
  } catch (error) {
1815
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
1672
1816
  throw new _mastra_core_error.MastraError({
1673
1817
  id: (0, _mastra_core_storage.createStorageErrorId)("CLOUDFLARE_DO", "DELETE_MESSAGES", "FAILED"),
1674
1818
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -1980,6 +2124,22 @@ var ScoresStorageDO = class extends _mastra_core_storage.ScoresStorage {
1980
2124
  };
1981
2125
  //#endregion
1982
2126
  //#region src/do/storage/domains/workflows/index.ts
2127
+ const RUN_OWNER_COLUMNS = `generation, ownerId, leaseExpiresAt, ${DB_NOW_MS} AS nowMs`;
2128
+ /**
2129
+ * A run's first generation is the database clock, so a run claimed again after
2130
+ * its ownership record was pruned still gets a higher generation than its fence.
2131
+ */
2132
+ const FIRST_GENERATION = `MAX(1, ${DB_NOW_MS})`;
2133
+ function toRunOwnershipRecord(runId, row) {
2134
+ const leaseExpiresAt = row.leaseExpiresAt === null ? null : Number(row.leaseExpiresAt);
2135
+ return {
2136
+ runId,
2137
+ generation: Number(row.generation),
2138
+ ownerId: String(row.ownerId),
2139
+ leaseExpiresAt: leaseExpiresAt === null ? null : new Date(leaseExpiresAt),
2140
+ live: leaseExpiresAt !== null && leaseExpiresAt > Number(row.nowMs)
2141
+ };
2142
+ }
1983
2143
  var WorkflowsStorageDO = class extends _mastra_core_storage.WorkflowsStorage {
1984
2144
  #db;
1985
2145
  constructor(config) {
@@ -1994,9 +2154,148 @@ var WorkflowsStorageDO = class extends _mastra_core_storage.WorkflowsStorage {
1994
2154
  tableName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
1995
2155
  schema: _mastra_core_storage.TABLE_SCHEMAS[_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT]
1996
2156
  });
2157
+ await this.#db.createTable({
2158
+ tableName: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS,
2159
+ schema: _mastra_core_storage.RUN_FENCING_TABLE_SCHEMAS[_mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS]
2160
+ });
1997
2161
  }
1998
2162
  async dangerouslyClearAll() {
1999
2163
  await this.#db.clearTable({ tableName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT });
2164
+ await this.#db.clearTable({ tableName: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS });
2165
+ }
2166
+ supportsRunFencing() {
2167
+ return true;
2168
+ }
2169
+ get #ownersTable() {
2170
+ return this.#db.getTableName(_mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS);
2171
+ }
2172
+ #runFenceCheck(fence, runId, operation) {
2173
+ const resolved = (0, _mastra_core_storage.resolveRunFence)(this, fence, runId);
2174
+ return resolved && {
2175
+ claimsTable: this.#ownersTable,
2176
+ fence: resolved,
2177
+ operation
2178
+ };
2179
+ }
2180
+ async #readRunOwner(runId) {
2181
+ const row = await this.#db.executeQuery({
2182
+ sql: `SELECT ${RUN_OWNER_COLUMNS} FROM ${this.#ownersTable} WHERE runId = ?`,
2183
+ params: [runId],
2184
+ first: true
2185
+ });
2186
+ return row ? toRunOwnershipRecord(runId, row) : null;
2187
+ }
2188
+ #ownershipError(operation, runId, error) {
2189
+ return new _mastra_core_error.MastraError({
2190
+ id: (0, _mastra_core_storage.createStorageErrorId)("CLOUDFLARE_DO", operation, "FAILED"),
2191
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
2192
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
2193
+ details: { runId }
2194
+ }, error);
2195
+ }
2196
+ async claimRunOwnership({ runId, ownerId, leaseMs, force, expectedGeneration }) {
2197
+ const notLive = `(leaseExpiresAt IS NULL OR leaseExpiresAt <= ${DB_NOW_MS})`;
2198
+ let statement;
2199
+ if (expectedGeneration === void 0) statement = {
2200
+ sql: `INSERT INTO ${this.#ownersTable} (runId, generation, ownerId, leaseExpiresAt, updatedAt)
2201
+ VALUES (?, ${FIRST_GENERATION}, ?, ${DB_NOW_MS} + ?, ${DB_NOW_MS})
2202
+ ON CONFLICT(runId) DO UPDATE SET
2203
+ generation = generation + 1,
2204
+ ownerId = excluded.ownerId,
2205
+ leaseExpiresAt = excluded.leaseExpiresAt,
2206
+ updatedAt = excluded.updatedAt
2207
+ ${force ? "" : `WHERE ${notLive}`}`,
2208
+ params: [
2209
+ runId,
2210
+ ownerId,
2211
+ leaseMs
2212
+ ]
2213
+ };
2214
+ else if (expectedGeneration === 0) statement = {
2215
+ sql: `INSERT INTO ${this.#ownersTable} (runId, generation, ownerId, leaseExpiresAt, updatedAt)
2216
+ VALUES (?, ${FIRST_GENERATION}, ?, ${DB_NOW_MS} + ?, ${DB_NOW_MS})
2217
+ ON CONFLICT(runId) DO NOTHING`,
2218
+ params: [
2219
+ runId,
2220
+ ownerId,
2221
+ leaseMs
2222
+ ]
2223
+ };
2224
+ else statement = {
2225
+ sql: `UPDATE ${this.#ownersTable}
2226
+ SET generation = generation + 1, ownerId = ?, leaseExpiresAt = ${DB_NOW_MS} + ?, updatedAt = ${DB_NOW_MS}
2227
+ WHERE runId = ? AND generation = ?${force ? "" : ` AND ${notLive}`}`,
2228
+ params: [
2229
+ ownerId,
2230
+ leaseMs,
2231
+ runId,
2232
+ expectedGeneration
2233
+ ]
2234
+ };
2235
+ try {
2236
+ const claimed = (await this.#db.executeQuery({
2237
+ sql: `${statement.sql} RETURNING ${RUN_OWNER_COLUMNS}`,
2238
+ params: statement.params
2239
+ }))[0];
2240
+ if (claimed) return {
2241
+ acquired: true,
2242
+ record: toRunOwnershipRecord(runId, claimed)
2243
+ };
2244
+ return {
2245
+ acquired: false,
2246
+ record: await this.#readRunOwner(runId)
2247
+ };
2248
+ } catch (error) {
2249
+ throw this.#ownershipError("CLAIM_RUN_OWNERSHIP", runId, error);
2250
+ }
2251
+ }
2252
+ async renewRunOwnership({ leaseMs, ...fence }) {
2253
+ try {
2254
+ const renewed = (await this.#db.executeQuery({
2255
+ sql: `UPDATE ${this.#ownersTable} SET leaseExpiresAt = ${DB_NOW_MS} + ?, updatedAt = ${DB_NOW_MS}
2256
+ WHERE runId = ? AND generation = ? AND ownerId = ? AND leaseExpiresAt IS NOT NULL
2257
+ RETURNING ${RUN_OWNER_COLUMNS}`,
2258
+ params: [
2259
+ leaseMs,
2260
+ fence.runId,
2261
+ fence.generation,
2262
+ fence.ownerId
2263
+ ]
2264
+ }))[0];
2265
+ if (renewed) return {
2266
+ renewed: true,
2267
+ record: toRunOwnershipRecord(fence.runId, renewed)
2268
+ };
2269
+ return {
2270
+ renewed: false,
2271
+ record: await this.#readRunOwner(fence.runId)
2272
+ };
2273
+ } catch (error) {
2274
+ throw this.#ownershipError("RENEW_RUN_OWNERSHIP", fence.runId, error);
2275
+ }
2276
+ }
2277
+ async releaseRunOwnership(fence) {
2278
+ try {
2279
+ return (await this.#db.executeQuery({
2280
+ sql: `UPDATE ${this.#ownersTable} SET leaseExpiresAt = NULL, updatedAt = ${DB_NOW_MS}
2281
+ WHERE runId = ? AND generation = ? AND ownerId = ?
2282
+ RETURNING runId`,
2283
+ params: [
2284
+ fence.runId,
2285
+ fence.generation,
2286
+ fence.ownerId
2287
+ ]
2288
+ })).length > 0;
2289
+ } catch (error) {
2290
+ throw this.#ownershipError("RELEASE_RUN_OWNERSHIP", fence.runId, error);
2291
+ }
2292
+ }
2293
+ async getRunOwnership({ runId }) {
2294
+ try {
2295
+ return await this.#readRunOwner(runId);
2296
+ } catch (error) {
2297
+ throw this.#ownershipError("GET_RUN_OWNERSHIP", runId, error);
2298
+ }
2000
2299
  }
2001
2300
  updateWorkflowResults({}) {
2002
2301
  throw new Error("Method not implemented.");
@@ -2004,9 +2303,10 @@ var WorkflowsStorageDO = class extends _mastra_core_storage.WorkflowsStorage {
2004
2303
  updateWorkflowState({}) {
2005
2304
  throw new Error("Method not implemented.");
2006
2305
  }
2007
- async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt }) {
2306
+ async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt, fence }) {
2008
2307
  const fullTableName = this.#db.getTableName(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT);
2009
2308
  const now = (/* @__PURE__ */ new Date()).toISOString();
2309
+ const check = this.#runFenceCheck(fence, runId, "persistWorkflowSnapshot");
2010
2310
  const currentSnapshot = await this.#db.load({
2011
2311
  tableName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
2012
2312
  keys: {
@@ -2039,13 +2339,11 @@ var WorkflowsStorageDO = class extends _mastra_core_storage.WorkflowsStorage {
2039
2339
  workflowName,
2040
2340
  runId
2041
2341
  });
2042
- const { sql, params } = createSqlBuilder().insert(fullTableName, columns, values, ["workflow_name", "run_id"], updateMap).build();
2342
+ const query = createSqlBuilder().insert(fullTableName, columns, values, ["workflow_name", "run_id"], updateMap, runFenceGuard(check));
2043
2343
  try {
2044
- await this.#db.executeQuery({
2045
- sql,
2046
- params
2047
- });
2344
+ await executeFenced(this.#db, check, query.build());
2048
2345
  } catch (error) {
2346
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
2049
2347
  throw new _mastra_core_error.MastraError({
2050
2348
  id: (0, _mastra_core_storage.createStorageErrorId)("CLOUDFLARE_DO", "PERSIST_WORKFLOW_SNAPSHOT", "FAILED"),
2051
2349
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -2200,16 +2498,16 @@ var WorkflowsStorageDO = class extends _mastra_core_storage.WorkflowsStorage {
2200
2498
  }, error);
2201
2499
  }
2202
2500
  }
2203
- async deleteWorkflowRunById({ runId, workflowName }) {
2501
+ async deleteWorkflowRunById({ runId, workflowName, fence }) {
2204
2502
  const fullTableName = this.#db.getTableName(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT);
2503
+ const check = this.#runFenceCheck(fence, runId, "deleteWorkflowRunById");
2205
2504
  try {
2206
- const sql = `DELETE FROM ${fullTableName} WHERE workflow_name = ? AND run_id = ?`;
2207
- const params = [workflowName, runId];
2208
- await this.#db.executeQuery({
2209
- sql,
2210
- params
2211
- });
2505
+ const query = createSqlBuilder().delete(fullTableName).where("workflow_name = ?", workflowName).andWhere("run_id = ?", runId);
2506
+ const guard = runFenceGuard(check);
2507
+ if (guard) query.andWhere(guard.sql, ...guard.params);
2508
+ await executeFenced(this.#db, check, query.build());
2212
2509
  } catch (error) {
2510
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
2213
2511
  throw new _mastra_core_error.MastraError({
2214
2512
  id: (0, _mastra_core_storage.createStorageErrorId)("CLOUDFLARE_DO", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"),
2215
2513
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -2346,4 +2644,4 @@ Object.defineProperty(exports, "WorkflowsStorageDO", {
2346
2644
  }
2347
2645
  });
2348
2646
 
2349
- //# sourceMappingURL=do-zOdIsGy9.cjs.map
2647
+ //# sourceMappingURL=do-Uemcag89.cjs.map