@db-lyon/flowkit 0.5.3 → 0.7.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/README.md +55 -0
- package/dist/config/schema.d.ts +247 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +9 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/flow/runner.d.ts +64 -0
- package/dist/flow/runner.d.ts.map +1 -1
- package/dist/flow/runner.js +187 -40
- package/dist/flow/runner.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,iBAAiB,wCAAwB,CAAC;AAEvD,eAAO,MAAM,oBAAoB;;;;;IAK/B,sEAAsE;;;;;;;;;;;;;;;;;EAGtE,CAAC;AAEH,eAAO,MAAM,cAAc;;;;IAKvB,0DAA0D;;IAE1D,8CAA8C;;IAE9C,iEAAiE
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"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"}
|
package/dist/config/schema.js
CHANGED
|
@@ -20,6 +20,15 @@ export const FlowStepSchema = z
|
|
|
20
20
|
retryDelay: z.number().int().nonnegative().optional(),
|
|
21
21
|
/** Only retry when the error message contains this substring. */
|
|
22
22
|
retryOn: z.string().optional(),
|
|
23
|
+
/**
|
|
24
|
+
* Conditional execution. A boolean runs/skips the step directly; a string is
|
|
25
|
+
* an expression evaluated at runtime by the runner's `conditionEvaluator`
|
|
26
|
+
* (or the built-in `${...}`-reference truthiness check if none is set). A
|
|
27
|
+
* falsy result skips the step without failing the flow.
|
|
28
|
+
*/
|
|
29
|
+
when: z.union([z.string(), z.boolean()]).optional(),
|
|
30
|
+
/** If true, a failure of this step is recorded but does not abort the flow. */
|
|
31
|
+
ignore_failure: z.boolean().optional(),
|
|
23
32
|
})
|
|
24
33
|
.refine((data) => {
|
|
25
34
|
if (data.task === 'None')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;AAEvD,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7B,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,OAAO,EAAE,iBAAiB,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IACjD,sEAAsE;IACtE,UAAU,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAClC,UAAU,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;CACnC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC;KAC5B,MAAM,CAAC;IACN,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,OAAO,EAAE,iBAAiB,CAAC,QAAQ,EAAE;IACrC,0DAA0D;IAC1D,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE;IAClD,8CAA8C;IAC9C,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE;IACrD,iEAAiE;IACjE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"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"}
|
package/dist/flow/runner.d.ts
CHANGED
|
@@ -4,6 +4,12 @@ import type { TaskResult } from '../task/base-task.js';
|
|
|
4
4
|
import type { TaskContext } from '../task/base-task.js';
|
|
5
5
|
import type { TaskRegistry } from '../task/registry.js';
|
|
6
6
|
export type HookPhase = 'on_start' | 'on_success' | 'on_failure' | 'finally';
|
|
7
|
+
/**
|
|
8
|
+
* Per-task option overrides injected by an enclosing flow step, keyed by the
|
|
9
|
+
* inner task (or flow) name. When a flow step carries `options`, those options
|
|
10
|
+
* are interpreted as this map and threaded down into the nested flow.
|
|
11
|
+
*/
|
|
12
|
+
export type ParentOptions = Record<string, Record<string, unknown>>;
|
|
7
13
|
export interface FlowRunOptions {
|
|
8
14
|
flowName: string;
|
|
9
15
|
skip?: string[];
|
|
@@ -12,7 +18,31 @@ export interface FlowRunOptions {
|
|
|
12
18
|
params?: Record<string, unknown>;
|
|
13
19
|
/** If true, invoke rollback records from completed steps in reverse order on failure. */
|
|
14
20
|
rollback_on_failure?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Plan mode only: recursively expand nested-flow steps into their child steps
|
|
23
|
+
* (each annotated with a hierarchical `path`). Default false preserves the
|
|
24
|
+
* flat, one-line-per-flow-step plan.
|
|
25
|
+
*/
|
|
26
|
+
expandNestedFlows?: boolean;
|
|
15
27
|
}
|
|
28
|
+
/** Context handed to a `conditionEvaluator` when resolving a string `when:`. */
|
|
29
|
+
export interface ConditionContext {
|
|
30
|
+
steps: FlowStepResult[];
|
|
31
|
+
params?: Record<string, unknown>;
|
|
32
|
+
context: TaskContext;
|
|
33
|
+
error?: {
|
|
34
|
+
message: string;
|
|
35
|
+
name: string;
|
|
36
|
+
stack?: string;
|
|
37
|
+
step?: string;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Evaluates a string `when:` expression to a boolean. Supply one to use a real
|
|
42
|
+
* expression language (e.g. jinja-style with project/org context). When absent,
|
|
43
|
+
* the runner falls back to resolving `${...}` references and testing truthiness.
|
|
44
|
+
*/
|
|
45
|
+
export type ConditionEvaluator = (expression: string, ctx: ConditionContext) => boolean | Promise<boolean>;
|
|
16
46
|
export interface FlowStepResult {
|
|
17
47
|
stepNumber: number;
|
|
18
48
|
type: 'task' | 'flow';
|
|
@@ -22,6 +52,10 @@ export interface FlowStepResult {
|
|
|
22
52
|
duration: number;
|
|
23
53
|
/** Number of attempts including the first try (≥1 when executed). */
|
|
24
54
|
attempts?: number;
|
|
55
|
+
/** Why the step was skipped: 'static' (skip list / task: None) or 'when' (condition false). */
|
|
56
|
+
skipReason?: 'static' | 'when';
|
|
57
|
+
/** True when the step failed but `ignore_failure` let the flow continue. */
|
|
58
|
+
ignoredFailure?: boolean;
|
|
25
59
|
}
|
|
26
60
|
export interface HookError {
|
|
27
61
|
phase: HookPhase;
|
|
@@ -55,8 +89,16 @@ export interface PlanStep {
|
|
|
55
89
|
retries?: number;
|
|
56
90
|
retryDelay?: number;
|
|
57
91
|
retryOn?: string;
|
|
92
|
+
/** Conditional execution — evaluated at run time, so plan reports it unresolved. */
|
|
93
|
+
when?: string | boolean;
|
|
94
|
+
/** Whether a failure of this step is tolerated. */
|
|
95
|
+
ignore_failure?: boolean;
|
|
58
96
|
/** For hook steps: the phase they belong to. Undefined for main steps. */
|
|
59
97
|
phase?: HookPhase;
|
|
98
|
+
/** Hierarchical id (e.g. "2/1") — only set when a plan expands nested flows. */
|
|
99
|
+
path?: string;
|
|
100
|
+
/** Nesting depth — 0 for top-level, increments per expanded nested flow. */
|
|
101
|
+
depth?: number;
|
|
60
102
|
}
|
|
61
103
|
export interface FlowRunnerHooks {
|
|
62
104
|
beforeRun?(flowName: string, plan: PlanStep[]): Promise<void>;
|
|
@@ -72,6 +114,8 @@ export interface FlowRunnerConfig {
|
|
|
72
114
|
context: TaskContext;
|
|
73
115
|
hooks?: FlowRunnerHooks;
|
|
74
116
|
logger?: Logger;
|
|
117
|
+
/** Optional evaluator for string `when:` expressions. */
|
|
118
|
+
conditionEvaluator?: ConditionEvaluator;
|
|
75
119
|
}
|
|
76
120
|
export declare class FlowRunner {
|
|
77
121
|
private logger;
|
|
@@ -80,12 +124,32 @@ export declare class FlowRunner {
|
|
|
80
124
|
private registry;
|
|
81
125
|
private ctx;
|
|
82
126
|
private hooks;
|
|
127
|
+
private conditionEvaluator?;
|
|
83
128
|
private runDepth;
|
|
84
129
|
constructor(config: FlowRunnerConfig);
|
|
85
130
|
run(options: FlowRunOptions): Promise<FlowRunResult>;
|
|
131
|
+
/**
|
|
132
|
+
* Run a single task by name, directly — the leaf unit of work, without a flow.
|
|
133
|
+
* A flow is a composition of these; this is the same primitive each flow step
|
|
134
|
+
* executes (see `executeTask`), so a task behaves identically whether it's run
|
|
135
|
+
* on its own or as a step. `options` merge over the task's configured defaults.
|
|
136
|
+
*/
|
|
137
|
+
runTask(taskName: string, options?: Record<string, unknown>): Promise<TaskResult>;
|
|
138
|
+
/** Instantiate and run a task with fully-resolved options (the shared leaf). */
|
|
139
|
+
private executeTask;
|
|
140
|
+
private runWith;
|
|
86
141
|
resolveExecutionPlan(flow: FlowDefinition, skipSet: Set<string>): PlanStep[];
|
|
87
142
|
private planStepFromDef;
|
|
88
143
|
private planHookSteps;
|
|
144
|
+
/**
|
|
145
|
+
* Merge an enclosing flow step's per-task override map onto inherited parent
|
|
146
|
+
* options. Inner (closer) overrides win over outer for the same task+key.
|
|
147
|
+
*/
|
|
148
|
+
private mergeParentOptions;
|
|
149
|
+
/** Recursively expand a plan step's nested flow into its child steps. */
|
|
150
|
+
private expandPlanStep;
|
|
151
|
+
/** Evaluate a step's `when:` to a boolean. Undefined `when` always runs. */
|
|
152
|
+
private evaluateWhen;
|
|
89
153
|
private executeFlow;
|
|
90
154
|
private runHookStep;
|
|
91
155
|
private executeTaskStepWithRetry;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/flow/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAY,MAAM,qBAAqB,CAAC;AACpF,OAAO,KAAK,EAAE,UAAU,EAAkB,MAAM,sBAAsB,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAOxD,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS,CAAC;AAE7E,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,mFAAmF;IACnF,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,yFAAyF;IACzF,mBAAmB,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/flow/runner.ts"],"names":[],"mappings":"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"}
|
package/dist/flow/runner.js
CHANGED
|
@@ -10,6 +10,7 @@ export class FlowRunner {
|
|
|
10
10
|
registry;
|
|
11
11
|
ctx;
|
|
12
12
|
hooks;
|
|
13
|
+
conditionEvaluator;
|
|
13
14
|
runDepth = 0;
|
|
14
15
|
constructor(config) {
|
|
15
16
|
this.logger = (config.logger ?? noopLogger).child({ component: 'flow-runner' });
|
|
@@ -17,13 +18,33 @@ export class FlowRunner {
|
|
|
17
18
|
this.flows = config.flows;
|
|
18
19
|
this.registry = config.registry;
|
|
19
20
|
this.hooks = config.hooks ?? {};
|
|
21
|
+
this.conditionEvaluator = config.conditionEvaluator;
|
|
20
22
|
this.ctx = { ...config.context, registry: config.registry };
|
|
21
23
|
}
|
|
22
24
|
async run(options) {
|
|
25
|
+
return this.runWith(options, {});
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Run a single task by name, directly — the leaf unit of work, without a flow.
|
|
29
|
+
* A flow is a composition of these; this is the same primitive each flow step
|
|
30
|
+
* executes (see `executeTask`), so a task behaves identically whether it's run
|
|
31
|
+
* on its own or as a step. `options` merge over the task's configured defaults.
|
|
32
|
+
*/
|
|
33
|
+
async runTask(taskName, options = {}) {
|
|
34
|
+
const taskDef = this.resolveTaskDefinition(taskName);
|
|
35
|
+
this.logger.info({ task: taskName }, `Running task ${taskName}`);
|
|
36
|
+
return this.executeTask(taskDef.class_path, { ...taskDef.options, ...options });
|
|
37
|
+
}
|
|
38
|
+
/** Instantiate and run a task with fully-resolved options (the shared leaf). */
|
|
39
|
+
async executeTask(classPath, options) {
|
|
40
|
+
const task = await this.registry.create(classPath, this.ctx, options);
|
|
41
|
+
return task.run();
|
|
42
|
+
}
|
|
43
|
+
async runWith(options, parentOptions) {
|
|
23
44
|
this.runDepth++;
|
|
24
45
|
const isTopLevel = this.runDepth === 1;
|
|
25
46
|
try {
|
|
26
|
-
return await this.executeFlow(options, isTopLevel);
|
|
47
|
+
return await this.executeFlow(options, isTopLevel, parentOptions);
|
|
27
48
|
}
|
|
28
49
|
finally {
|
|
29
50
|
this.runDepth--;
|
|
@@ -50,6 +71,8 @@ export class FlowRunner {
|
|
|
50
71
|
retries: step.retries,
|
|
51
72
|
retryDelay: step.retryDelay,
|
|
52
73
|
retryOn: step.retryOn,
|
|
74
|
+
when: step.when,
|
|
75
|
+
ignore_failure: step.ignore_failure,
|
|
53
76
|
};
|
|
54
77
|
}
|
|
55
78
|
planHookSteps(hookSteps, phase, skipSet, baseStepNumber) {
|
|
@@ -60,10 +83,66 @@ export class FlowRunner {
|
|
|
60
83
|
phase,
|
|
61
84
|
}));
|
|
62
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Merge an enclosing flow step's per-task override map onto inherited parent
|
|
88
|
+
* options. Inner (closer) overrides win over outer for the same task+key.
|
|
89
|
+
*/
|
|
90
|
+
mergeParentOptions(base, overrideMap) {
|
|
91
|
+
if (!overrideMap)
|
|
92
|
+
return base;
|
|
93
|
+
const out = { ...base };
|
|
94
|
+
for (const [name, opts] of Object.entries(overrideMap)) {
|
|
95
|
+
if (opts && typeof opts === 'object' && !Array.isArray(opts)) {
|
|
96
|
+
out[name] = { ...(base[name] ?? {}), ...opts };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
/** Recursively expand a plan step's nested flow into its child steps. */
|
|
102
|
+
expandPlanStep(planStep, parentOptions, pathPrefix, depth, ancestors, skipSet) {
|
|
103
|
+
const self = { ...planStep, path: pathPrefix, depth };
|
|
104
|
+
if (planStep.type !== 'flow' || planStep.skipped || ancestors.has(planStep.name)) {
|
|
105
|
+
return [self];
|
|
106
|
+
}
|
|
107
|
+
const childFlow = this.flows[planStep.name];
|
|
108
|
+
if (!childFlow)
|
|
109
|
+
return [self];
|
|
110
|
+
const childParentOptions = this.mergeParentOptions(parentOptions, planStep.options);
|
|
111
|
+
const nextAncestors = new Set(ancestors).add(planStep.name);
|
|
112
|
+
const childPlan = this.resolveExecutionPlan(childFlow, skipSet);
|
|
113
|
+
const children = childPlan.flatMap((cs) => this.expandPlanStep(cs, childParentOptions, `${pathPrefix}/${cs.stepNumber}`, depth + 1, nextAncestors, skipSet));
|
|
114
|
+
return [self, ...children];
|
|
115
|
+
}
|
|
116
|
+
/** Evaluate a step's `when:` to a boolean. Undefined `when` always runs. */
|
|
117
|
+
async evaluateWhen(when, completedSteps, params, errorCtx) {
|
|
118
|
+
if (when === undefined)
|
|
119
|
+
return true;
|
|
120
|
+
if (typeof when === 'boolean')
|
|
121
|
+
return when;
|
|
122
|
+
const error = errorCtx
|
|
123
|
+
? {
|
|
124
|
+
message: errorCtx.error.message,
|
|
125
|
+
name: errorCtx.error.name,
|
|
126
|
+
stack: errorCtx.error.stack,
|
|
127
|
+
step: errorCtx.step,
|
|
128
|
+
}
|
|
129
|
+
: undefined;
|
|
130
|
+
if (this.conditionEvaluator) {
|
|
131
|
+
return await this.conditionEvaluator(when, {
|
|
132
|
+
steps: completedSteps,
|
|
133
|
+
params,
|
|
134
|
+
context: this.ctx,
|
|
135
|
+
error,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
// Built-in fallback: resolve ${...} references, then test truthiness.
|
|
139
|
+
const resolved = resolveReferences(when, { steps: completedSteps, error });
|
|
140
|
+
return truthy(resolved);
|
|
141
|
+
}
|
|
63
142
|
// ---------------------------------------------------------------------------
|
|
64
143
|
// Internal
|
|
65
144
|
// ---------------------------------------------------------------------------
|
|
66
|
-
async executeFlow(options, isTopLevel) {
|
|
145
|
+
async executeFlow(options, isTopLevel, parentOptions) {
|
|
67
146
|
const startTime = Date.now();
|
|
68
147
|
const skipSet = new Set(options.skip ?? []);
|
|
69
148
|
const completedSteps = [];
|
|
@@ -77,9 +156,12 @@ export class FlowRunner {
|
|
|
77
156
|
const executionPlan = this.resolveExecutionPlan(flow, skipSet);
|
|
78
157
|
// Plan mode — dump all phases for visibility, nothing runs.
|
|
79
158
|
if (options.plan) {
|
|
159
|
+
const mainPlan = options.expandNestedFlows
|
|
160
|
+
? executionPlan.flatMap((s) => this.expandPlanStep(s, parentOptions, String(s.stepNumber), 0, new Set([options.flowName]), skipSet))
|
|
161
|
+
: executionPlan;
|
|
80
162
|
const fullPlan = [
|
|
81
163
|
...this.planHookSteps(flow.on_start, 'on_start', skipSet, -3000),
|
|
82
|
-
...
|
|
164
|
+
...mainPlan,
|
|
83
165
|
...this.planHookSteps(flow.on_success, 'on_success', skipSet, 10_000),
|
|
84
166
|
...this.planHookSteps(flow.on_failure, 'on_failure', skipSet, 20_000),
|
|
85
167
|
...this.planHookSteps(flow.finally, 'finally', skipSet, 30_000),
|
|
@@ -92,6 +174,7 @@ export class FlowRunner {
|
|
|
92
174
|
name: s.name,
|
|
93
175
|
skipped: s.skipped,
|
|
94
176
|
duration: 0,
|
|
177
|
+
...(s.path !== undefined ? { path: s.path, depth: s.depth } : {}),
|
|
95
178
|
})),
|
|
96
179
|
duration: 0,
|
|
97
180
|
};
|
|
@@ -105,7 +188,7 @@ export class FlowRunner {
|
|
|
105
188
|
{
|
|
106
189
|
const startPlan = this.planHookSteps(flow.on_start, 'on_start', skipSet, -3000);
|
|
107
190
|
for (const hookStep of startPlan) {
|
|
108
|
-
const ok = await this.runHookStep(hookStep, options, completedSteps, undefined, hookErrors);
|
|
191
|
+
const ok = await this.runHookStep(hookStep, options, completedSteps, parentOptions, undefined, hookErrors);
|
|
109
192
|
if (!ok) {
|
|
110
193
|
flowError = hookErrors[hookErrors.length - 1]?.error;
|
|
111
194
|
flowErrorStepName = hookStep.name;
|
|
@@ -116,13 +199,46 @@ export class FlowRunner {
|
|
|
116
199
|
// ---- main steps ----
|
|
117
200
|
if (!flowError) {
|
|
118
201
|
for (const planStep of executionPlan) {
|
|
119
|
-
|
|
202
|
+
// Resolve conditional execution (`when:`) at run time.
|
|
203
|
+
let conditionMet = true;
|
|
204
|
+
let conditionError;
|
|
205
|
+
if (!planStep.skipped && planStep.when !== undefined) {
|
|
206
|
+
try {
|
|
207
|
+
conditionMet = await this.evaluateWhen(planStep.when, completedSteps, options.params);
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
conditionError = err instanceof Error ? err : new Error(String(err));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (conditionError) {
|
|
214
|
+
// A condition that throws is treated like a step failure.
|
|
215
|
+
const sr = {
|
|
216
|
+
stepNumber: planStep.stepNumber,
|
|
217
|
+
type: planStep.type,
|
|
218
|
+
name: planStep.name,
|
|
219
|
+
skipped: false,
|
|
220
|
+
duration: 0,
|
|
221
|
+
result: { success: false, error: conditionError },
|
|
222
|
+
};
|
|
223
|
+
completedSteps.push(sr);
|
|
224
|
+
await this.hooks.afterStep?.(planStep, sr);
|
|
225
|
+
if (planStep.ignore_failure) {
|
|
226
|
+
sr.ignoredFailure = true;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
flowError = conditionError;
|
|
230
|
+
flowErrorStepName = planStep.name;
|
|
231
|
+
await this.hooks.onStepError?.(planStep, conditionError, completedSteps);
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
if (planStep.skipped || !conditionMet) {
|
|
120
235
|
const sr = {
|
|
121
236
|
stepNumber: planStep.stepNumber,
|
|
122
237
|
type: planStep.type,
|
|
123
238
|
name: planStep.name,
|
|
124
239
|
skipped: true,
|
|
125
240
|
duration: 0,
|
|
241
|
+
skipReason: planStep.skipped ? 'static' : 'when',
|
|
126
242
|
};
|
|
127
243
|
completedSteps.push(sr);
|
|
128
244
|
await this.hooks.afterStep?.(planStep, sr);
|
|
@@ -133,7 +249,7 @@ export class FlowRunner {
|
|
|
133
249
|
try {
|
|
134
250
|
let stepResult;
|
|
135
251
|
if (planStep.type === 'task') {
|
|
136
|
-
const { result: taskResult, attempts } = await this.executeTaskStepWithRetry(planStep, options.params, completedSteps);
|
|
252
|
+
const { result: taskResult, attempts } = await this.executeTaskStepWithRetry(planStep, options.params, completedSteps, parentOptions);
|
|
137
253
|
stepResult = {
|
|
138
254
|
stepNumber: planStep.stepNumber,
|
|
139
255
|
type: 'task',
|
|
@@ -151,11 +267,8 @@ export class FlowRunner {
|
|
|
151
267
|
}
|
|
152
268
|
}
|
|
153
269
|
else {
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
flowName: planStep.name,
|
|
157
|
-
plan: false,
|
|
158
|
-
});
|
|
270
|
+
const childParentOptions = this.mergeParentOptions(parentOptions, planStep.options);
|
|
271
|
+
const nestedResult = await this.runWith({ ...options, flowName: planStep.name, plan: false }, childParentOptions);
|
|
159
272
|
stepResult = {
|
|
160
273
|
stepNumber: planStep.stepNumber,
|
|
161
274
|
type: 'flow',
|
|
@@ -163,6 +276,7 @@ export class FlowRunner {
|
|
|
163
276
|
result: {
|
|
164
277
|
success: nestedResult.success,
|
|
165
278
|
data: { stepCount: nestedResult.steps.length },
|
|
279
|
+
error: nestedResult.success ? undefined : nestedResult.error,
|
|
166
280
|
},
|
|
167
281
|
skipped: false,
|
|
168
282
|
duration: Date.now() - stepStart,
|
|
@@ -176,31 +290,39 @@ export class FlowRunner {
|
|
|
176
290
|
});
|
|
177
291
|
}
|
|
178
292
|
}
|
|
179
|
-
if (!nestedResult.success) {
|
|
180
|
-
flowError = nestedResult.error ?? new Error(`Nested flow ${planStep.name} failed`);
|
|
181
|
-
flowErrorStepName = planStep.name;
|
|
182
|
-
}
|
|
183
293
|
}
|
|
184
294
|
completedSteps.push(stepResult);
|
|
185
295
|
await this.hooks.afterStep?.(planStep, stepResult);
|
|
186
296
|
if (!stepResult.result?.success) {
|
|
297
|
+
if (planStep.ignore_failure) {
|
|
298
|
+
stepResult.ignoredFailure = true;
|
|
299
|
+
this.logger.info({ step: planStep.stepNumber, task: planStep.name }, `Step ${planStep.name} failed but ignore_failure is set; continuing`);
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
187
302
|
flowError =
|
|
188
|
-
|
|
189
|
-
flowErrorStepName =
|
|
303
|
+
stepResult.result?.error ?? new Error(`Step ${planStep.name} failed`);
|
|
304
|
+
flowErrorStepName = planStep.name;
|
|
190
305
|
await this.hooks.onStepError?.(planStep, flowError, completedSteps);
|
|
191
306
|
break;
|
|
192
307
|
}
|
|
193
308
|
}
|
|
194
309
|
catch (error) {
|
|
195
310
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
196
|
-
|
|
311
|
+
const sr = {
|
|
197
312
|
stepNumber: planStep.stepNumber,
|
|
198
313
|
type: planStep.type,
|
|
199
314
|
name: planStep.name,
|
|
200
315
|
skipped: false,
|
|
201
316
|
duration: Date.now() - stepStart,
|
|
202
317
|
result: { success: false, error: err },
|
|
203
|
-
}
|
|
318
|
+
};
|
|
319
|
+
completedSteps.push(sr);
|
|
320
|
+
if (planStep.ignore_failure) {
|
|
321
|
+
sr.ignoredFailure = true;
|
|
322
|
+
await this.hooks.afterStep?.(planStep, sr);
|
|
323
|
+
this.logger.info({ step: planStep.stepNumber, task: planStep.name }, `Step ${planStep.name} threw but ignore_failure is set; continuing`);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
204
326
|
flowError = err;
|
|
205
327
|
flowErrorStepName = planStep.name;
|
|
206
328
|
await this.hooks.onStepError?.(planStep, err, completedSteps);
|
|
@@ -212,13 +334,13 @@ export class FlowRunner {
|
|
|
212
334
|
if (flowError) {
|
|
213
335
|
const failPlan = this.planHookSteps(flow.on_failure, 'on_failure', skipSet, 20_000);
|
|
214
336
|
for (const hookStep of failPlan) {
|
|
215
|
-
await this.runHookStep(hookStep, options, completedSteps, { error: flowError, step: flowErrorStepName }, hookErrors);
|
|
337
|
+
await this.runHookStep(hookStep, options, completedSteps, parentOptions, { error: flowError, step: flowErrorStepName }, hookErrors);
|
|
216
338
|
}
|
|
217
339
|
}
|
|
218
340
|
else {
|
|
219
341
|
const successPlan = this.planHookSteps(flow.on_success, 'on_success', skipSet, 10_000);
|
|
220
342
|
for (const hookStep of successPlan) {
|
|
221
|
-
await this.runHookStep(hookStep, options, completedSteps, undefined, hookErrors);
|
|
343
|
+
await this.runHookStep(hookStep, options, completedSteps, parentOptions, undefined, hookErrors);
|
|
222
344
|
}
|
|
223
345
|
}
|
|
224
346
|
// ---- rollback ----
|
|
@@ -230,7 +352,7 @@ export class FlowRunner {
|
|
|
230
352
|
{
|
|
231
353
|
const finallyPlan = this.planHookSteps(flow.finally, 'finally', skipSet, 30_000);
|
|
232
354
|
for (const hookStep of finallyPlan) {
|
|
233
|
-
await this.runHookStep(hookStep, options, completedSteps, flowError ? { error: flowError, step: flowErrorStepName } : undefined, hookErrors);
|
|
355
|
+
await this.runHookStep(hookStep, options, completedSteps, parentOptions, flowError ? { error: flowError, step: flowErrorStepName } : undefined, hookErrors);
|
|
234
356
|
}
|
|
235
357
|
}
|
|
236
358
|
const result = {
|
|
@@ -246,16 +368,29 @@ export class FlowRunner {
|
|
|
246
368
|
}
|
|
247
369
|
return result;
|
|
248
370
|
}
|
|
249
|
-
async runHookStep(hookStep, options, completedSteps, errorCtx, hookErrors) {
|
|
371
|
+
async runHookStep(hookStep, options, completedSteps, parentOptions, errorCtx, hookErrors) {
|
|
250
372
|
if (hookStep.skipped)
|
|
251
373
|
return true;
|
|
374
|
+
// Hook steps honor `when:` too — a falsy condition skips them silently.
|
|
375
|
+
if (hookStep.when !== undefined) {
|
|
376
|
+
try {
|
|
377
|
+
const ok = await this.evaluateWhen(hookStep.when, completedSteps, options.params, errorCtx);
|
|
378
|
+
if (!ok)
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
catch (err) {
|
|
382
|
+
hookErrors.push({
|
|
383
|
+
phase: hookStep.phase,
|
|
384
|
+
name: hookStep.name,
|
|
385
|
+
error: err instanceof Error ? err : new Error(String(err)),
|
|
386
|
+
});
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
252
390
|
try {
|
|
253
391
|
if (hookStep.type === 'flow') {
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
flowName: hookStep.name,
|
|
257
|
-
plan: false,
|
|
258
|
-
});
|
|
392
|
+
const childParentOptions = this.mergeParentOptions(parentOptions, hookStep.options);
|
|
393
|
+
const nested = await this.runWith({ ...options, flowName: hookStep.name, plan: false }, childParentOptions);
|
|
259
394
|
if (!nested.success) {
|
|
260
395
|
hookErrors.push({
|
|
261
396
|
phase: hookStep.phase,
|
|
@@ -266,7 +401,7 @@ export class FlowRunner {
|
|
|
266
401
|
}
|
|
267
402
|
return true;
|
|
268
403
|
}
|
|
269
|
-
const { result } = await this.executeTaskStepWithRetry(hookStep, options.params, completedSteps, errorCtx);
|
|
404
|
+
const { result } = await this.executeTaskStepWithRetry(hookStep, options.params, completedSteps, parentOptions, errorCtx);
|
|
270
405
|
if (!result.success) {
|
|
271
406
|
hookErrors.push({
|
|
272
407
|
phase: hookStep.phase,
|
|
@@ -286,13 +421,13 @@ export class FlowRunner {
|
|
|
286
421
|
return false;
|
|
287
422
|
}
|
|
288
423
|
}
|
|
289
|
-
async executeTaskStepWithRetry(step, flowParams, completedSteps, errorCtx) {
|
|
424
|
+
async executeTaskStepWithRetry(step, flowParams, completedSteps, parentOptions, errorCtx) {
|
|
290
425
|
const maxAttempts = Math.max(1, 1 + (step.retries ?? 0));
|
|
291
426
|
const delayMs = step.retryDelay ?? 0;
|
|
292
427
|
const retryOn = step.retryOn;
|
|
293
428
|
let lastResult = { success: false, error: new Error('no attempts executed') };
|
|
294
429
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
295
|
-
lastResult = await this.executeTaskStep(step, flowParams, completedSteps, errorCtx);
|
|
430
|
+
lastResult = await this.executeTaskStep(step, flowParams, completedSteps, parentOptions, errorCtx);
|
|
296
431
|
if (lastResult.success)
|
|
297
432
|
return { result: lastResult, attempts: attempt };
|
|
298
433
|
const errMsg = lastResult.error?.message ?? '';
|
|
@@ -307,9 +442,15 @@ export class FlowRunner {
|
|
|
307
442
|
}
|
|
308
443
|
return { result: lastResult, attempts: maxAttempts };
|
|
309
444
|
}
|
|
310
|
-
async executeTaskStep(step, flowParams, completedSteps, errorCtx) {
|
|
445
|
+
async executeTaskStep(step, flowParams, completedSteps, parentOptions, errorCtx) {
|
|
311
446
|
const taskDef = this.resolveTaskDefinition(step.name);
|
|
312
|
-
|
|
447
|
+
// Precedence (low → high): task default → enclosing-flow override → step inline → runtime params.
|
|
448
|
+
const rawOptions = {
|
|
449
|
+
...taskDef.options,
|
|
450
|
+
...(parentOptions[step.name] ?? {}),
|
|
451
|
+
...step.options,
|
|
452
|
+
...flowParams,
|
|
453
|
+
};
|
|
313
454
|
const refCtx = {
|
|
314
455
|
steps: completedSteps,
|
|
315
456
|
error: errorCtx
|
|
@@ -332,8 +473,7 @@ export class FlowRunner {
|
|
|
332
473
|
};
|
|
333
474
|
}
|
|
334
475
|
this.logger.info({ step: step.stepNumber, task: step.name, type: step.type }, `Executing step ${step.stepNumber}: ${step.name}`);
|
|
335
|
-
|
|
336
|
-
return task.run();
|
|
476
|
+
return this.executeTask(taskDef.class_path, mergedOptions);
|
|
337
477
|
}
|
|
338
478
|
async performRollback(records) {
|
|
339
479
|
const result = { attempted: 0, succeeded: 0, errors: [] };
|
|
@@ -342,11 +482,7 @@ export class FlowRunner {
|
|
|
342
482
|
result.attempted++;
|
|
343
483
|
try {
|
|
344
484
|
const taskDef = this.resolveTaskDefinition(rec.taskName);
|
|
345
|
-
const
|
|
346
|
-
...taskDef.options,
|
|
347
|
-
...rec.payload,
|
|
348
|
-
});
|
|
349
|
-
const r = await task.run();
|
|
485
|
+
const r = await this.executeTask(taskDef.class_path, { ...taskDef.options, ...rec.payload });
|
|
350
486
|
if (r.success) {
|
|
351
487
|
result.succeeded++;
|
|
352
488
|
}
|
|
@@ -374,6 +510,17 @@ export class FlowRunner {
|
|
|
374
510
|
return { class_path: taskName, options: {} };
|
|
375
511
|
}
|
|
376
512
|
}
|
|
513
|
+
/** Truthiness of a resolved `when:` value, with string special-cases. */
|
|
514
|
+
function truthy(value) {
|
|
515
|
+
if (typeof value === 'boolean')
|
|
516
|
+
return value;
|
|
517
|
+
if (value == null)
|
|
518
|
+
return false;
|
|
519
|
+
if (typeof value === 'number')
|
|
520
|
+
return value !== 0;
|
|
521
|
+
const s = String(value).trim().toLowerCase();
|
|
522
|
+
return !(s === '' || s === 'false' || s === '0' || s === 'null' || s === 'undefined');
|
|
523
|
+
}
|
|
377
524
|
function sleep(ms) {
|
|
378
525
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
379
526
|
}
|