@mastra/pg 1.35.0 → 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.
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +158 -2
- 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 +779 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +780 -58
- 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/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/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ErrorCategory, ErrorDomain, MastraError } from "@mastra/core/error";
|
|
2
2
|
import * as coreStorage from "@mastra/core/storage";
|
|
3
|
-
import { AgentsStorage, BRANCH_SPAN_TYPES, BackgroundTasksStorage, BlobStore, ChannelsStorage, DATASETS_SCHEMA, DATASET_ITEMS_SCHEMA, DATASET_VERSIONS_SCHEMA, DatasetsStorage, EXPERIMENTS_SCHEMA, EXPERIMENT_RESULTS_SCHEMA, EntityType, ExperimentsStorage, FactoryStorage, FavoritesStorage, KnowledgeConflictError, KnowledgeNotFoundError, KnowledgeStorage, MCPClientsStorage, MCPServersStorage, METRIC_DISTINCT_COLUMNS, MastraCompositeStore, MemoryStorage, NotificationsStorage, OBSERVATIONAL_MEMORY_TABLE_SCHEMA, ObservabilityStorage, PromptBlocksStorage, RUN_FENCING_TABLE_SCHEMAS, RunFenceConflictError, SchedulesStorage, ScorerDefinitionsStorage, ScoresStorage, SkillsStorage, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_BACKGROUND_TASKS, TABLE_CHANNEL_CONFIG, TABLE_CHANNEL_INSTALLATIONS, TABLE_CONFIGS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_FAVORITES, TABLE_MCP_CLIENTS, TABLE_MCP_CLIENT_VERSIONS, TABLE_MCP_SERVERS, TABLE_MCP_SERVER_VERSIONS, TABLE_MEMORY_RUN_FENCES, TABLE_MESSAGES, TABLE_NOTIFICATIONS, TABLE_PROMPT_BLOCKS, TABLE_PROMPT_BLOCK_VERSIONS, TABLE_RESOURCES, TABLE_SCHEDULES, TABLE_SCHEDULE_TRIGGERS, TABLE_SCHEMAS, TABLE_SCORERS, TABLE_SCORER_DEFINITIONS, TABLE_SCORER_DEFINITION_VERSIONS, TABLE_SKILLS, TABLE_SKILL_BLOBS, TABLE_SKILL_VERSIONS, TABLE_SPANS, TABLE_THREADS, TABLE_THREAD_STATE, TABLE_TOOL_PROVIDER_CONNECTIONS, TABLE_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_RUN_OWNERS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, ThreadStateStorage, ToolProviderConnectionsStorage, TraceStatus, UniqueViolationError, WorkflowDefinitionsStorage, WorkflowsStorage, WorkspacesStorage, assertWorkflowStepGuard, calculatePagination, computeTraceStatus, createKnowledgeUlid, createStorageErrorId, createVectorErrorId, ensureDate, executeRetentionPrune, getDefaultValue, getSqlType, getStringAttribute, hasErrorCode, isRunFenceConflictError, isWorkflowStepConflictError, listBranchesArgsSchema, listFeedbackArgsSchema, listLogsArgsSchema, listMetricsArgsSchema, listScoresArgsSchema, listTracesArgsSchema, matchesExpectedWorkflowStatus, matchesRunFence, mergeWorkflowStepResult, normalizePerPage, normalizeScheduleTarget, parseDuration, resolveListOrderBy, resolveRetentionTargets, resolveRunFence, retentionCutoffMs, runRetentionBatches, safelyParseJSON, storageMessageMatchesMetadataFilter, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
|
|
3
|
+
import { AgentsStorage, BRANCH_SPAN_TYPES, BackgroundTasksStorage, BlobStore, ChannelsStorage, DATASETS_SCHEMA, DATASET_ITEMS_SCHEMA, DATASET_VERSIONS_SCHEMA, DatasetsStorage, EXPERIMENTS_SCHEMA, EXPERIMENT_RESULTS_SCHEMA, EntityType, ExperimentsStorage, FactoryStorage, FavoritesStorage, KnowledgeConflictError, KnowledgeNotFoundError, KnowledgeStorage, MCPClientsStorage, MCPServersStorage, METRIC_DISTINCT_COLUMNS, MastraCompositeStore, MemoryStorage, NotificationsStorage, OBSERVATIONAL_MEMORY_TABLE_SCHEMA, ObservabilityStorage, PromptBlocksStorage, RUN_FENCING_TABLE_SCHEMAS, RunFenceConflictError, STEP_RESULT_SUMMARY_FIELDS, SchedulesStorage, ScorerDefinitionsStorage, ScoresStorage, SkillsStorage, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_BACKGROUND_TASKS, TABLE_CHANNEL_CONFIG, TABLE_CHANNEL_INSTALLATIONS, TABLE_CONFIGS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_FAVORITES, TABLE_MCP_CLIENTS, TABLE_MCP_CLIENT_VERSIONS, TABLE_MCP_SERVERS, TABLE_MCP_SERVER_VERSIONS, TABLE_MEMORY_RUN_FENCES, TABLE_MESSAGES, TABLE_NOTIFICATIONS, TABLE_PROMPT_BLOCKS, TABLE_PROMPT_BLOCK_VERSIONS, TABLE_RESOURCES, TABLE_SCHEDULES, TABLE_SCHEDULE_TRIGGERS, TABLE_SCHEMAS, TABLE_SCORERS, TABLE_SCORER_DEFINITIONS, TABLE_SCORER_DEFINITION_VERSIONS, TABLE_SKILLS, TABLE_SKILL_BLOBS, TABLE_SKILL_VERSIONS, TABLE_SPANS, TABLE_THREADS, TABLE_THREAD_STATE, TABLE_TOOL_PROVIDER_CONNECTIONS, TABLE_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_RUNS, TABLE_WORKFLOW_RUN_ENTRIES, TABLE_WORKFLOW_RUN_OWNERS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, ThreadStateStorage, ToolProviderConnectionsStorage, TraceStatus, UniqueViolationError, WORKFLOW_RUN_JOURNAL_TABLE_CONFIGS, WorkflowDefinitionsStorage, WorkflowsStorage, WorkspacesStorage, assertWorkflowStepGuard, calculatePagination, computeTraceStatus, createKnowledgeUlid, createStorageErrorId, createVectorErrorId, ensureDate, executeRetentionPrune, getDefaultValue, getSqlType, getStringAttribute, hasErrorCode, isRunFenceConflictError, isWorkflowStepConflictError, latestStepResults, listBranchesArgsSchema, listFeedbackArgsSchema, listLogsArgsSchema, listMetricsArgsSchema, listScoresArgsSchema, listTracesArgsSchema, matchesExpectedWorkflowStatus, matchesRunFence, materializeState, materializeWorkflowRun, mergeWorkflowStepResult, normalizePerPage, normalizeScheduleTarget, parseDuration, resolveListOrderBy, resolveRetentionTargets, resolveRunFence, retentionCutoffMs, runRetentionBatches, safelyParseJSON, stateBaseOf, storageMessageMatchesMetadataFilter, summarizeStepResult, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
|
|
4
4
|
import { parseFieldKey, parseSqlIdentifier } from "@mastra/core/utils";
|
|
5
5
|
import { MastraVector, validateTopK, validateUpsertInput } from "@mastra/core/vector";
|
|
6
6
|
import { Mutex } from "async-mutex";
|
|
@@ -5219,13 +5219,13 @@ function cutoffFor(policy, anchorType, now = Date.now()) {
|
|
|
5219
5219
|
return anchorType === "epoch-ms" ? cutoffMs : new Date(cutoffMs);
|
|
5220
5220
|
}
|
|
5221
5221
|
const runBatchedDelete = runRetentionBatches;
|
|
5222
|
-
function runPrune({ db, domain, targets, options }) {
|
|
5222
|
+
function runPrune({ db, domain, targets, options, deleteBatch }) {
|
|
5223
5223
|
return executeRetentionPrune({
|
|
5224
5224
|
domain,
|
|
5225
5225
|
targets,
|
|
5226
5226
|
options,
|
|
5227
5227
|
cutoffFor: (target, now) => cutoffFor(target.policy, target.anchorType ?? "timestamp", now),
|
|
5228
|
-
deleteBatch: (target, cutoff, limit) => db.pruneBatch({
|
|
5228
|
+
deleteBatch: (target, cutoff, limit) => deleteBatch?.(target, cutoff, limit) ?? db.pruneBatch({
|
|
5229
5229
|
tableName: target.table,
|
|
5230
5230
|
column: target.column,
|
|
5231
5231
|
cutoff,
|
|
@@ -25602,6 +25602,16 @@ function workflowSnapshotStatusIndexSQL(indexName, schemaName, concurrently = fa
|
|
|
25602
25602
|
});
|
|
25603
25603
|
return `CREATE INDEX ${concurrently ? "CONCURRENTLY " : ""}IF NOT EXISTS "${indexName}" ON ${tableName} (workflow_name, (snapshot ->> 'status'), "createdAt" DESC)`;
|
|
25604
25604
|
}
|
|
25605
|
+
/**
|
|
25606
|
+
* Storage v2 index names share the long `<schema>_mastra_workflow_runs_` prefix, so a long
|
|
25607
|
+
* schema name would collapse them to one 63-byte identifier under plain truncation; hash instead.
|
|
25608
|
+
*/
|
|
25609
|
+
function runIndexName(schemaPrefix, baseName) {
|
|
25610
|
+
return buildConstraintName({
|
|
25611
|
+
baseName: `${schemaPrefix}${baseName}`,
|
|
25612
|
+
hashWhenTruncated: true
|
|
25613
|
+
});
|
|
25614
|
+
}
|
|
25605
25615
|
/** Base name (before any schema prefix) of the expression index backing the threadId filter. */
|
|
25606
25616
|
const WORKFLOW_SNAPSHOT_THREAD_ID_INDEX = "mastra_workflow_snapshot_threadid_idx";
|
|
25607
25617
|
/**
|
|
@@ -25646,13 +25656,126 @@ function workflowSnapshotThreadIdIndexSQL(indexName, schemaName, concurrently =
|
|
|
25646
25656
|
});
|
|
25647
25657
|
return `CREATE INDEX ${concurrently ? "CONCURRENTLY " : ""}IF NOT EXISTS "${indexName}" ON ${tableName} ((${WORKFLOW_SNAPSHOT_THREAD_ID_EXPR}))`;
|
|
25648
25658
|
}
|
|
25659
|
+
/** Base name (before any schema prefix) of the index finding the `state-base` entries built on a run. */
|
|
25660
|
+
const WORKFLOW_RUN_ENTRIES_STATE_BASE_INDEX = "mastra_workflow_run_entries_state_base_idx";
|
|
25661
|
+
/** The run a `state-base` entry builds on; `#detachStateChildren` must use these exact expressions. */
|
|
25662
|
+
const STATE_BASE_RUN_EXPR = `(payload ->> 'workflowName'), (payload ->> 'runId')`;
|
|
25663
|
+
/**
|
|
25664
|
+
* Partial expression index finding the `state-base` entries built on a run, so deleting a run whose
|
|
25665
|
+
* state others build on doesn't scan every entry. See {@link workflowSnapshotStatusIndexSQL} for
|
|
25666
|
+
* `concurrently`.
|
|
25667
|
+
*/
|
|
25668
|
+
function stateBaseIndexSQL(indexName, schemaName, concurrently = false) {
|
|
25669
|
+
const tableName = getTableName({
|
|
25670
|
+
indexName: TABLE_WORKFLOW_RUN_ENTRIES,
|
|
25671
|
+
schemaName: getSchemaName(schemaName)
|
|
25672
|
+
});
|
|
25673
|
+
return `CREATE INDEX ${concurrently ? "CONCURRENTLY " : ""}IF NOT EXISTS "${indexName}" ON ${tableName} (${STATE_BASE_RUN_EXPR}) WHERE kind = 'state-base'`;
|
|
25674
|
+
}
|
|
25675
|
+
/**
|
|
25676
|
+
* The entries a storage v2 write adds: one per step, in order, then one per step patch, then the
|
|
25677
|
+
* state or state base (a checkpoint with neither starts the state over at `{}`), then the state
|
|
25678
|
+
* patch unless a full state was given.
|
|
25679
|
+
*/
|
|
25680
|
+
function journalEntries(steps, { state, stateBase, statePatch }, checkpoint, stepPatches = {}) {
|
|
25681
|
+
const entries = Object.entries(steps).map(([stepId, result]) => ({
|
|
25682
|
+
kind: "step",
|
|
25683
|
+
step_id: stepId,
|
|
25684
|
+
payload: result
|
|
25685
|
+
}));
|
|
25686
|
+
for (const [stepId, ops] of Object.entries(stepPatches)) entries.push({
|
|
25687
|
+
kind: "step-patch",
|
|
25688
|
+
step_id: stepId,
|
|
25689
|
+
payload: ops
|
|
25690
|
+
});
|
|
25691
|
+
if (state !== void 0) entries.push({
|
|
25692
|
+
kind: "state",
|
|
25693
|
+
step_id: null,
|
|
25694
|
+
payload: state
|
|
25695
|
+
});
|
|
25696
|
+
else if (stateBase !== void 0) entries.push({
|
|
25697
|
+
kind: "state-base",
|
|
25698
|
+
step_id: null,
|
|
25699
|
+
payload: stateBase
|
|
25700
|
+
});
|
|
25701
|
+
else if (checkpoint) entries.push({
|
|
25702
|
+
kind: "state",
|
|
25703
|
+
step_id: null,
|
|
25704
|
+
payload: {}
|
|
25705
|
+
});
|
|
25706
|
+
if (state === void 0 && statePatch !== void 0) entries.push({
|
|
25707
|
+
kind: "state-patch",
|
|
25708
|
+
step_id: null,
|
|
25709
|
+
payload: statePatch
|
|
25710
|
+
});
|
|
25711
|
+
return entries;
|
|
25712
|
+
}
|
|
25713
|
+
/**
|
|
25714
|
+
* Rows `e(elem, ord)` of the jsonb array parameter `param` of {@link EntryInput}, and the
|
|
25715
|
+
* entry columns they insert. `elem -> 'payload'` keeps a JSON null apart from an undefined
|
|
25716
|
+
* payload (dropped by `JSON.stringify`), which becomes SQL NULL.
|
|
25717
|
+
*/
|
|
25718
|
+
const entryRows = (param) => `jsonb_array_elements(${param}::jsonb) WITH ORDINALITY AS e(elem, ord)`;
|
|
25719
|
+
const ENTRY_VALUES = `e.elem ->> 'kind', e.elem ->> 'step_id', e.elem -> 'payload'`;
|
|
25720
|
+
/** The entries of {@link EntryRow} rows; an undefined payload reads back as undefined, as in v1. */
|
|
25721
|
+
function toEntries(rows) {
|
|
25722
|
+
return rows.map((row) => {
|
|
25723
|
+
const payload = row.absent ? void 0 : row.payload;
|
|
25724
|
+
switch (row.kind) {
|
|
25725
|
+
case "step": return {
|
|
25726
|
+
seq: row.seq,
|
|
25727
|
+
kind: "step",
|
|
25728
|
+
stepId: row.step_id,
|
|
25729
|
+
result: payload
|
|
25730
|
+
};
|
|
25731
|
+
case "step-patch": return {
|
|
25732
|
+
seq: row.seq,
|
|
25733
|
+
kind: "step-patch",
|
|
25734
|
+
stepId: row.step_id,
|
|
25735
|
+
ops: payload
|
|
25736
|
+
};
|
|
25737
|
+
case "state": return {
|
|
25738
|
+
seq: row.seq,
|
|
25739
|
+
kind: "state",
|
|
25740
|
+
state: payload
|
|
25741
|
+
};
|
|
25742
|
+
case "state-patch": return {
|
|
25743
|
+
seq: row.seq,
|
|
25744
|
+
kind: "state-patch",
|
|
25745
|
+
ops: payload
|
|
25746
|
+
};
|
|
25747
|
+
case "state-base": return {
|
|
25748
|
+
seq: row.seq,
|
|
25749
|
+
kind: "state-base",
|
|
25750
|
+
base: payload
|
|
25751
|
+
};
|
|
25752
|
+
case "state-draft": {
|
|
25753
|
+
const draft = payload;
|
|
25754
|
+
return "state" in draft ? {
|
|
25755
|
+
seq: row.seq,
|
|
25756
|
+
kind: "state-draft",
|
|
25757
|
+
state: draft.state
|
|
25758
|
+
} : {
|
|
25759
|
+
seq: row.seq,
|
|
25760
|
+
kind: "state-draft",
|
|
25761
|
+
on: draft.on,
|
|
25762
|
+
ops: draft.ops
|
|
25763
|
+
};
|
|
25764
|
+
}
|
|
25765
|
+
}
|
|
25766
|
+
});
|
|
25767
|
+
}
|
|
25649
25768
|
var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
|
|
25650
25769
|
#db;
|
|
25651
25770
|
#schema;
|
|
25652
25771
|
#skipDefaultIndexes;
|
|
25653
25772
|
#indexes;
|
|
25654
25773
|
/** Tables managed by this domain */
|
|
25655
|
-
static MANAGED_TABLES = [
|
|
25774
|
+
static MANAGED_TABLES = [
|
|
25775
|
+
TABLE_WORKFLOW_SNAPSHOT,
|
|
25776
|
+
TABLE_WORKFLOW_RUNS,
|
|
25777
|
+
TABLE_WORKFLOW_RUN_ENTRIES
|
|
25778
|
+
];
|
|
25656
25779
|
/**
|
|
25657
25780
|
* Workflow run snapshots accumulate as runs execute. Anchored on the
|
|
25658
25781
|
* timezone-aware `updatedAtZ` mirror column (last activity) so suspended or
|
|
@@ -25715,11 +25838,43 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
|
|
|
25715
25838
|
};
|
|
25716
25839
|
}
|
|
25717
25840
|
static getDefaultIndexDefs(schemaPrefix) {
|
|
25718
|
-
return [
|
|
25719
|
-
|
|
25720
|
-
|
|
25721
|
-
|
|
25722
|
-
|
|
25841
|
+
return [
|
|
25842
|
+
{
|
|
25843
|
+
name: `${schemaPrefix}mastra_workflow_snapshot_name_createdat_idx`,
|
|
25844
|
+
table: TABLE_WORKFLOW_SNAPSHOT,
|
|
25845
|
+
columns: ["workflow_name", "createdAt DESC"]
|
|
25846
|
+
},
|
|
25847
|
+
{
|
|
25848
|
+
name: runIndexName(schemaPrefix, "mastra_workflow_runs_name_createdat_idx"),
|
|
25849
|
+
table: TABLE_WORKFLOW_RUNS,
|
|
25850
|
+
columns: ["workflow_name", "createdAt DESC"]
|
|
25851
|
+
},
|
|
25852
|
+
{
|
|
25853
|
+
name: runIndexName(schemaPrefix, "mastra_workflow_runs_name_status_createdat_idx"),
|
|
25854
|
+
table: TABLE_WORKFLOW_RUNS,
|
|
25855
|
+
columns: [
|
|
25856
|
+
"workflow_name",
|
|
25857
|
+
"status",
|
|
25858
|
+
"createdAt DESC"
|
|
25859
|
+
]
|
|
25860
|
+
},
|
|
25861
|
+
{
|
|
25862
|
+
name: runIndexName(schemaPrefix, "mastra_workflow_runs_threadid_idx"),
|
|
25863
|
+
table: TABLE_WORKFLOW_RUNS,
|
|
25864
|
+
columns: ["threadId"]
|
|
25865
|
+
},
|
|
25866
|
+
{
|
|
25867
|
+
name: runIndexName(schemaPrefix, "mastra_workflow_run_entries_step_idx"),
|
|
25868
|
+
table: TABLE_WORKFLOW_RUN_ENTRIES,
|
|
25869
|
+
columns: [
|
|
25870
|
+
"workflow_name",
|
|
25871
|
+
"run_id",
|
|
25872
|
+
"kind",
|
|
25873
|
+
"step_id",
|
|
25874
|
+
"seq"
|
|
25875
|
+
]
|
|
25876
|
+
}
|
|
25877
|
+
];
|
|
25723
25878
|
}
|
|
25724
25879
|
/**
|
|
25725
25880
|
* Returns all DDL statements for this domain: table with unique constraint.
|
|
@@ -25735,9 +25890,17 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
|
|
|
25735
25890
|
schemaName,
|
|
25736
25891
|
includeAllConstraints: true
|
|
25737
25892
|
}));
|
|
25893
|
+
for (const [tableName, config] of Object.entries(WORKFLOW_RUN_JOURNAL_TABLE_CONFIGS)) statements.push(generateTableSQL({
|
|
25894
|
+
tableName,
|
|
25895
|
+
schema: config.columns,
|
|
25896
|
+
schemaName,
|
|
25897
|
+
compositePrimaryKey: config.compositePrimaryKey,
|
|
25898
|
+
includeAllConstraints: true
|
|
25899
|
+
}));
|
|
25738
25900
|
for (const idx of WorkflowsPG.getDefaultIndexDefs(schemaPrefix)) statements.push(generateIndexSQL(idx, schemaName));
|
|
25739
25901
|
statements.push(`${workflowSnapshotStatusIndexSQL(workflowSnapshotStatusIndexName(parsedSchema), schemaName)};`);
|
|
25740
25902
|
statements.push(`${workflowSnapshotThreadIdIndexSQL(workflowSnapshotThreadIdIndexName(parsedSchema), schemaName)};`);
|
|
25903
|
+
statements.push(`${stateBaseIndexSQL(runIndexName(schemaPrefix, WORKFLOW_RUN_ENTRIES_STATE_BASE_INDEX), schemaName)};`);
|
|
25741
25904
|
statements.push(generateTableSQL({
|
|
25742
25905
|
tableName: TABLE_WORKFLOW_RUN_OWNERS,
|
|
25743
25906
|
schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_WORKFLOW_RUN_OWNERS],
|
|
@@ -25763,6 +25926,12 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
|
|
|
25763
25926
|
} catch (error) {
|
|
25764
25927
|
this.logger?.warn?.(`Failed to create index ${indexDef.name}:`, error);
|
|
25765
25928
|
}
|
|
25929
|
+
const stateBaseIndexName = runIndexName(this.#schema !== "public" ? `${schemaNamePrefix(this.#schema)}_` : "", WORKFLOW_RUN_ENTRIES_STATE_BASE_INDEX);
|
|
25930
|
+
try {
|
|
25931
|
+
await this.#db.createIndexFromStatement(stateBaseIndexName, stateBaseIndexSQL(stateBaseIndexName, this.#schema, true));
|
|
25932
|
+
} catch (error) {
|
|
25933
|
+
this.logger?.warn?.(`Failed to create index ${stateBaseIndexName}:`, error);
|
|
25934
|
+
}
|
|
25766
25935
|
if (await this.#db.getColumnType(TABLE_WORKFLOW_SNAPSHOT, "snapshot") !== "jsonb") return;
|
|
25767
25936
|
const indexName = workflowSnapshotStatusIndexName(this.#schema);
|
|
25768
25937
|
try {
|
|
@@ -25791,6 +25960,11 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
|
|
|
25791
25960
|
tableName: TABLE_WORKFLOW_RUN_OWNERS,
|
|
25792
25961
|
schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_WORKFLOW_RUN_OWNERS]
|
|
25793
25962
|
});
|
|
25963
|
+
for (const [tableName, config] of Object.entries(WORKFLOW_RUN_JOURNAL_TABLE_CONFIGS)) await this.#db.createTable({
|
|
25964
|
+
tableName,
|
|
25965
|
+
schema: config.columns,
|
|
25966
|
+
compositePrimaryKey: config.compositePrimaryKey
|
|
25967
|
+
});
|
|
25794
25968
|
await this.createDefaultIndexes();
|
|
25795
25969
|
await this.createCustomIndexes();
|
|
25796
25970
|
}
|
|
@@ -25817,8 +25991,22 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
|
|
|
25817
25991
|
this.logger?.warn?.(`Failed to create retention index for ${entry.table}:`, error);
|
|
25818
25992
|
}
|
|
25819
25993
|
}
|
|
25994
|
+
if (!policies.workflowSnapshot) return;
|
|
25995
|
+
try {
|
|
25996
|
+
await this.#db.ensureIndex({
|
|
25997
|
+
indexName: `${prefix}mastra_workflow_runs_retention_idx`,
|
|
25998
|
+
tableName: TABLE_WORKFLOW_RUNS,
|
|
25999
|
+
column: "updatedAtZ"
|
|
26000
|
+
});
|
|
26001
|
+
} catch (error) {
|
|
26002
|
+
this.logger?.warn?.(`Failed to create retention index for ${TABLE_WORKFLOW_RUNS}:`, error);
|
|
26003
|
+
}
|
|
25820
26004
|
}
|
|
25821
|
-
/**
|
|
26005
|
+
/**
|
|
26006
|
+
* Delete workflow runs and run ownership rows older than their policy's `maxAge`, batched.
|
|
26007
|
+
* The `workflowSnapshot` policy covers both storage versions: storage v2 runs are pruned by
|
|
26008
|
+
* their header's last write, together with their entries.
|
|
26009
|
+
*/
|
|
25822
26010
|
async prune(policies, options) {
|
|
25823
26011
|
await this.ensureRetentionIndexes(policies);
|
|
25824
26012
|
const targets = resolveTargets({
|
|
@@ -25826,11 +26014,40 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
|
|
|
25826
26014
|
descriptor: WorkflowsPG.retentionTables,
|
|
25827
26015
|
order: ["workflowSnapshot", "runOwnership"]
|
|
25828
26016
|
});
|
|
26017
|
+
const snapshotTarget = targets.findIndex((target) => target.table === TABLE_WORKFLOW_SNAPSHOT);
|
|
26018
|
+
if (snapshotTarget !== -1) targets.splice(snapshotTarget + 1, 0, {
|
|
26019
|
+
...targets[snapshotTarget],
|
|
26020
|
+
table: TABLE_WORKFLOW_RUNS
|
|
26021
|
+
});
|
|
25829
26022
|
return runPrune({
|
|
25830
26023
|
db: this.#db,
|
|
25831
26024
|
domain: "workflows",
|
|
25832
26025
|
targets,
|
|
25833
|
-
options
|
|
26026
|
+
options,
|
|
26027
|
+
deleteBatch: (target, cutoff, limit) => target.table === TABLE_WORKFLOW_RUNS ? this.#pruneRunsBatch(cutoff, limit) : void 0
|
|
26028
|
+
});
|
|
26029
|
+
}
|
|
26030
|
+
/**
|
|
26031
|
+
* Deletes up to `limit` aged storage v2 runs, header and entries together, first detaching the
|
|
26032
|
+
* runs whose state others build on (see `#detachStateChildren`).
|
|
26033
|
+
*/
|
|
26034
|
+
async #pruneRunsBatch(cutoff, limit) {
|
|
26035
|
+
return this.#db.client.tx(async (t) => {
|
|
26036
|
+
const doomed = await t.manyOrNone(`SELECT workflow_name, run_id, "stateShared" FROM ${this.#runsTable()}
|
|
26037
|
+
WHERE "updatedAtZ" < $1
|
|
26038
|
+
ORDER BY "updatedAtZ"
|
|
26039
|
+
LIMIT $2
|
|
26040
|
+
FOR UPDATE SKIP LOCKED`, [cutoff, limit]);
|
|
26041
|
+
if (!doomed.length) return 0;
|
|
26042
|
+
for (const run of doomed) if (run.stateShared) await this.#detachStateChildren(t, run.workflow_name, run.run_id);
|
|
26043
|
+
return (await t.query(`WITH doomed AS (
|
|
26044
|
+
SELECT * FROM unnest($1::text[], $2::text[]) AS d(workflow_name, run_id)
|
|
26045
|
+
), entries AS (
|
|
26046
|
+
DELETE FROM ${this.#entriesTable()} e USING doomed d
|
|
26047
|
+
WHERE e.workflow_name = d.workflow_name AND e.run_id = d.run_id
|
|
26048
|
+
)
|
|
26049
|
+
DELETE FROM ${this.#runsTable()} r USING doomed d
|
|
26050
|
+
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;
|
|
25834
26051
|
});
|
|
25835
26052
|
}
|
|
25836
26053
|
/**
|
|
@@ -25847,6 +26064,459 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
|
|
|
25847
26064
|
async dangerouslyClearAll() {
|
|
25848
26065
|
await this.#db.clearTable({ tableName: TABLE_WORKFLOW_SNAPSHOT });
|
|
25849
26066
|
await this.#db.clearTable({ tableName: TABLE_WORKFLOW_RUN_OWNERS });
|
|
26067
|
+
await this.#db.clearTable({ tableName: TABLE_WORKFLOW_RUNS });
|
|
26068
|
+
await this.#db.clearTable({ tableName: TABLE_WORKFLOW_RUN_ENTRIES });
|
|
26069
|
+
}
|
|
26070
|
+
supportsRunJournal() {
|
|
26071
|
+
return true;
|
|
26072
|
+
}
|
|
26073
|
+
#runsTable() {
|
|
26074
|
+
return getTableName({
|
|
26075
|
+
indexName: TABLE_WORKFLOW_RUNS,
|
|
26076
|
+
schemaName: getSchemaName(this.#schema)
|
|
26077
|
+
});
|
|
26078
|
+
}
|
|
26079
|
+
#entriesTable() {
|
|
26080
|
+
return getTableName({
|
|
26081
|
+
indexName: TABLE_WORKFLOW_RUN_ENTRIES,
|
|
26082
|
+
schemaName: getSchemaName(this.#schema)
|
|
26083
|
+
});
|
|
26084
|
+
}
|
|
26085
|
+
/**
|
|
26086
|
+
* A jsonb array of {@link EntryRow} for the storage v2 run `alias` refers to: each step's
|
|
26087
|
+
* latest entry, ordered by when each step was first written so `context` keys keep their write
|
|
26088
|
+
* order, the step patches after it (in `seq` order, for `latestStepResults`), and the entries
|
|
26089
|
+
* `materializeState` uses for the latest state: the latest full state or state base and every
|
|
26090
|
+
* state patch after it, in `seq` order. Drafts are left out.
|
|
26091
|
+
*
|
|
26092
|
+
* With `summaryFieldsParam` (the parameter holding `STEP_RESULT_SUMMARY_FIELDS`), successful
|
|
26093
|
+
* steps other than `input` come back as their summary, built here so their data is never sent,
|
|
26094
|
+
* unless step patches follow it (the caller summarizes the patched result instead).
|
|
26095
|
+
* Like `summarizeStepResult`, it keeps a field that is JSON null and leaves out one that is absent.
|
|
26096
|
+
*/
|
|
26097
|
+
#entriesSql(alias, { summaryFieldsParam } = {}) {
|
|
26098
|
+
let payload = "x.payload";
|
|
26099
|
+
if (summaryFieldsParam) {
|
|
26100
|
+
const summarized = `(x.kind = 'step' AND x.step_id <> 'input' AND x.payload ->> 'status' = 'success'
|
|
26101
|
+
AND (x.patch_seq IS NULL OR x.patch_seq < x.seq))`;
|
|
26102
|
+
payload = `CASE WHEN ${summarized} THEN (
|
|
26103
|
+
SELECT COALESCE(jsonb_object_agg(f.key, f.value), '{}'::jsonb)
|
|
26104
|
+
FROM jsonb_each(x.payload) f WHERE f.key = ANY(${summaryFieldsParam}::text[])
|
|
26105
|
+
) ELSE x.payload END, 'summarized', ${summarized}`;
|
|
26106
|
+
}
|
|
26107
|
+
return `(SELECT COALESCE(jsonb_agg(jsonb_build_object(
|
|
26108
|
+
'seq', x.seq, 'kind', x.kind, 'step_id', x.step_id, 'absent', x.payload IS NULL, 'payload', ${payload}
|
|
26109
|
+
) ORDER BY CASE WHEN x.kind = 'step' THEN x.first_seq ELSE x.seq END), '[]'::jsonb)
|
|
26110
|
+
FROM (
|
|
26111
|
+
SELECT e.seq, e.kind, e.step_id, e.payload,
|
|
26112
|
+
ROW_NUMBER() OVER (PARTITION BY e.kind, e.step_id ORDER BY e.seq DESC) AS rn,
|
|
26113
|
+
MIN(e.seq) OVER (PARTITION BY e.kind, e.step_id) AS first_seq,
|
|
26114
|
+
MAX(CASE WHEN e.kind = 'step' THEN e.seq END) OVER (PARTITION BY e.step_id) AS step_seq,
|
|
26115
|
+
MAX(CASE WHEN e.kind = 'step-patch' THEN e.seq END) OVER (PARTITION BY e.step_id) AS patch_seq,
|
|
26116
|
+
MAX(CASE WHEN e.kind IN ('state', 'state-base') THEN e.seq END) OVER () AS base_seq
|
|
26117
|
+
FROM ${this.#entriesTable()} e
|
|
26118
|
+
WHERE e.workflow_name = ${alias}.workflow_name AND e.run_id = ${alias}.run_id AND e.kind <> 'state-draft'
|
|
26119
|
+
) x
|
|
26120
|
+
WHERE CASE x.kind
|
|
26121
|
+
WHEN 'step' THEN x.rn = 1
|
|
26122
|
+
WHEN 'step-patch' THEN x.seq > x.step_seq
|
|
26123
|
+
ELSE x.base_seq IS NULL OR x.seq >= x.base_seq
|
|
26124
|
+
END)`;
|
|
26125
|
+
}
|
|
26126
|
+
/**
|
|
26127
|
+
* A jsonb array of {@link EntryRow} with the entries `materializeState` uses for the state of the
|
|
26128
|
+
* storage v2 run `alias` refers to as of a version: `seqParam` its `seq`, `draftParam` whether
|
|
26129
|
+
* it is a draft. For a draft, every state entry up to it, as the drafts it is on can build on
|
|
26130
|
+
* any earlier version; otherwise the latest full state or state base up to it and the state
|
|
26131
|
+
* patches after it.
|
|
26132
|
+
*/
|
|
26133
|
+
#stateEntriesSql(alias, seqParam, draftParam) {
|
|
26134
|
+
return `(SELECT COALESCE(jsonb_agg(jsonb_build_object(
|
|
26135
|
+
'seq', x.seq, 'kind', x.kind, 'step_id', x.step_id, 'absent', x.payload IS NULL, 'payload', x.payload
|
|
26136
|
+
) ORDER BY x.seq), '[]'::jsonb)
|
|
26137
|
+
FROM (
|
|
26138
|
+
SELECT e.seq, e.kind, e.step_id, e.payload,
|
|
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
|
|
26142
|
+
AND e.kind NOT IN ('step', 'step-patch') AND e.seq <= ${seqParam}::int
|
|
26143
|
+
) x
|
|
26144
|
+
WHERE ${draftParam}::boolean OR (x.kind <> 'state-draft' AND (x.base_seq IS NULL OR x.seq >= x.base_seq)))`;
|
|
26145
|
+
}
|
|
26146
|
+
/**
|
|
26147
|
+
* Selects v1 snapshot rows and storage v2 runs as one result, newest first. Each version
|
|
26148
|
+
* has its own WHERE clause; `null` leaves it out. Storage v2 rows carry their header and
|
|
26149
|
+
* entries, or in summary mode only the summary snapshot. `page` is appended to the
|
|
26150
|
+
* combined, ordered rows (e.g. `LIMIT 1`).
|
|
26151
|
+
*/
|
|
26152
|
+
async #runsQuery({ v1Where, v2Where, v1Snapshot = "snapshot", summary, page = "" }) {
|
|
26153
|
+
const branches = [];
|
|
26154
|
+
if (v1Where !== null) {
|
|
26155
|
+
const optional = {
|
|
26156
|
+
resourceId: "text",
|
|
26157
|
+
createdAtZ: "timestamptz",
|
|
26158
|
+
updatedAtZ: "timestamptz"
|
|
26159
|
+
};
|
|
26160
|
+
const columns = {};
|
|
26161
|
+
for (const [column, type] of Object.entries(optional)) columns[column] = await this.#db.getColumnType(TABLE_WORKFLOW_SNAPSHOT, column) !== null ? `"${column}"` : `NULL::${type} AS "${column}"`;
|
|
26162
|
+
branches.push(`SELECT workflow_name, run_id, ${columns.resourceId}, ${v1Snapshot} AS snapshot, NULL::jsonb AS header,
|
|
26163
|
+
"createdAt", ${columns.createdAtZ}, "updatedAt", ${columns.updatedAtZ}, 1 AS version
|
|
26164
|
+
FROM ${getTableName({
|
|
26165
|
+
indexName: TABLE_WORKFLOW_SNAPSHOT,
|
|
26166
|
+
schemaName: getSchemaName(this.#schema)
|
|
26167
|
+
})} ${v1Where}`);
|
|
26168
|
+
}
|
|
26169
|
+
if (v2Where !== null) {
|
|
26170
|
+
const columns = summary ? `jsonb_build_object('status', header -> 'status', 'timestamp', header -> 'timestamp') AS snapshot, NULL::jsonb AS header` : `NULL AS snapshot, header`;
|
|
26171
|
+
branches.push(`SELECT workflow_name, run_id, "resourceId", ${columns},
|
|
26172
|
+
"createdAt", "createdAtZ", "updatedAt", "updatedAtZ", 2 AS version
|
|
26173
|
+
FROM ${this.#runsTable()} ${v2Where}`);
|
|
26174
|
+
}
|
|
26175
|
+
return `SELECT p.*${summary ? "" : `, CASE WHEN p.version = 2 THEN ${this.#entriesSql("p")} END AS entries`}
|
|
26176
|
+
FROM (${branches.join(" UNION ALL ")} ORDER BY "createdAt" DESC ${page}) p
|
|
26177
|
+
ORDER BY p."createdAt" DESC`;
|
|
26178
|
+
}
|
|
26179
|
+
/**
|
|
26180
|
+
* The `base` that `materializeState` takes for a run's entries (as of `at`, or latest): the state
|
|
26181
|
+
* version of another run it builds on (`stateBaseOf`), resolved recursively, else undefined. `{}`
|
|
26182
|
+
* when that run isn't stored. One query per run in the chain.
|
|
26183
|
+
*/
|
|
26184
|
+
async #stateBase(q, entries, at) {
|
|
26185
|
+
const ref = stateBaseOf(entries, at);
|
|
26186
|
+
if (!ref) return void 0;
|
|
26187
|
+
return (await this.#stateAt(q, ref.workflowName, ref.runId, ref))?.state ?? {};
|
|
26188
|
+
}
|
|
26189
|
+
/** A storage v2 run's state as of the version `at`, or null when the run isn't stored. */
|
|
26190
|
+
async #stateAt(q, workflowName, runId, at) {
|
|
26191
|
+
const row = await q.oneOrNone(`SELECT ${this.#stateEntriesSql("r", "$3", "$4")} AS entries
|
|
26192
|
+
FROM ${this.#runsTable()} r WHERE r.workflow_name = $1 AND r.run_id = $2`, [
|
|
26193
|
+
workflowName,
|
|
26194
|
+
runId,
|
|
26195
|
+
at.seq,
|
|
26196
|
+
at.draft === true
|
|
26197
|
+
]);
|
|
26198
|
+
if (!row) return null;
|
|
26199
|
+
const entries = toEntries(row.entries);
|
|
26200
|
+
return materializeState(entries, {
|
|
26201
|
+
at,
|
|
26202
|
+
base: await this.#stateBase(q, entries, at)
|
|
26203
|
+
});
|
|
26204
|
+
}
|
|
26205
|
+
/**
|
|
26206
|
+
* Before a run is deleted, stores the full state in place of every `state-base` entry built on
|
|
26207
|
+
* it, so the runs built on it keep reading the same state. Runs inside the delete's transaction.
|
|
26208
|
+
*/
|
|
26209
|
+
async #detachStateChildren(q, workflowName, runId) {
|
|
26210
|
+
const children = await q.manyOrNone(`SELECT workflow_name, run_id, seq, payload FROM ${this.#entriesTable()}
|
|
26211
|
+
WHERE kind = 'state-base' AND (${STATE_BASE_RUN_EXPR}) = ($1, $2)`, [workflowName, runId]);
|
|
26212
|
+
const states = /* @__PURE__ */ new Map();
|
|
26213
|
+
for (const child of children) {
|
|
26214
|
+
const version = `${child.payload.seq}:${child.payload.draft === true}`;
|
|
26215
|
+
let state = states.get(version);
|
|
26216
|
+
if (!state) {
|
|
26217
|
+
state = (await this.#stateAt(q, workflowName, runId, child.payload))?.state ?? {};
|
|
26218
|
+
states.set(version, state);
|
|
26219
|
+
}
|
|
26220
|
+
await q.none(`UPDATE ${this.#entriesTable()} SET kind = 'state', payload = $4::jsonb
|
|
26221
|
+
WHERE workflow_name = $1 AND run_id = $2 AND seq = $3`, [
|
|
26222
|
+
child.workflow_name,
|
|
26223
|
+
child.run_id,
|
|
26224
|
+
child.seq,
|
|
26225
|
+
toPgJson(state)
|
|
26226
|
+
]);
|
|
26227
|
+
}
|
|
26228
|
+
}
|
|
26229
|
+
/** Rebuilds a storage v2 run's `WorkflowRunState` from its header and `#entriesSql` rows. */
|
|
26230
|
+
async #materializeRun(client, header, rows) {
|
|
26231
|
+
const entries = toEntries(rows);
|
|
26232
|
+
return materializeWorkflowRun(header, entries, await this.#stateBase(client, entries));
|
|
26233
|
+
}
|
|
26234
|
+
/** Parses a row of `#runsQuery`. */
|
|
26235
|
+
async #parseRun(client, row) {
|
|
26236
|
+
if (row.version !== 2) return this.parseWorkflowRun(row);
|
|
26237
|
+
const snapshot = row.entries ? await this.#materializeRun(client, row.header, row.entries) : row.snapshot;
|
|
26238
|
+
return {
|
|
26239
|
+
...this.parseWorkflowRun({
|
|
26240
|
+
...row,
|
|
26241
|
+
snapshot
|
|
26242
|
+
}),
|
|
26243
|
+
storageVersion: 2
|
|
26244
|
+
};
|
|
26245
|
+
}
|
|
26246
|
+
#runError(operation, workflowName, runId, error) {
|
|
26247
|
+
return new MastraError({
|
|
26248
|
+
id: createStorageErrorId("PG", operation, "FAILED"),
|
|
26249
|
+
domain: ErrorDomain.STORAGE,
|
|
26250
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
26251
|
+
details: {
|
|
26252
|
+
workflowName,
|
|
26253
|
+
runId
|
|
26254
|
+
}
|
|
26255
|
+
}, error);
|
|
26256
|
+
}
|
|
26257
|
+
async loadWorkflowRunHeader({ workflowName, runId }) {
|
|
26258
|
+
try {
|
|
26259
|
+
return await this.#db.client.oneOrNone(`SELECT header, "lastSeq", "startStateSeq", "runToken" FROM ${this.#runsTable()} WHERE workflow_name = $1 AND run_id = $2`, [workflowName, runId]);
|
|
26260
|
+
} catch (error) {
|
|
26261
|
+
throw this.#runError("LOAD_WORKFLOW_RUN_HEADER", workflowName, runId, error);
|
|
26262
|
+
}
|
|
26263
|
+
}
|
|
26264
|
+
async loadWorkflowRun({ workflowName, runId, successfulSteps }) {
|
|
26265
|
+
try {
|
|
26266
|
+
const summary = successfulSteps === "summary";
|
|
26267
|
+
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
|
|
26268
|
+
FROM ${this.#runsTable()} r WHERE r.workflow_name = $1 AND r.run_id = $2`, summary ? [
|
|
26269
|
+
workflowName,
|
|
26270
|
+
runId,
|
|
26271
|
+
STEP_RESULT_SUMMARY_FIELDS
|
|
26272
|
+
] : [workflowName, runId]);
|
|
26273
|
+
if (!run) return null;
|
|
26274
|
+
const entries = toEntries(run.entries);
|
|
26275
|
+
const summarizedInQuery = new Set(run.entries.filter((row) => row.summarized).map((row) => row.step_id));
|
|
26276
|
+
const steps = {};
|
|
26277
|
+
const summarizedStepIds = [];
|
|
26278
|
+
for (const [stepId, result] of latestStepResults(entries)) {
|
|
26279
|
+
steps[stepId] = result;
|
|
26280
|
+
if (summary && stepId !== "input" && result?.status === "success") {
|
|
26281
|
+
if (!summarizedInQuery.has(stepId)) steps[stepId] = summarizeStepResult(result);
|
|
26282
|
+
summarizedStepIds.push(stepId);
|
|
26283
|
+
}
|
|
26284
|
+
}
|
|
26285
|
+
const base = await this.#stateBase(this.#db.client, entries);
|
|
26286
|
+
const { state, statePatchBytes } = materializeState(entries, { base });
|
|
26287
|
+
return {
|
|
26288
|
+
header: run.header,
|
|
26289
|
+
lastSeq: run.lastSeq,
|
|
26290
|
+
startStateSeq: run.startStateSeq,
|
|
26291
|
+
runToken: run.runToken,
|
|
26292
|
+
state,
|
|
26293
|
+
statePatchBytes,
|
|
26294
|
+
steps,
|
|
26295
|
+
summarizedStepIds
|
|
26296
|
+
};
|
|
26297
|
+
} catch (error) {
|
|
26298
|
+
throw this.#runError("LOAD_WORKFLOW_RUN", workflowName, runId, error);
|
|
26299
|
+
}
|
|
26300
|
+
}
|
|
26301
|
+
async loadWorkflowRunSteps({ workflowName, runId, stepIds, expectedLastSeq, expectedRunToken }) {
|
|
26302
|
+
try {
|
|
26303
|
+
const run = await this.#db.client.oneOrNone(`SELECT r."lastSeq", r."runToken", (
|
|
26304
|
+
SELECT COALESCE(jsonb_agg(jsonb_build_object(
|
|
26305
|
+
'seq', x.seq, 'kind', x.kind, 'step_id', x.step_id, 'absent', x.payload IS NULL, 'payload', x.payload
|
|
26306
|
+
) ORDER BY x.seq), '[]'::jsonb)
|
|
26307
|
+
FROM (
|
|
26308
|
+
SELECT e.seq, e.kind, e.step_id, e.payload,
|
|
26309
|
+
MAX(CASE WHEN e.kind = 'step' THEN e.seq END) OVER (PARTITION BY e.step_id) AS step_seq
|
|
26310
|
+
FROM ${this.#entriesTable()} e
|
|
26311
|
+
WHERE e.workflow_name = r.workflow_name AND e.run_id = r.run_id
|
|
26312
|
+
AND e.kind IN ('step', 'step-patch') AND e.step_id = ANY($3::text[])
|
|
26313
|
+
) x
|
|
26314
|
+
WHERE x.seq >= x.step_seq
|
|
26315
|
+
) AS entries
|
|
26316
|
+
FROM ${this.#runsTable()} r WHERE r.workflow_name = $1 AND r.run_id = $2`, [
|
|
26317
|
+
workflowName,
|
|
26318
|
+
runId,
|
|
26319
|
+
stepIds
|
|
26320
|
+
]);
|
|
26321
|
+
if (!run) return null;
|
|
26322
|
+
if (expectedLastSeq !== void 0 && run.lastSeq !== expectedLastSeq) return null;
|
|
26323
|
+
if (expectedRunToken !== void 0 && run.runToken !== expectedRunToken) return null;
|
|
26324
|
+
return Object.fromEntries(latestStepResults(toEntries(run.entries)));
|
|
26325
|
+
} catch (error) {
|
|
26326
|
+
throw this.#runError("LOAD_WORKFLOW_RUN_STEPS", workflowName, runId, error);
|
|
26327
|
+
}
|
|
26328
|
+
}
|
|
26329
|
+
async updateWorkflowRunHeader({ workflowName, runId, opts, fence: explicitFence }) {
|
|
26330
|
+
const fence = resolveRunFence(this, explicitFence, runId);
|
|
26331
|
+
try {
|
|
26332
|
+
return await this.#db.client.tx(async (t) => {
|
|
26333
|
+
if (fence) await assertRunFence(t, this.#runOwnersTable(), fence, "updateWorkflowRunHeader");
|
|
26334
|
+
const run = await t.oneOrNone(`SELECT header FROM ${this.#runsTable()} WHERE workflow_name = $1 AND run_id = $2 FOR UPDATE`, [workflowName, runId]);
|
|
26335
|
+
if (!run) return void 0;
|
|
26336
|
+
const { expectedStatus, ...patch } = opts;
|
|
26337
|
+
if (!matchesExpectedWorkflowStatus(run.header.status, expectedStatus)) return void 0;
|
|
26338
|
+
const header = {
|
|
26339
|
+
...run.header,
|
|
26340
|
+
...patch
|
|
26341
|
+
};
|
|
26342
|
+
const now = /* @__PURE__ */ new Date();
|
|
26343
|
+
return await t.one(`UPDATE ${this.#runsTable()}
|
|
26344
|
+
SET header = $3::jsonb, status = $4, "updatedAt" = $5, "updatedAtZ" = $6
|
|
26345
|
+
WHERE workflow_name = $1 AND run_id = $2
|
|
26346
|
+
RETURNING header, "lastSeq", "startStateSeq", "runToken"`, [
|
|
26347
|
+
workflowName,
|
|
26348
|
+
runId,
|
|
26349
|
+
toPgJson(header),
|
|
26350
|
+
header.status,
|
|
26351
|
+
now,
|
|
26352
|
+
now
|
|
26353
|
+
]);
|
|
26354
|
+
});
|
|
26355
|
+
} catch (error) {
|
|
26356
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
26357
|
+
throw this.#runError("UPDATE_WORKFLOW_RUN_HEADER", workflowName, runId, error);
|
|
26358
|
+
}
|
|
26359
|
+
}
|
|
26360
|
+
/**
|
|
26361
|
+
* Upserts a storage v2 run's header from `$1`–`$12` ({@link #headerParams}), adds `$7` (the
|
|
26362
|
+
* number of entries the write adds) to `lastSeq`, and returns the new `lastSeq`, `stateShared`
|
|
26363
|
+
* and `runToken`. `$12` is the run token of a run the write creates; a stored run keeps its own.
|
|
26364
|
+
* A checkpoint's state entries come last, so its `startStateSeq` is the new `lastSeq`; an append
|
|
26365
|
+
* keeps the stored one unless given `$13`. The header's row lock is held
|
|
26366
|
+
* until commit, so the writes to one run commit in `seq` order. With `expectedParam` (a text
|
|
26367
|
+
* array of statuses), a stored run whose status isn't in it is left as is and nothing is returned.
|
|
26368
|
+
*/
|
|
26369
|
+
#upsertHeaderSql(checkpoint, expectedParam) {
|
|
26370
|
+
const startStateSeq = checkpoint ? {
|
|
26371
|
+
insert: "$7::int",
|
|
26372
|
+
update: "r.\"lastSeq\" + EXCLUDED.\"lastSeq\""
|
|
26373
|
+
} : {
|
|
26374
|
+
insert: "COALESCE($13::int, 0)",
|
|
26375
|
+
update: "COALESCE($13::int, r.\"startStateSeq\")"
|
|
26376
|
+
};
|
|
26377
|
+
return `INSERT INTO ${this.#runsTable()} AS r
|
|
26378
|
+
(workflow_name, run_id, "resourceId", "threadId", status, header, "lastSeq", "startStateSeq", "stateShared",
|
|
26379
|
+
"runToken", "createdAt", "updatedAt", "createdAtZ", "updatedAtZ")
|
|
26380
|
+
VALUES ($1, $2, $3, $4, $5, $6::jsonb, $7::int, ${startStateSeq.insert}, false, $12, $8, $9, $10, $11)
|
|
26381
|
+
ON CONFLICT (workflow_name, run_id) DO UPDATE
|
|
26382
|
+
SET "resourceId" = COALESCE(EXCLUDED."resourceId", r."resourceId"), "threadId" = EXCLUDED."threadId",
|
|
26383
|
+
status = EXCLUDED.status, header = EXCLUDED.header, "lastSeq" = r."lastSeq" + EXCLUDED."lastSeq",
|
|
26384
|
+
"startStateSeq" = ${startStateSeq.update},
|
|
26385
|
+
"updatedAt" = EXCLUDED."updatedAt", "updatedAtZ" = EXCLUDED."updatedAtZ"
|
|
26386
|
+
${expectedParam ? `WHERE r.status = ANY(${expectedParam}::text[])` : ""}
|
|
26387
|
+
RETURNING "lastSeq", "stateShared", "runToken"`;
|
|
26388
|
+
}
|
|
26389
|
+
#headerParams(run, entryCount) {
|
|
26390
|
+
const now = /* @__PURE__ */ new Date();
|
|
26391
|
+
return [
|
|
26392
|
+
run.workflowName,
|
|
26393
|
+
run.runId,
|
|
26394
|
+
run.resourceId,
|
|
26395
|
+
run.threadId ?? null,
|
|
26396
|
+
run.header.status,
|
|
26397
|
+
toPgJson(run.header),
|
|
26398
|
+
entryCount,
|
|
26399
|
+
now,
|
|
26400
|
+
now,
|
|
26401
|
+
now,
|
|
26402
|
+
now,
|
|
26403
|
+
crypto.randomUUID()
|
|
26404
|
+
];
|
|
26405
|
+
}
|
|
26406
|
+
async writeWorkflowRunCheckpoint({ fence: explicitFence, expectedStatus, steps, state, stateBase, statePatch, ...run }) {
|
|
26407
|
+
const entries = journalEntries(steps, {
|
|
26408
|
+
state,
|
|
26409
|
+
stateBase,
|
|
26410
|
+
statePatch
|
|
26411
|
+
}, true);
|
|
26412
|
+
const fence = resolveRunFence(this, explicitFence, run.runId);
|
|
26413
|
+
const expected = expectedStatus === void 0 ? [] : [[expectedStatus].flat()];
|
|
26414
|
+
try {
|
|
26415
|
+
return await this.#db.client.tx(async (t) => {
|
|
26416
|
+
if (fence) await assertRunFence(t, this.#runOwnersTable(), fence, "writeWorkflowRunCheckpoint");
|
|
26417
|
+
const base = state === void 0 ? stateBase : void 0;
|
|
26418
|
+
let baseLastSeq;
|
|
26419
|
+
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;
|
|
26420
|
+
const header = await t.oneOrNone(this.#upsertHeaderSql(true, expected.length ? "$13" : void 0), [...this.#headerParams(run, entries.length), ...expected]);
|
|
26421
|
+
if (!header) return null;
|
|
26422
|
+
if (base) {
|
|
26423
|
+
await t.none(`UPDATE ${this.#runsTable()} SET "stateShared" = true
|
|
26424
|
+
WHERE workflow_name = $1 AND run_id = $2 AND NOT "stateShared"`, [base.workflowName, base.runId]);
|
|
26425
|
+
const { stored } = await t.one(`SELECT EXISTS (
|
|
26426
|
+
SELECT 1 FROM ${this.#entriesTable()} WHERE workflow_name = $1 AND run_id = $2
|
|
26427
|
+
AND CASE WHEN $4::boolean THEN kind = 'state-draft' AND seq = $3::int
|
|
26428
|
+
ELSE kind IN ('state', 'state-base') AND seq <= $3::int END
|
|
26429
|
+
) AS stored`, [
|
|
26430
|
+
base.workflowName,
|
|
26431
|
+
base.runId,
|
|
26432
|
+
base.seq,
|
|
26433
|
+
base.draft === true
|
|
26434
|
+
]);
|
|
26435
|
+
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`);
|
|
26436
|
+
}
|
|
26437
|
+
const { lastSeq, stateShared, runToken } = header;
|
|
26438
|
+
await t.none(`DELETE FROM ${this.#entriesTable()}
|
|
26439
|
+
WHERE workflow_name = $1 AND run_id = $2${stateShared ? ` AND kind IN ('step', 'step-patch')` : ""}`, [run.workflowName, run.runId]);
|
|
26440
|
+
await t.none(`INSERT INTO ${this.#entriesTable()} (workflow_name, run_id, seq, kind, step_id, payload)
|
|
26441
|
+
SELECT $1, $2, $3::int - $4::int + e.ord, ${ENTRY_VALUES}
|
|
26442
|
+
FROM ${entryRows("$5")}`, [
|
|
26443
|
+
run.workflowName,
|
|
26444
|
+
run.runId,
|
|
26445
|
+
lastSeq,
|
|
26446
|
+
entries.length,
|
|
26447
|
+
toPgJson(entries)
|
|
26448
|
+
]);
|
|
26449
|
+
return {
|
|
26450
|
+
lastSeq,
|
|
26451
|
+
runToken
|
|
26452
|
+
};
|
|
26453
|
+
});
|
|
26454
|
+
} catch (error) {
|
|
26455
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
26456
|
+
throw this.#runError("WRITE_WORKFLOW_RUN_CHECKPOINT", run.workflowName, run.runId, error);
|
|
26457
|
+
}
|
|
26458
|
+
}
|
|
26459
|
+
/** One statement: the header upsert numbers the entries inserted after it. */
|
|
26460
|
+
async appendWorkflowRunEntries({ fence, steps, stepPatches, state, statePatch, startStateSeq, expectedStatus, ...run }) {
|
|
26461
|
+
const entries = journalEntries(steps, {
|
|
26462
|
+
state,
|
|
26463
|
+
statePatch
|
|
26464
|
+
}, false, stepPatches);
|
|
26465
|
+
const expected = expectedStatus === void 0 ? [] : [[expectedStatus].flat()];
|
|
26466
|
+
try {
|
|
26467
|
+
return await withRunFence(this.#db.client, this.#runOwnersTable(), resolveRunFence(this, fence, run.runId), "appendWorkflowRunEntries", (q) => q.oneOrNone(`WITH h AS (${this.#upsertHeaderSql(false, expected.length ? "$15" : void 0)}),
|
|
26468
|
+
e AS (
|
|
26469
|
+
INSERT INTO ${this.#entriesTable()} (workflow_name, run_id, seq, kind, step_id, payload)
|
|
26470
|
+
SELECT $1, $2, h."lastSeq" - $7::int + e.ord, ${ENTRY_VALUES}
|
|
26471
|
+
FROM h, ${entryRows("$14")}
|
|
26472
|
+
)
|
|
26473
|
+
SELECT "lastSeq", "runToken" FROM h`, [
|
|
26474
|
+
...this.#headerParams(run, entries.length),
|
|
26475
|
+
startStateSeq ?? null,
|
|
26476
|
+
toPgJson(entries),
|
|
26477
|
+
...expected
|
|
26478
|
+
]));
|
|
26479
|
+
} catch (error) {
|
|
26480
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
26481
|
+
throw this.#runError("APPEND_WORKFLOW_RUN_ENTRIES", run.workflowName, run.runId, error);
|
|
26482
|
+
}
|
|
26483
|
+
}
|
|
26484
|
+
async loadWorkflowRunState({ workflowName, runId, seq }) {
|
|
26485
|
+
try {
|
|
26486
|
+
return (await this.#stateAt(this.#db.client, workflowName, runId, { seq }))?.state ?? null;
|
|
26487
|
+
} catch (error) {
|
|
26488
|
+
throw this.#runError("LOAD_WORKFLOW_RUN_STATE", workflowName, runId, error);
|
|
26489
|
+
}
|
|
26490
|
+
}
|
|
26491
|
+
async appendWorkflowRunStateDraft(args) {
|
|
26492
|
+
const { workflowName, runId } = args;
|
|
26493
|
+
const payload = "state" in args ? { state: args.state } : {
|
|
26494
|
+
on: args.on.draft ? {
|
|
26495
|
+
seq: args.on.seq,
|
|
26496
|
+
draft: true
|
|
26497
|
+
} : { seq: args.on.seq },
|
|
26498
|
+
ops: args.ops
|
|
26499
|
+
};
|
|
26500
|
+
let row;
|
|
26501
|
+
try {
|
|
26502
|
+
row = await withRunFence(this.#db.client, this.#runOwnersTable(), resolveRunFence(this, void 0, runId), "appendWorkflowRunStateDraft", (q) => q.oneOrNone(`WITH h AS (
|
|
26503
|
+
UPDATE ${this.#runsTable()} SET "lastSeq" = "lastSeq" + 1, "stateShared" = true
|
|
26504
|
+
WHERE workflow_name = $1 AND run_id = $2
|
|
26505
|
+
RETURNING "lastSeq"
|
|
26506
|
+
)
|
|
26507
|
+
INSERT INTO ${this.#entriesTable()} (workflow_name, run_id, seq, kind, step_id, payload)
|
|
26508
|
+
SELECT $1, $2, h."lastSeq", 'state-draft', NULL, $3::jsonb FROM h
|
|
26509
|
+
RETURNING seq`, [
|
|
26510
|
+
workflowName,
|
|
26511
|
+
runId,
|
|
26512
|
+
toPgJson(payload)
|
|
26513
|
+
]));
|
|
26514
|
+
} catch (error) {
|
|
26515
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
26516
|
+
throw this.#runError("APPEND_WORKFLOW_RUN_STATE_DRAFT", workflowName, runId, error);
|
|
26517
|
+
}
|
|
26518
|
+
if (!row) throw new Error(`Storage v2 run ${workflowName}/${runId} is not stored`);
|
|
26519
|
+
return row;
|
|
25850
26520
|
}
|
|
25851
26521
|
supportsRunFencing() {
|
|
25852
26522
|
return true;
|
|
@@ -25975,6 +26645,7 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
|
|
|
25975
26645
|
});
|
|
25976
26646
|
const existingSnapshotResult = await t.oneOrNone(`SELECT snapshot FROM ${tableName} WHERE workflow_name = $1 AND run_id = $2 FOR UPDATE`, [workflowName, runId]);
|
|
25977
26647
|
if (!existingSnapshotResult) {
|
|
26648
|
+
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})`);
|
|
25978
26649
|
assertWorkflowStepGuard({
|
|
25979
26650
|
snapshot: null,
|
|
25980
26651
|
stepId,
|
|
@@ -26047,11 +26718,36 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
|
|
|
26047
26718
|
}, error);
|
|
26048
26719
|
}
|
|
26049
26720
|
}
|
|
26050
|
-
async updateWorkflowState({ workflowName, runId, opts, fence: explicitFence }) {
|
|
26721
|
+
async updateWorkflowState({ workflowName, runId, opts, fence: explicitFence, storageVersion }) {
|
|
26051
26722
|
const fence = resolveRunFence(this, explicitFence, runId);
|
|
26052
26723
|
try {
|
|
26053
26724
|
return await this.#db.client.tx(async (t) => {
|
|
26054
26725
|
if (fence) await assertRunFence(t, this.#runOwnersTable(), fence, "updateWorkflowState");
|
|
26726
|
+
if (storageVersion !== 1) {
|
|
26727
|
+
const run = await t.oneOrNone(`SELECT header FROM ${this.#runsTable()} WHERE workflow_name = $1 AND run_id = $2 FOR UPDATE`, [workflowName, runId]);
|
|
26728
|
+
if (run) {
|
|
26729
|
+
const { expectedStatus, ...patch } = opts;
|
|
26730
|
+
if (!matchesExpectedWorkflowStatus(run.header.status, expectedStatus)) return;
|
|
26731
|
+
const header = {
|
|
26732
|
+
...run.header,
|
|
26733
|
+
...patch
|
|
26734
|
+
};
|
|
26735
|
+
const now = /* @__PURE__ */ new Date();
|
|
26736
|
+
const { entries } = await t.one(`UPDATE ${this.#runsTable()} r
|
|
26737
|
+
SET header = $3::jsonb, status = $4, "updatedAt" = $5, "updatedAtZ" = $6
|
|
26738
|
+
WHERE r.workflow_name = $1 AND r.run_id = $2
|
|
26739
|
+
RETURNING ${this.#entriesSql("r")} AS entries`, [
|
|
26740
|
+
workflowName,
|
|
26741
|
+
runId,
|
|
26742
|
+
toPgJson(header),
|
|
26743
|
+
header.status,
|
|
26744
|
+
now,
|
|
26745
|
+
now
|
|
26746
|
+
]);
|
|
26747
|
+
return this.#materializeRun(t, header, entries);
|
|
26748
|
+
}
|
|
26749
|
+
if (storageVersion === 2) return void 0;
|
|
26750
|
+
}
|
|
26055
26751
|
const tableName = getTableName({
|
|
26056
26752
|
indexName: TABLE_WORKFLOW_SNAPSHOT,
|
|
26057
26753
|
schemaName: getSchemaName(this.#schema)
|
|
@@ -26131,10 +26827,14 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
|
|
|
26131
26827
|
async loadWorkflowRunStatus({ workflowName, runId }) {
|
|
26132
26828
|
try {
|
|
26133
26829
|
const statusExpr = await this.#statusExpression("snapshot");
|
|
26134
|
-
return (await this.#db.client.oneOrNone(`SELECT
|
|
26830
|
+
return (await this.#db.client.oneOrNone(`SELECT COALESCE(
|
|
26831
|
+
(SELECT status FROM ${this.#runsTable()} WHERE workflow_name = $1 AND run_id = $2),
|
|
26832
|
+
(SELECT ${statusExpr} FROM ${getTableName({
|
|
26135
26833
|
indexName: TABLE_WORKFLOW_SNAPSHOT,
|
|
26136
26834
|
schemaName: getSchemaName(this.#schema)
|
|
26137
|
-
})}
|
|
26835
|
+
})}
|
|
26836
|
+
WHERE "workflow_name" = $1 AND "run_id" = $2 ORDER BY "createdAt" DESC LIMIT 1)
|
|
26837
|
+
) AS status`, [workflowName, runId]))?.status ?? null;
|
|
26138
26838
|
} catch (error) {
|
|
26139
26839
|
throw new MastraError({
|
|
26140
26840
|
id: createStorageErrorId("PG", "LOAD_WORKFLOW_RUN_STATUS", "FAILED"),
|
|
@@ -26147,8 +26847,14 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
|
|
|
26147
26847
|
}, error);
|
|
26148
26848
|
}
|
|
26149
26849
|
}
|
|
26150
|
-
async loadWorkflowSnapshot({ workflowName, runId }) {
|
|
26850
|
+
async loadWorkflowSnapshot({ workflowName, runId, storageVersion }) {
|
|
26151
26851
|
try {
|
|
26852
|
+
if (storageVersion !== 1) {
|
|
26853
|
+
const run = await this.#db.client.oneOrNone(`SELECT r.header, ${this.#entriesSql("r")} AS entries
|
|
26854
|
+
FROM ${this.#runsTable()} r WHERE r.workflow_name = $1 AND r.run_id = $2`, [workflowName, runId]);
|
|
26855
|
+
if (run) return await this.#materializeRun(this.#db.client, run.header, run.entries);
|
|
26856
|
+
if (storageVersion === 2) return null;
|
|
26857
|
+
}
|
|
26152
26858
|
const result = await this.#db.load({
|
|
26153
26859
|
tableName: TABLE_WORKFLOW_SNAPSHOT,
|
|
26154
26860
|
keys: {
|
|
@@ -26165,7 +26871,7 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
|
|
|
26165
26871
|
}, error);
|
|
26166
26872
|
}
|
|
26167
26873
|
}
|
|
26168
|
-
async getWorkflowRunById({ runId, workflowName }) {
|
|
26874
|
+
async getWorkflowRunById({ runId, workflowName, storageVersion }) {
|
|
26169
26875
|
try {
|
|
26170
26876
|
const conditions = [];
|
|
26171
26877
|
const values = [];
|
|
@@ -26181,18 +26887,14 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
|
|
|
26181
26887
|
paramIndex++;
|
|
26182
26888
|
}
|
|
26183
26889
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
26184
|
-
const query =
|
|
26185
|
-
|
|
26186
|
-
|
|
26187
|
-
|
|
26188
|
-
})
|
|
26189
|
-
|
|
26190
|
-
ORDER BY "createdAt" DESC LIMIT 1
|
|
26191
|
-
`;
|
|
26192
|
-
const queryValues = values;
|
|
26193
|
-
const result = await this.#db.readClient.oneOrNone(query, queryValues);
|
|
26890
|
+
const query = await this.#runsQuery({
|
|
26891
|
+
v1Where: storageVersion === 2 ? null : whereClause,
|
|
26892
|
+
v2Where: storageVersion === 1 ? null : whereClause,
|
|
26893
|
+
page: "LIMIT 1"
|
|
26894
|
+
});
|
|
26895
|
+
const result = await this.#db.readClient.oneOrNone(query, values);
|
|
26194
26896
|
if (!result) return null;
|
|
26195
|
-
return this.
|
|
26897
|
+
return await this.#parseRun(this.#db.readClient, result);
|
|
26196
26898
|
} catch (error) {
|
|
26197
26899
|
throw new MastraError({
|
|
26198
26900
|
id: createStorageErrorId("PG", "GET_WORKFLOW_RUN_BY_ID", "FAILED"),
|
|
@@ -26205,12 +26907,28 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
|
|
|
26205
26907
|
}, error);
|
|
26206
26908
|
}
|
|
26207
26909
|
}
|
|
26208
|
-
async deleteWorkflowRunById({ runId, workflowName, fence }) {
|
|
26910
|
+
async deleteWorkflowRunById({ runId, workflowName, storageVersion, fence }) {
|
|
26911
|
+
const resolvedFence = resolveRunFence(this, fence, runId);
|
|
26209
26912
|
try {
|
|
26210
|
-
|
|
26211
|
-
|
|
26212
|
-
|
|
26213
|
-
|
|
26913
|
+
if (storageVersion === 1) {
|
|
26914
|
+
await withRunFence(this.#db.client, this.#runOwnersTable(), resolvedFence, "deleteWorkflowRunById", (q) => q.none(`DELETE FROM ${getTableName({
|
|
26915
|
+
indexName: TABLE_WORKFLOW_SNAPSHOT,
|
|
26916
|
+
schemaName: getSchemaName(this.#schema)
|
|
26917
|
+
})} WHERE run_id = $1 AND workflow_name = $2`, [runId, workflowName]));
|
|
26918
|
+
return;
|
|
26919
|
+
}
|
|
26920
|
+
await this.#db.client.tx(async (t) => {
|
|
26921
|
+
if (resolvedFence) await assertRunFence(t, this.#runOwnersTable(), resolvedFence, "deleteWorkflowRunById");
|
|
26922
|
+
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);
|
|
26923
|
+
for (const table of [
|
|
26924
|
+
this.#runsTable(),
|
|
26925
|
+
this.#entriesTable(),
|
|
26926
|
+
...storageVersion === 2 ? [] : [getTableName({
|
|
26927
|
+
indexName: TABLE_WORKFLOW_SNAPSHOT,
|
|
26928
|
+
schemaName: getSchemaName(this.#schema)
|
|
26929
|
+
})]
|
|
26930
|
+
]) await t.none(`DELETE FROM ${table} WHERE run_id = $1 AND workflow_name = $2`, [runId, workflowName]);
|
|
26931
|
+
});
|
|
26214
26932
|
} catch (error) {
|
|
26215
26933
|
if (isRunFenceConflictError(error)) throw error;
|
|
26216
26934
|
throw new MastraError({
|
|
@@ -26227,77 +26945,81 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
|
|
|
26227
26945
|
async listWorkflowRuns({ workflowName, fromDate, toDate, perPage, page, resourceId, threadId, status, summary } = {}) {
|
|
26228
26946
|
try {
|
|
26229
26947
|
const conditions = [];
|
|
26948
|
+
const v2Conditions = [];
|
|
26230
26949
|
const values = [];
|
|
26231
26950
|
let paramIndex = 1;
|
|
26232
26951
|
if (workflowName) {
|
|
26233
26952
|
conditions.push(`workflow_name = $${paramIndex}`);
|
|
26953
|
+
v2Conditions.push(`workflow_name = $${paramIndex}`);
|
|
26234
26954
|
values.push(workflowName);
|
|
26235
26955
|
paramIndex++;
|
|
26236
26956
|
}
|
|
26237
26957
|
if (status) {
|
|
26238
26958
|
const statusExpr = await this.#db.getColumnType(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'`;
|
|
26239
26959
|
conditions.push(`${statusExpr} = $${paramIndex}`);
|
|
26960
|
+
v2Conditions.push(`status = $${paramIndex}`);
|
|
26240
26961
|
values.push(status);
|
|
26241
26962
|
paramIndex++;
|
|
26242
26963
|
}
|
|
26243
26964
|
if (resourceId) {
|
|
26244
|
-
if (await this.#db.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId")) {
|
|
26245
|
-
|
|
26246
|
-
|
|
26247
|
-
|
|
26248
|
-
|
|
26965
|
+
if (await this.#db.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId")) conditions.push(`"resourceId" = $${paramIndex}`);
|
|
26966
|
+
else this.logger?.warn?.(`[${TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
|
|
26967
|
+
v2Conditions.push(`"resourceId" = $${paramIndex}`);
|
|
26968
|
+
values.push(resourceId);
|
|
26969
|
+
paramIndex++;
|
|
26249
26970
|
}
|
|
26250
26971
|
if (threadId) {
|
|
26251
|
-
if (await this.#db.getColumnType(TABLE_WORKFLOW_SNAPSHOT, "snapshot") === "jsonb") {
|
|
26252
|
-
|
|
26253
|
-
|
|
26254
|
-
|
|
26255
|
-
|
|
26972
|
+
if (await this.#db.getColumnType(TABLE_WORKFLOW_SNAPSHOT, "snapshot") === "jsonb") conditions.push(`${WORKFLOW_SNAPSHOT_THREAD_ID_EXPR} = $${paramIndex}`);
|
|
26973
|
+
else this.logger?.warn?.(`[${TABLE_WORKFLOW_SNAPSHOT}] snapshot column is not jsonb. Skipping threadId filter.`);
|
|
26974
|
+
v2Conditions.push(`"threadId" = $${paramIndex}`);
|
|
26975
|
+
values.push(threadId);
|
|
26976
|
+
paramIndex++;
|
|
26256
26977
|
}
|
|
26257
26978
|
if (fromDate) {
|
|
26258
26979
|
conditions.push(`"createdAt" >= $${paramIndex}`);
|
|
26980
|
+
v2Conditions.push(`"createdAt" >= $${paramIndex}`);
|
|
26259
26981
|
values.push(fromDate);
|
|
26260
26982
|
paramIndex++;
|
|
26261
26983
|
}
|
|
26262
26984
|
if (toDate) {
|
|
26263
26985
|
conditions.push(`"createdAt" <= $${paramIndex}`);
|
|
26986
|
+
v2Conditions.push(`"createdAt" <= $${paramIndex}`);
|
|
26264
26987
|
values.push(toDate);
|
|
26265
26988
|
paramIndex++;
|
|
26266
26989
|
}
|
|
26267
26990
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
26991
|
+
const v2WhereClause = v2Conditions.length > 0 ? `WHERE ${v2Conditions.join(" AND ")}` : "";
|
|
26268
26992
|
let total = 0;
|
|
26269
26993
|
const usePagination = typeof perPage === "number" && typeof page === "number";
|
|
26270
26994
|
if (usePagination) {
|
|
26271
|
-
const countResult = await this.#db.client.one(`SELECT COUNT(*)
|
|
26995
|
+
const countResult = await this.#db.client.one(`SELECT (SELECT COUNT(*) FROM ${getTableName({
|
|
26272
26996
|
indexName: TABLE_WORKFLOW_SNAPSHOT,
|
|
26273
26997
|
schemaName: getSchemaName(this.#schema)
|
|
26274
|
-
})} ${whereClause}
|
|
26998
|
+
})} ${whereClause})
|
|
26999
|
+
+ (SELECT COUNT(*) FROM ${this.#runsTable()} ${v2WhereClause}) AS count`, values);
|
|
26275
27000
|
total = Number(countResult.count);
|
|
26276
27001
|
}
|
|
26277
27002
|
const normalizedPerPage = usePagination ? normalizePerPage(perPage, Number.MAX_SAFE_INTEGER) : 0;
|
|
26278
27003
|
const offset = usePagination ? page * normalizedPerPage : void 0;
|
|
26279
|
-
let
|
|
27004
|
+
let v1Snapshot = "snapshot";
|
|
26280
27005
|
if (summary) {
|
|
26281
27006
|
const snapshotJson = await this.#db.getColumnType(TABLE_WORKFLOW_SNAPSHOT, "snapshot") === "jsonb" ? "snapshot" : `regexp_replace(snapshot::text, '\\\\u(0000|[Dd][89A-Fa-f][0-9A-Fa-f]{2})', '', 'g')::jsonb`;
|
|
26282
|
-
|
|
27007
|
+
v1Snapshot = `jsonb_build_object('status', ${snapshotJson} -> 'status', 'timestamp', ${snapshotJson} -> 'timestamp')`;
|
|
26283
27008
|
}
|
|
26284
|
-
const query =
|
|
26285
|
-
|
|
26286
|
-
|
|
26287
|
-
|
|
26288
|
-
|
|
26289
|
-
|
|
26290
|
-
|
|
26291
|
-
${usePagination ? ` LIMIT $${paramIndex} OFFSET $${paramIndex + 1}` : ""}
|
|
26292
|
-
`;
|
|
27009
|
+
const query = await this.#runsQuery({
|
|
27010
|
+
v1Where: whereClause,
|
|
27011
|
+
v2Where: v2WhereClause,
|
|
27012
|
+
v1Snapshot,
|
|
27013
|
+
summary,
|
|
27014
|
+
page: usePagination ? `LIMIT $${paramIndex} OFFSET $${paramIndex + 1}` : ""
|
|
27015
|
+
});
|
|
26293
27016
|
const queryValues = usePagination ? [
|
|
26294
27017
|
...values,
|
|
26295
27018
|
normalizedPerPage,
|
|
26296
27019
|
offset
|
|
26297
27020
|
] : values;
|
|
26298
|
-
const
|
|
26299
|
-
|
|
26300
|
-
});
|
|
27021
|
+
const result = await this.#db.client.manyOrNone(query, queryValues);
|
|
27022
|
+
const runs = await Promise.all((result || []).map((row) => this.#parseRun(this.#db.client, row)));
|
|
26301
27023
|
return {
|
|
26302
27024
|
runs,
|
|
26303
27025
|
total: total || runs.length
|