@mastra/pg 1.35.0 → 1.36.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.
- package/dist/docs/SKILL.md +2 -13
- package/dist/docs/assets/SOURCE_MAP.json +158 -2
- package/dist/docs/references/integrations-databases-neon.md +37 -1
- package/dist/docs/references/integrations-databases-postgresql.md +46 -0
- package/dist/docs/references/reference-processors-message-history-processor.md +1 -1
- package/dist/docs/references/reference-processors-semantic-recall-processor.md +1 -1
- package/dist/docs/references/reference-tools-vector-query-tool.md +3 -3
- package/dist/index.cjs +797 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +798 -65
- package/dist/index.js.map +1 -1
- package/dist/storage/db/index.d.ts +3 -3
- package/dist/storage/db/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/feedback.d.ts +1 -0
- package/dist/storage/domains/observability/v-next/feedback.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/index.d.ts +1 -0
- package/dist/storage/domains/observability/v-next/index.d.ts.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts +87 -7
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/dist/storage/retention.d.ts +3 -1
- package/dist/storage/retention.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/docs/references/docs-deployment-workers.md +0 -404
- package/dist/docs/references/docs-memory-semantic-recall.md +0 -485
- package/dist/docs/references/docs-memory-working-memory.md +0 -450
- package/dist/docs/references/docs-storage.md +0 -224
- package/dist/docs/references/reference-rag-retrieval.md +0 -672
- package/dist/docs/references/reference-rag-vector-databases.md +0 -808
- package/dist/docs/references/reference-storage-composite.md +0 -433
- package/dist/docs/references/reference-storage-retention.md +0 -310
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,
|
|
@@ -18337,6 +18337,11 @@ async function batchCreateFeedback(client, schema, args, partitioning) {
|
|
|
18337
18337
|
const rows = [...new Map(args.feedbacks.map((feedback) => [feedback.feedbackId, feedback])).values()].map(feedbackRecordToRow);
|
|
18338
18338
|
await insertRows(client, schema, TABLE_FEEDBACK_EVENTS, rows, partitioning, FEEDBACK_UPSERT_CLAUSE);
|
|
18339
18339
|
}
|
|
18340
|
+
async function getFeedbackById(client, schema, feedbackId) {
|
|
18341
|
+
const row = await client.oneOrNone(`SELECT ${FEEDBACK_SELECT_COLUMNS} FROM ${qualifiedTable(schema, TABLE_FEEDBACK_EVENTS)}
|
|
18342
|
+
WHERE "feedbackId" = $1 ORDER BY "cursorId" DESC LIMIT 1`, [feedbackId]);
|
|
18343
|
+
return row ? rowToFeedbackRecord(row) : null;
|
|
18344
|
+
}
|
|
18340
18345
|
async function updateFeedbackReviewStatus(client, schema, args) {
|
|
18341
18346
|
const { feedbackId, reviewStatus } = parseUpdateFeedbackReviewStatusArgs(args);
|
|
18342
18347
|
const row = await client.oneOrNone(`UPDATE ${qualifiedTable(schema, TABLE_FEEDBACK_EVENTS)}
|
|
@@ -22172,6 +22177,9 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
22172
22177
|
async getScoreById(scoreId) {
|
|
22173
22178
|
return this.#run("GET_SCORE_BY_ID", () => getScoreById(this.#readClient, this.#schema, scoreId), { scoreId });
|
|
22174
22179
|
}
|
|
22180
|
+
async getFeedbackById(feedbackId) {
|
|
22181
|
+
return this.#run("GET_FEEDBACK_BY_ID", () => getFeedbackById(this.#readClient, this.#schema, feedbackId), { feedbackId });
|
|
22182
|
+
}
|
|
22175
22183
|
async listFeedback(args) {
|
|
22176
22184
|
return this.#run("LIST_FEEDBACK", () => listFeedback(this.#readClient, this.#schema, args));
|
|
22177
22185
|
}
|
|
@@ -25625,6 +25633,16 @@ function workflowSnapshotStatusIndexSQL(indexName, schemaName, concurrently = fa
|
|
|
25625
25633
|
});
|
|
25626
25634
|
return `CREATE INDEX ${concurrently ? "CONCURRENTLY " : ""}IF NOT EXISTS "${indexName}" ON ${tableName} (workflow_name, (snapshot ->> 'status'), "createdAt" DESC)`;
|
|
25627
25635
|
}
|
|
25636
|
+
/**
|
|
25637
|
+
* Storage v2 index names share the long `<schema>_mastra_workflow_runs_` prefix, so a long
|
|
25638
|
+
* schema name would collapse them to one 63-byte identifier under plain truncation; hash instead.
|
|
25639
|
+
*/
|
|
25640
|
+
function runIndexName(schemaPrefix, baseName) {
|
|
25641
|
+
return buildConstraintName({
|
|
25642
|
+
baseName: `${schemaPrefix}${baseName}`,
|
|
25643
|
+
hashWhenTruncated: true
|
|
25644
|
+
});
|
|
25645
|
+
}
|
|
25628
25646
|
/** Base name (before any schema prefix) of the expression index backing the threadId filter. */
|
|
25629
25647
|
const WORKFLOW_SNAPSHOT_THREAD_ID_INDEX = "mastra_workflow_snapshot_threadid_idx";
|
|
25630
25648
|
/**
|
|
@@ -25669,13 +25687,126 @@ function workflowSnapshotThreadIdIndexSQL(indexName, schemaName, concurrently =
|
|
|
25669
25687
|
});
|
|
25670
25688
|
return `CREATE INDEX ${concurrently ? "CONCURRENTLY " : ""}IF NOT EXISTS "${indexName}" ON ${tableName} ((${WORKFLOW_SNAPSHOT_THREAD_ID_EXPR}))`;
|
|
25671
25689
|
}
|
|
25690
|
+
/** Base name (before any schema prefix) of the index finding the `state-base` entries built on a run. */
|
|
25691
|
+
const WORKFLOW_RUN_ENTRIES_STATE_BASE_INDEX = "mastra_workflow_run_entries_state_base_idx";
|
|
25692
|
+
/** The run a `state-base` entry builds on; `#detachStateChildren` must use these exact expressions. */
|
|
25693
|
+
const STATE_BASE_RUN_EXPR = `(payload ->> 'workflowName'), (payload ->> 'runId')`;
|
|
25694
|
+
/**
|
|
25695
|
+
* Partial expression index finding the `state-base` entries built on a run, so deleting a run whose
|
|
25696
|
+
* state others build on doesn't scan every entry. See {@link workflowSnapshotStatusIndexSQL} for
|
|
25697
|
+
* `concurrently`.
|
|
25698
|
+
*/
|
|
25699
|
+
function stateBaseIndexSQL(indexName, schemaName, concurrently = false) {
|
|
25700
|
+
const tableName = getTableName({
|
|
25701
|
+
indexName: _mastra_core_storage.TABLE_WORKFLOW_RUN_ENTRIES,
|
|
25702
|
+
schemaName: getSchemaName(schemaName)
|
|
25703
|
+
});
|
|
25704
|
+
return `CREATE INDEX ${concurrently ? "CONCURRENTLY " : ""}IF NOT EXISTS "${indexName}" ON ${tableName} (${STATE_BASE_RUN_EXPR}) WHERE kind = 'state-base'`;
|
|
25705
|
+
}
|
|
25706
|
+
/**
|
|
25707
|
+
* The entries a storage v2 write adds: one per step, in order, then one per step patch, then the
|
|
25708
|
+
* state or state base (a checkpoint with neither starts the state over at `{}`), then the state
|
|
25709
|
+
* patch unless a full state was given.
|
|
25710
|
+
*/
|
|
25711
|
+
function journalEntries(steps, { state, stateBase, statePatch }, checkpoint, stepPatches = {}) {
|
|
25712
|
+
const entries = Object.entries(steps).map(([stepId, result]) => ({
|
|
25713
|
+
kind: "step",
|
|
25714
|
+
step_id: stepId,
|
|
25715
|
+
payload: result
|
|
25716
|
+
}));
|
|
25717
|
+
for (const [stepId, ops] of Object.entries(stepPatches)) entries.push({
|
|
25718
|
+
kind: "step-patch",
|
|
25719
|
+
step_id: stepId,
|
|
25720
|
+
payload: ops
|
|
25721
|
+
});
|
|
25722
|
+
if (state !== void 0) entries.push({
|
|
25723
|
+
kind: "state",
|
|
25724
|
+
step_id: null,
|
|
25725
|
+
payload: state
|
|
25726
|
+
});
|
|
25727
|
+
else if (stateBase !== void 0) entries.push({
|
|
25728
|
+
kind: "state-base",
|
|
25729
|
+
step_id: null,
|
|
25730
|
+
payload: stateBase
|
|
25731
|
+
});
|
|
25732
|
+
else if (checkpoint) entries.push({
|
|
25733
|
+
kind: "state",
|
|
25734
|
+
step_id: null,
|
|
25735
|
+
payload: {}
|
|
25736
|
+
});
|
|
25737
|
+
if (state === void 0 && statePatch !== void 0) entries.push({
|
|
25738
|
+
kind: "state-patch",
|
|
25739
|
+
step_id: null,
|
|
25740
|
+
payload: statePatch
|
|
25741
|
+
});
|
|
25742
|
+
return entries;
|
|
25743
|
+
}
|
|
25744
|
+
/**
|
|
25745
|
+
* Rows `e(elem, ord)` of the jsonb array parameter `param` of {@link EntryInput}, and the
|
|
25746
|
+
* entry columns they insert. `elem -> 'payload'` keeps a JSON null apart from an undefined
|
|
25747
|
+
* payload (dropped by `JSON.stringify`), which becomes SQL NULL.
|
|
25748
|
+
*/
|
|
25749
|
+
const entryRows = (param) => `jsonb_array_elements(${param}::jsonb) WITH ORDINALITY AS e(elem, ord)`;
|
|
25750
|
+
const ENTRY_VALUES = `e.elem ->> 'kind', e.elem ->> 'step_id', e.elem -> 'payload'`;
|
|
25751
|
+
/** The entries of {@link EntryRow} rows; an undefined payload reads back as undefined, as in v1. */
|
|
25752
|
+
function toEntries(rows) {
|
|
25753
|
+
return rows.map((row) => {
|
|
25754
|
+
const payload = row.absent ? void 0 : row.payload;
|
|
25755
|
+
switch (row.kind) {
|
|
25756
|
+
case "step": return {
|
|
25757
|
+
seq: row.seq,
|
|
25758
|
+
kind: "step",
|
|
25759
|
+
stepId: row.step_id,
|
|
25760
|
+
result: payload
|
|
25761
|
+
};
|
|
25762
|
+
case "step-patch": return {
|
|
25763
|
+
seq: row.seq,
|
|
25764
|
+
kind: "step-patch",
|
|
25765
|
+
stepId: row.step_id,
|
|
25766
|
+
ops: payload
|
|
25767
|
+
};
|
|
25768
|
+
case "state": return {
|
|
25769
|
+
seq: row.seq,
|
|
25770
|
+
kind: "state",
|
|
25771
|
+
state: payload
|
|
25772
|
+
};
|
|
25773
|
+
case "state-patch": return {
|
|
25774
|
+
seq: row.seq,
|
|
25775
|
+
kind: "state-patch",
|
|
25776
|
+
ops: payload
|
|
25777
|
+
};
|
|
25778
|
+
case "state-base": return {
|
|
25779
|
+
seq: row.seq,
|
|
25780
|
+
kind: "state-base",
|
|
25781
|
+
base: payload
|
|
25782
|
+
};
|
|
25783
|
+
case "state-draft": {
|
|
25784
|
+
const draft = payload;
|
|
25785
|
+
return "state" in draft ? {
|
|
25786
|
+
seq: row.seq,
|
|
25787
|
+
kind: "state-draft",
|
|
25788
|
+
state: draft.state
|
|
25789
|
+
} : {
|
|
25790
|
+
seq: row.seq,
|
|
25791
|
+
kind: "state-draft",
|
|
25792
|
+
on: draft.on,
|
|
25793
|
+
ops: draft.ops
|
|
25794
|
+
};
|
|
25795
|
+
}
|
|
25796
|
+
}
|
|
25797
|
+
});
|
|
25798
|
+
}
|
|
25672
25799
|
var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorage {
|
|
25673
25800
|
#db;
|
|
25674
25801
|
#schema;
|
|
25675
25802
|
#skipDefaultIndexes;
|
|
25676
25803
|
#indexes;
|
|
25677
25804
|
/** Tables managed by this domain */
|
|
25678
|
-
static MANAGED_TABLES = [
|
|
25805
|
+
static MANAGED_TABLES = [
|
|
25806
|
+
_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
|
|
25807
|
+
_mastra_core_storage.TABLE_WORKFLOW_RUNS,
|
|
25808
|
+
_mastra_core_storage.TABLE_WORKFLOW_RUN_ENTRIES
|
|
25809
|
+
];
|
|
25679
25810
|
/**
|
|
25680
25811
|
* Workflow run snapshots accumulate as runs execute. Anchored on the
|
|
25681
25812
|
* timezone-aware `updatedAtZ` mirror column (last activity) so suspended or
|
|
@@ -25738,11 +25869,43 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
|
|
|
25738
25869
|
};
|
|
25739
25870
|
}
|
|
25740
25871
|
static getDefaultIndexDefs(schemaPrefix) {
|
|
25741
|
-
return [
|
|
25742
|
-
|
|
25743
|
-
|
|
25744
|
-
|
|
25745
|
-
|
|
25872
|
+
return [
|
|
25873
|
+
{
|
|
25874
|
+
name: `${schemaPrefix}mastra_workflow_snapshot_name_createdat_idx`,
|
|
25875
|
+
table: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
|
|
25876
|
+
columns: ["workflow_name", "createdAt DESC"]
|
|
25877
|
+
},
|
|
25878
|
+
{
|
|
25879
|
+
name: runIndexName(schemaPrefix, "mastra_workflow_runs_name_createdat_idx"),
|
|
25880
|
+
table: _mastra_core_storage.TABLE_WORKFLOW_RUNS,
|
|
25881
|
+
columns: ["workflow_name", "createdAt DESC"]
|
|
25882
|
+
},
|
|
25883
|
+
{
|
|
25884
|
+
name: runIndexName(schemaPrefix, "mastra_workflow_runs_name_status_createdat_idx"),
|
|
25885
|
+
table: _mastra_core_storage.TABLE_WORKFLOW_RUNS,
|
|
25886
|
+
columns: [
|
|
25887
|
+
"workflow_name",
|
|
25888
|
+
"status",
|
|
25889
|
+
"createdAt DESC"
|
|
25890
|
+
]
|
|
25891
|
+
},
|
|
25892
|
+
{
|
|
25893
|
+
name: runIndexName(schemaPrefix, "mastra_workflow_runs_threadid_idx"),
|
|
25894
|
+
table: _mastra_core_storage.TABLE_WORKFLOW_RUNS,
|
|
25895
|
+
columns: ["threadId"]
|
|
25896
|
+
},
|
|
25897
|
+
{
|
|
25898
|
+
name: runIndexName(schemaPrefix, "mastra_workflow_run_entries_step_idx"),
|
|
25899
|
+
table: _mastra_core_storage.TABLE_WORKFLOW_RUN_ENTRIES,
|
|
25900
|
+
columns: [
|
|
25901
|
+
"workflow_name",
|
|
25902
|
+
"run_id",
|
|
25903
|
+
"kind",
|
|
25904
|
+
"step_id",
|
|
25905
|
+
"seq"
|
|
25906
|
+
]
|
|
25907
|
+
}
|
|
25908
|
+
];
|
|
25746
25909
|
}
|
|
25747
25910
|
/**
|
|
25748
25911
|
* Returns all DDL statements for this domain: table with unique constraint.
|
|
@@ -25758,9 +25921,17 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
|
|
|
25758
25921
|
schemaName,
|
|
25759
25922
|
includeAllConstraints: true
|
|
25760
25923
|
}));
|
|
25924
|
+
for (const [tableName, config] of Object.entries(_mastra_core_storage.WORKFLOW_RUN_JOURNAL_TABLE_CONFIGS)) statements.push(generateTableSQL({
|
|
25925
|
+
tableName,
|
|
25926
|
+
schema: config.columns,
|
|
25927
|
+
schemaName,
|
|
25928
|
+
compositePrimaryKey: config.compositePrimaryKey,
|
|
25929
|
+
includeAllConstraints: true
|
|
25930
|
+
}));
|
|
25761
25931
|
for (const idx of WorkflowsPG.getDefaultIndexDefs(schemaPrefix)) statements.push(generateIndexSQL(idx, schemaName));
|
|
25762
25932
|
statements.push(`${workflowSnapshotStatusIndexSQL(workflowSnapshotStatusIndexName(parsedSchema), schemaName)};`);
|
|
25763
25933
|
statements.push(`${workflowSnapshotThreadIdIndexSQL(workflowSnapshotThreadIdIndexName(parsedSchema), schemaName)};`);
|
|
25934
|
+
statements.push(`${stateBaseIndexSQL(runIndexName(schemaPrefix, WORKFLOW_RUN_ENTRIES_STATE_BASE_INDEX), schemaName)};`);
|
|
25764
25935
|
statements.push(generateTableSQL({
|
|
25765
25936
|
tableName: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS,
|
|
25766
25937
|
schema: _mastra_core_storage.RUN_FENCING_TABLE_SCHEMAS[_mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS],
|
|
@@ -25786,6 +25957,12 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
|
|
|
25786
25957
|
} catch (error) {
|
|
25787
25958
|
this.logger?.warn?.(`Failed to create index ${indexDef.name}:`, error);
|
|
25788
25959
|
}
|
|
25960
|
+
const stateBaseIndexName = runIndexName(this.#schema !== "public" ? `${schemaNamePrefix(this.#schema)}_` : "", WORKFLOW_RUN_ENTRIES_STATE_BASE_INDEX);
|
|
25961
|
+
try {
|
|
25962
|
+
await this.#db.createIndexFromStatement(stateBaseIndexName, stateBaseIndexSQL(stateBaseIndexName, this.#schema, true));
|
|
25963
|
+
} catch (error) {
|
|
25964
|
+
this.logger?.warn?.(`Failed to create index ${stateBaseIndexName}:`, error);
|
|
25965
|
+
}
|
|
25789
25966
|
if (await this.#db.getColumnType(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, "snapshot") !== "jsonb") return;
|
|
25790
25967
|
const indexName = workflowSnapshotStatusIndexName(this.#schema);
|
|
25791
25968
|
try {
|
|
@@ -25814,6 +25991,11 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
|
|
|
25814
25991
|
tableName: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS,
|
|
25815
25992
|
schema: _mastra_core_storage.RUN_FENCING_TABLE_SCHEMAS[_mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS]
|
|
25816
25993
|
});
|
|
25994
|
+
for (const [tableName, config] of Object.entries(_mastra_core_storage.WORKFLOW_RUN_JOURNAL_TABLE_CONFIGS)) await this.#db.createTable({
|
|
25995
|
+
tableName,
|
|
25996
|
+
schema: config.columns,
|
|
25997
|
+
compositePrimaryKey: config.compositePrimaryKey
|
|
25998
|
+
});
|
|
25817
25999
|
await this.createDefaultIndexes();
|
|
25818
26000
|
await this.createCustomIndexes();
|
|
25819
26001
|
}
|
|
@@ -25840,8 +26022,22 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
|
|
|
25840
26022
|
this.logger?.warn?.(`Failed to create retention index for ${entry.table}:`, error);
|
|
25841
26023
|
}
|
|
25842
26024
|
}
|
|
26025
|
+
if (!policies.workflowSnapshot) return;
|
|
26026
|
+
try {
|
|
26027
|
+
await this.#db.ensureIndex({
|
|
26028
|
+
indexName: `${prefix}mastra_workflow_runs_retention_idx`,
|
|
26029
|
+
tableName: _mastra_core_storage.TABLE_WORKFLOW_RUNS,
|
|
26030
|
+
column: "updatedAtZ"
|
|
26031
|
+
});
|
|
26032
|
+
} catch (error) {
|
|
26033
|
+
this.logger?.warn?.(`Failed to create retention index for ${_mastra_core_storage.TABLE_WORKFLOW_RUNS}:`, error);
|
|
26034
|
+
}
|
|
25843
26035
|
}
|
|
25844
|
-
/**
|
|
26036
|
+
/**
|
|
26037
|
+
* Delete workflow runs and run ownership rows older than their policy's `maxAge`, batched.
|
|
26038
|
+
* The `workflowSnapshot` policy covers both storage versions: storage v2 runs are pruned by
|
|
26039
|
+
* their header's last write, together with their entries.
|
|
26040
|
+
*/
|
|
25845
26041
|
async prune(policies, options) {
|
|
25846
26042
|
await this.ensureRetentionIndexes(policies);
|
|
25847
26043
|
const targets = resolveTargets({
|
|
@@ -25849,11 +26045,40 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
|
|
|
25849
26045
|
descriptor: WorkflowsPG.retentionTables,
|
|
25850
26046
|
order: ["workflowSnapshot", "runOwnership"]
|
|
25851
26047
|
});
|
|
26048
|
+
const snapshotTarget = targets.findIndex((target) => target.table === _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT);
|
|
26049
|
+
if (snapshotTarget !== -1) targets.splice(snapshotTarget + 1, 0, {
|
|
26050
|
+
...targets[snapshotTarget],
|
|
26051
|
+
table: _mastra_core_storage.TABLE_WORKFLOW_RUNS
|
|
26052
|
+
});
|
|
25852
26053
|
return runPrune({
|
|
25853
26054
|
db: this.#db,
|
|
25854
26055
|
domain: "workflows",
|
|
25855
26056
|
targets,
|
|
25856
|
-
options
|
|
26057
|
+
options,
|
|
26058
|
+
deleteBatch: (target, cutoff, limit) => target.table === _mastra_core_storage.TABLE_WORKFLOW_RUNS ? this.#pruneRunsBatch(cutoff, limit) : void 0
|
|
26059
|
+
});
|
|
26060
|
+
}
|
|
26061
|
+
/**
|
|
26062
|
+
* Deletes up to `limit` aged storage v2 runs, header and entries together, first detaching the
|
|
26063
|
+
* runs whose state others build on (see `#detachStateChildren`).
|
|
26064
|
+
*/
|
|
26065
|
+
async #pruneRunsBatch(cutoff, limit) {
|
|
26066
|
+
return this.#db.client.tx(async (t) => {
|
|
26067
|
+
const doomed = await t.manyOrNone(`SELECT workflow_name, run_id, "stateShared" FROM ${this.#runsTable()}
|
|
26068
|
+
WHERE "updatedAtZ" < $1
|
|
26069
|
+
ORDER BY "updatedAtZ"
|
|
26070
|
+
LIMIT $2
|
|
26071
|
+
FOR UPDATE SKIP LOCKED`, [cutoff, limit]);
|
|
26072
|
+
if (!doomed.length) return 0;
|
|
26073
|
+
for (const run of doomed) if (run.stateShared) await this.#detachStateChildren(t, run.workflow_name, run.run_id);
|
|
26074
|
+
return (await t.query(`WITH doomed AS (
|
|
26075
|
+
SELECT * FROM unnest($1::text[], $2::text[]) AS d(workflow_name, run_id)
|
|
26076
|
+
), entries AS (
|
|
26077
|
+
DELETE FROM ${this.#entriesTable()} e USING doomed d
|
|
26078
|
+
WHERE e.workflow_name = d.workflow_name AND e.run_id = d.run_id
|
|
26079
|
+
)
|
|
26080
|
+
DELETE FROM ${this.#runsTable()} r USING doomed d
|
|
26081
|
+
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
26082
|
});
|
|
25858
26083
|
}
|
|
25859
26084
|
/**
|
|
@@ -25870,6 +26095,459 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
|
|
|
25870
26095
|
async dangerouslyClearAll() {
|
|
25871
26096
|
await this.#db.clearTable({ tableName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT });
|
|
25872
26097
|
await this.#db.clearTable({ tableName: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS });
|
|
26098
|
+
await this.#db.clearTable({ tableName: _mastra_core_storage.TABLE_WORKFLOW_RUNS });
|
|
26099
|
+
await this.#db.clearTable({ tableName: _mastra_core_storage.TABLE_WORKFLOW_RUN_ENTRIES });
|
|
26100
|
+
}
|
|
26101
|
+
supportsRunJournal() {
|
|
26102
|
+
return true;
|
|
26103
|
+
}
|
|
26104
|
+
#runsTable() {
|
|
26105
|
+
return getTableName({
|
|
26106
|
+
indexName: _mastra_core_storage.TABLE_WORKFLOW_RUNS,
|
|
26107
|
+
schemaName: getSchemaName(this.#schema)
|
|
26108
|
+
});
|
|
26109
|
+
}
|
|
26110
|
+
#entriesTable() {
|
|
26111
|
+
return getTableName({
|
|
26112
|
+
indexName: _mastra_core_storage.TABLE_WORKFLOW_RUN_ENTRIES,
|
|
26113
|
+
schemaName: getSchemaName(this.#schema)
|
|
26114
|
+
});
|
|
26115
|
+
}
|
|
26116
|
+
/**
|
|
26117
|
+
* A jsonb array of {@link EntryRow} for the storage v2 run `alias` refers to: each step's
|
|
26118
|
+
* latest entry, ordered by when each step was first written so `context` keys keep their write
|
|
26119
|
+
* order, the step patches after it (in `seq` order, for `latestStepResults`), and the entries
|
|
26120
|
+
* `materializeState` uses for the latest state: the latest full state or state base and every
|
|
26121
|
+
* state patch after it, in `seq` order. Drafts are left out.
|
|
26122
|
+
*
|
|
26123
|
+
* With `summaryFieldsParam` (the parameter holding `STEP_RESULT_SUMMARY_FIELDS`), successful
|
|
26124
|
+
* steps other than `input` come back as their summary, built here so their data is never sent,
|
|
26125
|
+
* unless step patches follow it (the caller summarizes the patched result instead).
|
|
26126
|
+
* Like `summarizeStepResult`, it keeps a field that is JSON null and leaves out one that is absent.
|
|
26127
|
+
*/
|
|
26128
|
+
#entriesSql(alias, { summaryFieldsParam } = {}) {
|
|
26129
|
+
let payload = "x.payload";
|
|
26130
|
+
if (summaryFieldsParam) {
|
|
26131
|
+
const summarized = `(x.kind = 'step' AND x.step_id <> 'input' AND x.payload ->> 'status' = 'success'
|
|
26132
|
+
AND (x.patch_seq IS NULL OR x.patch_seq < x.seq))`;
|
|
26133
|
+
payload = `CASE WHEN ${summarized} THEN (
|
|
26134
|
+
SELECT COALESCE(jsonb_object_agg(f.key, f.value), '{}'::jsonb)
|
|
26135
|
+
FROM jsonb_each(x.payload) f WHERE f.key = ANY(${summaryFieldsParam}::text[])
|
|
26136
|
+
) ELSE x.payload END, 'summarized', ${summarized}`;
|
|
26137
|
+
}
|
|
26138
|
+
return `(SELECT COALESCE(jsonb_agg(jsonb_build_object(
|
|
26139
|
+
'seq', x.seq, 'kind', x.kind, 'step_id', x.step_id, 'absent', x.payload IS NULL, 'payload', ${payload}
|
|
26140
|
+
) ORDER BY CASE WHEN x.kind = 'step' THEN x.first_seq ELSE x.seq END), '[]'::jsonb)
|
|
26141
|
+
FROM (
|
|
26142
|
+
SELECT e.seq, e.kind, e.step_id, e.payload,
|
|
26143
|
+
ROW_NUMBER() OVER (PARTITION BY e.kind, e.step_id ORDER BY e.seq DESC) AS rn,
|
|
26144
|
+
MIN(e.seq) OVER (PARTITION BY e.kind, e.step_id) AS first_seq,
|
|
26145
|
+
MAX(CASE WHEN e.kind = 'step' THEN e.seq END) OVER (PARTITION BY e.step_id) AS step_seq,
|
|
26146
|
+
MAX(CASE WHEN e.kind = 'step-patch' THEN e.seq END) OVER (PARTITION BY e.step_id) AS patch_seq,
|
|
26147
|
+
MAX(CASE WHEN e.kind IN ('state', 'state-base') THEN e.seq END) OVER () AS base_seq
|
|
26148
|
+
FROM ${this.#entriesTable()} e
|
|
26149
|
+
WHERE e.workflow_name = ${alias}.workflow_name AND e.run_id = ${alias}.run_id AND e.kind <> 'state-draft'
|
|
26150
|
+
) x
|
|
26151
|
+
WHERE CASE x.kind
|
|
26152
|
+
WHEN 'step' THEN x.rn = 1
|
|
26153
|
+
WHEN 'step-patch' THEN x.seq > x.step_seq
|
|
26154
|
+
ELSE x.base_seq IS NULL OR x.seq >= x.base_seq
|
|
26155
|
+
END)`;
|
|
26156
|
+
}
|
|
26157
|
+
/**
|
|
26158
|
+
* A jsonb array of {@link EntryRow} with the entries `materializeState` uses for the state of the
|
|
26159
|
+
* storage v2 run `alias` refers to as of a version: `seqParam` its `seq`, `draftParam` whether
|
|
26160
|
+
* it is a draft. For a draft, every state entry up to it, as the drafts it is on can build on
|
|
26161
|
+
* any earlier version; otherwise the latest full state or state base up to it and the state
|
|
26162
|
+
* patches after it.
|
|
26163
|
+
*/
|
|
26164
|
+
#stateEntriesSql(alias, seqParam, draftParam) {
|
|
26165
|
+
return `(SELECT COALESCE(jsonb_agg(jsonb_build_object(
|
|
26166
|
+
'seq', x.seq, 'kind', x.kind, 'step_id', x.step_id, 'absent', x.payload IS NULL, 'payload', x.payload
|
|
26167
|
+
) ORDER BY x.seq), '[]'::jsonb)
|
|
26168
|
+
FROM (
|
|
26169
|
+
SELECT e.seq, e.kind, e.step_id, e.payload,
|
|
26170
|
+
MAX(CASE WHEN e.kind IN ('state', 'state-base') THEN e.seq END) OVER () AS base_seq
|
|
26171
|
+
FROM ${this.#entriesTable()} e
|
|
26172
|
+
WHERE e.workflow_name = ${alias}.workflow_name AND e.run_id = ${alias}.run_id
|
|
26173
|
+
AND e.kind NOT IN ('step', 'step-patch') AND e.seq <= ${seqParam}::int
|
|
26174
|
+
) x
|
|
26175
|
+
WHERE ${draftParam}::boolean OR (x.kind <> 'state-draft' AND (x.base_seq IS NULL OR x.seq >= x.base_seq)))`;
|
|
26176
|
+
}
|
|
26177
|
+
/**
|
|
26178
|
+
* Selects v1 snapshot rows and storage v2 runs as one result, newest first. Each version
|
|
26179
|
+
* has its own WHERE clause; `null` leaves it out. Storage v2 rows carry their header and
|
|
26180
|
+
* entries, or in summary mode only the summary snapshot. `page` is appended to the
|
|
26181
|
+
* combined, ordered rows (e.g. `LIMIT 1`).
|
|
26182
|
+
*/
|
|
26183
|
+
async #runsQuery({ v1Where, v2Where, v1Snapshot = "snapshot", summary, page = "" }) {
|
|
26184
|
+
const branches = [];
|
|
26185
|
+
if (v1Where !== null) {
|
|
26186
|
+
const optional = {
|
|
26187
|
+
resourceId: "text",
|
|
26188
|
+
createdAtZ: "timestamptz",
|
|
26189
|
+
updatedAtZ: "timestamptz"
|
|
26190
|
+
};
|
|
26191
|
+
const columns = {};
|
|
26192
|
+
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}"`;
|
|
26193
|
+
branches.push(`SELECT workflow_name, run_id, ${columns.resourceId}, ${v1Snapshot} AS snapshot, NULL::jsonb AS header,
|
|
26194
|
+
"createdAt", ${columns.createdAtZ}, "updatedAt", ${columns.updatedAtZ}, 1 AS version
|
|
26195
|
+
FROM ${getTableName({
|
|
26196
|
+
indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
|
|
26197
|
+
schemaName: getSchemaName(this.#schema)
|
|
26198
|
+
})} ${v1Where}`);
|
|
26199
|
+
}
|
|
26200
|
+
if (v2Where !== null) {
|
|
26201
|
+
const columns = summary ? `jsonb_build_object('status', header -> 'status', 'timestamp', header -> 'timestamp') AS snapshot, NULL::jsonb AS header` : `NULL AS snapshot, header`;
|
|
26202
|
+
branches.push(`SELECT workflow_name, run_id, "resourceId", ${columns},
|
|
26203
|
+
"createdAt", "createdAtZ", "updatedAt", "updatedAtZ", 2 AS version
|
|
26204
|
+
FROM ${this.#runsTable()} ${v2Where}`);
|
|
26205
|
+
}
|
|
26206
|
+
return `SELECT p.*${summary ? "" : `, CASE WHEN p.version = 2 THEN ${this.#entriesSql("p")} END AS entries`}
|
|
26207
|
+
FROM (${branches.join(" UNION ALL ")} ORDER BY "createdAt" DESC ${page}) p
|
|
26208
|
+
ORDER BY p."createdAt" DESC`;
|
|
26209
|
+
}
|
|
26210
|
+
/**
|
|
26211
|
+
* The `base` that `materializeState` takes for a run's entries (as of `at`, or latest): the state
|
|
26212
|
+
* version of another run it builds on (`stateBaseOf`), resolved recursively, else undefined. `{}`
|
|
26213
|
+
* when that run isn't stored. One query per run in the chain.
|
|
26214
|
+
*/
|
|
26215
|
+
async #stateBase(q, entries, at) {
|
|
26216
|
+
const ref = (0, _mastra_core_storage.stateBaseOf)(entries, at);
|
|
26217
|
+
if (!ref) return void 0;
|
|
26218
|
+
return (await this.#stateAt(q, ref.workflowName, ref.runId, ref))?.state ?? {};
|
|
26219
|
+
}
|
|
26220
|
+
/** A storage v2 run's state as of the version `at`, or null when the run isn't stored. */
|
|
26221
|
+
async #stateAt(q, workflowName, runId, at) {
|
|
26222
|
+
const row = await q.oneOrNone(`SELECT ${this.#stateEntriesSql("r", "$3", "$4")} AS entries
|
|
26223
|
+
FROM ${this.#runsTable()} r WHERE r.workflow_name = $1 AND r.run_id = $2`, [
|
|
26224
|
+
workflowName,
|
|
26225
|
+
runId,
|
|
26226
|
+
at.seq,
|
|
26227
|
+
at.draft === true
|
|
26228
|
+
]);
|
|
26229
|
+
if (!row) return null;
|
|
26230
|
+
const entries = toEntries(row.entries);
|
|
26231
|
+
return (0, _mastra_core_storage.materializeState)(entries, {
|
|
26232
|
+
at,
|
|
26233
|
+
base: await this.#stateBase(q, entries, at)
|
|
26234
|
+
});
|
|
26235
|
+
}
|
|
26236
|
+
/**
|
|
26237
|
+
* Before a run is deleted, stores the full state in place of every `state-base` entry built on
|
|
26238
|
+
* it, so the runs built on it keep reading the same state. Runs inside the delete's transaction.
|
|
26239
|
+
*/
|
|
26240
|
+
async #detachStateChildren(q, workflowName, runId) {
|
|
26241
|
+
const children = await q.manyOrNone(`SELECT workflow_name, run_id, seq, payload FROM ${this.#entriesTable()}
|
|
26242
|
+
WHERE kind = 'state-base' AND (${STATE_BASE_RUN_EXPR}) = ($1, $2)`, [workflowName, runId]);
|
|
26243
|
+
const states = /* @__PURE__ */ new Map();
|
|
26244
|
+
for (const child of children) {
|
|
26245
|
+
const version = `${child.payload.seq}:${child.payload.draft === true}`;
|
|
26246
|
+
let state = states.get(version);
|
|
26247
|
+
if (!state) {
|
|
26248
|
+
state = (await this.#stateAt(q, workflowName, runId, child.payload))?.state ?? {};
|
|
26249
|
+
states.set(version, state);
|
|
26250
|
+
}
|
|
26251
|
+
await q.none(`UPDATE ${this.#entriesTable()} SET kind = 'state', payload = $4::jsonb
|
|
26252
|
+
WHERE workflow_name = $1 AND run_id = $2 AND seq = $3`, [
|
|
26253
|
+
child.workflow_name,
|
|
26254
|
+
child.run_id,
|
|
26255
|
+
child.seq,
|
|
26256
|
+
toPgJson(state)
|
|
26257
|
+
]);
|
|
26258
|
+
}
|
|
26259
|
+
}
|
|
26260
|
+
/** Rebuilds a storage v2 run's `WorkflowRunState` from its header and `#entriesSql` rows. */
|
|
26261
|
+
async #materializeRun(client, header, rows) {
|
|
26262
|
+
const entries = toEntries(rows);
|
|
26263
|
+
return (0, _mastra_core_storage.materializeWorkflowRun)(header, entries, await this.#stateBase(client, entries));
|
|
26264
|
+
}
|
|
26265
|
+
/** Parses a row of `#runsQuery`. */
|
|
26266
|
+
async #parseRun(client, row) {
|
|
26267
|
+
if (row.version !== 2) return this.parseWorkflowRun(row);
|
|
26268
|
+
const snapshot = row.entries ? await this.#materializeRun(client, row.header, row.entries) : row.snapshot;
|
|
26269
|
+
return {
|
|
26270
|
+
...this.parseWorkflowRun({
|
|
26271
|
+
...row,
|
|
26272
|
+
snapshot
|
|
26273
|
+
}),
|
|
26274
|
+
storageVersion: 2
|
|
26275
|
+
};
|
|
26276
|
+
}
|
|
26277
|
+
#runError(operation, workflowName, runId, error) {
|
|
26278
|
+
return new _mastra_core_error.MastraError({
|
|
26279
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("PG", operation, "FAILED"),
|
|
26280
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
26281
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
26282
|
+
details: {
|
|
26283
|
+
workflowName,
|
|
26284
|
+
runId
|
|
26285
|
+
}
|
|
26286
|
+
}, error);
|
|
26287
|
+
}
|
|
26288
|
+
async loadWorkflowRunHeader({ workflowName, runId }) {
|
|
26289
|
+
try {
|
|
26290
|
+
return await this.#db.client.oneOrNone(`SELECT header, "lastSeq", "startStateSeq", "runToken" FROM ${this.#runsTable()} WHERE workflow_name = $1 AND run_id = $2`, [workflowName, runId]);
|
|
26291
|
+
} catch (error) {
|
|
26292
|
+
throw this.#runError("LOAD_WORKFLOW_RUN_HEADER", workflowName, runId, error);
|
|
26293
|
+
}
|
|
26294
|
+
}
|
|
26295
|
+
async loadWorkflowRun({ workflowName, runId, successfulSteps }) {
|
|
26296
|
+
try {
|
|
26297
|
+
const summary = successfulSteps === "summary";
|
|
26298
|
+
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
|
|
26299
|
+
FROM ${this.#runsTable()} r WHERE r.workflow_name = $1 AND r.run_id = $2`, summary ? [
|
|
26300
|
+
workflowName,
|
|
26301
|
+
runId,
|
|
26302
|
+
_mastra_core_storage.STEP_RESULT_SUMMARY_FIELDS
|
|
26303
|
+
] : [workflowName, runId]);
|
|
26304
|
+
if (!run) return null;
|
|
26305
|
+
const entries = toEntries(run.entries);
|
|
26306
|
+
const summarizedInQuery = new Set(run.entries.filter((row) => row.summarized).map((row) => row.step_id));
|
|
26307
|
+
const steps = {};
|
|
26308
|
+
const summarizedStepIds = [];
|
|
26309
|
+
for (const [stepId, result] of (0, _mastra_core_storage.latestStepResults)(entries)) {
|
|
26310
|
+
steps[stepId] = result;
|
|
26311
|
+
if (summary && stepId !== "input" && result?.status === "success") {
|
|
26312
|
+
if (!summarizedInQuery.has(stepId)) steps[stepId] = (0, _mastra_core_storage.summarizeStepResult)(result);
|
|
26313
|
+
summarizedStepIds.push(stepId);
|
|
26314
|
+
}
|
|
26315
|
+
}
|
|
26316
|
+
const base = await this.#stateBase(this.#db.client, entries);
|
|
26317
|
+
const { state, statePatchBytes } = (0, _mastra_core_storage.materializeState)(entries, { base });
|
|
26318
|
+
return {
|
|
26319
|
+
header: run.header,
|
|
26320
|
+
lastSeq: run.lastSeq,
|
|
26321
|
+
startStateSeq: run.startStateSeq,
|
|
26322
|
+
runToken: run.runToken,
|
|
26323
|
+
state,
|
|
26324
|
+
statePatchBytes,
|
|
26325
|
+
steps,
|
|
26326
|
+
summarizedStepIds
|
|
26327
|
+
};
|
|
26328
|
+
} catch (error) {
|
|
26329
|
+
throw this.#runError("LOAD_WORKFLOW_RUN", workflowName, runId, error);
|
|
26330
|
+
}
|
|
26331
|
+
}
|
|
26332
|
+
async loadWorkflowRunSteps({ workflowName, runId, stepIds, expectedLastSeq, expectedRunToken }) {
|
|
26333
|
+
try {
|
|
26334
|
+
const run = await this.#db.client.oneOrNone(`SELECT r."lastSeq", r."runToken", (
|
|
26335
|
+
SELECT COALESCE(jsonb_agg(jsonb_build_object(
|
|
26336
|
+
'seq', x.seq, 'kind', x.kind, 'step_id', x.step_id, 'absent', x.payload IS NULL, 'payload', x.payload
|
|
26337
|
+
) ORDER BY x.seq), '[]'::jsonb)
|
|
26338
|
+
FROM (
|
|
26339
|
+
SELECT e.seq, e.kind, e.step_id, e.payload,
|
|
26340
|
+
MAX(CASE WHEN e.kind = 'step' THEN e.seq END) OVER (PARTITION BY e.step_id) AS step_seq
|
|
26341
|
+
FROM ${this.#entriesTable()} e
|
|
26342
|
+
WHERE e.workflow_name = r.workflow_name AND e.run_id = r.run_id
|
|
26343
|
+
AND e.kind IN ('step', 'step-patch') AND e.step_id = ANY($3::text[])
|
|
26344
|
+
) x
|
|
26345
|
+
WHERE x.seq >= x.step_seq
|
|
26346
|
+
) AS entries
|
|
26347
|
+
FROM ${this.#runsTable()} r WHERE r.workflow_name = $1 AND r.run_id = $2`, [
|
|
26348
|
+
workflowName,
|
|
26349
|
+
runId,
|
|
26350
|
+
stepIds
|
|
26351
|
+
]);
|
|
26352
|
+
if (!run) return null;
|
|
26353
|
+
if (expectedLastSeq !== void 0 && run.lastSeq !== expectedLastSeq) return null;
|
|
26354
|
+
if (expectedRunToken !== void 0 && run.runToken !== expectedRunToken) return null;
|
|
26355
|
+
return Object.fromEntries((0, _mastra_core_storage.latestStepResults)(toEntries(run.entries)));
|
|
26356
|
+
} catch (error) {
|
|
26357
|
+
throw this.#runError("LOAD_WORKFLOW_RUN_STEPS", workflowName, runId, error);
|
|
26358
|
+
}
|
|
26359
|
+
}
|
|
26360
|
+
async updateWorkflowRunHeader({ workflowName, runId, opts, fence: explicitFence }) {
|
|
26361
|
+
const fence = (0, _mastra_core_storage.resolveRunFence)(this, explicitFence, runId);
|
|
26362
|
+
try {
|
|
26363
|
+
return await this.#db.client.tx(async (t) => {
|
|
26364
|
+
if (fence) await assertRunFence(t, this.#runOwnersTable(), fence, "updateWorkflowRunHeader");
|
|
26365
|
+
const run = await t.oneOrNone(`SELECT header FROM ${this.#runsTable()} WHERE workflow_name = $1 AND run_id = $2 FOR UPDATE`, [workflowName, runId]);
|
|
26366
|
+
if (!run) return void 0;
|
|
26367
|
+
const { expectedStatus, ...patch } = opts;
|
|
26368
|
+
if (!(0, _mastra_core_storage.matchesExpectedWorkflowStatus)(run.header.status, expectedStatus)) return void 0;
|
|
26369
|
+
const header = {
|
|
26370
|
+
...run.header,
|
|
26371
|
+
...patch
|
|
26372
|
+
};
|
|
26373
|
+
const now = /* @__PURE__ */ new Date();
|
|
26374
|
+
return await t.one(`UPDATE ${this.#runsTable()}
|
|
26375
|
+
SET header = $3::jsonb, status = $4, "updatedAt" = $5, "updatedAtZ" = $6
|
|
26376
|
+
WHERE workflow_name = $1 AND run_id = $2
|
|
26377
|
+
RETURNING header, "lastSeq", "startStateSeq", "runToken"`, [
|
|
26378
|
+
workflowName,
|
|
26379
|
+
runId,
|
|
26380
|
+
toPgJson(header),
|
|
26381
|
+
header.status,
|
|
26382
|
+
now,
|
|
26383
|
+
now
|
|
26384
|
+
]);
|
|
26385
|
+
});
|
|
26386
|
+
} catch (error) {
|
|
26387
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
26388
|
+
throw this.#runError("UPDATE_WORKFLOW_RUN_HEADER", workflowName, runId, error);
|
|
26389
|
+
}
|
|
26390
|
+
}
|
|
26391
|
+
/**
|
|
26392
|
+
* Upserts a storage v2 run's header from `$1`–`$12` ({@link #headerParams}), adds `$7` (the
|
|
26393
|
+
* number of entries the write adds) to `lastSeq`, and returns the new `lastSeq`, `stateShared`
|
|
26394
|
+
* and `runToken`. `$12` is the run token of a run the write creates; a stored run keeps its own.
|
|
26395
|
+
* A checkpoint's state entries come last, so its `startStateSeq` is the new `lastSeq`; an append
|
|
26396
|
+
* keeps the stored one unless given `$13`. The header's row lock is held
|
|
26397
|
+
* until commit, so the writes to one run commit in `seq` order. With `expectedParam` (a text
|
|
26398
|
+
* array of statuses), a stored run whose status isn't in it is left as is and nothing is returned.
|
|
26399
|
+
*/
|
|
26400
|
+
#upsertHeaderSql(checkpoint, expectedParam) {
|
|
26401
|
+
const startStateSeq = checkpoint ? {
|
|
26402
|
+
insert: "$7::int",
|
|
26403
|
+
update: "r.\"lastSeq\" + EXCLUDED.\"lastSeq\""
|
|
26404
|
+
} : {
|
|
26405
|
+
insert: "COALESCE($13::int, 0)",
|
|
26406
|
+
update: "COALESCE($13::int, r.\"startStateSeq\")"
|
|
26407
|
+
};
|
|
26408
|
+
return `INSERT INTO ${this.#runsTable()} AS r
|
|
26409
|
+
(workflow_name, run_id, "resourceId", "threadId", status, header, "lastSeq", "startStateSeq", "stateShared",
|
|
26410
|
+
"runToken", "createdAt", "updatedAt", "createdAtZ", "updatedAtZ")
|
|
26411
|
+
VALUES ($1, $2, $3, $4, $5, $6::jsonb, $7::int, ${startStateSeq.insert}, false, $12, $8, $9, $10, $11)
|
|
26412
|
+
ON CONFLICT (workflow_name, run_id) DO UPDATE
|
|
26413
|
+
SET "resourceId" = COALESCE(EXCLUDED."resourceId", r."resourceId"), "threadId" = EXCLUDED."threadId",
|
|
26414
|
+
status = EXCLUDED.status, header = EXCLUDED.header, "lastSeq" = r."lastSeq" + EXCLUDED."lastSeq",
|
|
26415
|
+
"startStateSeq" = ${startStateSeq.update},
|
|
26416
|
+
"updatedAt" = EXCLUDED."updatedAt", "updatedAtZ" = EXCLUDED."updatedAtZ"
|
|
26417
|
+
${expectedParam ? `WHERE r.status = ANY(${expectedParam}::text[])` : ""}
|
|
26418
|
+
RETURNING "lastSeq", "stateShared", "runToken"`;
|
|
26419
|
+
}
|
|
26420
|
+
#headerParams(run, entryCount) {
|
|
26421
|
+
const now = /* @__PURE__ */ new Date();
|
|
26422
|
+
return [
|
|
26423
|
+
run.workflowName,
|
|
26424
|
+
run.runId,
|
|
26425
|
+
run.resourceId,
|
|
26426
|
+
run.threadId ?? null,
|
|
26427
|
+
run.header.status,
|
|
26428
|
+
toPgJson(run.header),
|
|
26429
|
+
entryCount,
|
|
26430
|
+
now,
|
|
26431
|
+
now,
|
|
26432
|
+
now,
|
|
26433
|
+
now,
|
|
26434
|
+
crypto.randomUUID()
|
|
26435
|
+
];
|
|
26436
|
+
}
|
|
26437
|
+
async writeWorkflowRunCheckpoint({ fence: explicitFence, expectedStatus, steps, state, stateBase, statePatch, ...run }) {
|
|
26438
|
+
const entries = journalEntries(steps, {
|
|
26439
|
+
state,
|
|
26440
|
+
stateBase,
|
|
26441
|
+
statePatch
|
|
26442
|
+
}, true);
|
|
26443
|
+
const fence = (0, _mastra_core_storage.resolveRunFence)(this, explicitFence, run.runId);
|
|
26444
|
+
const expected = expectedStatus === void 0 ? [] : [[expectedStatus].flat()];
|
|
26445
|
+
try {
|
|
26446
|
+
return await this.#db.client.tx(async (t) => {
|
|
26447
|
+
if (fence) await assertRunFence(t, this.#runOwnersTable(), fence, "writeWorkflowRunCheckpoint");
|
|
26448
|
+
const base = state === void 0 ? stateBase : void 0;
|
|
26449
|
+
let baseLastSeq;
|
|
26450
|
+
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;
|
|
26451
|
+
const header = await t.oneOrNone(this.#upsertHeaderSql(true, expected.length ? "$13" : void 0), [...this.#headerParams(run, entries.length), ...expected]);
|
|
26452
|
+
if (!header) return null;
|
|
26453
|
+
if (base) {
|
|
26454
|
+
await t.none(`UPDATE ${this.#runsTable()} SET "stateShared" = true
|
|
26455
|
+
WHERE workflow_name = $1 AND run_id = $2 AND NOT "stateShared"`, [base.workflowName, base.runId]);
|
|
26456
|
+
const { stored } = await t.one(`SELECT EXISTS (
|
|
26457
|
+
SELECT 1 FROM ${this.#entriesTable()} WHERE workflow_name = $1 AND run_id = $2
|
|
26458
|
+
AND CASE WHEN $4::boolean THEN kind = 'state-draft' AND seq = $3::int
|
|
26459
|
+
ELSE kind IN ('state', 'state-base') AND seq <= $3::int END
|
|
26460
|
+
) AS stored`, [
|
|
26461
|
+
base.workflowName,
|
|
26462
|
+
base.runId,
|
|
26463
|
+
base.seq,
|
|
26464
|
+
base.draft === true
|
|
26465
|
+
]);
|
|
26466
|
+
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`);
|
|
26467
|
+
}
|
|
26468
|
+
const { lastSeq, stateShared, runToken } = header;
|
|
26469
|
+
await t.none(`DELETE FROM ${this.#entriesTable()}
|
|
26470
|
+
WHERE workflow_name = $1 AND run_id = $2${stateShared ? ` AND kind IN ('step', 'step-patch')` : ""}`, [run.workflowName, run.runId]);
|
|
26471
|
+
await t.none(`INSERT INTO ${this.#entriesTable()} (workflow_name, run_id, seq, kind, step_id, payload)
|
|
26472
|
+
SELECT $1, $2, $3::int - $4::int + e.ord, ${ENTRY_VALUES}
|
|
26473
|
+
FROM ${entryRows("$5")}`, [
|
|
26474
|
+
run.workflowName,
|
|
26475
|
+
run.runId,
|
|
26476
|
+
lastSeq,
|
|
26477
|
+
entries.length,
|
|
26478
|
+
toPgJson(entries)
|
|
26479
|
+
]);
|
|
26480
|
+
return {
|
|
26481
|
+
lastSeq,
|
|
26482
|
+
runToken
|
|
26483
|
+
};
|
|
26484
|
+
});
|
|
26485
|
+
} catch (error) {
|
|
26486
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
26487
|
+
throw this.#runError("WRITE_WORKFLOW_RUN_CHECKPOINT", run.workflowName, run.runId, error);
|
|
26488
|
+
}
|
|
26489
|
+
}
|
|
26490
|
+
/** One statement: the header upsert numbers the entries inserted after it. */
|
|
26491
|
+
async appendWorkflowRunEntries({ fence, steps, stepPatches, state, statePatch, startStateSeq, expectedStatus, ...run }) {
|
|
26492
|
+
const entries = journalEntries(steps, {
|
|
26493
|
+
state,
|
|
26494
|
+
statePatch
|
|
26495
|
+
}, false, stepPatches);
|
|
26496
|
+
const expected = expectedStatus === void 0 ? [] : [[expectedStatus].flat()];
|
|
26497
|
+
try {
|
|
26498
|
+
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)}),
|
|
26499
|
+
e AS (
|
|
26500
|
+
INSERT INTO ${this.#entriesTable()} (workflow_name, run_id, seq, kind, step_id, payload)
|
|
26501
|
+
SELECT $1, $2, h."lastSeq" - $7::int + e.ord, ${ENTRY_VALUES}
|
|
26502
|
+
FROM h, ${entryRows("$14")}
|
|
26503
|
+
)
|
|
26504
|
+
SELECT "lastSeq", "runToken" FROM h`, [
|
|
26505
|
+
...this.#headerParams(run, entries.length),
|
|
26506
|
+
startStateSeq ?? null,
|
|
26507
|
+
toPgJson(entries),
|
|
26508
|
+
...expected
|
|
26509
|
+
]));
|
|
26510
|
+
} catch (error) {
|
|
26511
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
26512
|
+
throw this.#runError("APPEND_WORKFLOW_RUN_ENTRIES", run.workflowName, run.runId, error);
|
|
26513
|
+
}
|
|
26514
|
+
}
|
|
26515
|
+
async loadWorkflowRunState({ workflowName, runId, seq }) {
|
|
26516
|
+
try {
|
|
26517
|
+
return (await this.#stateAt(this.#db.client, workflowName, runId, { seq }))?.state ?? null;
|
|
26518
|
+
} catch (error) {
|
|
26519
|
+
throw this.#runError("LOAD_WORKFLOW_RUN_STATE", workflowName, runId, error);
|
|
26520
|
+
}
|
|
26521
|
+
}
|
|
26522
|
+
async appendWorkflowRunStateDraft(args) {
|
|
26523
|
+
const { workflowName, runId } = args;
|
|
26524
|
+
const payload = "state" in args ? { state: args.state } : {
|
|
26525
|
+
on: args.on.draft ? {
|
|
26526
|
+
seq: args.on.seq,
|
|
26527
|
+
draft: true
|
|
26528
|
+
} : { seq: args.on.seq },
|
|
26529
|
+
ops: args.ops
|
|
26530
|
+
};
|
|
26531
|
+
let row;
|
|
26532
|
+
try {
|
|
26533
|
+
row = await withRunFence(this.#db.client, this.#runOwnersTable(), (0, _mastra_core_storage.resolveRunFence)(this, void 0, runId), "appendWorkflowRunStateDraft", (q) => q.oneOrNone(`WITH h AS (
|
|
26534
|
+
UPDATE ${this.#runsTable()} SET "lastSeq" = "lastSeq" + 1, "stateShared" = true
|
|
26535
|
+
WHERE workflow_name = $1 AND run_id = $2
|
|
26536
|
+
RETURNING "lastSeq"
|
|
26537
|
+
)
|
|
26538
|
+
INSERT INTO ${this.#entriesTable()} (workflow_name, run_id, seq, kind, step_id, payload)
|
|
26539
|
+
SELECT $1, $2, h."lastSeq", 'state-draft', NULL, $3::jsonb FROM h
|
|
26540
|
+
RETURNING seq`, [
|
|
26541
|
+
workflowName,
|
|
26542
|
+
runId,
|
|
26543
|
+
toPgJson(payload)
|
|
26544
|
+
]));
|
|
26545
|
+
} catch (error) {
|
|
26546
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
26547
|
+
throw this.#runError("APPEND_WORKFLOW_RUN_STATE_DRAFT", workflowName, runId, error);
|
|
26548
|
+
}
|
|
26549
|
+
if (!row) throw new Error(`Storage v2 run ${workflowName}/${runId} is not stored`);
|
|
26550
|
+
return row;
|
|
25873
26551
|
}
|
|
25874
26552
|
supportsRunFencing() {
|
|
25875
26553
|
return true;
|
|
@@ -25998,6 +26676,7 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
|
|
|
25998
26676
|
});
|
|
25999
26677
|
const existingSnapshotResult = await t.oneOrNone(`SELECT snapshot FROM ${tableName} WHERE workflow_name = $1 AND run_id = $2 FOR UPDATE`, [workflowName, runId]);
|
|
26000
26678
|
if (!existingSnapshotResult) {
|
|
26679
|
+
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
26680
|
(0, _mastra_core_storage.assertWorkflowStepGuard)({
|
|
26002
26681
|
snapshot: null,
|
|
26003
26682
|
stepId,
|
|
@@ -26070,11 +26749,36 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
|
|
|
26070
26749
|
}, error);
|
|
26071
26750
|
}
|
|
26072
26751
|
}
|
|
26073
|
-
async updateWorkflowState({ workflowName, runId, opts, fence: explicitFence }) {
|
|
26752
|
+
async updateWorkflowState({ workflowName, runId, opts, fence: explicitFence, storageVersion }) {
|
|
26074
26753
|
const fence = (0, _mastra_core_storage.resolveRunFence)(this, explicitFence, runId);
|
|
26075
26754
|
try {
|
|
26076
26755
|
return await this.#db.client.tx(async (t) => {
|
|
26077
26756
|
if (fence) await assertRunFence(t, this.#runOwnersTable(), fence, "updateWorkflowState");
|
|
26757
|
+
if (storageVersion !== 1) {
|
|
26758
|
+
const run = await t.oneOrNone(`SELECT header FROM ${this.#runsTable()} WHERE workflow_name = $1 AND run_id = $2 FOR UPDATE`, [workflowName, runId]);
|
|
26759
|
+
if (run) {
|
|
26760
|
+
const { expectedStatus, ...patch } = opts;
|
|
26761
|
+
if (!(0, _mastra_core_storage.matchesExpectedWorkflowStatus)(run.header.status, expectedStatus)) return;
|
|
26762
|
+
const header = {
|
|
26763
|
+
...run.header,
|
|
26764
|
+
...patch
|
|
26765
|
+
};
|
|
26766
|
+
const now = /* @__PURE__ */ new Date();
|
|
26767
|
+
const { entries } = await t.one(`UPDATE ${this.#runsTable()} r
|
|
26768
|
+
SET header = $3::jsonb, status = $4, "updatedAt" = $5, "updatedAtZ" = $6
|
|
26769
|
+
WHERE r.workflow_name = $1 AND r.run_id = $2
|
|
26770
|
+
RETURNING ${this.#entriesSql("r")} AS entries`, [
|
|
26771
|
+
workflowName,
|
|
26772
|
+
runId,
|
|
26773
|
+
toPgJson(header),
|
|
26774
|
+
header.status,
|
|
26775
|
+
now,
|
|
26776
|
+
now
|
|
26777
|
+
]);
|
|
26778
|
+
return this.#materializeRun(t, header, entries);
|
|
26779
|
+
}
|
|
26780
|
+
if (storageVersion === 2) return void 0;
|
|
26781
|
+
}
|
|
26078
26782
|
const tableName = getTableName({
|
|
26079
26783
|
indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
|
|
26080
26784
|
schemaName: getSchemaName(this.#schema)
|
|
@@ -26154,10 +26858,14 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
|
|
|
26154
26858
|
async loadWorkflowRunStatus({ workflowName, runId }) {
|
|
26155
26859
|
try {
|
|
26156
26860
|
const statusExpr = await this.#statusExpression("snapshot");
|
|
26157
|
-
return (await this.#db.client.oneOrNone(`SELECT
|
|
26861
|
+
return (await this.#db.client.oneOrNone(`SELECT COALESCE(
|
|
26862
|
+
(SELECT status FROM ${this.#runsTable()} WHERE workflow_name = $1 AND run_id = $2),
|
|
26863
|
+
(SELECT ${statusExpr} FROM ${getTableName({
|
|
26158
26864
|
indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
|
|
26159
26865
|
schemaName: getSchemaName(this.#schema)
|
|
26160
|
-
})}
|
|
26866
|
+
})}
|
|
26867
|
+
WHERE "workflow_name" = $1 AND "run_id" = $2 ORDER BY "createdAt" DESC LIMIT 1)
|
|
26868
|
+
) AS status`, [workflowName, runId]))?.status ?? null;
|
|
26161
26869
|
} catch (error) {
|
|
26162
26870
|
throw new _mastra_core_error.MastraError({
|
|
26163
26871
|
id: (0, _mastra_core_storage.createStorageErrorId)("PG", "LOAD_WORKFLOW_RUN_STATUS", "FAILED"),
|
|
@@ -26170,8 +26878,14 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
|
|
|
26170
26878
|
}, error);
|
|
26171
26879
|
}
|
|
26172
26880
|
}
|
|
26173
|
-
async loadWorkflowSnapshot({ workflowName, runId }) {
|
|
26881
|
+
async loadWorkflowSnapshot({ workflowName, runId, storageVersion }) {
|
|
26174
26882
|
try {
|
|
26883
|
+
if (storageVersion !== 1) {
|
|
26884
|
+
const run = await this.#db.client.oneOrNone(`SELECT r.header, ${this.#entriesSql("r")} AS entries
|
|
26885
|
+
FROM ${this.#runsTable()} r WHERE r.workflow_name = $1 AND r.run_id = $2`, [workflowName, runId]);
|
|
26886
|
+
if (run) return await this.#materializeRun(this.#db.client, run.header, run.entries);
|
|
26887
|
+
if (storageVersion === 2) return null;
|
|
26888
|
+
}
|
|
26175
26889
|
const result = await this.#db.load({
|
|
26176
26890
|
tableName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
|
|
26177
26891
|
keys: {
|
|
@@ -26188,7 +26902,7 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
|
|
|
26188
26902
|
}, error);
|
|
26189
26903
|
}
|
|
26190
26904
|
}
|
|
26191
|
-
async getWorkflowRunById({ runId, workflowName }) {
|
|
26905
|
+
async getWorkflowRunById({ runId, workflowName, storageVersion }) {
|
|
26192
26906
|
try {
|
|
26193
26907
|
const conditions = [];
|
|
26194
26908
|
const values = [];
|
|
@@ -26204,18 +26918,14 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
|
|
|
26204
26918
|
paramIndex++;
|
|
26205
26919
|
}
|
|
26206
26920
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
26207
|
-
const query =
|
|
26208
|
-
|
|
26209
|
-
|
|
26210
|
-
|
|
26211
|
-
})
|
|
26212
|
-
|
|
26213
|
-
ORDER BY "createdAt" DESC LIMIT 1
|
|
26214
|
-
`;
|
|
26215
|
-
const queryValues = values;
|
|
26216
|
-
const result = await this.#db.readClient.oneOrNone(query, queryValues);
|
|
26921
|
+
const query = await this.#runsQuery({
|
|
26922
|
+
v1Where: storageVersion === 2 ? null : whereClause,
|
|
26923
|
+
v2Where: storageVersion === 1 ? null : whereClause,
|
|
26924
|
+
page: "LIMIT 1"
|
|
26925
|
+
});
|
|
26926
|
+
const result = await this.#db.readClient.oneOrNone(query, values);
|
|
26217
26927
|
if (!result) return null;
|
|
26218
|
-
return this.
|
|
26928
|
+
return await this.#parseRun(this.#db.readClient, result);
|
|
26219
26929
|
} catch (error) {
|
|
26220
26930
|
throw new _mastra_core_error.MastraError({
|
|
26221
26931
|
id: (0, _mastra_core_storage.createStorageErrorId)("PG", "GET_WORKFLOW_RUN_BY_ID", "FAILED"),
|
|
@@ -26228,12 +26938,28 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
|
|
|
26228
26938
|
}, error);
|
|
26229
26939
|
}
|
|
26230
26940
|
}
|
|
26231
|
-
async deleteWorkflowRunById({ runId, workflowName, fence }) {
|
|
26941
|
+
async deleteWorkflowRunById({ runId, workflowName, storageVersion, fence }) {
|
|
26942
|
+
const resolvedFence = (0, _mastra_core_storage.resolveRunFence)(this, fence, runId);
|
|
26232
26943
|
try {
|
|
26233
|
-
|
|
26234
|
-
|
|
26235
|
-
|
|
26236
|
-
|
|
26944
|
+
if (storageVersion === 1) {
|
|
26945
|
+
await withRunFence(this.#db.client, this.#runOwnersTable(), resolvedFence, "deleteWorkflowRunById", (q) => q.none(`DELETE FROM ${getTableName({
|
|
26946
|
+
indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
|
|
26947
|
+
schemaName: getSchemaName(this.#schema)
|
|
26948
|
+
})} WHERE run_id = $1 AND workflow_name = $2`, [runId, workflowName]));
|
|
26949
|
+
return;
|
|
26950
|
+
}
|
|
26951
|
+
await this.#db.client.tx(async (t) => {
|
|
26952
|
+
if (resolvedFence) await assertRunFence(t, this.#runOwnersTable(), resolvedFence, "deleteWorkflowRunById");
|
|
26953
|
+
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);
|
|
26954
|
+
for (const table of [
|
|
26955
|
+
this.#runsTable(),
|
|
26956
|
+
this.#entriesTable(),
|
|
26957
|
+
...storageVersion === 2 ? [] : [getTableName({
|
|
26958
|
+
indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
|
|
26959
|
+
schemaName: getSchemaName(this.#schema)
|
|
26960
|
+
})]
|
|
26961
|
+
]) await t.none(`DELETE FROM ${table} WHERE run_id = $1 AND workflow_name = $2`, [runId, workflowName]);
|
|
26962
|
+
});
|
|
26237
26963
|
} catch (error) {
|
|
26238
26964
|
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
26239
26965
|
throw new _mastra_core_error.MastraError({
|
|
@@ -26250,77 +26976,84 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
|
|
|
26250
26976
|
async listWorkflowRuns({ workflowName, fromDate, toDate, perPage, page, resourceId, threadId, status, summary } = {}) {
|
|
26251
26977
|
try {
|
|
26252
26978
|
const conditions = [];
|
|
26979
|
+
const v2Conditions = [];
|
|
26253
26980
|
const values = [];
|
|
26254
26981
|
let paramIndex = 1;
|
|
26255
26982
|
if (workflowName) {
|
|
26256
26983
|
conditions.push(`workflow_name = $${paramIndex}`);
|
|
26984
|
+
v2Conditions.push(`workflow_name = $${paramIndex}`);
|
|
26257
26985
|
values.push(workflowName);
|
|
26258
26986
|
paramIndex++;
|
|
26259
26987
|
}
|
|
26260
26988
|
if (status) {
|
|
26261
26989
|
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
26990
|
conditions.push(`${statusExpr} = $${paramIndex}`);
|
|
26991
|
+
v2Conditions.push(`status = $${paramIndex}`);
|
|
26263
26992
|
values.push(status);
|
|
26264
26993
|
paramIndex++;
|
|
26265
26994
|
}
|
|
26266
26995
|
if (resourceId) {
|
|
26267
|
-
if (await this.#db.hasColumn(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, "resourceId")) {
|
|
26268
|
-
|
|
26269
|
-
|
|
26270
|
-
|
|
26271
|
-
|
|
26996
|
+
if (await this.#db.hasColumn(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, "resourceId")) conditions.push(`"resourceId" = $${paramIndex}`);
|
|
26997
|
+
else this.logger?.warn?.(`[${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
|
|
26998
|
+
v2Conditions.push(`"resourceId" = $${paramIndex}`);
|
|
26999
|
+
values.push(resourceId);
|
|
27000
|
+
paramIndex++;
|
|
26272
27001
|
}
|
|
26273
27002
|
if (threadId) {
|
|
26274
|
-
if (await this.#db.getColumnType(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, "snapshot") === "jsonb") {
|
|
26275
|
-
|
|
26276
|
-
|
|
26277
|
-
|
|
26278
|
-
|
|
27003
|
+
if (await this.#db.getColumnType(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, "snapshot") === "jsonb") conditions.push(`${WORKFLOW_SNAPSHOT_THREAD_ID_EXPR} = $${paramIndex}`);
|
|
27004
|
+
else this.logger?.warn?.(`[${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT}] snapshot column is not jsonb. Skipping threadId filter.`);
|
|
27005
|
+
v2Conditions.push(`"threadId" = $${paramIndex}`);
|
|
27006
|
+
values.push(threadId);
|
|
27007
|
+
paramIndex++;
|
|
26279
27008
|
}
|
|
26280
27009
|
if (fromDate) {
|
|
26281
27010
|
conditions.push(`"createdAt" >= $${paramIndex}`);
|
|
27011
|
+
v2Conditions.push(`"createdAt" >= $${paramIndex}`);
|
|
26282
27012
|
values.push(fromDate);
|
|
26283
27013
|
paramIndex++;
|
|
26284
27014
|
}
|
|
26285
27015
|
if (toDate) {
|
|
26286
27016
|
conditions.push(`"createdAt" <= $${paramIndex}`);
|
|
27017
|
+
v2Conditions.push(`"createdAt" <= $${paramIndex}`);
|
|
26287
27018
|
values.push(toDate);
|
|
26288
27019
|
paramIndex++;
|
|
26289
27020
|
}
|
|
26290
27021
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
26291
|
-
|
|
27022
|
+
const v2WhereClause = v2Conditions.length > 0 ? `WHERE ${v2Conditions.join(" AND ")}` : "";
|
|
26292
27023
|
const usePagination = typeof perPage === "number" && typeof page === "number";
|
|
26293
|
-
if (usePagination) {
|
|
26294
|
-
const countResult = await this.#db.client.one(`SELECT COUNT(*) as count FROM ${getTableName({
|
|
26295
|
-
indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
|
|
26296
|
-
schemaName: getSchemaName(this.#schema)
|
|
26297
|
-
})} ${whereClause}`, values);
|
|
26298
|
-
total = Number(countResult.count);
|
|
26299
|
-
}
|
|
26300
27024
|
const normalizedPerPage = usePagination ? (0, _mastra_core_storage.normalizePerPage)(perPage, Number.MAX_SAFE_INTEGER) : 0;
|
|
26301
|
-
const offset = usePagination ? page * normalizedPerPage :
|
|
26302
|
-
let
|
|
27025
|
+
const offset = usePagination ? page * normalizedPerPage : 0;
|
|
27026
|
+
let v1Snapshot = "snapshot";
|
|
26303
27027
|
if (summary) {
|
|
26304
27028
|
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
|
-
|
|
27029
|
+
v1Snapshot = `jsonb_build_object('status', ${snapshotJson} -> 'status', 'timestamp', ${snapshotJson} -> 'timestamp')`;
|
|
26306
27030
|
}
|
|
26307
|
-
const query =
|
|
26308
|
-
|
|
26309
|
-
|
|
26310
|
-
|
|
26311
|
-
|
|
26312
|
-
|
|
26313
|
-
|
|
26314
|
-
${usePagination ? ` LIMIT $${paramIndex} OFFSET $${paramIndex + 1}` : ""}
|
|
26315
|
-
`;
|
|
27031
|
+
const query = await this.#runsQuery({
|
|
27032
|
+
v1Where: whereClause,
|
|
27033
|
+
v2Where: v2WhereClause,
|
|
27034
|
+
v1Snapshot,
|
|
27035
|
+
summary,
|
|
27036
|
+
page: usePagination ? `LIMIT $${paramIndex} OFFSET $${paramIndex + 1}` : ""
|
|
27037
|
+
});
|
|
26316
27038
|
const queryValues = usePagination ? [
|
|
26317
27039
|
...values,
|
|
26318
27040
|
normalizedPerPage,
|
|
26319
27041
|
offset
|
|
26320
27042
|
] : values;
|
|
26321
|
-
const
|
|
26322
|
-
|
|
26323
|
-
|
|
27043
|
+
const result = await this.#db.client.manyOrNone(query, queryValues);
|
|
27044
|
+
const runs = await Promise.all((result || []).map((row) => this.#parseRun(this.#db.client, row)));
|
|
27045
|
+
let total = 0;
|
|
27046
|
+
if (usePagination) {
|
|
27047
|
+
total = offset + runs.length;
|
|
27048
|
+
if (runs.length === normalizedPerPage || runs.length === 0 && offset > 0) {
|
|
27049
|
+
const countResult = await this.#db.client.one(`SELECT (SELECT COUNT(*) FROM ${getTableName({
|
|
27050
|
+
indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
|
|
27051
|
+
schemaName: getSchemaName(this.#schema)
|
|
27052
|
+
})} ${whereClause})
|
|
27053
|
+
+ (SELECT COUNT(*) FROM ${this.#runsTable()} ${v2WhereClause}) AS count`, values);
|
|
27054
|
+
total = Number(countResult.count);
|
|
27055
|
+
}
|
|
27056
|
+
}
|
|
26324
27057
|
return {
|
|
26325
27058
|
runs,
|
|
26326
27059
|
total: total || runs.length
|