@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/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/reference-core-mastra-class.md +12 -4
- package/dist/docs/references/reference-storage-retention.md +21 -18
- package/dist/index.cjs +776 -110
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +777 -111
- package/dist/index.js.map +1 -1
- package/dist/shared/file-transaction-gate.d.ts +28 -0
- package/dist/shared/file-transaction-gate.d.ts.map +1 -0
- package/dist/shared/reset-after-busy-client.d.ts +34 -0
- package/dist/shared/reset-after-busy-client.d.ts.map +1 -0
- package/dist/storage/db/index.d.ts +8 -3
- package/dist/storage/db/index.d.ts.map +1 -1
- package/dist/storage/db/run-fencing.d.ts +41 -0
- package/dist/storage/db/run-fencing.d.ts.map +1 -0
- package/dist/storage/db/utils.d.ts.map +1 -1
- package/dist/storage/domains/memory/index.d.ts +21 -7
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts +21 -6
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/dist/storage/factory-storage.d.ts.map +1 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/retention.d.ts +3 -3
- package/dist/storage/retention.d.ts.map +1 -1
- package/dist/vector/index.d.ts.map +1 -1
- package/package.json +5 -5
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
|
|
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
|
-
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
|
|
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
|
|
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
|
|
9530
|
-
return (await
|
|
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
|
-
|
|
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
|
-
|
|
9542
|
-
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
9550
|
-
|
|
9551
|
-
})
|
|
9552
|
-
|
|
9553
|
-
|
|
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
|
|
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.#
|
|
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.#
|
|
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.#
|
|
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.#
|
|
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 = {
|
|
14456
|
-
|
|
14457
|
-
|
|
14458
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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);
|