@mastra/libsql 1.26.0-alpha.2 → 1.26.0-alpha.3

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 CHANGED
@@ -4,15 +4,358 @@ let _mastra_core_error = require("@mastra/core/error");
4
4
  let _mastra_core_storage = require("@mastra/core/storage");
5
5
  let _mastra_core_utils = require("@mastra/core/utils");
6
6
  let _mastra_core_vector = require("@mastra/core/vector");
7
+ let path = require("path");
8
+ let async_hooks = require("async_hooks");
7
9
  let _mastra_core_vector_filter = require("@mastra/core/vector/filter");
8
10
  let fs_promises = require("fs/promises");
9
- let path = require("path");
10
11
  let _mastra_core_base = require("@mastra/core/base");
11
12
  let crypto$1 = require("crypto");
12
13
  let _mastra_core_features = require("@mastra/core/features");
13
14
  let _mastra_core_agent = require("@mastra/core/agent");
14
15
  let _mastra_core_evals = require("@mastra/core/evals");
15
16
  let _mastra_core_storage_domains_skills = require("@mastra/core/storage/domains/skills");
17
+ //#region src/shared/file-transaction-gate.ts
18
+ /** Interactive transactions open in this process, per database file. */
19
+ const openTransactions = /* @__PURE__ */ new Map();
20
+ /** The database file a local `file:` url points at, or `undefined` for anything else. */
21
+ function localDatabaseFileKey(url) {
22
+ if (!url.startsWith("file:") || url.includes(":memory:")) return void 0;
23
+ const path$2 = url.slice(5).split("?")[0];
24
+ return path$2 ? (0, path.resolve)(path$2.replace(/^\/\/(?=\/)/, "")) : void 0;
25
+ }
26
+ /**
27
+ * Wraps a local `file:` client so its calls wait for interactive transactions
28
+ * that other clients in this process hold open on the same database file,
29
+ * instead of blocking the thread.
30
+ *
31
+ * `@libsql/client` runs local statements synchronously. When one client holds
32
+ * a write transaction open across an `await` and another client writes the same
33
+ * file, SQLite's busy handler spins on the event loop's only thread for
34
+ * `busy_timeout`, so the transaction holding the lock can't resume and the write
35
+ * fails with `SQLITE_BUSY`. Waiting lets the transaction finish first.
36
+ *
37
+ * Only calls on other clients wait; a client already orders its own writes
38
+ * behind its transactions (see `withClientWriteLock`). A call waits at most
39
+ * `maxWaitMs` (the busy timeout), then goes ahead as it would without the gate,
40
+ * so code that awaits another client from inside its own transaction can't hang.
41
+ * Clients in other processes are unaffected and still rely on `busy_timeout`.
42
+ */
43
+ function gateLocalFileClient(client, key, { maxWaitMs = 5e3 } = {}) {
44
+ let gated;
45
+ const othersOpen = () => [...openTransactions.get(key) ?? []].filter((open) => open.owner !== gated);
46
+ const waitForOthers = () => {
47
+ if (othersOpen().length === 0) return;
48
+ return (async () => {
49
+ let timer;
50
+ const gaveUp = new Promise((r) => timer = setTimeout(r, maxWaitMs, "timeout"));
51
+ try {
52
+ for (let open = othersOpen(); open.length > 0; open = othersOpen()) if (await Promise.race([Promise.all(open.map((o) => o.done)), gaveUp]) === "timeout") return false;
53
+ return true;
54
+ } finally {
55
+ clearTimeout(timer);
56
+ }
57
+ })();
58
+ };
59
+ const call = (run) => {
60
+ const waiting = waitForOthers();
61
+ return waiting ? waiting.then(run) : run();
62
+ };
63
+ const transaction = async (open, mode) => {
64
+ const deadline = Date.now() + maxWaitMs;
65
+ for (let waiting = waitForOthers(); waiting; waiting = Date.now() < deadline ? waitForOthers() : void 0) if (!await waiting) break;
66
+ let finish;
67
+ const entry = {
68
+ owner: gated,
69
+ done: new Promise((r) => finish = r)
70
+ };
71
+ let set = openTransactions.get(key);
72
+ if (!set) openTransactions.set(key, set = /* @__PURE__ */ new Set());
73
+ set.add(entry);
74
+ const end = () => {
75
+ if (!set.delete(entry)) return;
76
+ if (set.size === 0 && openTransactions.get(key) === set) openTransactions.delete(key);
77
+ finish();
78
+ };
79
+ let tx;
80
+ try {
81
+ tx = await open(mode);
82
+ } catch (error) {
83
+ end();
84
+ throw error;
85
+ }
86
+ return new Proxy(tx, { get(target, prop) {
87
+ switch (prop) {
88
+ case "execute":
89
+ case "batch":
90
+ case "executeMultiple": {
91
+ const method = Reflect.get(target, prop);
92
+ return async (...args) => {
93
+ try {
94
+ return await method.apply(target, args);
95
+ } finally {
96
+ if (target.closed) end();
97
+ }
98
+ };
99
+ }
100
+ case "commit":
101
+ case "rollback": {
102
+ const method = Reflect.get(target, prop);
103
+ return async () => {
104
+ try {
105
+ await method.call(target);
106
+ } finally {
107
+ end();
108
+ }
109
+ };
110
+ }
111
+ case "close": {
112
+ const method = Reflect.get(target, prop);
113
+ return () => {
114
+ try {
115
+ method.call(target);
116
+ } finally {
117
+ end();
118
+ }
119
+ };
120
+ }
121
+ default: {
122
+ const value = Reflect.get(target, prop);
123
+ return typeof value === "function" ? value.bind(target) : value;
124
+ }
125
+ }
126
+ } });
127
+ };
128
+ gated = new Proxy(client, { get(target, prop) {
129
+ switch (prop) {
130
+ case "transaction": {
131
+ const open = Reflect.get(target, prop);
132
+ return (mode) => transaction(open.bind(target), mode);
133
+ }
134
+ case "execute":
135
+ case "batch":
136
+ case "executeMultiple":
137
+ case "migrate": {
138
+ const method = Reflect.get(target, prop);
139
+ return (...args) => call(() => method.apply(target, args));
140
+ }
141
+ default: {
142
+ const value = Reflect.get(target, prop);
143
+ return typeof value === "function" ? value.bind(target) : value;
144
+ }
145
+ }
146
+ } });
147
+ return gated;
148
+ }
149
+ /** Applies {@link gateLocalFileClient} when `url` is a local database file; returns `client` otherwise. */
150
+ function gateIfLocalFile(client, url, options) {
151
+ const key = localDatabaseFileKey(url);
152
+ return key ? gateLocalFileClient(client, key, options) : client;
153
+ }
154
+ //#endregion
155
+ //#region src/shared/reset-after-busy-client.ts
156
+ function isBusy(error) {
157
+ return error?.code === "SQLITE_BUSY";
158
+ }
159
+ function firstOpen(hold) {
160
+ while (hold && !hold.open) hold = hold.outer;
161
+ return hold;
162
+ }
163
+ /**
164
+ * Wraps a pooled local `file:` client so a statement refused with `SQLITE_BUSY`
165
+ * doesn't leave its connection unusable.
166
+ *
167
+ * libsql leaves a write statement whose run failed busy in progress until it is
168
+ * garbage collected (tursodatabase/libsql-js#237). Until then no transaction on
169
+ * that connection can commit ("cannot commit transaction - SQL statements in
170
+ * progress"), and an autocommit write on it stays uncommitted while holding the
171
+ * database's write lock. The pool hands the same connection out again, so
172
+ * retrying the write doesn't help.
173
+ *
174
+ * After a busy failure the wrapper reopens the client's connections, which
175
+ * finalizes the stuck statement. Reopening closes every connection, including
176
+ * borrowed ones, so it waits until no call or transaction made through the
177
+ * wrapper is in flight. New calls wait for the reset rather than join the calls
178
+ * it waits for, so a client that is never idle still gets reset. A call made
179
+ * while its caller holds an open transaction, or from inside another call,
180
+ * doesn't wait: the reset waits for what its caller holds, so waiting would
181
+ * deadlock. Reopened connections start from the pool's connection options,
182
+ * so `afterReset` runs right after to restore any per-connection settings
183
+ * applied after opening, and calls that waited for the reset go on once it
184
+ * settles, even if it fails. A call waits at most `maxWaitMs` (default 5s, the
185
+ * busy timeout) so a long-held transaction can't stall every later call; after
186
+ * that it goes ahead on the current pool. A transaction SQLite rolled back on its
187
+ * own is released as soon as the failing call returns. It must use the client it was given, not the
188
+ * wrapper: calls through the wrapper would wait for the reset it is part of.
189
+ * Every other member passes through untouched.
190
+ */
191
+ function resetConnectionsAfterBusy(client, { afterReset, maxWaitMs = 5e3 } = {}) {
192
+ let inFlight = 0;
193
+ let pendingReset;
194
+ const holds = new async_hooks.AsyncLocalStorage();
195
+ const finishReset = () => {
196
+ const reset = pendingReset;
197
+ pendingReset = void 0;
198
+ reset?.finish();
199
+ };
200
+ const settle = () => {
201
+ inFlight--;
202
+ if (client.closed) return finishReset();
203
+ if (!pendingReset || inFlight > 0) return;
204
+ client.reconnect();
205
+ if (!afterReset) return finishReset();
206
+ const reset = pendingReset;
207
+ Promise.resolve().then(afterReset).catch(() => {}).finally(() => {
208
+ if (pendingReset === reset) finishReset();
209
+ });
210
+ };
211
+ const noteBusy = (error) => {
212
+ if (!isBusy(error) || pendingReset) return;
213
+ let finish;
214
+ pendingReset = {
215
+ done: new Promise((resolve) => finish = resolve),
216
+ finish
217
+ };
218
+ };
219
+ const enter = (callerHolds) => {
220
+ if (!pendingReset || callerHolds || client.closed) {
221
+ inFlight++;
222
+ return;
223
+ }
224
+ return (async () => {
225
+ let timer;
226
+ const gaveUp = new Promise((resolve) => timer = setTimeout(resolve, maxWaitMs, "timeout"));
227
+ try {
228
+ while (pendingReset && !client.closed) if (await Promise.race([pendingReset.done, gaveUp]) === "timeout") break;
229
+ } finally {
230
+ clearTimeout(timer);
231
+ }
232
+ inFlight++;
233
+ })();
234
+ };
235
+ const run = async (call) => {
236
+ const hold = {
237
+ open: true,
238
+ outer: firstOpen(holds.getStore())
239
+ };
240
+ const waiting = enter(hold.outer !== void 0);
241
+ if (waiting) await waiting;
242
+ try {
243
+ return await holds.run(hold, call);
244
+ } catch (error) {
245
+ noteBusy(error);
246
+ throw error;
247
+ } finally {
248
+ hold.open = false;
249
+ settle();
250
+ }
251
+ };
252
+ const transaction = (open, mode) => {
253
+ const hold = {
254
+ open: true,
255
+ outer: firstOpen(holds.getStore())
256
+ };
257
+ const waiting = enter(hold.outer !== void 0);
258
+ holds.enterWith(hold);
259
+ return openTransaction(open, mode, hold, waiting);
260
+ };
261
+ const openTransaction = async (open, mode, hold, waiting) => {
262
+ let tx;
263
+ try {
264
+ if (waiting) await waiting;
265
+ tx = await open(mode);
266
+ } catch (error) {
267
+ hold.open = false;
268
+ noteBusy(error);
269
+ settle();
270
+ throw error;
271
+ }
272
+ const end = () => {
273
+ if (!hold.open) return;
274
+ hold.open = false;
275
+ settle();
276
+ };
277
+ return new Proxy(tx, { get(target, prop) {
278
+ switch (prop) {
279
+ case "execute":
280
+ case "batch":
281
+ case "executeMultiple": {
282
+ const method = Reflect.get(target, prop);
283
+ return async (...args) => {
284
+ try {
285
+ return await method.apply(target, args);
286
+ } catch (error) {
287
+ noteBusy(error);
288
+ if (target.closed) try {
289
+ target.close();
290
+ } finally {
291
+ end();
292
+ }
293
+ throw error;
294
+ }
295
+ };
296
+ }
297
+ case "commit":
298
+ case "rollback": {
299
+ const method = Reflect.get(target, prop);
300
+ return async () => {
301
+ try {
302
+ await method.call(target);
303
+ } catch (error) {
304
+ noteBusy(error);
305
+ throw error;
306
+ } finally {
307
+ end();
308
+ }
309
+ };
310
+ }
311
+ case "close": {
312
+ const method = Reflect.get(target, prop);
313
+ return () => {
314
+ try {
315
+ method.call(target);
316
+ } finally {
317
+ end();
318
+ }
319
+ };
320
+ }
321
+ default: {
322
+ const value = Reflect.get(target, prop);
323
+ return typeof value === "function" ? value.bind(target) : value;
324
+ }
325
+ }
326
+ } });
327
+ };
328
+ return new Proxy(client, { get(target, prop) {
329
+ switch (prop) {
330
+ case "transaction": {
331
+ const open = Reflect.get(target, prop);
332
+ return (mode) => transaction(open.bind(target), mode);
333
+ }
334
+ case "execute":
335
+ case "batch":
336
+ case "executeMultiple":
337
+ case "migrate": {
338
+ const method = Reflect.get(target, prop);
339
+ return (...args) => run(() => method.apply(target, args));
340
+ }
341
+ case "close": {
342
+ const method = Reflect.get(target, prop);
343
+ return () => {
344
+ try {
345
+ method.call(target);
346
+ } finally {
347
+ finishReset();
348
+ }
349
+ };
350
+ }
351
+ default: {
352
+ const value = Reflect.get(target, prop);
353
+ return typeof value === "function" ? value.bind(target) : value;
354
+ }
355
+ }
356
+ } });
357
+ }
358
+ //#endregion
16
359
  //#region src/shared/single-connection-client.ts
17
360
  /**
18
361
  * Whether `@libsql/client` backs this database with exactly one connection.
@@ -521,7 +864,7 @@ var LibSQLVector = class extends _mastra_core_vector.MastraVector {
521
864
  this.turso = isSingleConnectionDatabase({
522
865
  url,
523
866
  syncUrl
524
- }) ? gateSingleConnectionClient(client) : client;
867
+ }) ? gateSingleConnectionClient(client) : isLocalDb ? gateIfLocalFile(resetConnectionsAfterBusy(client, { maxWaitMs: 5e3 }), url, { maxWaitMs: 5e3 }) : client;
525
868
  this.maxRetries = maxRetries;
526
869
  this.initialBackoffMs = initialBackoffMs;
527
870
  if (!Number.isInteger(vectorTopKOverFetchMultiplier) || vectorTopKOverFetchMultiplier < 1) throw new Error("vectorTopKOverFetchMultiplier must be a positive integer");
@@ -1112,6 +1455,110 @@ var LibSQLVector = class extends _mastra_core_vector.MastraVector {
1112
1455
  }
1113
1456
  };
1114
1457
  //#endregion
1458
+ //#region src/storage/db/write-lock.ts
1459
+ /**
1460
+ * Per-client write serialization.
1461
+ *
1462
+ * `@libsql/client` >= 0.18.0 pools connections for local `file:` databases, but
1463
+ * SQLite still admits one writer at a time: an interactive
1464
+ * `client.transaction('write')` holds `BEGIN` open across every
1465
+ * `await tx.execute(...)`, and any other write on the same database in that
1466
+ * window contends on the file lock and can fail with `SQLITE_BUSY` once
1467
+ * `busy_timeout` expires.
1468
+ *
1469
+ * This is dormant under the default engine but the evented engine runs many
1470
+ * concurrent workflow snapshot writes per agent run, so a write issued by an
1471
+ * unrelated domain (e.g. creating a dataset experiment) can fail spuriously.
1472
+ *
1473
+ * Serializing every write on a given client closes that window: writes — both
1474
+ * autocommit statements and full interactive transactions — run one at a time,
1475
+ * so none can interleave with an open transaction. Reads are intentionally not
1476
+ * gated; WAL readers never observe a partial write and must not queue behind a
1477
+ * long-running writer.
1478
+ *
1479
+ * `:memory:` databases and embedded replicas get a single pooled connection
1480
+ * instead; see `shared/single-connection-client.ts`, which gates *all* calls
1481
+ * (reads included) behind open transactions for those clients.
1482
+ */
1483
+ const clientWriteChains = /* @__PURE__ */ new WeakMap();
1484
+ /**
1485
+ * Runs `fn` after every previously-enqueued write on `client` has settled, and
1486
+ * returns its result. The chain advances regardless of whether `fn` resolves or
1487
+ * rejects, so one failed write never wedges the queue.
1488
+ */
1489
+ function withClientWriteLock(client, fn) {
1490
+ const result = (clientWriteChains.get(client) ?? Promise.resolve()).then(fn, fn);
1491
+ clientWriteChains.set(client, result.then(() => void 0, () => void 0));
1492
+ return result;
1493
+ }
1494
+ //#endregion
1495
+ //#region src/storage/db/run-fencing.ts
1496
+ /**
1497
+ * Epoch milliseconds on the database clock. `julianday('now')` rather than
1498
+ * `unixepoch('subsec')`, which needs SQLite 3.42 on the server.
1499
+ */
1500
+ const DB_NOW_MS = `CAST(ROUND((julianday('now') - 2440587.5) * 86400000) AS INTEGER)`;
1501
+ /**
1502
+ * Runs `fn` in a write transaction, behind every other write on `client`.
1503
+ * `'write'` opens the transaction with `BEGIN IMMEDIATE`, which takes the
1504
+ * database's write lock up front, so nothing written between a read and a
1505
+ * write inside `fn` can come from another connection.
1506
+ */
1507
+ function inWriteTransaction(client, fn) {
1508
+ return withClientWriteLock(client, async () => {
1509
+ const tx = await client.transaction("write");
1510
+ try {
1511
+ const result = await fn(tx);
1512
+ await tx.commit();
1513
+ return result;
1514
+ } catch (error) {
1515
+ if (!tx.closed) await tx.rollback();
1516
+ throw error;
1517
+ } finally {
1518
+ if (!tx.closed) tx.close();
1519
+ }
1520
+ });
1521
+ }
1522
+ /**
1523
+ * The check half of a fenced write; call it first inside the write's
1524
+ * transaction. The transaction already holds the database's write lock, so
1525
+ * a claim can't commit between this check and the write.
1526
+ */
1527
+ async function assertRunFence(tx, { claimsTable, fence, operation }) {
1528
+ const row = (await tx.execute({
1529
+ sql: `SELECT generation, ownerId FROM ${claimsTable} WHERE runId = ?`,
1530
+ args: [fence.runId]
1531
+ })).rows[0];
1532
+ if (!(0, _mastra_core_storage.matchesRunFence)(row ? {
1533
+ generation: Number(row.generation),
1534
+ ownerId: String(row.ownerId)
1535
+ } : null, fence)) throw new _mastra_core_storage.RunFenceConflictError(fence, operation);
1536
+ }
1537
+ /**
1538
+ * Runs `write` behind the client's write lock: directly when there is no
1539
+ * fence to check, or in a write transaction behind the check when there is.
1540
+ * `write` must issue every statement through the writer it is given; on a
1541
+ * single-connection client, a call on the client itself would wait for the
1542
+ * open transaction forever.
1543
+ */
1544
+ function withRunFence(client, check, write) {
1545
+ if (!check) return withClientWriteLock(client, () => write({
1546
+ execute: (statement) => client.execute(statement),
1547
+ batch: (statements) => client.batch(statements, "write")
1548
+ }));
1549
+ return inWriteTransaction(client, async (tx) => {
1550
+ await assertRunFence(tx, check);
1551
+ return write({
1552
+ execute: (statement) => tx.execute(statement),
1553
+ batch: async (statements) => {
1554
+ const results = [];
1555
+ for (const statement of statements) results.push(await tx.execute(statement));
1556
+ return results;
1557
+ }
1558
+ });
1559
+ });
1560
+ }
1561
+ //#endregion
1115
1562
  //#region src/storage/db/utils.ts
1116
1563
  const MAX_DEPTH = 100;
1117
1564
  /**
@@ -1204,6 +1651,7 @@ function createExecuteWriteOperationWithRetry({ logger, maxRetries, initialBacko
1204
1651
  while (attempts < maxRetries) try {
1205
1652
  return await operationFn();
1206
1653
  } catch (error) {
1654
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
1207
1655
  logger.debug(`LibSQLStore: Error caught in retry loop for ${operationDescription}`, {
1208
1656
  errorType: error.constructor.name,
1209
1657
  errorCode: error.code,
@@ -1364,43 +1812,6 @@ function transformFromSqlRow({ tableName, sqlRow }) {
1364
1812
  return result;
1365
1813
  }
1366
1814
  //#endregion
1367
- //#region src/storage/db/write-lock.ts
1368
- /**
1369
- * Per-client write serialization.
1370
- *
1371
- * `@libsql/client` >= 0.18.0 pools connections for local `file:` databases, but
1372
- * SQLite still admits one writer at a time: an interactive
1373
- * `client.transaction('write')` holds `BEGIN` open across every
1374
- * `await tx.execute(...)`, and any other write on the same database in that
1375
- * window contends on the file lock and can fail with `SQLITE_BUSY` once
1376
- * `busy_timeout` expires.
1377
- *
1378
- * This is dormant under the default engine but the evented engine runs many
1379
- * concurrent workflow snapshot writes per agent run, so a write issued by an
1380
- * unrelated domain (e.g. creating a dataset experiment) can fail spuriously.
1381
- *
1382
- * Serializing every write on a given client closes that window: writes — both
1383
- * autocommit statements and full interactive transactions — run one at a time,
1384
- * so none can interleave with an open transaction. Reads are intentionally not
1385
- * gated; WAL readers never observe a partial write and must not queue behind a
1386
- * long-running writer.
1387
- *
1388
- * `:memory:` databases and embedded replicas get a single pooled connection
1389
- * instead; see `shared/single-connection-client.ts`, which gates *all* calls
1390
- * (reads included) behind open transactions for those clients.
1391
- */
1392
- const clientWriteChains = /* @__PURE__ */ new WeakMap();
1393
- /**
1394
- * Runs `fn` after every previously-enqueued write on `client` has settled, and
1395
- * returns its result. The chain advances regardless of whether `fn` resolves or
1396
- * rejects, so one failed write never wedges the queue.
1397
- */
1398
- function withClientWriteLock(client, fn) {
1399
- const result = (clientWriteChains.get(client) ?? Promise.resolve()).then(fn, fn);
1400
- clientWriteChains.set(client, result.then(() => void 0, () => void 0));
1401
- return result;
1402
- }
1403
- //#endregion
1404
1815
  //#region src/storage/db/index.ts
1405
1816
  /**
1406
1817
  * Default SQLite `busy_timeout` (ms) for local LibSQL connections. Chosen to
@@ -1501,10 +1912,10 @@ var LibSQLDB = class extends _mastra_core_base.MastraBase {
1501
1912
  /**
1502
1913
  * Internal insert implementation without retry logic.
1503
1914
  */
1504
- async doInsert({ tableName, record }) {
1915
+ async doInsert({ tableName, record, check }) {
1505
1916
  const filteredRecord = await this.filterRecordToKnownColumns(tableName, record);
1506
1917
  if (Object.keys(filteredRecord).length === 0) return;
1507
- await withClientWriteLock(this.client, () => this.client.execute(prepareStatement({
1918
+ await withRunFence(this.client, check, (writer) => writer.execute(prepareStatement({
1508
1919
  tableName,
1509
1920
  record: filteredRecord
1510
1921
  })));
@@ -1515,6 +1926,7 @@ var LibSQLDB = class extends _mastra_core_base.MastraBase {
1515
1926
  * @param args - The insert arguments
1516
1927
  * @param args.tableName - The name of the table to insert into
1517
1928
  * @param args.record - The record to insert (key-value pairs)
1929
+ * @param args.check - The run fence the insert must carry, if any
1518
1930
  */
1519
1931
  insert(args) {
1520
1932
  return this.executeWriteOperationWithRetry(() => this.doInsert(args), `insert into table ${args.tableName}`);
@@ -8919,6 +9331,10 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
8919
9331
  * Retention-eligible tables. `threads`, `messages`, and `resources` all anchor
8920
9332
  * on `createdAt` and are indexed for fast batched deletes. Cascade order is
8921
9333
  * enforced in `prune()` (children before threads), not here.
9334
+ *
9335
+ * Run fences anchor on `retiredAt` (epoch ms), which stays NULL until the
9336
+ * execution holding the fence settles, so a running execution's fence is
9337
+ * never pruned.
8922
9338
  */
8923
9339
  static retentionTables = {
8924
9340
  messages: {
@@ -8935,6 +9351,12 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
8935
9351
  table: _mastra_core_storage.TABLE_THREADS,
8936
9352
  column: "createdAt",
8937
9353
  indexed: true
9354
+ },
9355
+ runFences: {
9356
+ table: _mastra_core_storage.TABLE_MEMORY_RUN_FENCES,
9357
+ column: "retiredAt",
9358
+ indexed: true,
9359
+ anchorType: "epoch-ms"
8938
9360
  }
8939
9361
  };
8940
9362
  #client;
@@ -8957,6 +9379,13 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
8957
9379
  #write(operation, fn) {
8958
9380
  return this.#db.executeWriteOperationWithRetry(() => withClientWriteLock(this.#client, fn), operation);
8959
9381
  }
9382
+ /**
9383
+ * `#write` for a write that may carry a run fence: with a check, it runs in a write
9384
+ * transaction behind the check, and a retry reruns the whole transaction.
9385
+ */
9386
+ #fencedWrite(operation, check, write) {
9387
+ return this.#db.executeWriteOperationWithRetry(() => withRunFence(this.#client, check, write), operation);
9388
+ }
8960
9389
  async init() {
8961
9390
  await this.#db.createTable({
8962
9391
  tableName: _mastra_core_storage.TABLE_THREADS,
@@ -8970,6 +9399,10 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
8970
9399
  tableName: _mastra_core_storage.TABLE_RESOURCES,
8971
9400
  schema: _mastra_core_storage.TABLE_SCHEMAS[_mastra_core_storage.TABLE_RESOURCES]
8972
9401
  });
9402
+ await this.#db.createTable({
9403
+ tableName: _mastra_core_storage.TABLE_MEMORY_RUN_FENCES,
9404
+ schema: _mastra_core_storage.RUN_FENCING_TABLE_SCHEMAS[_mastra_core_storage.TABLE_MEMORY_RUN_FENCES]
9405
+ });
8973
9406
  const omSchema = _mastra_core_storage.OBSERVATIONAL_MEMORY_TABLE_SCHEMA?.[OM_TABLE];
8974
9407
  if (omSchema) {
8975
9408
  await this.#db.createTable({
@@ -9021,6 +9454,70 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
9021
9454
  await this.#db.deleteData({ tableName: _mastra_core_storage.TABLE_THREADS });
9022
9455
  await this.#db.deleteData({ tableName: _mastra_core_storage.TABLE_RESOURCES });
9023
9456
  await this.#db.deleteData({ tableName: OM_TABLE });
9457
+ await this.#db.deleteData({ tableName: _mastra_core_storage.TABLE_MEMORY_RUN_FENCES });
9458
+ }
9459
+ supportsRunFencing() {
9460
+ return true;
9461
+ }
9462
+ async raiseRunFence(fence) {
9463
+ try {
9464
+ return await this.#db.executeWriteOperationWithRetry(() => inWriteTransaction(this.#client, async (tx) => {
9465
+ const row = (await tx.execute({
9466
+ sql: `SELECT generation, ownerId FROM ${_mastra_core_storage.TABLE_MEMORY_RUN_FENCES} WHERE runId = ?`,
9467
+ args: [fence.runId]
9468
+ })).rows[0];
9469
+ const generation = row ? Number(row.generation) : void 0;
9470
+ const raises = generation === void 0 || generation < fence.generation;
9471
+ const reaffirms = generation === fence.generation && String(row.ownerId) === fence.ownerId;
9472
+ if (!raises && !reaffirms) return false;
9473
+ await tx.execute({
9474
+ sql: `INSERT INTO ${_mastra_core_storage.TABLE_MEMORY_RUN_FENCES} (runId, generation, ownerId) VALUES (?, ?, ?)
9475
+ ON CONFLICT(runId) DO UPDATE SET generation = excluded.generation, ownerId = excluded.ownerId, retiredAt = NULL`,
9476
+ args: [
9477
+ fence.runId,
9478
+ fence.generation,
9479
+ fence.ownerId
9480
+ ]
9481
+ });
9482
+ return true;
9483
+ }), "raiseRunFence");
9484
+ } catch (error) {
9485
+ throw new _mastra_core_error.MastraError({
9486
+ id: (0, _mastra_core_storage.createStorageErrorId)("LIBSQL", "RAISE_RUN_FENCE", "FAILED"),
9487
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
9488
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
9489
+ details: { runId: fence.runId }
9490
+ }, error);
9491
+ }
9492
+ }
9493
+ async retireRunFence(fence) {
9494
+ try {
9495
+ return (await this.#db.executeWriteOperationWithRetry(() => withClientWriteLock(this.#client, () => this.#client.execute({
9496
+ sql: `UPDATE ${_mastra_core_storage.TABLE_MEMORY_RUN_FENCES} SET retiredAt = ${DB_NOW_MS}
9497
+ WHERE runId = ? AND generation = ? AND ownerId = ?`,
9498
+ args: [
9499
+ fence.runId,
9500
+ fence.generation,
9501
+ fence.ownerId
9502
+ ]
9503
+ })), "retireRunFence")).rowsAffected > 0;
9504
+ } catch (error) {
9505
+ throw new _mastra_core_error.MastraError({
9506
+ id: (0, _mastra_core_storage.createStorageErrorId)("LIBSQL", "RETIRE_RUN_FENCE", "FAILED"),
9507
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
9508
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
9509
+ details: { runId: fence.runId }
9510
+ }, error);
9511
+ }
9512
+ }
9513
+ /** The check a write must pass: its own fence, otherwise the one in scope. */
9514
+ #runFenceCheck(fence, operation) {
9515
+ const resolved = (0, _mastra_core_storage.resolveRunFence)(this, fence);
9516
+ return resolved && {
9517
+ claimsTable: _mastra_core_storage.TABLE_MEMORY_RUN_FENCES,
9518
+ fence: resolved,
9519
+ operation
9520
+ };
9024
9521
  }
9025
9522
  /**
9026
9523
  * Delete rows older than each policy's `maxAge`, batched and cancellable.
@@ -9036,7 +9533,8 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
9036
9533
  order: [
9037
9534
  "messages",
9038
9535
  "resources",
9039
- "threads"
9536
+ "threads",
9537
+ "runFences"
9040
9538
  ]
9041
9539
  });
9042
9540
  return runPrune({
@@ -9418,7 +9916,7 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
9418
9916
  throw mastraError;
9419
9917
  }
9420
9918
  }
9421
- async saveMessages({ messages }) {
9919
+ async saveMessages({ messages, fence }) {
9422
9920
  if (messages.length === 0) return { messages };
9423
9921
  try {
9424
9922
  const threadId = messages[0]?.threadId;
@@ -9456,13 +9954,16 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
9456
9954
  const BATCH_SIZE = 50;
9457
9955
  const messageStatements = batchStatements.slice(0, -1);
9458
9956
  const threadUpdateStatement = batchStatements[batchStatements.length - 1];
9459
- for (let i = 0; i < messageStatements.length; i += BATCH_SIZE) {
9460
- const batch = messageStatements.slice(i, i + BATCH_SIZE);
9461
- if (batch.length > 0) await this.#write("saveMessages", () => this.#client.batch(batch, "write"));
9462
- }
9463
- if (threadUpdateStatement) await this.#write("saveMessages", () => this.#client.execute(threadUpdateStatement));
9957
+ await this.#fencedWrite("saveMessages", this.#runFenceCheck(fence, "saveMessages"), async (writer) => {
9958
+ for (let i = 0; i < messageStatements.length; i += BATCH_SIZE) {
9959
+ const batch = messageStatements.slice(i, i + BATCH_SIZE);
9960
+ if (batch.length > 0) await writer.batch(batch);
9961
+ }
9962
+ if (threadUpdateStatement) await writer.execute(threadUpdateStatement);
9963
+ });
9464
9964
  return { messages: new _mastra_core_agent.MessageList().add(messages, "memory").get.all.db() };
9465
9965
  } catch (error) {
9966
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
9466
9967
  throw new _mastra_core_error.MastraError({
9467
9968
  id: (0, _mastra_core_storage.createStorageErrorId)("LIBSQL", "SAVE_MESSAGES", "FAILED"),
9468
9969
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -9470,11 +9971,14 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
9470
9971
  }, error);
9471
9972
  }
9472
9973
  }
9473
- async updateMessages({ messages }) {
9974
+ async updateMessages({ messages, fence }) {
9474
9975
  if (messages.length === 0) return [];
9976
+ return this.#fencedWrite("updateMessages", this.#runFenceCheck(fence, "updateMessages"), (writer) => this.#updateMessages(writer, messages));
9977
+ }
9978
+ async #updateMessages(writer, messages) {
9475
9979
  const messageIds = messages.map((m) => m.id);
9476
9980
  const selectSql = `SELECT * FROM ${_mastra_core_storage.TABLE_MESSAGES} WHERE id IN (${messageIds.map(() => "?").join(",")})`;
9477
- const existingMessages = (await this.#client.execute({
9981
+ const existingMessages = (await writer.execute({
9478
9982
  sql: selectSql,
9479
9983
  args: messageIds
9480
9984
  })).rows.map((row) => this.parseRow(row));
@@ -9526,48 +10030,44 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
9526
10030
  sql: `UPDATE ${_mastra_core_storage.TABLE_THREADS} SET updatedAt = ? WHERE id = ?`,
9527
10031
  args: [now, threadId]
9528
10032
  });
9529
- await this.#write("updateMessages", () => this.#client.batch(batchStatements, "write"));
9530
- return (await this.#client.execute({
10033
+ await writer.batch(batchStatements);
10034
+ return (await writer.execute({
9531
10035
  sql: selectSql,
9532
10036
  args: messageIds
9533
10037
  })).rows.map((row) => this.parseRow(row));
9534
10038
  }
9535
- async deleteMessages(messageIds) {
10039
+ async deleteMessages(messageIds, options) {
9536
10040
  if (!messageIds || messageIds.length === 0) return;
9537
10041
  try {
9538
10042
  const BATCH_SIZE = 100;
9539
- await this.#write("deleteMessages", async () => {
10043
+ const check = this.#runFenceCheck(options?.fence, "deleteMessages");
10044
+ await this.#db.executeWriteOperationWithRetry(() => inWriteTransaction(this.#client, async (tx) => {
10045
+ if (check) await assertRunFence(tx, check);
9540
10046
  const threadIds = /* @__PURE__ */ new Set();
9541
- const tx = await this.#client.transaction("write");
9542
- try {
9543
- for (let i = 0; i < messageIds.length; i += BATCH_SIZE) {
9544
- const batch = messageIds.slice(i, i + BATCH_SIZE);
9545
- const placeholders = batch.map(() => "?").join(",");
9546
- (await tx.execute({
9547
- sql: `SELECT DISTINCT thread_id FROM "${_mastra_core_storage.TABLE_MESSAGES}" WHERE id IN (${placeholders})`,
9548
- args: batch
9549
- })).rows?.forEach((row) => {
9550
- if (row.thread_id) threadIds.add(row.thread_id);
9551
- });
9552
- await tx.execute({
9553
- sql: `DELETE FROM "${_mastra_core_storage.TABLE_MESSAGES}" WHERE id IN (${placeholders})`,
9554
- args: batch
9555
- });
9556
- }
9557
- if (threadIds.size > 0) {
9558
- const now = (/* @__PURE__ */ new Date()).toISOString();
9559
- for (const threadId of threadIds) await tx.execute({
9560
- sql: `UPDATE "${_mastra_core_storage.TABLE_THREADS}" SET "updatedAt" = ? WHERE id = ?`,
9561
- args: [now, threadId]
9562
- });
9563
- }
9564
- await tx.commit();
9565
- } catch (error) {
9566
- await tx.rollback();
9567
- throw error;
10047
+ for (let i = 0; i < messageIds.length; i += BATCH_SIZE) {
10048
+ const batch = messageIds.slice(i, i + BATCH_SIZE);
10049
+ const placeholders = batch.map(() => "?").join(",");
10050
+ (await tx.execute({
10051
+ sql: `SELECT DISTINCT thread_id FROM "${_mastra_core_storage.TABLE_MESSAGES}" WHERE id IN (${placeholders})`,
10052
+ args: batch
10053
+ })).rows?.forEach((row) => {
10054
+ if (row.thread_id) threadIds.add(row.thread_id);
10055
+ });
10056
+ await tx.execute({
10057
+ sql: `DELETE FROM "${_mastra_core_storage.TABLE_MESSAGES}" WHERE id IN (${placeholders})`,
10058
+ args: batch
10059
+ });
9568
10060
  }
9569
- });
10061
+ if (threadIds.size > 0) {
10062
+ const now = (/* @__PURE__ */ new Date()).toISOString();
10063
+ for (const threadId of threadIds) await tx.execute({
10064
+ sql: `UPDATE "${_mastra_core_storage.TABLE_THREADS}" SET "updatedAt" = ? WHERE id = ?`,
10065
+ args: [now, threadId]
10066
+ });
10067
+ }
10068
+ }), "deleteMessages");
9570
10069
  } catch (error) {
10070
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
9571
10071
  throw new _mastra_core_error.MastraError({
9572
10072
  id: (0, _mastra_core_storage.createStorageErrorId)("LIBSQL", "DELETE_MESSAGES", "FAILED"),
9573
10073
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -9591,13 +10091,18 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
9591
10091
  };
9592
10092
  }
9593
10093
  async saveResource({ resource }) {
10094
+ return this.#insertResource(resource, void 0);
10095
+ }
10096
+ async #insertResource(resource, check) {
9594
10097
  await this.#db.insert({
9595
10098
  tableName: _mastra_core_storage.TABLE_RESOURCES,
9596
- record: { ...resource }
10099
+ record: { ...resource },
10100
+ check
9597
10101
  });
9598
10102
  return resource;
9599
10103
  }
9600
- async updateResource({ resourceId, workingMemory, metadata }) {
10104
+ async updateResource({ resourceId, workingMemory, metadata, fence }) {
10105
+ const check = this.#runFenceCheck(fence, "updateResource");
9601
10106
  const existingResource = await this.getResourceById({ resourceId });
9602
10107
  if (!existingResource) {
9603
10108
  const newResource = {
@@ -9607,7 +10112,7 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
9607
10112
  createdAt: /* @__PURE__ */ new Date(),
9608
10113
  updatedAt: /* @__PURE__ */ new Date()
9609
10114
  };
9610
- return this.saveResource({ resource: newResource });
10115
+ return this.#insertResource(newResource, check);
9611
10116
  }
9612
10117
  const updatedResource = {
9613
10118
  ...existingResource,
@@ -9631,7 +10136,7 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
9631
10136
  updates.push("updatedAt = ?");
9632
10137
  values.push(updatedResource.updatedAt.toISOString());
9633
10138
  values.push(resourceId);
9634
- await this.#write("updateResource", () => this.#client.execute({
10139
+ await this.#fencedWrite("updateResource", check, (writer) => writer.execute({
9635
10140
  sql: `UPDATE ${_mastra_core_storage.TABLE_RESOURCES} SET ${updates.join(", ")} WHERE id = ?`,
9636
10141
  args: values
9637
10142
  }));
@@ -9835,14 +10340,16 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
9835
10340
  throw mastraError;
9836
10341
  }
9837
10342
  }
9838
- async saveThread({ thread }) {
10343
+ async saveThread({ thread, fence }) {
9839
10344
  try {
9840
10345
  await this.#db.insert({
9841
10346
  tableName: _mastra_core_storage.TABLE_THREADS,
9842
- record: { ...thread }
10347
+ record: { ...thread },
10348
+ check: this.#runFenceCheck(fence, "saveThread")
9843
10349
  });
9844
10350
  return thread;
9845
10351
  } catch (error) {
10352
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
9846
10353
  const mastraError = new _mastra_core_error.MastraError({
9847
10354
  id: (0, _mastra_core_storage.createStorageErrorId)("LIBSQL", "SAVE_THREAD", "FAILED"),
9848
10355
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -9854,7 +10361,8 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
9854
10361
  throw mastraError;
9855
10362
  }
9856
10363
  }
9857
- async updateThread({ id, title, metadata }) {
10364
+ async updateThread({ id, title, metadata, fence }) {
10365
+ const check = this.#runFenceCheck(fence, "updateThread");
9858
10366
  const thread = await this.getThreadById({ threadId: id });
9859
10367
  if (!thread) throw new _mastra_core_error.MastraError({
9860
10368
  id: (0, _mastra_core_storage.createStorageErrorId)("LIBSQL", "UPDATE_THREAD", "NOT_FOUND"),
@@ -9877,7 +10385,7 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
9877
10385
  updatedAt: now
9878
10386
  };
9879
10387
  try {
9880
- await this.#write("updateThread", () => this.#client.execute({
10388
+ await this.#fencedWrite("updateThread", check, (writer) => writer.execute({
9881
10389
  sql: `UPDATE ${_mastra_core_storage.TABLE_THREADS} SET title = COALESCE(?, title), metadata = jsonb(?), updatedAt = ? WHERE id = ?`,
9882
10390
  args: [
9883
10391
  title ?? null,
@@ -9888,6 +10396,7 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
9888
10396
  }));
9889
10397
  return updatedThread;
9890
10398
  } catch (error) {
10399
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
9891
10400
  throw new _mastra_core_error.MastraError({
9892
10401
  id: (0, _mastra_core_storage.createStorageErrorId)("LIBSQL", "UPDATE_THREAD", "FAILED"),
9893
10402
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -10298,7 +10807,7 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
10298
10807
  try {
10299
10808
  const now = /* @__PURE__ */ new Date();
10300
10809
  const observedMessageIdsJson = input.observedMessageIds ? JSON.stringify(input.observedMessageIds) : null;
10301
- if ((await this.#write("updateActiveObservations", () => this.#client.execute({
10810
+ if ((await this.#fencedWrite("updateActiveObservations", this.#runFenceCheck(void 0, "updateActiveObservations"), (writer) => writer.execute({
10302
10811
  sql: `UPDATE "${OM_TABLE}" SET
10303
10812
  "activeObservations" = ?,
10304
10813
  "lastObservedAt" = ?,
@@ -10336,9 +10845,11 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
10336
10845
  }
10337
10846
  async createReflectionGeneration(input) {
10338
10847
  try {
10848
+ const check = this.#runFenceCheck(void 0, "createReflectionGeneration");
10339
10849
  return await this.#write("createReflectionGeneration", async () => {
10340
10850
  const tx = await this.#client.transaction("write");
10341
10851
  try {
10852
+ if (check) await assertRunFence(tx, check);
10342
10853
  const record = await this.#insertReflectionGeneration(tx, input);
10343
10854
  await tx.commit();
10344
10855
  return record;
@@ -10675,9 +11186,11 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
10675
11186
  async updateBufferedObservations(input) {
10676
11187
  try {
10677
11188
  const nowStr = (/* @__PURE__ */ new Date()).toISOString();
11189
+ const check = this.#runFenceCheck(void 0, "updateBufferedObservations");
10678
11190
  await this.#write("updateBufferedObservations", async () => {
10679
11191
  const tx = await this.#client.transaction("write");
10680
11192
  try {
11193
+ if (check) await assertRunFence(tx, check);
10681
11194
  const current = await tx.execute({
10682
11195
  sql: `SELECT "bufferedObservationChunks" FROM "${OM_TABLE}" WHERE id = ?`,
10683
11196
  args: [input.id]
@@ -10756,9 +11269,11 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
10756
11269
  async swapBufferedToActive(input) {
10757
11270
  try {
10758
11271
  const nowStr = (/* @__PURE__ */ new Date()).toISOString();
11272
+ const check = this.#runFenceCheck(void 0, "swapBufferedToActive");
10759
11273
  return await this.#write("swapBufferedToActive", async () => {
10760
11274
  const tx = await this.#client.transaction("write");
10761
11275
  try {
11276
+ if (check) await assertRunFence(tx, check);
10762
11277
  const current = await tx.execute({
10763
11278
  sql: `SELECT * FROM "${OM_TABLE}" WHERE id = ?`,
10764
11279
  args: [input.id]
@@ -10905,7 +11420,7 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
10905
11420
  async updateBufferedReflection(input) {
10906
11421
  try {
10907
11422
  const nowStr = (/* @__PURE__ */ new Date()).toISOString();
10908
- if ((await this.#write("updateBufferedReflection", () => this.#client.execute({
11423
+ if ((await this.#fencedWrite("updateBufferedReflection", this.#runFenceCheck(void 0, "updateBufferedReflection"), (writer) => writer.execute({
10909
11424
  sql: `UPDATE "${OM_TABLE}" SET
10910
11425
  "bufferedReflection" = CASE
10911
11426
  WHEN "bufferedReflection" IS NOT NULL AND "bufferedReflection" != ''
@@ -10945,9 +11460,11 @@ var MemoryLibSQL = class MemoryLibSQL extends _mastra_core_storage.MemoryStorage
10945
11460
  }
10946
11461
  async swapBufferedReflectionToActive(input) {
10947
11462
  try {
11463
+ const check = this.#runFenceCheck(void 0, "swapBufferedReflectionToActive");
10948
11464
  return await this.#write("swapBufferedReflectionToActive", async () => {
10949
11465
  const tx = await this.#client.transaction("write");
10950
11466
  try {
11467
+ if (check) await assertRunFence(tx, check);
10951
11468
  const current = await tx.execute({
10952
11469
  sql: `SELECT * FROM "${OM_TABLE}" WHERE id = ?`,
10953
11470
  args: [input.currentRecord.id]
@@ -14447,16 +14964,39 @@ var WorkflowDefinitionsLibSQL = class extends _mastra_core_storage.WorkflowDefin
14447
14964
  };
14448
14965
  //#endregion
14449
14966
  //#region src/storage/domains/workflows/index.ts
14967
+ const RUN_OWNER_COLUMNS = `generation, ownerId, leaseExpiresAt, ${DB_NOW_MS} AS nowMs`;
14968
+ function toRunOwnershipRecord(runId, row) {
14969
+ const leaseExpiresAt = row.leaseExpiresAt === null ? null : Number(row.leaseExpiresAt);
14970
+ return {
14971
+ runId,
14972
+ generation: Number(row.generation),
14973
+ ownerId: String(row.ownerId),
14974
+ leaseExpiresAt: leaseExpiresAt === null ? null : new Date(leaseExpiresAt),
14975
+ live: leaseExpiresAt !== null && leaseExpiresAt > Number(row.nowMs)
14976
+ };
14977
+ }
14450
14978
  var WorkflowsLibSQL = class WorkflowsLibSQL extends _mastra_core_storage.WorkflowsStorage {
14451
14979
  /**
14452
14980
  * Workflow run snapshots accumulate as runs execute. Anchored on `updatedAt`
14453
14981
  * (last activity) so suspended/long-running runs are not pruned by start age.
14982
+ *
14983
+ * Run ownership rows are anchored on `updatedAt` (epoch ms), which every
14984
+ * claim, renewal and release refreshes, so a live execution's row is never
14985
+ * older than its renew interval.
14454
14986
  */
14455
- static retentionTables = { workflowSnapshot: {
14456
- table: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
14457
- column: "updatedAt",
14458
- indexed: true
14459
- } };
14987
+ static retentionTables = {
14988
+ workflowSnapshot: {
14989
+ table: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
14990
+ column: "updatedAt",
14991
+ indexed: true
14992
+ },
14993
+ runOwnership: {
14994
+ table: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS,
14995
+ column: "updatedAt",
14996
+ indexed: true,
14997
+ anchorType: "epoch-ms"
14998
+ }
14999
+ };
14460
15000
  #db;
14461
15001
  #client;
14462
15002
  executeWithRetry;
@@ -14508,16 +15048,132 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends _mastra_core_storage.Workflo
14508
15048
  schema,
14509
15049
  ifNotExists: ["resourceId"]
14510
15050
  });
15051
+ await this.#db.createTable({
15052
+ tableName: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS,
15053
+ schema: _mastra_core_storage.RUN_FENCING_TABLE_SCHEMAS[_mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS]
15054
+ });
14511
15055
  }
14512
15056
  async dangerouslyClearAll() {
14513
15057
  await this.#db.deleteData({ tableName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT });
15058
+ await this.#db.deleteData({ tableName: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS });
15059
+ }
15060
+ supportsRunFencing() {
15061
+ return true;
15062
+ }
15063
+ #runFenceCheck(fence, runId, operation) {
15064
+ const resolved = (0, _mastra_core_storage.resolveRunFence)(this, fence, runId);
15065
+ return resolved && {
15066
+ claimsTable: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS,
15067
+ fence: resolved,
15068
+ operation
15069
+ };
14514
15070
  }
14515
- /** Delete workflow snapshots older than the `workflowSnapshot` policy's `maxAge`, batched. */
15071
+ async #readRunOwner(q, runId) {
15072
+ const row = (await q.execute({
15073
+ sql: `SELECT ${RUN_OWNER_COLUMNS} FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS} WHERE runId = ?`,
15074
+ args: [runId]
15075
+ })).rows[0];
15076
+ return row ? toRunOwnershipRecord(runId, row) : null;
15077
+ }
15078
+ #ownershipError(operation, runId, error) {
15079
+ return new _mastra_core_error.MastraError({
15080
+ id: (0, _mastra_core_storage.createStorageErrorId)("LIBSQL", operation, "FAILED"),
15081
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
15082
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
15083
+ details: { runId }
15084
+ }, error);
15085
+ }
15086
+ async claimRunOwnership({ runId, ownerId, leaseMs, force, expectedGeneration }) {
15087
+ try {
15088
+ return await this.executeWithRetry(() => inWriteTransaction(this.#client, async (tx) => {
15089
+ const current = await this.#readRunOwner(tx, runId);
15090
+ const generation = current?.generation ?? 0;
15091
+ if (expectedGeneration !== void 0 && expectedGeneration !== generation) return {
15092
+ acquired: false,
15093
+ record: current
15094
+ };
15095
+ if (current?.live && !force) return {
15096
+ acquired: false,
15097
+ record: current
15098
+ };
15099
+ await tx.execute({
15100
+ sql: `INSERT INTO ${_mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS} (runId, generation, ownerId, leaseExpiresAt, updatedAt)
15101
+ VALUES (?, COALESCE(?, MAX(1, ${DB_NOW_MS})), ?, ${DB_NOW_MS} + ?, ${DB_NOW_MS})
15102
+ ON CONFLICT(runId) DO UPDATE SET
15103
+ generation = excluded.generation,
15104
+ ownerId = excluded.ownerId,
15105
+ leaseExpiresAt = excluded.leaseExpiresAt,
15106
+ updatedAt = excluded.updatedAt`,
15107
+ args: [
15108
+ runId,
15109
+ current ? generation + 1 : null,
15110
+ ownerId,
15111
+ leaseMs
15112
+ ]
15113
+ });
15114
+ return {
15115
+ acquired: true,
15116
+ record: await this.#readRunOwner(tx, runId)
15117
+ };
15118
+ }), "claimRunOwnership");
15119
+ } catch (error) {
15120
+ throw this.#ownershipError("CLAIM_RUN_OWNERSHIP", runId, error);
15121
+ }
15122
+ }
15123
+ async renewRunOwnership({ leaseMs, ...fence }) {
15124
+ try {
15125
+ return await this.executeWithRetry(() => inWriteTransaction(this.#client, async (tx) => {
15126
+ const renewed = await tx.execute({
15127
+ sql: `UPDATE ${_mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS} SET leaseExpiresAt = ${DB_NOW_MS} + ?, updatedAt = ${DB_NOW_MS}
15128
+ WHERE runId = ? AND generation = ? AND ownerId = ? AND leaseExpiresAt IS NOT NULL`,
15129
+ args: [
15130
+ leaseMs,
15131
+ fence.runId,
15132
+ fence.generation,
15133
+ fence.ownerId
15134
+ ]
15135
+ });
15136
+ const record = await this.#readRunOwner(tx, fence.runId);
15137
+ return renewed.rowsAffected > 0 ? {
15138
+ renewed: true,
15139
+ record
15140
+ } : {
15141
+ renewed: false,
15142
+ record
15143
+ };
15144
+ }), "renewRunOwnership");
15145
+ } catch (error) {
15146
+ throw this.#ownershipError("RENEW_RUN_OWNERSHIP", fence.runId, error);
15147
+ }
15148
+ }
15149
+ async releaseRunOwnership(fence) {
15150
+ try {
15151
+ return (await this.executeWithRetry(() => withClientWriteLock(this.#client, () => this.#client.execute({
15152
+ sql: `UPDATE ${_mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS} SET leaseExpiresAt = NULL, updatedAt = ${DB_NOW_MS}
15153
+ WHERE runId = ? AND generation = ? AND ownerId = ?`,
15154
+ args: [
15155
+ fence.runId,
15156
+ fence.generation,
15157
+ fence.ownerId
15158
+ ]
15159
+ })), "releaseRunOwnership")).rowsAffected > 0;
15160
+ } catch (error) {
15161
+ throw this.#ownershipError("RELEASE_RUN_OWNERSHIP", fence.runId, error);
15162
+ }
15163
+ }
15164
+ async getRunOwnership({ runId }) {
15165
+ try {
15166
+ return await this.#readRunOwner(this.#client, runId);
15167
+ } catch (error) {
15168
+ throw this.#ownershipError("GET_RUN_OWNERSHIP", runId, error);
15169
+ }
15170
+ }
15171
+ /** Delete workflow run snapshots and run ownership rows older than their policy's `maxAge`, batched. */
14516
15172
  async prune(policies, options) {
14517
15173
  const targets = resolveTargets({
14518
15174
  policies,
14519
15175
  descriptor: WorkflowsLibSQL.retentionTables,
14520
- order: ["workflowSnapshot"]
15176
+ order: ["workflowSnapshot", "runOwnership"]
14521
15177
  });
14522
15178
  return runPrune({
14523
15179
  db: this.#db,
@@ -14547,10 +15203,12 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends _mastra_core_storage.Workflo
14547
15203
  this.logger.warn("LibSQL Workflows: Failed to set PRAGMA settings.", err);
14548
15204
  }
14549
15205
  }
14550
- async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state }) {
15206
+ async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state, fence: explicitFence }) {
15207
+ const check = this.#runFenceCheck(explicitFence, runId, "updateWorkflowResults");
14551
15208
  return this.executeWithRetry(() => withClientWriteLock(this.#client, async () => {
14552
15209
  const tx = await this.#client.transaction("write");
14553
15210
  try {
15211
+ if (check) await assertRunFence(tx, check);
14554
15212
  const existingSnapshotResult = await tx.execute({
14555
15213
  sql: `SELECT json(snapshot) as snapshot FROM ${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
14556
15214
  args: [workflowName, runId]
@@ -14603,10 +15261,12 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends _mastra_core_storage.Workflo
14603
15261
  }
14604
15262
  }), "updateWorkflowResults");
14605
15263
  }
14606
- async updateWorkflowState({ workflowName, runId, opts }) {
15264
+ async updateWorkflowState({ workflowName, runId, opts, fence: explicitFence }) {
15265
+ const check = this.#runFenceCheck(explicitFence, runId, "updateWorkflowState");
14607
15266
  return this.executeWithRetry(() => withClientWriteLock(this.#client, async () => {
14608
15267
  const tx = await this.#client.transaction("write");
14609
15268
  try {
15269
+ if (check) await assertRunFence(tx, check);
14610
15270
  const existingSnapshotResult = await tx.execute({
14611
15271
  sql: `SELECT json(snapshot) as snapshot FROM ${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
14612
15272
  args: [workflowName, runId]
@@ -14647,7 +15307,7 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends _mastra_core_storage.Workflo
14647
15307
  }
14648
15308
  }), "updateWorkflowState");
14649
15309
  }
14650
- async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt }) {
15310
+ async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt, fence }) {
14651
15311
  const now = /* @__PURE__ */ new Date();
14652
15312
  const createdAtValue = (createdAt ?? now).toISOString();
14653
15313
  const updatedAtValue = (updatedAt ?? now).toISOString();
@@ -14655,7 +15315,8 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends _mastra_core_storage.Workflo
14655
15315
  workflowName,
14656
15316
  runId
14657
15317
  });
14658
- await this.executeWithRetry(() => withClientWriteLock(this.#client, () => this.#client.execute({
15318
+ const check = this.#runFenceCheck(fence, runId, "persistWorkflowSnapshot");
15319
+ await this.executeWithRetry(() => withRunFence(this.#client, check, (writer) => writer.execute({
14659
15320
  sql: `INSERT INTO ${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT} (workflow_name, run_id, resourceId, snapshot, createdAt, updatedAt)
14660
15321
  VALUES (?, ?, ?, jsonb(?), ?, ?)
14661
15322
  ON CONFLICT(workflow_name, run_id)
@@ -14711,14 +15372,16 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends _mastra_core_storage.Workflo
14711
15372
  }, error);
14712
15373
  }
14713
15374
  }
14714
- async deleteWorkflowRunById({ runId, workflowName }) {
15375
+ async deleteWorkflowRunById({ runId, workflowName, fence }) {
15376
+ const check = this.#runFenceCheck(fence, runId, "deleteWorkflowRunById");
14715
15377
  return this.executeWithRetry(async () => {
14716
15378
  try {
14717
- await this.#client.execute({
15379
+ await withRunFence(this.#client, check, (writer) => writer.execute({
14718
15380
  sql: `DELETE FROM ${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
14719
15381
  args: [workflowName, runId]
14720
- });
15382
+ }));
14721
15383
  } catch (error) {
15384
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
14722
15385
  throw new _mastra_core_error.MastraError({
14723
15386
  id: (0, _mastra_core_storage.createStorageErrorId)("LIBSQL", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"),
14724
15387
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -15588,7 +16251,7 @@ var LibSQLFactoryStorage = class extends _mastra_core_storage.FactoryStorage {
15588
16251
  ...config.authToken ? { authToken: config.authToken } : {},
15589
16252
  ...isLocalDb ? { timeout: DEFAULT_CONNECTION_TIMEOUT_MS } : {}
15590
16253
  });
15591
- this.#client = isSingleConnectionDatabase(config) ? gateSingleConnectionClient(client) : client;
16254
+ this.#client = isSingleConnectionDatabase(config) ? gateSingleConnectionClient(client) : isLocalDb ? gateIfLocalFile(resetConnectionsAfterBusy(client, { maxWaitMs: DEFAULT_CONNECTION_TIMEOUT_MS }), config.url, { maxWaitMs: DEFAULT_CONNECTION_TIMEOUT_MS }) : client;
15592
16255
  this.ops = new LibSQLFactoryStorageOps(this.#client, this.#schemas, (fn) => withClientWriteLock(this.#client, fn));
15593
16256
  }
15594
16257
  getMastraStorage() {
@@ -15754,7 +16417,10 @@ var LibSQLStore = class extends _mastra_core_storage.MastraCompositeStore {
15754
16417
  ...config.syncInterval !== void 0 ? { syncInterval: config.syncInterval } : {},
15755
16418
  ...this.isLocalDb ? { timeout: this.connectionTimeoutMs } : {}
15756
16419
  });
15757
- this.client = isSingleConnectionDatabase(config) ? gateSingleConnectionClient(client) : client;
16420
+ this.client = isSingleConnectionDatabase(config) ? gateSingleConnectionClient(client) : this.isLocalDb ? gateIfLocalFile(resetConnectionsAfterBusy(client, {
16421
+ afterReset: () => this.applyLocalPragmas(client),
16422
+ maxWaitMs: this.connectionTimeoutMs
16423
+ }), config.url, { maxWaitMs: this.connectionTimeoutMs }) : client;
15758
16424
  this.pragmasReady = this.isLocalDb ? this.applyLocalPragmas() : Promise.resolve();
15759
16425
  } else {
15760
16426
  this.client = config.client;
@@ -15820,7 +16486,7 @@ var LibSQLStore = class extends _mastra_core_storage.MastraCompositeStore {
15820
16486
  threadState
15821
16487
  };
15822
16488
  }
15823
- async applyLocalPragmas() {
16489
+ async applyLocalPragmas(client = this.client) {
15824
16490
  const pragmas = [
15825
16491
  ["journal_mode=WAL", "PRAGMA journal_mode=WAL;"],
15826
16492
  [`busy_timeout=${this.connectionTimeoutMs}`, `PRAGMA busy_timeout=${this.connectionTimeoutMs};`],
@@ -15830,7 +16496,7 @@ var LibSQLStore = class extends _mastra_core_storage.MastraCompositeStore {
15830
16496
  [`mmap_size=${this.localPragmas.mmapSize}`, `PRAGMA mmap_size=${this.localPragmas.mmapSize};`]
15831
16497
  ];
15832
16498
  for (const [label, sql] of pragmas) try {
15833
- await this.client.execute(sql);
16499
+ await client.execute(sql);
15834
16500
  this.logger.debug(`LibSQLStore: PRAGMA ${label} set.`);
15835
16501
  } catch (err) {
15836
16502
  this.logger.warn(`LibSQLStore: Failed to set PRAGMA ${label}.`, err);