@mastra/pg 1.35.0-alpha.3 → 1.36.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/docs/SKILL.md +48 -0
  2. package/dist/docs/assets/SOURCE_MAP.json +162 -0
  3. package/dist/docs/references/docs-deployment-workers.md +404 -0
  4. package/dist/docs/references/docs-memory-semantic-recall.md +485 -0
  5. package/dist/docs/references/docs-memory-working-memory.md +450 -0
  6. package/dist/docs/references/docs-storage.md +224 -0
  7. package/dist/docs/references/integrations-databases-clickhouse-managed-postgres.md +82 -0
  8. package/dist/docs/references/integrations-databases-dynamodb.md +286 -0
  9. package/dist/docs/references/integrations-databases-neon.md +222 -0
  10. package/dist/docs/references/integrations-databases-postgresql.md +592 -0
  11. package/dist/docs/references/integrations-vectors-pg.md +451 -0
  12. package/dist/docs/references/reference-memory-memory-class.md +237 -0
  13. package/dist/docs/references/reference-processors-message-history-processor.md +89 -0
  14. package/dist/docs/references/reference-processors-semantic-recall-processor.md +119 -0
  15. package/dist/docs/references/reference-processors-working-memory-processor.md +154 -0
  16. package/dist/docs/references/reference-rag-chunking-and-embedding.md +184 -0
  17. package/dist/docs/references/reference-rag-metadata-filters.md +238 -0
  18. package/dist/docs/references/reference-rag-overview.md +76 -0
  19. package/dist/docs/references/reference-rag-retrieval.md +672 -0
  20. package/dist/docs/references/reference-rag-vector-databases.md +808 -0
  21. package/dist/docs/references/reference-storage-composite.md +433 -0
  22. package/dist/docs/references/reference-storage-retention.md +310 -0
  23. package/dist/docs/references/reference-tools-vector-query-tool.md +500 -0
  24. package/dist/index.cjs +779 -57
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.js +780 -58
  27. package/dist/index.js.map +1 -1
  28. package/dist/storage/db/index.d.ts +3 -3
  29. package/dist/storage/db/index.d.ts.map +1 -1
  30. package/dist/storage/domains/workflows/index.d.ts +87 -7
  31. package/dist/storage/domains/workflows/index.d.ts.map +1 -1
  32. package/dist/storage/retention.d.ts +3 -1
  33. package/dist/storage/retention.d.ts.map +1 -1
  34. package/package.json +6 -6
package/dist/index.cjs CHANGED
@@ -5242,13 +5242,13 @@ function cutoffFor(policy, anchorType, now = Date.now()) {
5242
5242
  return anchorType === "epoch-ms" ? cutoffMs : new Date(cutoffMs);
5243
5243
  }
5244
5244
  const runBatchedDelete = _mastra_core_storage.runRetentionBatches;
5245
- function runPrune({ db, domain, targets, options }) {
5245
+ function runPrune({ db, domain, targets, options, deleteBatch }) {
5246
5246
  return (0, _mastra_core_storage.executeRetentionPrune)({
5247
5247
  domain,
5248
5248
  targets,
5249
5249
  options,
5250
5250
  cutoffFor: (target, now) => cutoffFor(target.policy, target.anchorType ?? "timestamp", now),
5251
- deleteBatch: (target, cutoff, limit) => db.pruneBatch({
5251
+ deleteBatch: (target, cutoff, limit) => deleteBatch?.(target, cutoff, limit) ?? db.pruneBatch({
5252
5252
  tableName: target.table,
5253
5253
  column: target.column,
5254
5254
  cutoff,
@@ -25625,6 +25625,16 @@ function workflowSnapshotStatusIndexSQL(indexName, schemaName, concurrently = fa
25625
25625
  });
25626
25626
  return `CREATE INDEX ${concurrently ? "CONCURRENTLY " : ""}IF NOT EXISTS "${indexName}" ON ${tableName} (workflow_name, (snapshot ->> 'status'), "createdAt" DESC)`;
25627
25627
  }
25628
+ /**
25629
+ * Storage v2 index names share the long `<schema>_mastra_workflow_runs_` prefix, so a long
25630
+ * schema name would collapse them to one 63-byte identifier under plain truncation; hash instead.
25631
+ */
25632
+ function runIndexName(schemaPrefix, baseName) {
25633
+ return buildConstraintName({
25634
+ baseName: `${schemaPrefix}${baseName}`,
25635
+ hashWhenTruncated: true
25636
+ });
25637
+ }
25628
25638
  /** Base name (before any schema prefix) of the expression index backing the threadId filter. */
25629
25639
  const WORKFLOW_SNAPSHOT_THREAD_ID_INDEX = "mastra_workflow_snapshot_threadid_idx";
25630
25640
  /**
@@ -25669,13 +25679,126 @@ function workflowSnapshotThreadIdIndexSQL(indexName, schemaName, concurrently =
25669
25679
  });
25670
25680
  return `CREATE INDEX ${concurrently ? "CONCURRENTLY " : ""}IF NOT EXISTS "${indexName}" ON ${tableName} ((${WORKFLOW_SNAPSHOT_THREAD_ID_EXPR}))`;
25671
25681
  }
25682
+ /** Base name (before any schema prefix) of the index finding the `state-base` entries built on a run. */
25683
+ const WORKFLOW_RUN_ENTRIES_STATE_BASE_INDEX = "mastra_workflow_run_entries_state_base_idx";
25684
+ /** The run a `state-base` entry builds on; `#detachStateChildren` must use these exact expressions. */
25685
+ const STATE_BASE_RUN_EXPR = `(payload ->> 'workflowName'), (payload ->> 'runId')`;
25686
+ /**
25687
+ * Partial expression index finding the `state-base` entries built on a run, so deleting a run whose
25688
+ * state others build on doesn't scan every entry. See {@link workflowSnapshotStatusIndexSQL} for
25689
+ * `concurrently`.
25690
+ */
25691
+ function stateBaseIndexSQL(indexName, schemaName, concurrently = false) {
25692
+ const tableName = getTableName({
25693
+ indexName: _mastra_core_storage.TABLE_WORKFLOW_RUN_ENTRIES,
25694
+ schemaName: getSchemaName(schemaName)
25695
+ });
25696
+ return `CREATE INDEX ${concurrently ? "CONCURRENTLY " : ""}IF NOT EXISTS "${indexName}" ON ${tableName} (${STATE_BASE_RUN_EXPR}) WHERE kind = 'state-base'`;
25697
+ }
25698
+ /**
25699
+ * The entries a storage v2 write adds: one per step, in order, then one per step patch, then the
25700
+ * state or state base (a checkpoint with neither starts the state over at `{}`), then the state
25701
+ * patch unless a full state was given.
25702
+ */
25703
+ function journalEntries(steps, { state, stateBase, statePatch }, checkpoint, stepPatches = {}) {
25704
+ const entries = Object.entries(steps).map(([stepId, result]) => ({
25705
+ kind: "step",
25706
+ step_id: stepId,
25707
+ payload: result
25708
+ }));
25709
+ for (const [stepId, ops] of Object.entries(stepPatches)) entries.push({
25710
+ kind: "step-patch",
25711
+ step_id: stepId,
25712
+ payload: ops
25713
+ });
25714
+ if (state !== void 0) entries.push({
25715
+ kind: "state",
25716
+ step_id: null,
25717
+ payload: state
25718
+ });
25719
+ else if (stateBase !== void 0) entries.push({
25720
+ kind: "state-base",
25721
+ step_id: null,
25722
+ payload: stateBase
25723
+ });
25724
+ else if (checkpoint) entries.push({
25725
+ kind: "state",
25726
+ step_id: null,
25727
+ payload: {}
25728
+ });
25729
+ if (state === void 0 && statePatch !== void 0) entries.push({
25730
+ kind: "state-patch",
25731
+ step_id: null,
25732
+ payload: statePatch
25733
+ });
25734
+ return entries;
25735
+ }
25736
+ /**
25737
+ * Rows `e(elem, ord)` of the jsonb array parameter `param` of {@link EntryInput}, and the
25738
+ * entry columns they insert. `elem -> 'payload'` keeps a JSON null apart from an undefined
25739
+ * payload (dropped by `JSON.stringify`), which becomes SQL NULL.
25740
+ */
25741
+ const entryRows = (param) => `jsonb_array_elements(${param}::jsonb) WITH ORDINALITY AS e(elem, ord)`;
25742
+ const ENTRY_VALUES = `e.elem ->> 'kind', e.elem ->> 'step_id', e.elem -> 'payload'`;
25743
+ /** The entries of {@link EntryRow} rows; an undefined payload reads back as undefined, as in v1. */
25744
+ function toEntries(rows) {
25745
+ return rows.map((row) => {
25746
+ const payload = row.absent ? void 0 : row.payload;
25747
+ switch (row.kind) {
25748
+ case "step": return {
25749
+ seq: row.seq,
25750
+ kind: "step",
25751
+ stepId: row.step_id,
25752
+ result: payload
25753
+ };
25754
+ case "step-patch": return {
25755
+ seq: row.seq,
25756
+ kind: "step-patch",
25757
+ stepId: row.step_id,
25758
+ ops: payload
25759
+ };
25760
+ case "state": return {
25761
+ seq: row.seq,
25762
+ kind: "state",
25763
+ state: payload
25764
+ };
25765
+ case "state-patch": return {
25766
+ seq: row.seq,
25767
+ kind: "state-patch",
25768
+ ops: payload
25769
+ };
25770
+ case "state-base": return {
25771
+ seq: row.seq,
25772
+ kind: "state-base",
25773
+ base: payload
25774
+ };
25775
+ case "state-draft": {
25776
+ const draft = payload;
25777
+ return "state" in draft ? {
25778
+ seq: row.seq,
25779
+ kind: "state-draft",
25780
+ state: draft.state
25781
+ } : {
25782
+ seq: row.seq,
25783
+ kind: "state-draft",
25784
+ on: draft.on,
25785
+ ops: draft.ops
25786
+ };
25787
+ }
25788
+ }
25789
+ });
25790
+ }
25672
25791
  var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorage {
25673
25792
  #db;
25674
25793
  #schema;
25675
25794
  #skipDefaultIndexes;
25676
25795
  #indexes;
25677
25796
  /** Tables managed by this domain */
25678
- static MANAGED_TABLES = [_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT];
25797
+ static MANAGED_TABLES = [
25798
+ _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
25799
+ _mastra_core_storage.TABLE_WORKFLOW_RUNS,
25800
+ _mastra_core_storage.TABLE_WORKFLOW_RUN_ENTRIES
25801
+ ];
25679
25802
  /**
25680
25803
  * Workflow run snapshots accumulate as runs execute. Anchored on the
25681
25804
  * timezone-aware `updatedAtZ` mirror column (last activity) so suspended or
@@ -25738,11 +25861,43 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
25738
25861
  };
25739
25862
  }
25740
25863
  static getDefaultIndexDefs(schemaPrefix) {
25741
- return [{
25742
- name: `${schemaPrefix}mastra_workflow_snapshot_name_createdat_idx`,
25743
- table: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
25744
- columns: ["workflow_name", "createdAt DESC"]
25745
- }];
25864
+ return [
25865
+ {
25866
+ name: `${schemaPrefix}mastra_workflow_snapshot_name_createdat_idx`,
25867
+ table: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
25868
+ columns: ["workflow_name", "createdAt DESC"]
25869
+ },
25870
+ {
25871
+ name: runIndexName(schemaPrefix, "mastra_workflow_runs_name_createdat_idx"),
25872
+ table: _mastra_core_storage.TABLE_WORKFLOW_RUNS,
25873
+ columns: ["workflow_name", "createdAt DESC"]
25874
+ },
25875
+ {
25876
+ name: runIndexName(schemaPrefix, "mastra_workflow_runs_name_status_createdat_idx"),
25877
+ table: _mastra_core_storage.TABLE_WORKFLOW_RUNS,
25878
+ columns: [
25879
+ "workflow_name",
25880
+ "status",
25881
+ "createdAt DESC"
25882
+ ]
25883
+ },
25884
+ {
25885
+ name: runIndexName(schemaPrefix, "mastra_workflow_runs_threadid_idx"),
25886
+ table: _mastra_core_storage.TABLE_WORKFLOW_RUNS,
25887
+ columns: ["threadId"]
25888
+ },
25889
+ {
25890
+ name: runIndexName(schemaPrefix, "mastra_workflow_run_entries_step_idx"),
25891
+ table: _mastra_core_storage.TABLE_WORKFLOW_RUN_ENTRIES,
25892
+ columns: [
25893
+ "workflow_name",
25894
+ "run_id",
25895
+ "kind",
25896
+ "step_id",
25897
+ "seq"
25898
+ ]
25899
+ }
25900
+ ];
25746
25901
  }
25747
25902
  /**
25748
25903
  * Returns all DDL statements for this domain: table with unique constraint.
@@ -25758,9 +25913,17 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
25758
25913
  schemaName,
25759
25914
  includeAllConstraints: true
25760
25915
  }));
25916
+ for (const [tableName, config] of Object.entries(_mastra_core_storage.WORKFLOW_RUN_JOURNAL_TABLE_CONFIGS)) statements.push(generateTableSQL({
25917
+ tableName,
25918
+ schema: config.columns,
25919
+ schemaName,
25920
+ compositePrimaryKey: config.compositePrimaryKey,
25921
+ includeAllConstraints: true
25922
+ }));
25761
25923
  for (const idx of WorkflowsPG.getDefaultIndexDefs(schemaPrefix)) statements.push(generateIndexSQL(idx, schemaName));
25762
25924
  statements.push(`${workflowSnapshotStatusIndexSQL(workflowSnapshotStatusIndexName(parsedSchema), schemaName)};`);
25763
25925
  statements.push(`${workflowSnapshotThreadIdIndexSQL(workflowSnapshotThreadIdIndexName(parsedSchema), schemaName)};`);
25926
+ statements.push(`${stateBaseIndexSQL(runIndexName(schemaPrefix, WORKFLOW_RUN_ENTRIES_STATE_BASE_INDEX), schemaName)};`);
25764
25927
  statements.push(generateTableSQL({
25765
25928
  tableName: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS,
25766
25929
  schema: _mastra_core_storage.RUN_FENCING_TABLE_SCHEMAS[_mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS],
@@ -25786,6 +25949,12 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
25786
25949
  } catch (error) {
25787
25950
  this.logger?.warn?.(`Failed to create index ${indexDef.name}:`, error);
25788
25951
  }
25952
+ const stateBaseIndexName = runIndexName(this.#schema !== "public" ? `${schemaNamePrefix(this.#schema)}_` : "", WORKFLOW_RUN_ENTRIES_STATE_BASE_INDEX);
25953
+ try {
25954
+ await this.#db.createIndexFromStatement(stateBaseIndexName, stateBaseIndexSQL(stateBaseIndexName, this.#schema, true));
25955
+ } catch (error) {
25956
+ this.logger?.warn?.(`Failed to create index ${stateBaseIndexName}:`, error);
25957
+ }
25789
25958
  if (await this.#db.getColumnType(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, "snapshot") !== "jsonb") return;
25790
25959
  const indexName = workflowSnapshotStatusIndexName(this.#schema);
25791
25960
  try {
@@ -25814,6 +25983,11 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
25814
25983
  tableName: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS,
25815
25984
  schema: _mastra_core_storage.RUN_FENCING_TABLE_SCHEMAS[_mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS]
25816
25985
  });
25986
+ for (const [tableName, config] of Object.entries(_mastra_core_storage.WORKFLOW_RUN_JOURNAL_TABLE_CONFIGS)) await this.#db.createTable({
25987
+ tableName,
25988
+ schema: config.columns,
25989
+ compositePrimaryKey: config.compositePrimaryKey
25990
+ });
25817
25991
  await this.createDefaultIndexes();
25818
25992
  await this.createCustomIndexes();
25819
25993
  }
@@ -25840,8 +26014,22 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
25840
26014
  this.logger?.warn?.(`Failed to create retention index for ${entry.table}:`, error);
25841
26015
  }
25842
26016
  }
26017
+ if (!policies.workflowSnapshot) return;
26018
+ try {
26019
+ await this.#db.ensureIndex({
26020
+ indexName: `${prefix}mastra_workflow_runs_retention_idx`,
26021
+ tableName: _mastra_core_storage.TABLE_WORKFLOW_RUNS,
26022
+ column: "updatedAtZ"
26023
+ });
26024
+ } catch (error) {
26025
+ this.logger?.warn?.(`Failed to create retention index for ${_mastra_core_storage.TABLE_WORKFLOW_RUNS}:`, error);
26026
+ }
25843
26027
  }
25844
- /** Delete workflow run snapshots and run ownership rows older than their policy's `maxAge`, batched. */
26028
+ /**
26029
+ * Delete workflow runs and run ownership rows older than their policy's `maxAge`, batched.
26030
+ * The `workflowSnapshot` policy covers both storage versions: storage v2 runs are pruned by
26031
+ * their header's last write, together with their entries.
26032
+ */
25845
26033
  async prune(policies, options) {
25846
26034
  await this.ensureRetentionIndexes(policies);
25847
26035
  const targets = resolveTargets({
@@ -25849,11 +26037,40 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
25849
26037
  descriptor: WorkflowsPG.retentionTables,
25850
26038
  order: ["workflowSnapshot", "runOwnership"]
25851
26039
  });
26040
+ const snapshotTarget = targets.findIndex((target) => target.table === _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT);
26041
+ if (snapshotTarget !== -1) targets.splice(snapshotTarget + 1, 0, {
26042
+ ...targets[snapshotTarget],
26043
+ table: _mastra_core_storage.TABLE_WORKFLOW_RUNS
26044
+ });
25852
26045
  return runPrune({
25853
26046
  db: this.#db,
25854
26047
  domain: "workflows",
25855
26048
  targets,
25856
- options
26049
+ options,
26050
+ deleteBatch: (target, cutoff, limit) => target.table === _mastra_core_storage.TABLE_WORKFLOW_RUNS ? this.#pruneRunsBatch(cutoff, limit) : void 0
26051
+ });
26052
+ }
26053
+ /**
26054
+ * Deletes up to `limit` aged storage v2 runs, header and entries together, first detaching the
26055
+ * runs whose state others build on (see `#detachStateChildren`).
26056
+ */
26057
+ async #pruneRunsBatch(cutoff, limit) {
26058
+ return this.#db.client.tx(async (t) => {
26059
+ const doomed = await t.manyOrNone(`SELECT workflow_name, run_id, "stateShared" FROM ${this.#runsTable()}
26060
+ WHERE "updatedAtZ" < $1
26061
+ ORDER BY "updatedAtZ"
26062
+ LIMIT $2
26063
+ FOR UPDATE SKIP LOCKED`, [cutoff, limit]);
26064
+ if (!doomed.length) return 0;
26065
+ for (const run of doomed) if (run.stateShared) await this.#detachStateChildren(t, run.workflow_name, run.run_id);
26066
+ return (await t.query(`WITH doomed AS (
26067
+ SELECT * FROM unnest($1::text[], $2::text[]) AS d(workflow_name, run_id)
26068
+ ), entries AS (
26069
+ DELETE FROM ${this.#entriesTable()} e USING doomed d
26070
+ WHERE e.workflow_name = d.workflow_name AND e.run_id = d.run_id
26071
+ )
26072
+ DELETE FROM ${this.#runsTable()} r USING doomed d
26073
+ WHERE r.workflow_name = d.workflow_name AND r.run_id = d.run_id`, [doomed.map((run) => run.workflow_name), doomed.map((run) => run.run_id)])).rowCount ?? 0;
25857
26074
  });
25858
26075
  }
25859
26076
  /**
@@ -25870,6 +26087,459 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
25870
26087
  async dangerouslyClearAll() {
25871
26088
  await this.#db.clearTable({ tableName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT });
25872
26089
  await this.#db.clearTable({ tableName: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS });
26090
+ await this.#db.clearTable({ tableName: _mastra_core_storage.TABLE_WORKFLOW_RUNS });
26091
+ await this.#db.clearTable({ tableName: _mastra_core_storage.TABLE_WORKFLOW_RUN_ENTRIES });
26092
+ }
26093
+ supportsRunJournal() {
26094
+ return true;
26095
+ }
26096
+ #runsTable() {
26097
+ return getTableName({
26098
+ indexName: _mastra_core_storage.TABLE_WORKFLOW_RUNS,
26099
+ schemaName: getSchemaName(this.#schema)
26100
+ });
26101
+ }
26102
+ #entriesTable() {
26103
+ return getTableName({
26104
+ indexName: _mastra_core_storage.TABLE_WORKFLOW_RUN_ENTRIES,
26105
+ schemaName: getSchemaName(this.#schema)
26106
+ });
26107
+ }
26108
+ /**
26109
+ * A jsonb array of {@link EntryRow} for the storage v2 run `alias` refers to: each step's
26110
+ * latest entry, ordered by when each step was first written so `context` keys keep their write
26111
+ * order, the step patches after it (in `seq` order, for `latestStepResults`), and the entries
26112
+ * `materializeState` uses for the latest state: the latest full state or state base and every
26113
+ * state patch after it, in `seq` order. Drafts are left out.
26114
+ *
26115
+ * With `summaryFieldsParam` (the parameter holding `STEP_RESULT_SUMMARY_FIELDS`), successful
26116
+ * steps other than `input` come back as their summary, built here so their data is never sent,
26117
+ * unless step patches follow it (the caller summarizes the patched result instead).
26118
+ * Like `summarizeStepResult`, it keeps a field that is JSON null and leaves out one that is absent.
26119
+ */
26120
+ #entriesSql(alias, { summaryFieldsParam } = {}) {
26121
+ let payload = "x.payload";
26122
+ if (summaryFieldsParam) {
26123
+ const summarized = `(x.kind = 'step' AND x.step_id <> 'input' AND x.payload ->> 'status' = 'success'
26124
+ AND (x.patch_seq IS NULL OR x.patch_seq < x.seq))`;
26125
+ payload = `CASE WHEN ${summarized} THEN (
26126
+ SELECT COALESCE(jsonb_object_agg(f.key, f.value), '{}'::jsonb)
26127
+ FROM jsonb_each(x.payload) f WHERE f.key = ANY(${summaryFieldsParam}::text[])
26128
+ ) ELSE x.payload END, 'summarized', ${summarized}`;
26129
+ }
26130
+ return `(SELECT COALESCE(jsonb_agg(jsonb_build_object(
26131
+ 'seq', x.seq, 'kind', x.kind, 'step_id', x.step_id, 'absent', x.payload IS NULL, 'payload', ${payload}
26132
+ ) ORDER BY CASE WHEN x.kind = 'step' THEN x.first_seq ELSE x.seq END), '[]'::jsonb)
26133
+ FROM (
26134
+ SELECT e.seq, e.kind, e.step_id, e.payload,
26135
+ ROW_NUMBER() OVER (PARTITION BY e.kind, e.step_id ORDER BY e.seq DESC) AS rn,
26136
+ MIN(e.seq) OVER (PARTITION BY e.kind, e.step_id) AS first_seq,
26137
+ MAX(CASE WHEN e.kind = 'step' THEN e.seq END) OVER (PARTITION BY e.step_id) AS step_seq,
26138
+ MAX(CASE WHEN e.kind = 'step-patch' THEN e.seq END) OVER (PARTITION BY e.step_id) AS patch_seq,
26139
+ MAX(CASE WHEN e.kind IN ('state', 'state-base') THEN e.seq END) OVER () AS base_seq
26140
+ FROM ${this.#entriesTable()} e
26141
+ WHERE e.workflow_name = ${alias}.workflow_name AND e.run_id = ${alias}.run_id AND e.kind <> 'state-draft'
26142
+ ) x
26143
+ WHERE CASE x.kind
26144
+ WHEN 'step' THEN x.rn = 1
26145
+ WHEN 'step-patch' THEN x.seq > x.step_seq
26146
+ ELSE x.base_seq IS NULL OR x.seq >= x.base_seq
26147
+ END)`;
26148
+ }
26149
+ /**
26150
+ * A jsonb array of {@link EntryRow} with the entries `materializeState` uses for the state of the
26151
+ * storage v2 run `alias` refers to as of a version: `seqParam` its `seq`, `draftParam` whether
26152
+ * it is a draft. For a draft, every state entry up to it, as the drafts it is on can build on
26153
+ * any earlier version; otherwise the latest full state or state base up to it and the state
26154
+ * patches after it.
26155
+ */
26156
+ #stateEntriesSql(alias, seqParam, draftParam) {
26157
+ return `(SELECT COALESCE(jsonb_agg(jsonb_build_object(
26158
+ 'seq', x.seq, 'kind', x.kind, 'step_id', x.step_id, 'absent', x.payload IS NULL, 'payload', x.payload
26159
+ ) ORDER BY x.seq), '[]'::jsonb)
26160
+ FROM (
26161
+ SELECT e.seq, e.kind, e.step_id, e.payload,
26162
+ MAX(CASE WHEN e.kind IN ('state', 'state-base') THEN e.seq END) OVER () AS base_seq
26163
+ FROM ${this.#entriesTable()} e
26164
+ WHERE e.workflow_name = ${alias}.workflow_name AND e.run_id = ${alias}.run_id
26165
+ AND e.kind NOT IN ('step', 'step-patch') AND e.seq <= ${seqParam}::int
26166
+ ) x
26167
+ WHERE ${draftParam}::boolean OR (x.kind <> 'state-draft' AND (x.base_seq IS NULL OR x.seq >= x.base_seq)))`;
26168
+ }
26169
+ /**
26170
+ * Selects v1 snapshot rows and storage v2 runs as one result, newest first. Each version
26171
+ * has its own WHERE clause; `null` leaves it out. Storage v2 rows carry their header and
26172
+ * entries, or in summary mode only the summary snapshot. `page` is appended to the
26173
+ * combined, ordered rows (e.g. `LIMIT 1`).
26174
+ */
26175
+ async #runsQuery({ v1Where, v2Where, v1Snapshot = "snapshot", summary, page = "" }) {
26176
+ const branches = [];
26177
+ if (v1Where !== null) {
26178
+ const optional = {
26179
+ resourceId: "text",
26180
+ createdAtZ: "timestamptz",
26181
+ updatedAtZ: "timestamptz"
26182
+ };
26183
+ const columns = {};
26184
+ for (const [column, type] of Object.entries(optional)) columns[column] = await this.#db.getColumnType(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, column) !== null ? `"${column}"` : `NULL::${type} AS "${column}"`;
26185
+ branches.push(`SELECT workflow_name, run_id, ${columns.resourceId}, ${v1Snapshot} AS snapshot, NULL::jsonb AS header,
26186
+ "createdAt", ${columns.createdAtZ}, "updatedAt", ${columns.updatedAtZ}, 1 AS version
26187
+ FROM ${getTableName({
26188
+ indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
26189
+ schemaName: getSchemaName(this.#schema)
26190
+ })} ${v1Where}`);
26191
+ }
26192
+ if (v2Where !== null) {
26193
+ const columns = summary ? `jsonb_build_object('status', header -> 'status', 'timestamp', header -> 'timestamp') AS snapshot, NULL::jsonb AS header` : `NULL AS snapshot, header`;
26194
+ branches.push(`SELECT workflow_name, run_id, "resourceId", ${columns},
26195
+ "createdAt", "createdAtZ", "updatedAt", "updatedAtZ", 2 AS version
26196
+ FROM ${this.#runsTable()} ${v2Where}`);
26197
+ }
26198
+ return `SELECT p.*${summary ? "" : `, CASE WHEN p.version = 2 THEN ${this.#entriesSql("p")} END AS entries`}
26199
+ FROM (${branches.join(" UNION ALL ")} ORDER BY "createdAt" DESC ${page}) p
26200
+ ORDER BY p."createdAt" DESC`;
26201
+ }
26202
+ /**
26203
+ * The `base` that `materializeState` takes for a run's entries (as of `at`, or latest): the state
26204
+ * version of another run it builds on (`stateBaseOf`), resolved recursively, else undefined. `{}`
26205
+ * when that run isn't stored. One query per run in the chain.
26206
+ */
26207
+ async #stateBase(q, entries, at) {
26208
+ const ref = (0, _mastra_core_storage.stateBaseOf)(entries, at);
26209
+ if (!ref) return void 0;
26210
+ return (await this.#stateAt(q, ref.workflowName, ref.runId, ref))?.state ?? {};
26211
+ }
26212
+ /** A storage v2 run's state as of the version `at`, or null when the run isn't stored. */
26213
+ async #stateAt(q, workflowName, runId, at) {
26214
+ const row = await q.oneOrNone(`SELECT ${this.#stateEntriesSql("r", "$3", "$4")} AS entries
26215
+ FROM ${this.#runsTable()} r WHERE r.workflow_name = $1 AND r.run_id = $2`, [
26216
+ workflowName,
26217
+ runId,
26218
+ at.seq,
26219
+ at.draft === true
26220
+ ]);
26221
+ if (!row) return null;
26222
+ const entries = toEntries(row.entries);
26223
+ return (0, _mastra_core_storage.materializeState)(entries, {
26224
+ at,
26225
+ base: await this.#stateBase(q, entries, at)
26226
+ });
26227
+ }
26228
+ /**
26229
+ * Before a run is deleted, stores the full state in place of every `state-base` entry built on
26230
+ * it, so the runs built on it keep reading the same state. Runs inside the delete's transaction.
26231
+ */
26232
+ async #detachStateChildren(q, workflowName, runId) {
26233
+ const children = await q.manyOrNone(`SELECT workflow_name, run_id, seq, payload FROM ${this.#entriesTable()}
26234
+ WHERE kind = 'state-base' AND (${STATE_BASE_RUN_EXPR}) = ($1, $2)`, [workflowName, runId]);
26235
+ const states = /* @__PURE__ */ new Map();
26236
+ for (const child of children) {
26237
+ const version = `${child.payload.seq}:${child.payload.draft === true}`;
26238
+ let state = states.get(version);
26239
+ if (!state) {
26240
+ state = (await this.#stateAt(q, workflowName, runId, child.payload))?.state ?? {};
26241
+ states.set(version, state);
26242
+ }
26243
+ await q.none(`UPDATE ${this.#entriesTable()} SET kind = 'state', payload = $4::jsonb
26244
+ WHERE workflow_name = $1 AND run_id = $2 AND seq = $3`, [
26245
+ child.workflow_name,
26246
+ child.run_id,
26247
+ child.seq,
26248
+ toPgJson(state)
26249
+ ]);
26250
+ }
26251
+ }
26252
+ /** Rebuilds a storage v2 run's `WorkflowRunState` from its header and `#entriesSql` rows. */
26253
+ async #materializeRun(client, header, rows) {
26254
+ const entries = toEntries(rows);
26255
+ return (0, _mastra_core_storage.materializeWorkflowRun)(header, entries, await this.#stateBase(client, entries));
26256
+ }
26257
+ /** Parses a row of `#runsQuery`. */
26258
+ async #parseRun(client, row) {
26259
+ if (row.version !== 2) return this.parseWorkflowRun(row);
26260
+ const snapshot = row.entries ? await this.#materializeRun(client, row.header, row.entries) : row.snapshot;
26261
+ return {
26262
+ ...this.parseWorkflowRun({
26263
+ ...row,
26264
+ snapshot
26265
+ }),
26266
+ storageVersion: 2
26267
+ };
26268
+ }
26269
+ #runError(operation, workflowName, runId, error) {
26270
+ return new _mastra_core_error.MastraError({
26271
+ id: (0, _mastra_core_storage.createStorageErrorId)("PG", operation, "FAILED"),
26272
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
26273
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
26274
+ details: {
26275
+ workflowName,
26276
+ runId
26277
+ }
26278
+ }, error);
26279
+ }
26280
+ async loadWorkflowRunHeader({ workflowName, runId }) {
26281
+ try {
26282
+ return await this.#db.client.oneOrNone(`SELECT header, "lastSeq", "startStateSeq", "runToken" FROM ${this.#runsTable()} WHERE workflow_name = $1 AND run_id = $2`, [workflowName, runId]);
26283
+ } catch (error) {
26284
+ throw this.#runError("LOAD_WORKFLOW_RUN_HEADER", workflowName, runId, error);
26285
+ }
26286
+ }
26287
+ async loadWorkflowRun({ workflowName, runId, successfulSteps }) {
26288
+ try {
26289
+ const summary = successfulSteps === "summary";
26290
+ const run = await this.#db.client.oneOrNone(`SELECT r.header, r."lastSeq", r."startStateSeq", r."runToken", ${this.#entriesSql("r", { summaryFieldsParam: summary ? "$3" : void 0 })} AS entries
26291
+ FROM ${this.#runsTable()} r WHERE r.workflow_name = $1 AND r.run_id = $2`, summary ? [
26292
+ workflowName,
26293
+ runId,
26294
+ _mastra_core_storage.STEP_RESULT_SUMMARY_FIELDS
26295
+ ] : [workflowName, runId]);
26296
+ if (!run) return null;
26297
+ const entries = toEntries(run.entries);
26298
+ const summarizedInQuery = new Set(run.entries.filter((row) => row.summarized).map((row) => row.step_id));
26299
+ const steps = {};
26300
+ const summarizedStepIds = [];
26301
+ for (const [stepId, result] of (0, _mastra_core_storage.latestStepResults)(entries)) {
26302
+ steps[stepId] = result;
26303
+ if (summary && stepId !== "input" && result?.status === "success") {
26304
+ if (!summarizedInQuery.has(stepId)) steps[stepId] = (0, _mastra_core_storage.summarizeStepResult)(result);
26305
+ summarizedStepIds.push(stepId);
26306
+ }
26307
+ }
26308
+ const base = await this.#stateBase(this.#db.client, entries);
26309
+ const { state, statePatchBytes } = (0, _mastra_core_storage.materializeState)(entries, { base });
26310
+ return {
26311
+ header: run.header,
26312
+ lastSeq: run.lastSeq,
26313
+ startStateSeq: run.startStateSeq,
26314
+ runToken: run.runToken,
26315
+ state,
26316
+ statePatchBytes,
26317
+ steps,
26318
+ summarizedStepIds
26319
+ };
26320
+ } catch (error) {
26321
+ throw this.#runError("LOAD_WORKFLOW_RUN", workflowName, runId, error);
26322
+ }
26323
+ }
26324
+ async loadWorkflowRunSteps({ workflowName, runId, stepIds, expectedLastSeq, expectedRunToken }) {
26325
+ try {
26326
+ const run = await this.#db.client.oneOrNone(`SELECT r."lastSeq", r."runToken", (
26327
+ SELECT COALESCE(jsonb_agg(jsonb_build_object(
26328
+ 'seq', x.seq, 'kind', x.kind, 'step_id', x.step_id, 'absent', x.payload IS NULL, 'payload', x.payload
26329
+ ) ORDER BY x.seq), '[]'::jsonb)
26330
+ FROM (
26331
+ SELECT e.seq, e.kind, e.step_id, e.payload,
26332
+ MAX(CASE WHEN e.kind = 'step' THEN e.seq END) OVER (PARTITION BY e.step_id) AS step_seq
26333
+ FROM ${this.#entriesTable()} e
26334
+ WHERE e.workflow_name = r.workflow_name AND e.run_id = r.run_id
26335
+ AND e.kind IN ('step', 'step-patch') AND e.step_id = ANY($3::text[])
26336
+ ) x
26337
+ WHERE x.seq >= x.step_seq
26338
+ ) AS entries
26339
+ FROM ${this.#runsTable()} r WHERE r.workflow_name = $1 AND r.run_id = $2`, [
26340
+ workflowName,
26341
+ runId,
26342
+ stepIds
26343
+ ]);
26344
+ if (!run) return null;
26345
+ if (expectedLastSeq !== void 0 && run.lastSeq !== expectedLastSeq) return null;
26346
+ if (expectedRunToken !== void 0 && run.runToken !== expectedRunToken) return null;
26347
+ return Object.fromEntries((0, _mastra_core_storage.latestStepResults)(toEntries(run.entries)));
26348
+ } catch (error) {
26349
+ throw this.#runError("LOAD_WORKFLOW_RUN_STEPS", workflowName, runId, error);
26350
+ }
26351
+ }
26352
+ async updateWorkflowRunHeader({ workflowName, runId, opts, fence: explicitFence }) {
26353
+ const fence = (0, _mastra_core_storage.resolveRunFence)(this, explicitFence, runId);
26354
+ try {
26355
+ return await this.#db.client.tx(async (t) => {
26356
+ if (fence) await assertRunFence(t, this.#runOwnersTable(), fence, "updateWorkflowRunHeader");
26357
+ const run = await t.oneOrNone(`SELECT header FROM ${this.#runsTable()} WHERE workflow_name = $1 AND run_id = $2 FOR UPDATE`, [workflowName, runId]);
26358
+ if (!run) return void 0;
26359
+ const { expectedStatus, ...patch } = opts;
26360
+ if (!(0, _mastra_core_storage.matchesExpectedWorkflowStatus)(run.header.status, expectedStatus)) return void 0;
26361
+ const header = {
26362
+ ...run.header,
26363
+ ...patch
26364
+ };
26365
+ const now = /* @__PURE__ */ new Date();
26366
+ return await t.one(`UPDATE ${this.#runsTable()}
26367
+ SET header = $3::jsonb, status = $4, "updatedAt" = $5, "updatedAtZ" = $6
26368
+ WHERE workflow_name = $1 AND run_id = $2
26369
+ RETURNING header, "lastSeq", "startStateSeq", "runToken"`, [
26370
+ workflowName,
26371
+ runId,
26372
+ toPgJson(header),
26373
+ header.status,
26374
+ now,
26375
+ now
26376
+ ]);
26377
+ });
26378
+ } catch (error) {
26379
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
26380
+ throw this.#runError("UPDATE_WORKFLOW_RUN_HEADER", workflowName, runId, error);
26381
+ }
26382
+ }
26383
+ /**
26384
+ * Upserts a storage v2 run's header from `$1`–`$12` ({@link #headerParams}), adds `$7` (the
26385
+ * number of entries the write adds) to `lastSeq`, and returns the new `lastSeq`, `stateShared`
26386
+ * and `runToken`. `$12` is the run token of a run the write creates; a stored run keeps its own.
26387
+ * A checkpoint's state entries come last, so its `startStateSeq` is the new `lastSeq`; an append
26388
+ * keeps the stored one unless given `$13`. The header's row lock is held
26389
+ * until commit, so the writes to one run commit in `seq` order. With `expectedParam` (a text
26390
+ * array of statuses), a stored run whose status isn't in it is left as is and nothing is returned.
26391
+ */
26392
+ #upsertHeaderSql(checkpoint, expectedParam) {
26393
+ const startStateSeq = checkpoint ? {
26394
+ insert: "$7::int",
26395
+ update: "r.\"lastSeq\" + EXCLUDED.\"lastSeq\""
26396
+ } : {
26397
+ insert: "COALESCE($13::int, 0)",
26398
+ update: "COALESCE($13::int, r.\"startStateSeq\")"
26399
+ };
26400
+ return `INSERT INTO ${this.#runsTable()} AS r
26401
+ (workflow_name, run_id, "resourceId", "threadId", status, header, "lastSeq", "startStateSeq", "stateShared",
26402
+ "runToken", "createdAt", "updatedAt", "createdAtZ", "updatedAtZ")
26403
+ VALUES ($1, $2, $3, $4, $5, $6::jsonb, $7::int, ${startStateSeq.insert}, false, $12, $8, $9, $10, $11)
26404
+ ON CONFLICT (workflow_name, run_id) DO UPDATE
26405
+ SET "resourceId" = COALESCE(EXCLUDED."resourceId", r."resourceId"), "threadId" = EXCLUDED."threadId",
26406
+ status = EXCLUDED.status, header = EXCLUDED.header, "lastSeq" = r."lastSeq" + EXCLUDED."lastSeq",
26407
+ "startStateSeq" = ${startStateSeq.update},
26408
+ "updatedAt" = EXCLUDED."updatedAt", "updatedAtZ" = EXCLUDED."updatedAtZ"
26409
+ ${expectedParam ? `WHERE r.status = ANY(${expectedParam}::text[])` : ""}
26410
+ RETURNING "lastSeq", "stateShared", "runToken"`;
26411
+ }
26412
+ #headerParams(run, entryCount) {
26413
+ const now = /* @__PURE__ */ new Date();
26414
+ return [
26415
+ run.workflowName,
26416
+ run.runId,
26417
+ run.resourceId,
26418
+ run.threadId ?? null,
26419
+ run.header.status,
26420
+ toPgJson(run.header),
26421
+ entryCount,
26422
+ now,
26423
+ now,
26424
+ now,
26425
+ now,
26426
+ crypto.randomUUID()
26427
+ ];
26428
+ }
26429
+ async writeWorkflowRunCheckpoint({ fence: explicitFence, expectedStatus, steps, state, stateBase, statePatch, ...run }) {
26430
+ const entries = journalEntries(steps, {
26431
+ state,
26432
+ stateBase,
26433
+ statePatch
26434
+ }, true);
26435
+ const fence = (0, _mastra_core_storage.resolveRunFence)(this, explicitFence, run.runId);
26436
+ const expected = expectedStatus === void 0 ? [] : [[expectedStatus].flat()];
26437
+ try {
26438
+ return await this.#db.client.tx(async (t) => {
26439
+ if (fence) await assertRunFence(t, this.#runOwnersTable(), fence, "writeWorkflowRunCheckpoint");
26440
+ const base = state === void 0 ? stateBase : void 0;
26441
+ let baseLastSeq;
26442
+ if (base) baseLastSeq = (await t.oneOrNone(`SELECT "lastSeq" FROM ${this.#runsTable()} WHERE workflow_name = $1 AND run_id = $2 FOR KEY SHARE`, [base.workflowName, base.runId]))?.lastSeq;
26443
+ const header = await t.oneOrNone(this.#upsertHeaderSql(true, expected.length ? "$13" : void 0), [...this.#headerParams(run, entries.length), ...expected]);
26444
+ if (!header) return null;
26445
+ if (base) {
26446
+ await t.none(`UPDATE ${this.#runsTable()} SET "stateShared" = true
26447
+ WHERE workflow_name = $1 AND run_id = $2 AND NOT "stateShared"`, [base.workflowName, base.runId]);
26448
+ const { stored } = await t.one(`SELECT EXISTS (
26449
+ SELECT 1 FROM ${this.#entriesTable()} WHERE workflow_name = $1 AND run_id = $2
26450
+ AND CASE WHEN $4::boolean THEN kind = 'state-draft' AND seq = $3::int
26451
+ ELSE kind IN ('state', 'state-base') AND seq <= $3::int END
26452
+ ) AS stored`, [
26453
+ base.workflowName,
26454
+ base.runId,
26455
+ base.seq,
26456
+ base.draft === true
26457
+ ]);
26458
+ if (!stored || baseLastSeq === void 0 || base.seq > baseLastSeq) throw new Error(`Workflow run ${run.workflowName}/${run.runId} builds on state version ${base.seq} of ${base.workflowName}/${base.runId}, which is not stored`);
26459
+ }
26460
+ const { lastSeq, stateShared, runToken } = header;
26461
+ await t.none(`DELETE FROM ${this.#entriesTable()}
26462
+ WHERE workflow_name = $1 AND run_id = $2${stateShared ? ` AND kind IN ('step', 'step-patch')` : ""}`, [run.workflowName, run.runId]);
26463
+ await t.none(`INSERT INTO ${this.#entriesTable()} (workflow_name, run_id, seq, kind, step_id, payload)
26464
+ SELECT $1, $2, $3::int - $4::int + e.ord, ${ENTRY_VALUES}
26465
+ FROM ${entryRows("$5")}`, [
26466
+ run.workflowName,
26467
+ run.runId,
26468
+ lastSeq,
26469
+ entries.length,
26470
+ toPgJson(entries)
26471
+ ]);
26472
+ return {
26473
+ lastSeq,
26474
+ runToken
26475
+ };
26476
+ });
26477
+ } catch (error) {
26478
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
26479
+ throw this.#runError("WRITE_WORKFLOW_RUN_CHECKPOINT", run.workflowName, run.runId, error);
26480
+ }
26481
+ }
26482
+ /** One statement: the header upsert numbers the entries inserted after it. */
26483
+ async appendWorkflowRunEntries({ fence, steps, stepPatches, state, statePatch, startStateSeq, expectedStatus, ...run }) {
26484
+ const entries = journalEntries(steps, {
26485
+ state,
26486
+ statePatch
26487
+ }, false, stepPatches);
26488
+ const expected = expectedStatus === void 0 ? [] : [[expectedStatus].flat()];
26489
+ try {
26490
+ return await withRunFence(this.#db.client, this.#runOwnersTable(), (0, _mastra_core_storage.resolveRunFence)(this, fence, run.runId), "appendWorkflowRunEntries", (q) => q.oneOrNone(`WITH h AS (${this.#upsertHeaderSql(false, expected.length ? "$15" : void 0)}),
26491
+ e AS (
26492
+ INSERT INTO ${this.#entriesTable()} (workflow_name, run_id, seq, kind, step_id, payload)
26493
+ SELECT $1, $2, h."lastSeq" - $7::int + e.ord, ${ENTRY_VALUES}
26494
+ FROM h, ${entryRows("$14")}
26495
+ )
26496
+ SELECT "lastSeq", "runToken" FROM h`, [
26497
+ ...this.#headerParams(run, entries.length),
26498
+ startStateSeq ?? null,
26499
+ toPgJson(entries),
26500
+ ...expected
26501
+ ]));
26502
+ } catch (error) {
26503
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
26504
+ throw this.#runError("APPEND_WORKFLOW_RUN_ENTRIES", run.workflowName, run.runId, error);
26505
+ }
26506
+ }
26507
+ async loadWorkflowRunState({ workflowName, runId, seq }) {
26508
+ try {
26509
+ return (await this.#stateAt(this.#db.client, workflowName, runId, { seq }))?.state ?? null;
26510
+ } catch (error) {
26511
+ throw this.#runError("LOAD_WORKFLOW_RUN_STATE", workflowName, runId, error);
26512
+ }
26513
+ }
26514
+ async appendWorkflowRunStateDraft(args) {
26515
+ const { workflowName, runId } = args;
26516
+ const payload = "state" in args ? { state: args.state } : {
26517
+ on: args.on.draft ? {
26518
+ seq: args.on.seq,
26519
+ draft: true
26520
+ } : { seq: args.on.seq },
26521
+ ops: args.ops
26522
+ };
26523
+ let row;
26524
+ try {
26525
+ row = await withRunFence(this.#db.client, this.#runOwnersTable(), (0, _mastra_core_storage.resolveRunFence)(this, void 0, runId), "appendWorkflowRunStateDraft", (q) => q.oneOrNone(`WITH h AS (
26526
+ UPDATE ${this.#runsTable()} SET "lastSeq" = "lastSeq" + 1, "stateShared" = true
26527
+ WHERE workflow_name = $1 AND run_id = $2
26528
+ RETURNING "lastSeq"
26529
+ )
26530
+ INSERT INTO ${this.#entriesTable()} (workflow_name, run_id, seq, kind, step_id, payload)
26531
+ SELECT $1, $2, h."lastSeq", 'state-draft', NULL, $3::jsonb FROM h
26532
+ RETURNING seq`, [
26533
+ workflowName,
26534
+ runId,
26535
+ toPgJson(payload)
26536
+ ]));
26537
+ } catch (error) {
26538
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
26539
+ throw this.#runError("APPEND_WORKFLOW_RUN_STATE_DRAFT", workflowName, runId, error);
26540
+ }
26541
+ if (!row) throw new Error(`Storage v2 run ${workflowName}/${runId} is not stored`);
26542
+ return row;
25873
26543
  }
25874
26544
  supportsRunFencing() {
25875
26545
  return true;
@@ -25998,6 +26668,7 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
25998
26668
  });
25999
26669
  const existingSnapshotResult = await t.oneOrNone(`SELECT snapshot FROM ${tableName} WHERE workflow_name = $1 AND run_id = $2 FOR UPDATE`, [workflowName, runId]);
26000
26670
  if (!existingSnapshotResult) {
26671
+ if (await t.oneOrNone(`SELECT 1 FROM ${this.#runsTable()} WHERE workflow_name = $1 AND run_id = $2`, [workflowName, runId])) throw new Error(`updateWorkflowResults does not support storage v2 runs (run ${runId})`);
26001
26672
  (0, _mastra_core_storage.assertWorkflowStepGuard)({
26002
26673
  snapshot: null,
26003
26674
  stepId,
@@ -26070,11 +26741,36 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
26070
26741
  }, error);
26071
26742
  }
26072
26743
  }
26073
- async updateWorkflowState({ workflowName, runId, opts, fence: explicitFence }) {
26744
+ async updateWorkflowState({ workflowName, runId, opts, fence: explicitFence, storageVersion }) {
26074
26745
  const fence = (0, _mastra_core_storage.resolveRunFence)(this, explicitFence, runId);
26075
26746
  try {
26076
26747
  return await this.#db.client.tx(async (t) => {
26077
26748
  if (fence) await assertRunFence(t, this.#runOwnersTable(), fence, "updateWorkflowState");
26749
+ if (storageVersion !== 1) {
26750
+ const run = await t.oneOrNone(`SELECT header FROM ${this.#runsTable()} WHERE workflow_name = $1 AND run_id = $2 FOR UPDATE`, [workflowName, runId]);
26751
+ if (run) {
26752
+ const { expectedStatus, ...patch } = opts;
26753
+ if (!(0, _mastra_core_storage.matchesExpectedWorkflowStatus)(run.header.status, expectedStatus)) return;
26754
+ const header = {
26755
+ ...run.header,
26756
+ ...patch
26757
+ };
26758
+ const now = /* @__PURE__ */ new Date();
26759
+ const { entries } = await t.one(`UPDATE ${this.#runsTable()} r
26760
+ SET header = $3::jsonb, status = $4, "updatedAt" = $5, "updatedAtZ" = $6
26761
+ WHERE r.workflow_name = $1 AND r.run_id = $2
26762
+ RETURNING ${this.#entriesSql("r")} AS entries`, [
26763
+ workflowName,
26764
+ runId,
26765
+ toPgJson(header),
26766
+ header.status,
26767
+ now,
26768
+ now
26769
+ ]);
26770
+ return this.#materializeRun(t, header, entries);
26771
+ }
26772
+ if (storageVersion === 2) return void 0;
26773
+ }
26078
26774
  const tableName = getTableName({
26079
26775
  indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
26080
26776
  schemaName: getSchemaName(this.#schema)
@@ -26154,10 +26850,14 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
26154
26850
  async loadWorkflowRunStatus({ workflowName, runId }) {
26155
26851
  try {
26156
26852
  const statusExpr = await this.#statusExpression("snapshot");
26157
- return (await this.#db.client.oneOrNone(`SELECT ${statusExpr} AS status FROM ${getTableName({
26853
+ return (await this.#db.client.oneOrNone(`SELECT COALESCE(
26854
+ (SELECT status FROM ${this.#runsTable()} WHERE workflow_name = $1 AND run_id = $2),
26855
+ (SELECT ${statusExpr} FROM ${getTableName({
26158
26856
  indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
26159
26857
  schemaName: getSchemaName(this.#schema)
26160
- })} WHERE "workflow_name" = $1 AND "run_id" = $2 ORDER BY "createdAt" DESC LIMIT 1`, [workflowName, runId]))?.status ?? null;
26858
+ })}
26859
+ WHERE "workflow_name" = $1 AND "run_id" = $2 ORDER BY "createdAt" DESC LIMIT 1)
26860
+ ) AS status`, [workflowName, runId]))?.status ?? null;
26161
26861
  } catch (error) {
26162
26862
  throw new _mastra_core_error.MastraError({
26163
26863
  id: (0, _mastra_core_storage.createStorageErrorId)("PG", "LOAD_WORKFLOW_RUN_STATUS", "FAILED"),
@@ -26170,8 +26870,14 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
26170
26870
  }, error);
26171
26871
  }
26172
26872
  }
26173
- async loadWorkflowSnapshot({ workflowName, runId }) {
26873
+ async loadWorkflowSnapshot({ workflowName, runId, storageVersion }) {
26174
26874
  try {
26875
+ if (storageVersion !== 1) {
26876
+ const run = await this.#db.client.oneOrNone(`SELECT r.header, ${this.#entriesSql("r")} AS entries
26877
+ FROM ${this.#runsTable()} r WHERE r.workflow_name = $1 AND r.run_id = $2`, [workflowName, runId]);
26878
+ if (run) return await this.#materializeRun(this.#db.client, run.header, run.entries);
26879
+ if (storageVersion === 2) return null;
26880
+ }
26175
26881
  const result = await this.#db.load({
26176
26882
  tableName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
26177
26883
  keys: {
@@ -26188,7 +26894,7 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
26188
26894
  }, error);
26189
26895
  }
26190
26896
  }
26191
- async getWorkflowRunById({ runId, workflowName }) {
26897
+ async getWorkflowRunById({ runId, workflowName, storageVersion }) {
26192
26898
  try {
26193
26899
  const conditions = [];
26194
26900
  const values = [];
@@ -26204,18 +26910,14 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
26204
26910
  paramIndex++;
26205
26911
  }
26206
26912
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
26207
- const query = `
26208
- SELECT * FROM ${getTableName({
26209
- indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
26210
- schemaName: getSchemaName(this.#schema)
26211
- })}
26212
- ${whereClause}
26213
- ORDER BY "createdAt" DESC LIMIT 1
26214
- `;
26215
- const queryValues = values;
26216
- const result = await this.#db.readClient.oneOrNone(query, queryValues);
26913
+ const query = await this.#runsQuery({
26914
+ v1Where: storageVersion === 2 ? null : whereClause,
26915
+ v2Where: storageVersion === 1 ? null : whereClause,
26916
+ page: "LIMIT 1"
26917
+ });
26918
+ const result = await this.#db.readClient.oneOrNone(query, values);
26217
26919
  if (!result) return null;
26218
- return this.parseWorkflowRun(result);
26920
+ return await this.#parseRun(this.#db.readClient, result);
26219
26921
  } catch (error) {
26220
26922
  throw new _mastra_core_error.MastraError({
26221
26923
  id: (0, _mastra_core_storage.createStorageErrorId)("PG", "GET_WORKFLOW_RUN_BY_ID", "FAILED"),
@@ -26228,12 +26930,28 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
26228
26930
  }, error);
26229
26931
  }
26230
26932
  }
26231
- async deleteWorkflowRunById({ runId, workflowName, fence }) {
26933
+ async deleteWorkflowRunById({ runId, workflowName, storageVersion, fence }) {
26934
+ const resolvedFence = (0, _mastra_core_storage.resolveRunFence)(this, fence, runId);
26232
26935
  try {
26233
- await withRunFence(this.#db.client, this.#runOwnersTable(), (0, _mastra_core_storage.resolveRunFence)(this, fence, runId), "deleteWorkflowRunById", (q) => q.none(`DELETE FROM ${getTableName({
26234
- indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
26235
- schemaName: getSchemaName(this.#schema)
26236
- })} WHERE run_id = $1 AND workflow_name = $2`, [runId, workflowName]));
26936
+ if (storageVersion === 1) {
26937
+ await withRunFence(this.#db.client, this.#runOwnersTable(), resolvedFence, "deleteWorkflowRunById", (q) => q.none(`DELETE FROM ${getTableName({
26938
+ indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
26939
+ schemaName: getSchemaName(this.#schema)
26940
+ })} WHERE run_id = $1 AND workflow_name = $2`, [runId, workflowName]));
26941
+ return;
26942
+ }
26943
+ await this.#db.client.tx(async (t) => {
26944
+ if (resolvedFence) await assertRunFence(t, this.#runOwnersTable(), resolvedFence, "deleteWorkflowRunById");
26945
+ if ((await t.oneOrNone(`SELECT "stateShared" FROM ${this.#runsTable()} WHERE workflow_name = $1 AND run_id = $2 FOR UPDATE`, [workflowName, runId]))?.stateShared) await this.#detachStateChildren(t, workflowName, runId);
26946
+ for (const table of [
26947
+ this.#runsTable(),
26948
+ this.#entriesTable(),
26949
+ ...storageVersion === 2 ? [] : [getTableName({
26950
+ indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
26951
+ schemaName: getSchemaName(this.#schema)
26952
+ })]
26953
+ ]) await t.none(`DELETE FROM ${table} WHERE run_id = $1 AND workflow_name = $2`, [runId, workflowName]);
26954
+ });
26237
26955
  } catch (error) {
26238
26956
  if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
26239
26957
  throw new _mastra_core_error.MastraError({
@@ -26250,77 +26968,81 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
26250
26968
  async listWorkflowRuns({ workflowName, fromDate, toDate, perPage, page, resourceId, threadId, status, summary } = {}) {
26251
26969
  try {
26252
26970
  const conditions = [];
26971
+ const v2Conditions = [];
26253
26972
  const values = [];
26254
26973
  let paramIndex = 1;
26255
26974
  if (workflowName) {
26256
26975
  conditions.push(`workflow_name = $${paramIndex}`);
26976
+ v2Conditions.push(`workflow_name = $${paramIndex}`);
26257
26977
  values.push(workflowName);
26258
26978
  paramIndex++;
26259
26979
  }
26260
26980
  if (status) {
26261
26981
  const statusExpr = await this.#db.getColumnType(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, "snapshot") === "jsonb" ? `snapshot ->> 'status'` : `regexp_replace(snapshot::text, '\\\\u(0000|[Dd][89A-Fa-f][0-9A-Fa-f]{2})', '', 'g')::jsonb ->> 'status'`;
26262
26982
  conditions.push(`${statusExpr} = $${paramIndex}`);
26983
+ v2Conditions.push(`status = $${paramIndex}`);
26263
26984
  values.push(status);
26264
26985
  paramIndex++;
26265
26986
  }
26266
26987
  if (resourceId) {
26267
- if (await this.#db.hasColumn(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, "resourceId")) {
26268
- conditions.push(`"resourceId" = $${paramIndex}`);
26269
- values.push(resourceId);
26270
- paramIndex++;
26271
- } else this.logger?.warn?.(`[${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
26988
+ if (await this.#db.hasColumn(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, "resourceId")) conditions.push(`"resourceId" = $${paramIndex}`);
26989
+ else this.logger?.warn?.(`[${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
26990
+ v2Conditions.push(`"resourceId" = $${paramIndex}`);
26991
+ values.push(resourceId);
26992
+ paramIndex++;
26272
26993
  }
26273
26994
  if (threadId) {
26274
- if (await this.#db.getColumnType(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, "snapshot") === "jsonb") {
26275
- conditions.push(`${WORKFLOW_SNAPSHOT_THREAD_ID_EXPR} = $${paramIndex}`);
26276
- values.push(threadId);
26277
- paramIndex++;
26278
- } else this.logger?.warn?.(`[${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT}] snapshot column is not jsonb. Skipping threadId filter.`);
26995
+ if (await this.#db.getColumnType(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, "snapshot") === "jsonb") conditions.push(`${WORKFLOW_SNAPSHOT_THREAD_ID_EXPR} = $${paramIndex}`);
26996
+ else this.logger?.warn?.(`[${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT}] snapshot column is not jsonb. Skipping threadId filter.`);
26997
+ v2Conditions.push(`"threadId" = $${paramIndex}`);
26998
+ values.push(threadId);
26999
+ paramIndex++;
26279
27000
  }
26280
27001
  if (fromDate) {
26281
27002
  conditions.push(`"createdAt" >= $${paramIndex}`);
27003
+ v2Conditions.push(`"createdAt" >= $${paramIndex}`);
26282
27004
  values.push(fromDate);
26283
27005
  paramIndex++;
26284
27006
  }
26285
27007
  if (toDate) {
26286
27008
  conditions.push(`"createdAt" <= $${paramIndex}`);
27009
+ v2Conditions.push(`"createdAt" <= $${paramIndex}`);
26287
27010
  values.push(toDate);
26288
27011
  paramIndex++;
26289
27012
  }
26290
27013
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
27014
+ const v2WhereClause = v2Conditions.length > 0 ? `WHERE ${v2Conditions.join(" AND ")}` : "";
26291
27015
  let total = 0;
26292
27016
  const usePagination = typeof perPage === "number" && typeof page === "number";
26293
27017
  if (usePagination) {
26294
- const countResult = await this.#db.client.one(`SELECT COUNT(*) as count FROM ${getTableName({
27018
+ const countResult = await this.#db.client.one(`SELECT (SELECT COUNT(*) FROM ${getTableName({
26295
27019
  indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
26296
27020
  schemaName: getSchemaName(this.#schema)
26297
- })} ${whereClause}`, values);
27021
+ })} ${whereClause})
27022
+ + (SELECT COUNT(*) FROM ${this.#runsTable()} ${v2WhereClause}) AS count`, values);
26298
27023
  total = Number(countResult.count);
26299
27024
  }
26300
27025
  const normalizedPerPage = usePagination ? (0, _mastra_core_storage.normalizePerPage)(perPage, Number.MAX_SAFE_INTEGER) : 0;
26301
27026
  const offset = usePagination ? page * normalizedPerPage : void 0;
26302
- let selectList = "*";
27027
+ let v1Snapshot = "snapshot";
26303
27028
  if (summary) {
26304
27029
  const snapshotJson = await this.#db.getColumnType(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, "snapshot") === "jsonb" ? "snapshot" : `regexp_replace(snapshot::text, '\\\\u(0000|[Dd][89A-Fa-f][0-9A-Fa-f]{2})', '', 'g')::jsonb`;
26305
- selectList = `workflow_name, run_id, "resourceId", "createdAt", "createdAtZ", "updatedAt", "updatedAtZ", jsonb_build_object('status', ${snapshotJson} -> 'status', 'timestamp', ${snapshotJson} -> 'timestamp') AS snapshot`;
27030
+ v1Snapshot = `jsonb_build_object('status', ${snapshotJson} -> 'status', 'timestamp', ${snapshotJson} -> 'timestamp')`;
26306
27031
  }
26307
- const query = `
26308
- SELECT ${selectList} FROM ${getTableName({
26309
- indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
26310
- schemaName: getSchemaName(this.#schema)
26311
- })}
26312
- ${whereClause}
26313
- ORDER BY "createdAt" DESC
26314
- ${usePagination ? ` LIMIT $${paramIndex} OFFSET $${paramIndex + 1}` : ""}
26315
- `;
27032
+ const query = await this.#runsQuery({
27033
+ v1Where: whereClause,
27034
+ v2Where: v2WhereClause,
27035
+ v1Snapshot,
27036
+ summary,
27037
+ page: usePagination ? `LIMIT $${paramIndex} OFFSET $${paramIndex + 1}` : ""
27038
+ });
26316
27039
  const queryValues = usePagination ? [
26317
27040
  ...values,
26318
27041
  normalizedPerPage,
26319
27042
  offset
26320
27043
  ] : values;
26321
- const runs = (await this.#db.client.manyOrNone(query, queryValues) || []).map((row) => {
26322
- return this.parseWorkflowRun(row);
26323
- });
27044
+ const result = await this.#db.client.manyOrNone(query, queryValues);
27045
+ const runs = await Promise.all((result || []).map((row) => this.#parseRun(this.#db.client, row)));
26324
27046
  return {
26325
27047
  runs,
26326
27048
  total: total || runs.length