@mastra/libsql 1.29.0 → 1.30.0-alpha.1

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.
Files changed (32) hide show
  1. package/dist/docs/SKILL.md +5 -11
  2. package/dist/docs/assets/SOURCE_MAP.json +148 -2
  3. package/dist/docs/references/docs-knowledge-load-your-content.md +86 -0
  4. package/dist/docs/references/docs-knowledge-overview.md +106 -0
  5. package/dist/docs/references/docs-knowledge-search.md +63 -0
  6. package/dist/docs/references/docs-memory-overview.md +19 -7
  7. package/dist/docs/references/reference-core-getMemory.md +2 -2
  8. package/dist/docs/references/reference-core-listMemory.md +3 -3
  9. package/dist/docs/references/reference-core-mastra-class.md +1 -1
  10. package/dist/docs/references/reference-memory-memory-class.md +1 -1
  11. package/dist/index.cjs +831 -44
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.js +832 -45
  14. package/dist/index.js.map +1 -1
  15. package/dist/storage/db/index.d.ts +4 -4
  16. package/dist/storage/db/index.d.ts.map +1 -1
  17. package/dist/storage/domains/observability/index.d.ts +1 -0
  18. package/dist/storage/domains/observability/index.d.ts.map +1 -1
  19. package/dist/storage/domains/workflows/index.d.ts +100 -6
  20. package/dist/storage/domains/workflows/index.d.ts.map +1 -1
  21. package/dist/storage/retention.d.ts +3 -1
  22. package/dist/storage/retention.d.ts.map +1 -1
  23. package/package.json +4 -4
  24. package/dist/docs/references/docs-deployment-workers.md +0 -404
  25. package/dist/docs/references/docs-memory-memory-processors.md +0 -442
  26. package/dist/docs/references/docs-memory-semantic-recall.md +0 -485
  27. package/dist/docs/references/docs-memory-working-memory.md +0 -450
  28. package/dist/docs/references/docs-storage.md +0 -224
  29. package/dist/docs/references/integrations-channels-github.md +0 -156
  30. package/dist/docs/references/reference-rag-retrieval.md +0 -672
  31. package/dist/docs/references/reference-storage-composite.md +0 -433
  32. package/dist/docs/references/reference-storage-retention.md +0 -310
package/dist/index.cjs CHANGED
@@ -1862,6 +1862,21 @@ function resolveClient(config) {
1862
1862
  function assertPositiveLimit(limit) {
1863
1863
  if (!Number.isSafeInteger(limit) || limit <= 0) throw new Error(`prune limit must be a positive integer; received ${limit}`);
1864
1864
  }
1865
+ /**
1866
+ * Parses JSON object/array text in the table's `jsonb` columns only. A `text` column, such as a
1867
+ * span preview of `{}`, must read back as the stored string.
1868
+ */
1869
+ function parseJsonColumns(tableName, row) {
1870
+ const schema = _mastra_core_storage.TABLE_SCHEMAS[tableName];
1871
+ return Object.fromEntries(Object.entries(row).map(([k, v]) => {
1872
+ if (schema[k]?.type !== "jsonb" || typeof v !== "string" || !(v.startsWith("{") || v.startsWith("["))) return [k, v];
1873
+ try {
1874
+ return [k, JSON.parse(v)];
1875
+ } catch {
1876
+ return [k, v];
1877
+ }
1878
+ }));
1879
+ }
1865
1880
  var LibSQLDB = class extends _mastra_core_base.MastraBase {
1866
1881
  client;
1867
1882
  maxRetries;
@@ -2157,7 +2172,7 @@ var LibSQLDB = class extends _mastra_core_base.MastraBase {
2157
2172
  /**
2158
2173
  * Selects a single record from the specified table by key(s).
2159
2174
  * Returns the most recently created record if multiple matches exist.
2160
- * Automatically parses JSON string values back to objects/arrays.
2175
+ * Parses `jsonb` column values back to objects/arrays.
2161
2176
  *
2162
2177
  * @typeParam R - The expected return type of the record
2163
2178
  * @param args - The select arguments
@@ -2175,14 +2190,7 @@ var LibSQLDB = class extends _mastra_core_base.MastraBase {
2175
2190
  args: values
2176
2191
  });
2177
2192
  if (!result.rows || result.rows.length === 0) return null;
2178
- const row = result.rows[0];
2179
- return Object.fromEntries(Object.entries(row || {}).map(([k, v]) => {
2180
- try {
2181
- return [k, typeof v === "string" ? v.startsWith("{") || v.startsWith("[") ? JSON.parse(v) : v : v];
2182
- } catch {
2183
- return [k, v];
2184
- }
2185
- }));
2193
+ return parseJsonColumns(tableName, result.rows[0] ?? {});
2186
2194
  }
2187
2195
  /**
2188
2196
  * Selects multiple records from the specified table with optional filtering, ordering, and pagination.
@@ -2207,15 +2215,7 @@ var LibSQLDB = class extends _mastra_core_base.MastraBase {
2207
2215
  return ((await this.client.execute({
2208
2216
  sql: statement,
2209
2217
  args: [...whereClause?.args ?? [], ...args ?? []]
2210
- })).rows ?? []).map((row) => {
2211
- return Object.fromEntries(Object.entries(row || {}).map(([k, v]) => {
2212
- try {
2213
- return [k, typeof v === "string" ? v.startsWith("{") || v.startsWith("[") ? JSON.parse(v) : v : v];
2214
- } catch {
2215
- return [k, v];
2216
- }
2217
- }));
2218
- });
2218
+ })).rows ?? []).map((row) => parseJsonColumns(tableName, row ?? {}));
2219
2219
  }
2220
2220
  /**
2221
2221
  * Returns the total count of records matching the optional WHERE clause.
@@ -3363,14 +3363,14 @@ function cutoffFor(policy, anchorType, now = Date.now()) {
3363
3363
  return anchorType === "epoch-ms" ? cutoffMs : new Date(cutoffMs).toISOString();
3364
3364
  }
3365
3365
  const runBatchedDelete = _mastra_core_storage.runRetentionBatches;
3366
- function runPrune({ db, domain, targets, options, logger }) {
3366
+ function runPrune({ db, domain, targets, options, logger, deleteBatch }) {
3367
3367
  return (0, _mastra_core_storage.executeRetentionPrune)({
3368
3368
  domain,
3369
3369
  targets,
3370
3370
  options,
3371
3371
  beforeTarget: (target) => ensureAnchorIndex(db, target, logger),
3372
3372
  cutoffFor: (target, now) => cutoffFor(target.policy, target.anchorType ?? "timestamp", now),
3373
- deleteBatch: (target, cutoff, limit) => db.pruneBatch({
3373
+ deleteBatch: (target, cutoff, limit) => deleteBatch?.(target, cutoff, limit) ?? db.pruneBatch({
3374
3374
  tableName: target.table,
3375
3375
  column: target.column,
3376
3376
  cutoff,
@@ -12017,9 +12017,19 @@ var ObservabilityLibSQL = class ObservabilityLibSQL extends _mastra_core_storage
12017
12017
  indexed: true
12018
12018
  } };
12019
12019
  #db;
12020
+ #persistenceLocation;
12021
+ get persistenceLocation() {
12022
+ return this.#persistenceLocation;
12023
+ }
12020
12024
  constructor(config) {
12021
12025
  super();
12022
12026
  const client = resolveClient(config);
12027
+ this.#persistenceLocation = config.embeddedReplica || client.protocol === "file" ? "local" : [
12028
+ "http",
12029
+ "https",
12030
+ "ws",
12031
+ "wss"
12032
+ ].includes(client.protocol) ? "remote" : void 0;
12023
12033
  this.#db = new LibSQLDB({
12024
12034
  client,
12025
12035
  maxRetries: config.maxRetries,
@@ -15136,6 +15146,228 @@ var WorkflowDefinitionsLibSQL = class extends _mastra_core_storage.WorkflowDefin
15136
15146
  };
15137
15147
  // src/storage/domains/workflows/index.ts
15138
15148
  const RUN_OWNER_COLUMNS = `generation, ownerId, leaseExpiresAt, ${DB_NOW_MS} AS nowMs`;
15149
+ /**
15150
+ * The entries a storage v2 write adds: one per step, in order, then one per step patch, then the
15151
+ * state or state base (a checkpoint with neither starts the state over at `{}`), then the state
15152
+ * patch unless a full state was given.
15153
+ */
15154
+ function journalEntries(steps, { state, stateBase, statePatch }, checkpoint, stepPatches = {}) {
15155
+ const entries = Object.entries(steps).map(([stepId, result]) => ({
15156
+ kind: "step",
15157
+ step_id: stepId,
15158
+ payload: result
15159
+ }));
15160
+ for (const [stepId, ops] of Object.entries(stepPatches)) entries.push({
15161
+ kind: "step-patch",
15162
+ step_id: stepId,
15163
+ payload: ops
15164
+ });
15165
+ if (state !== void 0) entries.push({
15166
+ kind: "state",
15167
+ step_id: null,
15168
+ payload: state
15169
+ });
15170
+ else if (stateBase !== void 0) entries.push({
15171
+ kind: "state-base",
15172
+ step_id: null,
15173
+ payload: stateBase
15174
+ });
15175
+ else if (checkpoint) entries.push({
15176
+ kind: "state",
15177
+ step_id: null,
15178
+ payload: {}
15179
+ });
15180
+ if (state === void 0 && statePatch !== void 0) entries.push({
15181
+ kind: "state-patch",
15182
+ step_id: null,
15183
+ payload: statePatch
15184
+ });
15185
+ return entries;
15186
+ }
15187
+ /**
15188
+ * Upserts a storage v2 run's header, adds `entryCount` (the number of entries the write adds) to
15189
+ * its `lastSeq`, for {@link insertEntries} to number them up to, and returns the new `lastSeq` and
15190
+ * the `runToken` (picked when the write creates the run, otherwise the stored one). A checkpoint's
15191
+ * state entries come last, so its `startStateSeq` is the new `lastSeq`; an append keeps the stored
15192
+ * one unless given `startStateSeq`.
15193
+ */
15194
+ function upsertHeader(run, entryCount, write) {
15195
+ const now = (/* @__PURE__ */ new Date()).toISOString();
15196
+ const startStateSeq = write.checkpoint ? {
15197
+ insert: "?",
15198
+ update: `${_mastra_core_storage.TABLE_WORKFLOW_RUNS}.lastSeq + excluded.lastSeq`,
15199
+ args: [entryCount]
15200
+ } : {
15201
+ insert: "COALESCE(?, 0)",
15202
+ update: `COALESCE(?, ${_mastra_core_storage.TABLE_WORKFLOW_RUNS}.startStateSeq)`,
15203
+ args: [write.startStateSeq ?? null]
15204
+ };
15205
+ return {
15206
+ sql: `INSERT INTO ${_mastra_core_storage.TABLE_WORKFLOW_RUNS}
15207
+ (workflow_name, run_id, resourceId, threadId, status, header, lastSeq, startStateSeq, stateShared, runToken, createdAt, updatedAt)
15208
+ VALUES (?, ?, ?, ?, ?, jsonb(?), ?, ${startStateSeq.insert}, 0, ?, ?, ?)
15209
+ ON CONFLICT(workflow_name, run_id) DO UPDATE SET
15210
+ resourceId = COALESCE(excluded.resourceId, ${_mastra_core_storage.TABLE_WORKFLOW_RUNS}.resourceId), threadId = excluded.threadId,
15211
+ status = excluded.status, header = excluded.header, lastSeq = ${_mastra_core_storage.TABLE_WORKFLOW_RUNS}.lastSeq + excluded.lastSeq,
15212
+ startStateSeq = ${startStateSeq.update}, updatedAt = excluded.updatedAt
15213
+ RETURNING lastSeq, runToken`,
15214
+ args: [
15215
+ run.workflowName,
15216
+ run.runId,
15217
+ run.resourceId ?? null,
15218
+ run.threadId ?? null,
15219
+ run.header.status,
15220
+ safeStringify(run.header),
15221
+ entryCount,
15222
+ ...startStateSeq.args,
15223
+ crypto.randomUUID(),
15224
+ now,
15225
+ now,
15226
+ ...write.checkpoint ? [] : startStateSeq.args
15227
+ ]
15228
+ };
15229
+ }
15230
+ /**
15231
+ * Inserts the entries in the JSON array parameter after the run's header has been bumped by
15232
+ * their count, numbering them up to the header's `lastSeq`. `-> '$.payload'` keeps a JSON null
15233
+ * apart from an undefined payload (dropped by `JSON.stringify`), which becomes SQL NULL.
15234
+ * Args: entry count, entries JSON, workflow name, run id.
15235
+ */
15236
+ const INSERT_ENTRIES = `INSERT INTO ${_mastra_core_storage.TABLE_WORKFLOW_RUN_ENTRIES} (workflow_name, run_id, seq, kind, step_id, payload)
15237
+ SELECT h.workflow_name, h.run_id, h.lastSeq - ? + j.key + 1, j.value ->> '$.kind', j.value ->> '$.step_id', jsonb(j.value -> '$.payload')
15238
+ FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} h, json_each(?) j
15239
+ WHERE h.workflow_name = ? AND h.run_id = ?`;
15240
+ function insertEntries(run, entries) {
15241
+ return {
15242
+ sql: INSERT_ENTRIES,
15243
+ args: [
15244
+ entries.length,
15245
+ safeStringify(entries),
15246
+ run.workflowName,
15247
+ run.runId
15248
+ ]
15249
+ };
15250
+ }
15251
+ /**
15252
+ * The run a `state-base` entry's payload names: the expressions of the state base index, which
15253
+ * a lookup must repeat exactly to use it.
15254
+ */
15255
+ const STATE_BASE_WORKFLOW_EXPR = `payload ->> '$.workflowName'`;
15256
+ const STATE_BASE_RUN_EXPR = `payload ->> '$.runId'`;
15257
+ /**
15258
+ * A JSON array of {@link EntryRow} for the storage v2 run `alias` refers to: each step's
15259
+ * latest entry, the step patches after it (for `latestStepResults`), and the entries
15260
+ * `materializeState` uses for the latest state: the latest full state or state base and every
15261
+ * state patch after it. Drafts are left out.
15262
+ *
15263
+ * With `summary`, successful steps other than `input` come back as their summary, built here
15264
+ * so their data is never sent, unless step patches follow it (the caller summarizes the patched
15265
+ * result instead). Like `summarizeStepResult`, it keeps a field that is JSON null
15266
+ * and leaves out one that is absent. It then takes one arg, `STEP_RESULT_SUMMARY_FIELDS` as
15267
+ * JSON, bound where this SQL sits in the statement.
15268
+ */
15269
+ function entriesSql(alias, { summary } = {}) {
15270
+ let payload = "json(x.payload)";
15271
+ if (summary) {
15272
+ const summarized = `(x.kind = 'step' AND x.step_id <> 'input' AND x.payload ->> '$.status' = 'success'
15273
+ AND (x.patch_seq IS NULL OR x.patch_seq < x.seq))`;
15274
+ payload = `json(CASE WHEN ${summarized} THEN (
15275
+ SELECT json_group_object(f.key, json(x.payload -> f.fullkey))
15276
+ FROM json_each(x.payload) f WHERE f.key IN (SELECT value FROM json_each(?))
15277
+ ) ELSE x.payload END), 'summarized', ${summarized}`;
15278
+ }
15279
+ return `(SELECT json_group_array(json_object(
15280
+ 'seq', x.seq, 'ord', CASE WHEN x.kind = 'step' THEN x.first_seq ELSE x.seq END, 'kind', x.kind, 'step_id', x.step_id,
15281
+ 'absent', x.payload IS NULL, 'payload', ${payload}
15282
+ ))
15283
+ FROM (
15284
+ SELECT e.seq, e.kind, e.step_id, e.payload,
15285
+ ROW_NUMBER() OVER (PARTITION BY e.kind, e.step_id ORDER BY e.seq DESC) AS rn,
15286
+ MIN(e.seq) OVER (PARTITION BY e.kind, e.step_id) AS first_seq,
15287
+ MAX(CASE WHEN e.kind = 'step' THEN e.seq END) OVER (PARTITION BY e.step_id) AS step_seq,
15288
+ MAX(CASE WHEN e.kind = 'step-patch' THEN e.seq END) OVER (PARTITION BY e.step_id) AS patch_seq,
15289
+ MAX(CASE WHEN e.kind IN ('state', 'state-base') THEN e.seq END) OVER () AS base_seq
15290
+ FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUN_ENTRIES} e
15291
+ WHERE e.workflow_name = ${alias}.workflow_name AND e.run_id = ${alias}.run_id AND e.kind <> 'state-draft'
15292
+ ) x
15293
+ WHERE CASE x.kind
15294
+ WHEN 'step' THEN x.rn = 1
15295
+ WHEN 'step-patch' THEN x.seq > x.step_seq
15296
+ ELSE x.base_seq IS NULL OR x.seq >= x.base_seq
15297
+ END)`;
15298
+ }
15299
+ /**
15300
+ * A JSON array of {@link EntryRow} with the entries `materializeState` uses for the state of the
15301
+ * storage v2 run `alias` refers to as of a version. For a draft, every state entry up to it, as
15302
+ * the drafts it is on can build on any earlier version; otherwise the latest full state or state
15303
+ * base up to it and the state patches after it. Takes two args, bound where this SQL sits in the
15304
+ * statement: the version's `seq`, then 1 when it is a draft, else 0.
15305
+ */
15306
+ function stateEntriesSql(alias) {
15307
+ return `(SELECT json_group_array(json_object(
15308
+ 'seq', x.seq, 'ord', x.seq, 'kind', x.kind, 'step_id', x.step_id, 'absent', x.payload IS NULL, 'payload', json(x.payload)
15309
+ ))
15310
+ FROM (
15311
+ SELECT e.seq, e.kind, e.step_id, e.payload,
15312
+ MAX(CASE WHEN e.kind IN ('state', 'state-base') THEN e.seq END) OVER () AS base_seq
15313
+ FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUN_ENTRIES} e
15314
+ WHERE e.workflow_name = ${alias}.workflow_name AND e.run_id = ${alias}.run_id
15315
+ AND e.kind NOT IN ('step', 'step-patch') AND e.seq <= ?
15316
+ ) x
15317
+ WHERE ? OR (x.kind <> 'state-draft' AND (x.base_seq IS NULL OR x.seq >= x.base_seq)))`;
15318
+ }
15319
+ /** `entriesSql`, `stateEntriesSql` or `loadWorkflowRunSteps` rows, in the order to apply them. */
15320
+ function entryRows(json) {
15321
+ return JSON.parse(json).sort((a, b) => a.ord - b.ord);
15322
+ }
15323
+ /** The entries of {@link entryRows}; an undefined payload reads back as undefined, as in v1. */
15324
+ function toEntries(rows) {
15325
+ return rows.map((row) => {
15326
+ const payload = row.absent ? void 0 : row.payload;
15327
+ switch (row.kind) {
15328
+ case "step": return {
15329
+ seq: row.seq,
15330
+ kind: "step",
15331
+ stepId: row.step_id,
15332
+ result: payload
15333
+ };
15334
+ case "step-patch": return {
15335
+ seq: row.seq,
15336
+ kind: "step-patch",
15337
+ stepId: row.step_id,
15338
+ ops: payload
15339
+ };
15340
+ case "state": return {
15341
+ seq: row.seq,
15342
+ kind: "state",
15343
+ state: payload
15344
+ };
15345
+ case "state-patch": return {
15346
+ seq: row.seq,
15347
+ kind: "state-patch",
15348
+ ops: payload
15349
+ };
15350
+ case "state-base": return {
15351
+ seq: row.seq,
15352
+ kind: "state-base",
15353
+ base: payload
15354
+ };
15355
+ case "state-draft": {
15356
+ const draft = payload;
15357
+ return "state" in draft ? {
15358
+ seq: row.seq,
15359
+ kind: "state-draft",
15360
+ state: draft.state
15361
+ } : {
15362
+ seq: row.seq,
15363
+ kind: "state-draft",
15364
+ on: draft.on,
15365
+ ops: draft.ops
15366
+ };
15367
+ }
15368
+ }
15369
+ });
15370
+ }
15139
15371
  function toRunOwnershipRecord(runId, row) {
15140
15372
  const leaseExpiresAt = row.leaseExpiresAt === null ? null : Number(row.leaseExpiresAt);
15141
15373
  return {
@@ -15226,14 +15458,36 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends _mastra_core_storage.Workflo
15226
15458
  tableName: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS,
15227
15459
  schema: _mastra_core_storage.RUN_FENCING_TABLE_SCHEMAS[_mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS]
15228
15460
  });
15461
+ for (const [tableName, config] of Object.entries(_mastra_core_storage.WORKFLOW_RUN_JOURNAL_TABLE_CONFIGS)) await this.#db.createTable({
15462
+ tableName,
15463
+ schema: config.columns,
15464
+ compositePrimaryKey: config.compositePrimaryKey
15465
+ });
15466
+ await withClientWriteLock(this.#client, () => this.#client.batch([
15467
+ `CREATE INDEX IF NOT EXISTS mastra_workflow_runs_name_createdat_idx ON ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} (workflow_name, createdAt DESC)`,
15468
+ `CREATE INDEX IF NOT EXISTS mastra_workflow_runs_name_status_createdat_idx ON ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} (workflow_name, status, createdAt DESC)`,
15469
+ `CREATE INDEX IF NOT EXISTS mastra_workflow_runs_threadid_idx ON ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} (threadId)`,
15470
+ `CREATE INDEX IF NOT EXISTS mastra_workflow_run_entries_step_idx ON ${_mastra_core_storage.TABLE_WORKFLOW_RUN_ENTRIES} (workflow_name, run_id, kind, step_id, seq)`,
15471
+ `CREATE INDEX IF NOT EXISTS mastra_workflow_run_entries_state_base_idx ON ${_mastra_core_storage.TABLE_WORKFLOW_RUN_ENTRIES}
15472
+ (${STATE_BASE_WORKFLOW_EXPR}, ${STATE_BASE_RUN_EXPR}) WHERE kind = 'state-base'`
15473
+ ].map((sql) => ({
15474
+ sql,
15475
+ args: []
15476
+ })), "write"));
15229
15477
  }
15230
15478
  async dangerouslyClearAll() {
15231
15479
  await this.#db.deleteData({ tableName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT });
15232
15480
  await this.#db.deleteData({ tableName: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS });
15481
+ await this.#db.deleteData({ tableName: _mastra_core_storage.TABLE_WORKFLOW_RUNS });
15482
+ await this.#db.deleteData({ tableName: _mastra_core_storage.TABLE_WORKFLOW_RUN_ENTRIES });
15233
15483
  }
15234
15484
  supportsRunFencing() {
15235
15485
  return true;
15236
15486
  }
15487
+ /** @experimental */
15488
+ supportsRunJournal() {
15489
+ return true;
15490
+ }
15237
15491
  #runFenceCheck(fence, runId, operation) {
15238
15492
  const resolved = (0, _mastra_core_storage.resolveRunFence)(this, fence, runId);
15239
15493
  return resolved && {
@@ -15242,6 +15496,399 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends _mastra_core_storage.Workflo
15242
15496
  operation
15243
15497
  };
15244
15498
  }
15499
+ /**
15500
+ * Runs a storage v2 write's statements behind the run's fence. With `expectedStatus` or
15501
+ * `stateBase`, in a write transaction that first reads the stored run: it writes nothing
15502
+ * (resolving to null) when the run is stored and its status doesn't match `expectedStatus`, and
15503
+ * throws when the state version `stateBase` names isn't stored.
15504
+ */
15505
+ #writeRun(check, run, { expectedStatus, stateBase }, statements) {
15506
+ if (expectedStatus === void 0 && stateBase === void 0) return withRunFence(this.#client, check, (writer) => writer.batch(statements));
15507
+ return inWriteTransaction(this.#client, async (tx) => {
15508
+ if (check) await assertRunFence(tx, check);
15509
+ if (expectedStatus !== void 0) {
15510
+ const { rows } = await tx.execute({
15511
+ sql: `SELECT status FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} WHERE workflow_name = ? AND run_id = ?`,
15512
+ args: [run.workflowName, run.runId]
15513
+ });
15514
+ if (rows[0] && !(0, _mastra_core_storage.matchesExpectedWorkflowStatus)(rows[0].status, expectedStatus)) return null;
15515
+ }
15516
+ if (stateBase !== void 0) {
15517
+ const { rows } = await tx.execute({
15518
+ sql: `SELECT 1 FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUN_ENTRIES} WHERE workflow_name = ? AND run_id = ?
15519
+ AND CASE WHEN ? THEN kind = 'state-draft' AND seq = ? ELSE kind IN ('state', 'state-base') AND seq <= ? END
15520
+ AND ? <= (SELECT lastSeq FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} WHERE workflow_name = ? AND run_id = ?)
15521
+ LIMIT 1`,
15522
+ args: [
15523
+ stateBase.workflowName,
15524
+ stateBase.runId,
15525
+ stateBase.draft ? 1 : 0,
15526
+ stateBase.seq,
15527
+ stateBase.seq,
15528
+ stateBase.seq,
15529
+ stateBase.workflowName,
15530
+ stateBase.runId
15531
+ ]
15532
+ });
15533
+ if (!rows[0]) throw new Error(`Workflow run ${run.workflowName}/${run.runId} builds on state version ${stateBase.seq} of ${stateBase.workflowName}/${stateBase.runId}, which is not stored`);
15534
+ }
15535
+ const results = [];
15536
+ for (const statement of statements) results.push(await tx.execute(statement));
15537
+ return results;
15538
+ });
15539
+ }
15540
+ /**
15541
+ * Selects v1 snapshot rows and storage v2 runs as one result, newest first. Each version
15542
+ * has its own WHERE clause; `null` leaves it out. Storage v2 rows carry their header and
15543
+ * entries, or in summary mode only the summary snapshot. `page` is appended to the
15544
+ * combined, ordered rows (e.g. `LIMIT 1`). Bind the v1 args, then the v2 args, then `page`'s.
15545
+ */
15546
+ #runsSql({ v1Where, v2Where, summary, page = "" }) {
15547
+ const branches = [];
15548
+ if (v1Where !== null) {
15549
+ const snapshot = summary ? `json_object('status', json_extract(snapshot, '$.status'), 'timestamp', json_extract(snapshot, '$.timestamp'))` : "json(snapshot)";
15550
+ branches.push(`SELECT workflow_name, run_id, resourceId, ${snapshot} AS snapshot, NULL AS header, createdAt, updatedAt, 1 AS version
15551
+ FROM ${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT} ${v1Where}`);
15552
+ }
15553
+ if (v2Where !== null) {
15554
+ const columns = summary ? `json_object('status', status, 'timestamp', json_extract(header, '$.timestamp')) AS snapshot, NULL AS header` : "NULL AS snapshot, json(header) AS header";
15555
+ branches.push(`SELECT workflow_name, run_id, resourceId, ${columns}, createdAt, updatedAt, 2 AS version
15556
+ FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} ${v2Where}`);
15557
+ }
15558
+ return `SELECT p.*${summary ? "" : `, CASE WHEN p.version = 2 THEN ${entriesSql("p")} END AS entries`}
15559
+ FROM (${branches.join(" UNION ALL ")} ORDER BY createdAt DESC ${page}) p
15560
+ ORDER BY p.createdAt DESC`;
15561
+ }
15562
+ /**
15563
+ * The `base` that `materializeState` takes for a run's entries (as of `at`, or latest): the state
15564
+ * version of another run it builds on (`stateBaseOf`), resolved recursively, else undefined. `{}`
15565
+ * when that run isn't stored. One query per run in the chain.
15566
+ */
15567
+ async #stateBase(q, entries, at) {
15568
+ const ref = (0, _mastra_core_storage.stateBaseOf)(entries, at);
15569
+ if (!ref) return void 0;
15570
+ return (await this.#stateAt(q, ref.workflowName, ref.runId, ref))?.state ?? {};
15571
+ }
15572
+ /** A storage v2 run's state as of the version `at`, or null when the run isn't stored. */
15573
+ async #stateAt(q, workflowName, runId, at) {
15574
+ const row = (await q.execute({
15575
+ sql: `SELECT ${stateEntriesSql("r")} AS entries
15576
+ FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} r WHERE r.workflow_name = ? AND r.run_id = ?`,
15577
+ args: [
15578
+ at.seq,
15579
+ at.draft === true ? 1 : 0,
15580
+ workflowName,
15581
+ runId
15582
+ ]
15583
+ })).rows[0];
15584
+ if (!row) return null;
15585
+ const entries = toEntries(entryRows(row.entries));
15586
+ return (0, _mastra_core_storage.materializeState)(entries, {
15587
+ at,
15588
+ base: await this.#stateBase(q, entries, at)
15589
+ });
15590
+ }
15591
+ /**
15592
+ * Before a run is deleted, stores the full state in place of every `state-base` entry built on
15593
+ * it, so the runs built on it keep reading the same state. Runs inside the delete's transaction.
15594
+ */
15595
+ async #detachStateChildren(q, workflowName, runId) {
15596
+ const children = await q.execute({
15597
+ sql: `SELECT workflow_name, run_id, seq, json(payload) AS payload FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUN_ENTRIES}
15598
+ WHERE kind = 'state-base' AND ${STATE_BASE_WORKFLOW_EXPR} = ? AND ${STATE_BASE_RUN_EXPR} = ?`,
15599
+ args: [workflowName, runId]
15600
+ });
15601
+ const states = /* @__PURE__ */ new Map();
15602
+ for (const child of children.rows) {
15603
+ const ref = JSON.parse(child.payload);
15604
+ const version = `${ref.seq}:${ref.draft === true}`;
15605
+ let state = states.get(version);
15606
+ if (!state) {
15607
+ state = (await this.#stateAt(q, workflowName, runId, ref))?.state ?? {};
15608
+ states.set(version, state);
15609
+ }
15610
+ await q.execute({
15611
+ sql: `UPDATE ${_mastra_core_storage.TABLE_WORKFLOW_RUN_ENTRIES} SET kind = 'state', payload = jsonb(?)
15612
+ WHERE workflow_name = ? AND run_id = ? AND seq = ?`,
15613
+ args: [
15614
+ safeStringify(state),
15615
+ child.workflow_name,
15616
+ child.run_id,
15617
+ child.seq
15618
+ ]
15619
+ });
15620
+ }
15621
+ }
15622
+ /** Rebuilds a storage v2 run's `WorkflowRunState` from its header and `entriesSql` JSON. */
15623
+ async #materializeRun(q, header, rows) {
15624
+ const parsedHeader = typeof header === "string" ? JSON.parse(header) : header;
15625
+ const entries = toEntries(entryRows(rows));
15626
+ return (0, _mastra_core_storage.materializeWorkflowRun)(parsedHeader, entries, await this.#stateBase(q, entries));
15627
+ }
15628
+ /** Parses a row of `#runsSql`. */
15629
+ async #parseRun(row) {
15630
+ if (Number(row.version) !== 2) return this.parseWorkflowRun(row);
15631
+ const snapshot = row.entries ? await this.#materializeRun(this.#client, row.header, row.entries) : row.snapshot;
15632
+ return {
15633
+ ...this.parseWorkflowRun({
15634
+ ...row,
15635
+ snapshot
15636
+ }),
15637
+ storageVersion: 2
15638
+ };
15639
+ }
15640
+ #runError(operation, workflowName, runId, error) {
15641
+ return new _mastra_core_error.MastraError({
15642
+ id: (0, _mastra_core_storage.createStorageErrorId)("LIBSQL", operation, "FAILED"),
15643
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
15644
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
15645
+ details: {
15646
+ workflowName,
15647
+ runId
15648
+ }
15649
+ }, error);
15650
+ }
15651
+ async loadWorkflowRunHeader({ workflowName, runId }) {
15652
+ try {
15653
+ const row = (await this.#client.execute({
15654
+ sql: `SELECT json(header) AS header, lastSeq, startStateSeq, runToken FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} WHERE workflow_name = ? AND run_id = ?`,
15655
+ args: [workflowName, runId]
15656
+ })).rows[0];
15657
+ return row ? {
15658
+ header: JSON.parse(row.header),
15659
+ lastSeq: Number(row.lastSeq),
15660
+ startStateSeq: Number(row.startStateSeq),
15661
+ runToken: row.runToken
15662
+ } : null;
15663
+ } catch (error) {
15664
+ throw this.#runError("LOAD_WORKFLOW_RUN_HEADER", workflowName, runId, error);
15665
+ }
15666
+ }
15667
+ async loadWorkflowRun({ workflowName, runId, successfulSteps }) {
15668
+ try {
15669
+ const summary = successfulSteps === "summary";
15670
+ const run = (await this.#client.execute({
15671
+ sql: `SELECT json(r.header) AS header, r.lastSeq, r.startStateSeq, r.runToken, ${entriesSql("r", { summary })} AS entries
15672
+ FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} r WHERE r.workflow_name = ? AND r.run_id = ?`,
15673
+ args: summary ? [
15674
+ JSON.stringify(_mastra_core_storage.STEP_RESULT_SUMMARY_FIELDS),
15675
+ workflowName,
15676
+ runId
15677
+ ] : [workflowName, runId]
15678
+ })).rows[0];
15679
+ if (!run) return null;
15680
+ const header = JSON.parse(run.header);
15681
+ const rows = entryRows(run.entries);
15682
+ const entries = toEntries(rows);
15683
+ const summarizedInQuery = new Set(rows.filter((row) => row.summarized).map((row) => row.step_id));
15684
+ const steps = {};
15685
+ const summarizedStepIds = [];
15686
+ for (const [stepId, result] of (0, _mastra_core_storage.latestStepResults)(entries)) {
15687
+ steps[stepId] = result;
15688
+ if (summary && stepId !== "input" && result?.status === "success") {
15689
+ if (!summarizedInQuery.has(stepId)) steps[stepId] = (0, _mastra_core_storage.summarizeStepResult)(result);
15690
+ summarizedStepIds.push(stepId);
15691
+ }
15692
+ }
15693
+ const base = await this.#stateBase(this.#client, entries);
15694
+ const { state, statePatchBytes } = (0, _mastra_core_storage.materializeState)(entries, { base });
15695
+ return {
15696
+ header,
15697
+ lastSeq: Number(run.lastSeq),
15698
+ startStateSeq: Number(run.startStateSeq),
15699
+ runToken: run.runToken,
15700
+ state,
15701
+ statePatchBytes,
15702
+ steps,
15703
+ summarizedStepIds
15704
+ };
15705
+ } catch (error) {
15706
+ throw this.#runError("LOAD_WORKFLOW_RUN", workflowName, runId, error);
15707
+ }
15708
+ }
15709
+ async loadWorkflowRunSteps({ workflowName, runId, stepIds, expectedLastSeq, expectedRunToken }) {
15710
+ try {
15711
+ const run = (await this.#client.execute({
15712
+ sql: `SELECT r.lastSeq, r.runToken, (SELECT json_group_array(json_object(
15713
+ 'seq', x.seq, 'ord', x.seq, 'kind', x.kind, 'step_id', x.step_id, 'absent', x.payload IS NULL, 'payload', json(x.payload)
15714
+ ))
15715
+ FROM (
15716
+ SELECT e.seq, e.kind, e.step_id, e.payload,
15717
+ MAX(CASE WHEN e.kind = 'step' THEN e.seq END) OVER (PARTITION BY e.step_id) AS step_seq
15718
+ FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUN_ENTRIES} e
15719
+ WHERE e.workflow_name = r.workflow_name AND e.run_id = r.run_id AND e.kind IN ('step', 'step-patch')
15720
+ AND e.step_id IN (SELECT value FROM json_each(?))
15721
+ ) x
15722
+ WHERE x.seq >= x.step_seq) AS entries
15723
+ FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} r WHERE r.workflow_name = ? AND r.run_id = ?`,
15724
+ args: [
15725
+ JSON.stringify(stepIds),
15726
+ workflowName,
15727
+ runId
15728
+ ]
15729
+ })).rows[0];
15730
+ if (!run) return null;
15731
+ if (expectedLastSeq !== void 0 && Number(run.lastSeq) !== expectedLastSeq) return null;
15732
+ if (expectedRunToken !== void 0 && run.runToken !== expectedRunToken) return null;
15733
+ return Object.fromEntries((0, _mastra_core_storage.latestStepResults)(toEntries(entryRows(run.entries))));
15734
+ } catch (error) {
15735
+ throw this.#runError("LOAD_WORKFLOW_RUN_STEPS", workflowName, runId, error);
15736
+ }
15737
+ }
15738
+ async updateWorkflowRunHeader({ workflowName, runId, opts, fence }) {
15739
+ const check = this.#runFenceCheck(fence, runId, "updateWorkflowRunHeader");
15740
+ try {
15741
+ return await this.executeWithRetry(() => inWriteTransaction(this.#client, async (tx) => {
15742
+ if (check) await assertRunFence(tx, check);
15743
+ const row = (await tx.execute({
15744
+ sql: `SELECT json(header) AS header, lastSeq, startStateSeq, runToken FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} WHERE workflow_name = ? AND run_id = ?`,
15745
+ args: [workflowName, runId]
15746
+ })).rows[0];
15747
+ if (!row) return void 0;
15748
+ const current = JSON.parse(row.header);
15749
+ const { expectedStatus, ...patch } = opts;
15750
+ if (!(0, _mastra_core_storage.matchesExpectedWorkflowStatus)(current.status, expectedStatus)) return void 0;
15751
+ const header = {
15752
+ ...current,
15753
+ ...patch
15754
+ };
15755
+ await tx.execute({
15756
+ sql: `UPDATE ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} SET header = jsonb(?), status = ?, updatedAt = ? WHERE workflow_name = ? AND run_id = ?`,
15757
+ args: [
15758
+ safeStringify(header),
15759
+ header.status,
15760
+ (/* @__PURE__ */ new Date()).toISOString(),
15761
+ workflowName,
15762
+ runId
15763
+ ]
15764
+ });
15765
+ return {
15766
+ header,
15767
+ lastSeq: Number(row.lastSeq),
15768
+ startStateSeq: Number(row.startStateSeq),
15769
+ runToken: row.runToken
15770
+ };
15771
+ }), "updateWorkflowRunHeader");
15772
+ } catch (error) {
15773
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
15774
+ throw this.#runError("UPDATE_WORKFLOW_RUN_HEADER", workflowName, runId, error);
15775
+ }
15776
+ }
15777
+ /**
15778
+ * One batch behind the write lock (see `#writeRun` for `expectedStatus` and `stateBase`): marking
15779
+ * the run a state base builds on as shared, the header upsert, then the deletion of the run's
15780
+ * entries (only its steps when another run builds on its state, see `stateShared`), then the new ones.
15781
+ */
15782
+ async writeWorkflowRunCheckpoint({ fence, expectedStatus, steps, state, stateBase, statePatch, ...run }) {
15783
+ const entries = journalEntries(steps, {
15784
+ state,
15785
+ stateBase,
15786
+ statePatch
15787
+ }, true);
15788
+ const check = this.#runFenceCheck(fence, run.runId, "writeWorkflowRunCheckpoint");
15789
+ try {
15790
+ const base = state === void 0 ? stateBase : void 0;
15791
+ const statements = [];
15792
+ if (base) statements.push({
15793
+ sql: `UPDATE ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} SET stateShared = 1 WHERE workflow_name = ? AND run_id = ? AND NOT stateShared`,
15794
+ args: [base.workflowName, base.runId]
15795
+ });
15796
+ statements.push(upsertHeader(run, entries.length, { checkpoint: true }), {
15797
+ sql: `DELETE FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUN_ENTRIES} WHERE workflow_name = ? AND run_id = ?
15798
+ AND (kind IN ('step', 'step-patch') OR NOT (SELECT stateShared FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} WHERE workflow_name = ? AND run_id = ?))`,
15799
+ args: [
15800
+ run.workflowName,
15801
+ run.runId,
15802
+ run.workflowName,
15803
+ run.runId
15804
+ ]
15805
+ }, insertEntries(run, entries));
15806
+ const results = await this.executeWithRetry(() => this.#writeRun(check, run, {
15807
+ expectedStatus,
15808
+ stateBase: base
15809
+ }, statements), "writeWorkflowRunCheckpoint");
15810
+ if (!results) return null;
15811
+ const header = results[statements.length - 3].rows[0];
15812
+ return {
15813
+ lastSeq: Number(header.lastSeq),
15814
+ runToken: header.runToken
15815
+ };
15816
+ } catch (error) {
15817
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
15818
+ throw this.#runError("WRITE_WORKFLOW_RUN_CHECKPOINT", run.workflowName, run.runId, error);
15819
+ }
15820
+ }
15821
+ /**
15822
+ * One batch behind the write lock (see `#writeRun` for `expectedStatus`): the header upsert adds
15823
+ * the entry count to `lastSeq`, and the insert numbers the entries up to it, so writes to one run
15824
+ * never interleave.
15825
+ */
15826
+ async appendWorkflowRunEntries({ fence, steps, stepPatches, state, statePatch, startStateSeq, expectedStatus, ...run }) {
15827
+ const entries = journalEntries(steps, {
15828
+ state,
15829
+ statePatch
15830
+ }, false, stepPatches);
15831
+ const check = this.#runFenceCheck(fence, run.runId, "appendWorkflowRunEntries");
15832
+ try {
15833
+ const results = await this.executeWithRetry(() => this.#writeRun(check, run, { expectedStatus }, [upsertHeader(run, entries.length, {
15834
+ checkpoint: false,
15835
+ startStateSeq
15836
+ }), insertEntries(run, entries)]), "appendWorkflowRunEntries");
15837
+ if (!results) return null;
15838
+ const header = results[0].rows[0];
15839
+ return {
15840
+ lastSeq: Number(header.lastSeq),
15841
+ runToken: header.runToken
15842
+ };
15843
+ } catch (error) {
15844
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
15845
+ throw this.#runError("APPEND_WORKFLOW_RUN_ENTRIES", run.workflowName, run.runId, error);
15846
+ }
15847
+ }
15848
+ async loadWorkflowRunState({ workflowName, runId, seq }) {
15849
+ try {
15850
+ return (await this.#stateAt(this.#client, workflowName, runId, { seq }))?.state ?? null;
15851
+ } catch (error) {
15852
+ throw this.#runError("LOAD_WORKFLOW_RUN_STATE", workflowName, runId, error);
15853
+ }
15854
+ }
15855
+ /**
15856
+ * One batch behind the write lock (or the run's fence): the header's `lastSeq` bump, then the
15857
+ * draft numbered at it.
15858
+ */
15859
+ async appendWorkflowRunStateDraft(args) {
15860
+ const { workflowName, runId } = args;
15861
+ const payload = "state" in args ? { state: args.state } : {
15862
+ on: args.on.draft ? {
15863
+ seq: args.on.seq,
15864
+ draft: true
15865
+ } : { seq: args.on.seq },
15866
+ ops: args.ops
15867
+ };
15868
+ let row;
15869
+ try {
15870
+ const [header] = await this.executeWithRetry(() => withRunFence(this.#client, this.#runFenceCheck(void 0, runId, "appendWorkflowRunStateDraft"), (writer) => writer.batch([{
15871
+ sql: `UPDATE ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} SET lastSeq = lastSeq + 1, stateShared = 1
15872
+ WHERE workflow_name = ? AND run_id = ? RETURNING lastSeq`,
15873
+ args: [workflowName, runId]
15874
+ }, {
15875
+ sql: `INSERT INTO ${_mastra_core_storage.TABLE_WORKFLOW_RUN_ENTRIES} (workflow_name, run_id, seq, kind, step_id, payload)
15876
+ SELECT workflow_name, run_id, lastSeq, 'state-draft', NULL, jsonb(?)
15877
+ FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} WHERE workflow_name = ? AND run_id = ?`,
15878
+ args: [
15879
+ safeStringify(payload),
15880
+ workflowName,
15881
+ runId
15882
+ ]
15883
+ }])), "appendWorkflowRunStateDraft");
15884
+ row = header.rows[0];
15885
+ } catch (error) {
15886
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
15887
+ throw this.#runError("APPEND_WORKFLOW_RUN_STATE_DRAFT", workflowName, runId, error);
15888
+ }
15889
+ if (!row) throw new Error(`Storage v2 run ${workflowName}/${runId} is not stored`);
15890
+ return { seq: Number(row.lastSeq) };
15891
+ }
15245
15892
  async #readRunOwner(q, runId) {
15246
15893
  const row = (await q.execute({
15247
15894
  sql: `SELECT ${RUN_OWNER_COLUMNS} FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS} WHERE runId = ?`,
@@ -15342,21 +15989,56 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends _mastra_core_storage.Workflo
15342
15989
  throw this.#ownershipError("GET_RUN_OWNERSHIP", runId, error);
15343
15990
  }
15344
15991
  }
15345
- /** Delete workflow run snapshots and run ownership rows older than their policy's `maxAge`, batched. */
15992
+ /**
15993
+ * Delete workflow runs and run ownership rows older than their policy's `maxAge`, batched.
15994
+ * The `workflowSnapshot` policy covers both storage versions: storage v2 runs are pruned by
15995
+ * their header's last write, together with their entries.
15996
+ */
15346
15997
  async prune(policies, options) {
15347
15998
  const targets = resolveTargets({
15348
15999
  policies,
15349
16000
  descriptor: WorkflowsLibSQL.retentionTables,
15350
16001
  order: ["workflowSnapshot", "runOwnership"]
15351
16002
  });
16003
+ const snapshotTarget = targets.findIndex((target) => target.table === _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT);
16004
+ if (snapshotTarget !== -1) targets.splice(snapshotTarget + 1, 0, {
16005
+ ...targets[snapshotTarget],
16006
+ table: _mastra_core_storage.TABLE_WORKFLOW_RUNS
16007
+ });
15352
16008
  return runPrune({
15353
16009
  db: this.#db,
15354
16010
  domain: "workflows",
15355
16011
  targets,
15356
16012
  options,
15357
- logger: this.logger
16013
+ logger: this.logger,
16014
+ deleteBatch: (target, cutoff, limit) => target.table === _mastra_core_storage.TABLE_WORKFLOW_RUNS ? this.#pruneRunsBatch(cutoff, limit) : void 0
15358
16015
  });
15359
16016
  }
16017
+ /**
16018
+ * Deletes up to `limit` aged storage v2 runs in one write transaction: detaches the runs built on
16019
+ * their state, then deletes their entries and headers. Every statement selects the same runs, and
16020
+ * the transaction holds the write lock, so they agree.
16021
+ */
16022
+ async #pruneRunsBatch(cutoff, limit) {
16023
+ const doomed = `SELECT workflow_name, run_id FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUNS}
16024
+ WHERE updatedAt < ? ORDER BY updatedAt, workflow_name, run_id LIMIT ?`;
16025
+ return this.executeWithRetry(() => inWriteTransaction(this.#client, async (tx) => {
16026
+ const shared = await tx.execute({
16027
+ sql: `SELECT d.workflow_name, d.run_id FROM (${doomed}) d
16028
+ JOIN ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} r USING (workflow_name, run_id) WHERE r.stateShared`,
16029
+ args: [cutoff, limit]
16030
+ });
16031
+ for (const run of shared.rows) await this.#detachStateChildren(tx, run.workflow_name, run.run_id);
16032
+ await tx.execute({
16033
+ sql: `DELETE FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUN_ENTRIES} WHERE (workflow_name, run_id) IN (${doomed})`,
16034
+ args: [cutoff, limit]
16035
+ });
16036
+ return (await tx.execute({
16037
+ sql: `DELETE FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} WHERE (workflow_name, run_id) IN (${doomed})`,
16038
+ args: [cutoff, limit]
16039
+ })).rowsAffected;
16040
+ }), `prune ${_mastra_core_storage.TABLE_WORKFLOW_RUNS}`);
16041
+ }
15360
16042
  async setupPragmaSettings() {
15361
16043
  try {
15362
16044
  await this.#client.execute("PRAGMA busy_timeout = 10000;");
@@ -15388,6 +16070,10 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends _mastra_core_storage.Workflo
15388
16070
  args: [workflowName, runId]
15389
16071
  });
15390
16072
  if (!existingSnapshotResult.rows?.[0]) {
16073
+ if ((await tx.execute({
16074
+ sql: `SELECT 1 FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} WHERE workflow_name = ? AND run_id = ?`,
16075
+ args: [workflowName, runId]
16076
+ })).rows.length > 0) throw new Error(`updateWorkflowResults does not support storage v2 runs (run ${runId})`);
15391
16077
  await tx.rollback();
15392
16078
  return {};
15393
16079
  }
@@ -15422,12 +16108,51 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends _mastra_core_storage.Workflo
15422
16108
  }
15423
16109
  }), "updateWorkflowResults");
15424
16110
  }
15425
- async updateWorkflowState({ workflowName, runId, opts, fence: explicitFence }) {
16111
+ async updateWorkflowState({ workflowName, runId, opts, fence: explicitFence, storageVersion }) {
15426
16112
  const check = this.#runFenceCheck(explicitFence, runId, "updateWorkflowState");
15427
16113
  return this.executeWithRetry(() => withClientWriteLock(this.#client, async () => {
15428
16114
  const tx = await this.#client.transaction("write");
15429
16115
  try {
15430
16116
  if (check) await assertRunFence(tx, check);
16117
+ if (storageVersion !== 1) {
16118
+ const row = (await tx.execute({
16119
+ sql: `SELECT json(header) AS header FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} WHERE workflow_name = ? AND run_id = ?`,
16120
+ args: [workflowName, runId]
16121
+ })).rows[0];
16122
+ if (row) {
16123
+ const current = JSON.parse(row.header);
16124
+ const { expectedStatus, ...patch } = opts;
16125
+ if (!(0, _mastra_core_storage.matchesExpectedWorkflowStatus)(current.status, expectedStatus)) {
16126
+ await tx.rollback();
16127
+ return;
16128
+ }
16129
+ const header = {
16130
+ ...current,
16131
+ ...patch
16132
+ };
16133
+ await tx.execute({
16134
+ sql: `UPDATE ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} SET header = jsonb(?), status = ?, updatedAt = ? WHERE workflow_name = ? AND run_id = ?`,
16135
+ args: [
16136
+ safeStringify(header),
16137
+ header.status,
16138
+ (/* @__PURE__ */ new Date()).toISOString(),
16139
+ workflowName,
16140
+ runId
16141
+ ]
16142
+ });
16143
+ const entries = await tx.execute({
16144
+ sql: `SELECT ${entriesSql("r")} AS entries FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} r WHERE r.workflow_name = ? AND r.run_id = ?`,
16145
+ args: [workflowName, runId]
16146
+ });
16147
+ const snapshot = await this.#materializeRun(tx, header, entries.rows[0].entries);
16148
+ await tx.commit();
16149
+ return snapshot;
16150
+ }
16151
+ if (storageVersion === 2) {
16152
+ await tx.rollback();
16153
+ return;
16154
+ }
16155
+ }
15431
16156
  const existingSnapshotResult = await tx.execute({
15432
16157
  sql: `SELECT json(snapshot) as snapshot FROM ${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
15433
16158
  args: [workflowName, runId]
@@ -15497,15 +16222,32 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends _mastra_core_storage.Workflo
15497
16222
  }
15498
16223
  async loadWorkflowRunStatus({ workflowName, runId }) {
15499
16224
  return (await this.#client.execute({
15500
- sql: `SELECT json_extract(snapshot, '$.status') AS status FROM ${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
15501
- args: [workflowName, runId]
16225
+ sql: `SELECT COALESCE(
16226
+ (SELECT status FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} WHERE workflow_name = ? AND run_id = ?),
16227
+ (SELECT json_extract(snapshot, '$.status') FROM ${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?)
16228
+ ) AS status`,
16229
+ args: [
16230
+ workflowName,
16231
+ runId,
16232
+ workflowName,
16233
+ runId
16234
+ ]
15502
16235
  })).rows?.[0]?.status ?? null;
15503
16236
  }
15504
- async loadWorkflowSnapshot({ workflowName, runId }) {
16237
+ async loadWorkflowSnapshot({ workflowName, runId, storageVersion }) {
15505
16238
  this.logger.debug("Loading workflow snapshot", {
15506
16239
  workflowName,
15507
16240
  runId
15508
16241
  });
16242
+ if (storageVersion !== 1) {
16243
+ const row = (await this.#client.execute({
16244
+ sql: `SELECT json(r.header) AS header, ${entriesSql("r")} AS entries
16245
+ FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} r WHERE r.workflow_name = ? AND r.run_id = ?`,
16246
+ args: [workflowName, runId]
16247
+ })).rows[0];
16248
+ if (row) return this.#materializeRun(this.#client, row.header, row.entries);
16249
+ if (storageVersion === 2) return null;
16250
+ }
15509
16251
  const d = await this.#db.select({
15510
16252
  tableName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
15511
16253
  keys: {
@@ -15515,7 +16257,7 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends _mastra_core_storage.Workflo
15515
16257
  });
15516
16258
  return d ? d.snapshot : null;
15517
16259
  }
15518
- async getWorkflowRunById({ runId, workflowName }) {
16260
+ async getWorkflowRunById({ runId, workflowName, storageVersion }) {
15519
16261
  const conditions = [];
15520
16262
  const args = [];
15521
16263
  if (runId) {
@@ -15529,11 +16271,15 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends _mastra_core_storage.Workflo
15529
16271
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
15530
16272
  try {
15531
16273
  const result = await this.#client.execute({
15532
- sql: `SELECT workflow_name, run_id, resourceId, json(snapshot) as snapshot, createdAt, updatedAt FROM ${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT} ${whereClause} ORDER BY createdAt DESC LIMIT 1`,
15533
- args
16274
+ sql: this.#runsSql({
16275
+ v1Where: storageVersion === 2 ? null : whereClause,
16276
+ v2Where: storageVersion === 1 ? null : whereClause,
16277
+ page: "LIMIT 1"
16278
+ }),
16279
+ args: [...storageVersion === 2 ? [] : args, ...storageVersion === 1 ? [] : args]
15534
16280
  });
15535
16281
  if (!result.rows?.[0]) return null;
15536
- return this.parseWorkflowRun(result.rows[0]);
16282
+ return await this.#parseRun(result.rows[0]);
15537
16283
  } catch (error) {
15538
16284
  throw new _mastra_core_error.MastraError({
15539
16285
  id: (0, _mastra_core_storage.createStorageErrorId)("LIBSQL", "GET_WORKFLOW_RUN_BY_ID", "FAILED"),
@@ -15542,14 +16288,33 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends _mastra_core_storage.Workflo
15542
16288
  }, error);
15543
16289
  }
15544
16290
  }
15545
- async deleteWorkflowRunById({ runId, workflowName, fence }) {
16291
+ async deleteWorkflowRunById({ runId, workflowName, storageVersion, fence }) {
15546
16292
  const check = this.#runFenceCheck(fence, runId, "deleteWorkflowRunById");
15547
16293
  return this.executeWithRetry(async () => {
15548
16294
  try {
15549
- await withRunFence(this.#client, check, (writer) => writer.execute({
15550
- sql: `DELETE FROM ${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
15551
- args: [workflowName, runId]
15552
- }));
16295
+ if (storageVersion === 1) {
16296
+ await withRunFence(this.#client, check, (writer) => writer.execute({
16297
+ sql: `DELETE FROM ${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
16298
+ args: [workflowName, runId]
16299
+ }));
16300
+ return;
16301
+ }
16302
+ await inWriteTransaction(this.#client, async (tx) => {
16303
+ if (check) await assertRunFence(tx, check);
16304
+ const run = await tx.execute({
16305
+ sql: `SELECT stateShared FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} WHERE workflow_name = ? AND run_id = ?`,
16306
+ args: [workflowName, runId]
16307
+ });
16308
+ if (Number(run.rows[0]?.stateShared)) await this.#detachStateChildren(tx, workflowName, runId);
16309
+ for (const table of [
16310
+ _mastra_core_storage.TABLE_WORKFLOW_RUNS,
16311
+ _mastra_core_storage.TABLE_WORKFLOW_RUN_ENTRIES,
16312
+ ...storageVersion === 2 ? [] : [_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT]
16313
+ ]) await tx.execute({
16314
+ sql: `DELETE FROM ${table} WHERE workflow_name = ? AND run_id = ?`,
16315
+ args: [workflowName, runId]
16316
+ });
16317
+ });
15553
16318
  } catch (error) {
15554
16319
  if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
15555
16320
  throw new _mastra_core_error.MastraError({
@@ -15568,52 +16333,74 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends _mastra_core_storage.Workflo
15568
16333
  try {
15569
16334
  const conditions = [];
15570
16335
  const args = [];
16336
+ const v2Conditions = [];
16337
+ const v2Args = [];
15571
16338
  if (workflowName) {
15572
16339
  conditions.push("workflow_name = ?");
15573
16340
  args.push(workflowName);
16341
+ v2Conditions.push("workflow_name = ?");
16342
+ v2Args.push(workflowName);
15574
16343
  }
15575
16344
  if (status) {
15576
16345
  conditions.push("json_extract(snapshot, '$.status') = ?");
15577
16346
  args.push(status);
16347
+ v2Conditions.push("status = ?");
16348
+ v2Args.push(status);
15578
16349
  }
15579
16350
  if (fromDate) {
15580
16351
  conditions.push("createdAt >= ?");
15581
16352
  args.push(fromDate.toISOString());
16353
+ v2Conditions.push("createdAt >= ?");
16354
+ v2Args.push(fromDate.toISOString());
15582
16355
  }
15583
16356
  if (toDate) {
15584
16357
  conditions.push("createdAt <= ?");
15585
16358
  args.push(toDate.toISOString());
16359
+ v2Conditions.push("createdAt <= ?");
16360
+ v2Args.push(toDate.toISOString());
15586
16361
  }
15587
16362
  if (resourceId) {
15588
16363
  if (await this.#db.hasColumn(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, "resourceId")) {
15589
16364
  conditions.push("resourceId = ?");
15590
16365
  args.push(resourceId);
15591
16366
  } else this.logger.warn(`[${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
16367
+ v2Conditions.push("resourceId = ?");
16368
+ v2Args.push(resourceId);
15592
16369
  }
15593
16370
  if (threadId) {
15594
16371
  conditions.push(`(EXISTS (SELECT 1 FROM json_each(snapshot, '$.context') AS je WHERE json_extract(je.value, '$.status') = 'suspended' AND json_extract(je.value, '$.suspendPayload.__streamState.messageList.memoryInfo.threadId') = ?) OR json_extract(snapshot, '$.context.input.messageListState.memoryInfo.threadId') = ?)`);
15595
16372
  args.push(threadId, threadId);
16373
+ v2Conditions.push("threadId = ?");
16374
+ v2Args.push(threadId);
15596
16375
  }
15597
16376
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
16377
+ const v2WhereClause = v2Conditions.length > 0 ? `WHERE ${v2Conditions.join(" AND ")}` : "";
15598
16378
  let total = 0;
15599
16379
  const usePagination = typeof perPage === "number" && typeof page === "number";
15600
16380
  if (usePagination) {
15601
16381
  const countResult = await this.#client.execute({
15602
- sql: `SELECT COUNT(*) as count FROM ${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT} ${whereClause}`,
15603
- args
16382
+ sql: `SELECT (SELECT COUNT(*) FROM ${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT} ${whereClause})
16383
+ + (SELECT COUNT(*) FROM ${_mastra_core_storage.TABLE_WORKFLOW_RUNS} ${v2WhereClause}) AS count`,
16384
+ args: [...args, ...v2Args]
15604
16385
  });
15605
16386
  total = Number(countResult.rows?.[0]?.count ?? 0);
15606
16387
  }
15607
16388
  const normalizedPerPage = usePagination ? (0, _mastra_core_storage.normalizePerPage)(perPage, Number.MAX_SAFE_INTEGER) : 0;
15608
16389
  const offset = usePagination ? page * normalizedPerPage : 0;
15609
- const runs = ((await this.#client.execute({
15610
- sql: `SELECT workflow_name, run_id, resourceId, ${summary ? `json_object('status', json_extract(snapshot, '$.status'), 'timestamp', json_extract(snapshot, '$.timestamp'))` : "json(snapshot)"} as snapshot, createdAt, updatedAt FROM ${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT} ${whereClause} ORDER BY createdAt DESC${usePagination ? ` LIMIT ? OFFSET ?` : ""}`,
15611
- args: usePagination ? [
16390
+ const result = await this.#client.execute({
16391
+ sql: this.#runsSql({
16392
+ v1Where: whereClause,
16393
+ v2Where: v2WhereClause,
16394
+ summary,
16395
+ page: usePagination ? "LIMIT ? OFFSET ?" : ""
16396
+ }),
16397
+ args: [
15612
16398
  ...args,
15613
- normalizedPerPage,
15614
- offset
15615
- ] : args
15616
- })).rows || []).map((row) => this.parseWorkflowRun(row));
16399
+ ...v2Args,
16400
+ ...usePagination ? [normalizedPerPage, offset] : []
16401
+ ]
16402
+ });
16403
+ const runs = await Promise.all((result.rows || []).map((row) => this.#parseRun(row)));
15617
16404
  return {
15618
16405
  runs,
15619
16406
  total: total || runs.length