@marktoflow/core 2.0.2 → 2.0.4
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 +69 -6
- package/dist/built-in-operations.d.ts +2 -136
- package/dist/built-in-operations.d.ts.map +1 -1
- package/dist/built-in-operations.js +7 -743
- package/dist/built-in-operations.js.map +1 -1
- package/dist/engine/conditions.d.ts +29 -0
- package/dist/engine/conditions.d.ts.map +1 -0
- package/dist/engine/conditions.js +109 -0
- package/dist/engine/conditions.js.map +1 -0
- package/dist/engine/control-flow.d.ts +35 -0
- package/dist/engine/control-flow.d.ts.map +1 -0
- package/dist/engine/control-flow.js +653 -0
- package/dist/engine/control-flow.js.map +1 -0
- package/dist/engine/index.d.ts +12 -0
- package/dist/engine/index.d.ts.map +1 -0
- package/dist/engine/index.js +11 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/engine/retry.d.ts +35 -0
- package/dist/engine/retry.d.ts.map +1 -0
- package/dist/engine/retry.js +86 -0
- package/dist/engine/retry.js.map +1 -0
- package/dist/engine/subworkflow.d.ts +31 -0
- package/dist/engine/subworkflow.d.ts.map +1 -0
- package/dist/engine/subworkflow.js +240 -0
- package/dist/engine/subworkflow.js.map +1 -0
- package/dist/engine/types.d.ts +55 -0
- package/dist/engine/types.d.ts.map +1 -0
- package/dist/engine/types.js +5 -0
- package/dist/{secrets → engine}/types.js.map +1 -1
- package/dist/engine/variable-resolution.d.ts +29 -0
- package/dist/engine/variable-resolution.d.ts.map +1 -0
- package/dist/engine/variable-resolution.js +130 -0
- package/dist/engine/variable-resolution.js.map +1 -0
- package/dist/engine.d.ts +17 -211
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +84 -1351
- package/dist/engine.js.map +1 -1
- package/dist/file-operations.js +1 -1
- package/dist/file-operations.js.map +1 -1
- package/dist/filters/array.d.ts +9 -0
- package/dist/filters/array.d.ts.map +1 -0
- package/dist/filters/array.js +41 -0
- package/dist/filters/array.js.map +1 -0
- package/dist/filters/date.d.ts +9 -0
- package/dist/filters/date.d.ts.map +1 -0
- package/dist/filters/date.js +51 -0
- package/dist/filters/date.js.map +1 -0
- package/dist/filters/index.d.ts +13 -0
- package/dist/filters/index.d.ts.map +1 -0
- package/dist/filters/index.js +13 -0
- package/dist/filters/index.js.map +1 -0
- package/dist/filters/json.d.ts +6 -0
- package/dist/filters/json.d.ts.map +1 -0
- package/dist/filters/json.js +15 -0
- package/dist/filters/json.js.map +1 -0
- package/dist/filters/logic.d.ts +8 -0
- package/dist/filters/logic.d.ts.map +1 -0
- package/dist/filters/logic.js +28 -0
- package/dist/filters/logic.js.map +1 -0
- package/dist/filters/math.d.ts +13 -0
- package/dist/filters/math.d.ts.map +1 -0
- package/dist/filters/math.js +39 -0
- package/dist/filters/math.js.map +1 -0
- package/dist/filters/object.d.ts +11 -0
- package/dist/filters/object.d.ts.map +1 -0
- package/dist/filters/object.js +64 -0
- package/dist/filters/object.js.map +1 -0
- package/dist/filters/regex.d.ts +7 -0
- package/dist/filters/regex.d.ts.map +1 -0
- package/dist/filters/regex.js +38 -0
- package/dist/filters/regex.js.map +1 -0
- package/dist/filters/string.d.ts +11 -0
- package/dist/filters/string.d.ts.map +1 -0
- package/dist/filters/string.js +35 -0
- package/dist/filters/string.js.map +1 -0
- package/dist/filters/type-checks.d.ts +10 -0
- package/dist/filters/type-checks.d.ts.map +1 -0
- package/dist/filters/type-checks.js +30 -0
- package/dist/filters/type-checks.js.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/nunjucks-filters.d.ts +2 -261
- package/dist/nunjucks-filters.d.ts.map +1 -1
- package/dist/nunjucks-filters.js +24 -582
- package/dist/nunjucks-filters.js.map +1 -1
- package/dist/operations/compress.d.ts +6 -0
- package/dist/operations/compress.d.ts.map +1 -0
- package/dist/operations/compress.js +36 -0
- package/dist/operations/compress.js.map +1 -0
- package/dist/operations/crypto.d.ts +5 -0
- package/dist/operations/crypto.d.ts.map +1 -0
- package/dist/operations/crypto.js +61 -0
- package/dist/operations/crypto.js.map +1 -0
- package/dist/operations/data-ops.d.ts +10 -0
- package/dist/operations/data-ops.d.ts.map +1 -0
- package/dist/operations/data-ops.js +124 -0
- package/dist/operations/data-ops.js.map +1 -0
- package/dist/operations/datetime.d.ts +5 -0
- package/dist/operations/datetime.d.ts.map +1 -0
- package/dist/operations/datetime.js +86 -0
- package/dist/operations/datetime.js.map +1 -0
- package/dist/operations/extract.d.ts +23 -0
- package/dist/operations/extract.d.ts.map +1 -0
- package/dist/operations/extract.js +31 -0
- package/dist/operations/extract.js.map +1 -0
- package/dist/operations/format.d.ts +14 -0
- package/dist/operations/format.d.ts.map +1 -0
- package/dist/operations/format.js +84 -0
- package/dist/operations/format.js.map +1 -0
- package/dist/operations/index.d.ts +13 -0
- package/dist/operations/index.d.ts.map +1 -0
- package/dist/operations/index.js +13 -0
- package/dist/operations/index.js.map +1 -0
- package/dist/operations/parse.d.ts +5 -0
- package/dist/operations/parse.d.ts.map +1 -0
- package/dist/operations/parse.js +59 -0
- package/dist/operations/parse.js.map +1 -0
- package/dist/operations/set.d.ts +21 -0
- package/dist/operations/set.d.ts.map +1 -0
- package/dist/operations/set.js +25 -0
- package/dist/operations/set.js.map +1 -0
- package/dist/operations/transform.d.ts +15 -0
- package/dist/operations/transform.d.ts.map +1 -0
- package/dist/operations/transform.js +110 -0
- package/dist/operations/transform.js.map +1 -0
- package/dist/parallel.d.ts +114 -0
- package/dist/parallel.d.ts.map +1 -0
- package/dist/parallel.js +325 -0
- package/dist/parallel.js.map +1 -0
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +2 -0
- package/dist/parser.js.map +1 -1
- package/dist/routing.js +2 -2
- package/dist/routing.js.map +1 -1
- package/dist/sdk-registry.d.ts.map +1 -1
- package/dist/sdk-registry.js +9 -3
- package/dist/sdk-registry.js.map +1 -1
- package/dist/utils/duration.d.ts +23 -0
- package/dist/utils/duration.d.ts.map +1 -0
- package/dist/utils/duration.js +41 -0
- package/dist/utils/duration.js.map +1 -0
- package/dist/utils/errors.d.ts +20 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +37 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/workflow-templates.d.ts +80 -0
- package/dist/workflow-templates.d.ts.map +1 -0
- package/dist/workflow-templates.js +248 -0
- package/dist/workflow-templates.js.map +1 -0
- package/package.json +30 -5
- package/dist/secrets/index.d.ts +0 -12
- package/dist/secrets/index.d.ts.map +0 -1
- package/dist/secrets/index.js +0 -11
- package/dist/secrets/index.js.map +0 -1
- package/dist/secrets/providers/aws.d.ts +0 -32
- package/dist/secrets/providers/aws.d.ts.map +0 -1
- package/dist/secrets/providers/aws.js +0 -118
- package/dist/secrets/providers/aws.js.map +0 -1
- package/dist/secrets/providers/azure.d.ts +0 -40
- package/dist/secrets/providers/azure.d.ts.map +0 -1
- package/dist/secrets/providers/azure.js +0 -170
- package/dist/secrets/providers/azure.js.map +0 -1
- package/dist/secrets/providers/env.d.ts +0 -26
- package/dist/secrets/providers/env.d.ts.map +0 -1
- package/dist/secrets/providers/env.js +0 -59
- package/dist/secrets/providers/env.js.map +0 -1
- package/dist/secrets/providers/vault.d.ts +0 -39
- package/dist/secrets/providers/vault.d.ts.map +0 -1
- package/dist/secrets/providers/vault.js +0 -180
- package/dist/secrets/providers/vault.js.map +0 -1
- package/dist/secrets/secret-manager.d.ts +0 -72
- package/dist/secrets/secret-manager.d.ts.map +0 -1
- package/dist/secrets/secret-manager.js +0 -226
- package/dist/secrets/secret-manager.js.map +0 -1
- package/dist/secrets/types.d.ts +0 -105
- package/dist/secrets/types.d.ts.map +0 -1
- package/dist/secrets/types.js +0 -8
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Control flow step execution for marktoflow workflow engine.
|
|
3
|
+
*
|
|
4
|
+
* Handles all control flow step types: if, switch, for-each, while,
|
|
5
|
+
* map, filter, reduce, parallel, try, script, wait, and merge.
|
|
6
|
+
*/
|
|
7
|
+
import { StepStatus, createStepResult, } from '../models.js';
|
|
8
|
+
import { resolveTemplates } from './variable-resolution.js';
|
|
9
|
+
import { evaluateCondition } from './conditions.js';
|
|
10
|
+
import { parseDuration } from '../utils/duration.js';
|
|
11
|
+
import { errorToString } from '../utils/errors.js';
|
|
12
|
+
import { executeScriptAsync } from '../script-executor.js';
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// If Step
|
|
15
|
+
// ============================================================================
|
|
16
|
+
export async function executeIfStep(step, context, sdkRegistry, stepExecutor, dispatch) {
|
|
17
|
+
const startedAt = new Date();
|
|
18
|
+
try {
|
|
19
|
+
// Evaluate condition
|
|
20
|
+
const conditionResult = evaluateCondition(step.condition, context);
|
|
21
|
+
// Determine which branch to execute
|
|
22
|
+
const branchSteps = conditionResult
|
|
23
|
+
? step.then || step.steps // 'steps' is alias for 'then'
|
|
24
|
+
: step.else;
|
|
25
|
+
if (!branchSteps || branchSteps.length === 0) {
|
|
26
|
+
return createStepResult(step.id, StepStatus.SKIPPED, null, startedAt);
|
|
27
|
+
}
|
|
28
|
+
// Execute the branch steps
|
|
29
|
+
const branchResults = [];
|
|
30
|
+
for (const branchStep of branchSteps) {
|
|
31
|
+
const result = await dispatch(branchStep, context, sdkRegistry, stepExecutor);
|
|
32
|
+
if (result.status === StepStatus.COMPLETED && branchStep.outputVariable) {
|
|
33
|
+
context.variables[branchStep.outputVariable] = result.output;
|
|
34
|
+
branchResults.push(result.output);
|
|
35
|
+
}
|
|
36
|
+
if (result.status === StepStatus.FAILED) {
|
|
37
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, result.error);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return createStepResult(step.id, StepStatus.COMPLETED, branchResults, startedAt);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, error instanceof Error ? error.message : String(error));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// Switch Step
|
|
48
|
+
// ============================================================================
|
|
49
|
+
export async function executeSwitchStep(step, context, sdkRegistry, stepExecutor, dispatch) {
|
|
50
|
+
const startedAt = new Date();
|
|
51
|
+
try {
|
|
52
|
+
// Resolve the switch expression
|
|
53
|
+
const expressionValue = String(resolveTemplates(step.expression, context));
|
|
54
|
+
// Find matching case
|
|
55
|
+
const caseSteps = step.cases[expressionValue] || step.default;
|
|
56
|
+
if (!caseSteps || caseSteps.length === 0) {
|
|
57
|
+
return createStepResult(step.id, StepStatus.SKIPPED, null, startedAt);
|
|
58
|
+
}
|
|
59
|
+
// Execute case steps
|
|
60
|
+
const caseResults = [];
|
|
61
|
+
for (const caseStep of caseSteps) {
|
|
62
|
+
const result = await dispatch(caseStep, context, sdkRegistry, stepExecutor);
|
|
63
|
+
if (result.status === StepStatus.COMPLETED && caseStep.outputVariable) {
|
|
64
|
+
context.variables[caseStep.outputVariable] = result.output;
|
|
65
|
+
caseResults.push(result.output);
|
|
66
|
+
}
|
|
67
|
+
if (result.status === StepStatus.FAILED) {
|
|
68
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, result.error);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return createStepResult(step.id, StepStatus.COMPLETED, caseResults, startedAt);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, error instanceof Error ? error.message : String(error));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// ============================================================================
|
|
78
|
+
// For-Each Step
|
|
79
|
+
// ============================================================================
|
|
80
|
+
export async function executeForEachStep(step, context, sdkRegistry, stepExecutor, dispatch) {
|
|
81
|
+
const startedAt = new Date();
|
|
82
|
+
const cleanupLoopVars = () => {
|
|
83
|
+
delete context.variables[step.itemVariable];
|
|
84
|
+
delete context.variables['loop'];
|
|
85
|
+
delete context.variables['batch'];
|
|
86
|
+
if (step.indexVariable)
|
|
87
|
+
delete context.variables[step.indexVariable];
|
|
88
|
+
};
|
|
89
|
+
try {
|
|
90
|
+
// Resolve items array
|
|
91
|
+
const items = resolveTemplates(step.items, context);
|
|
92
|
+
if (!Array.isArray(items)) {
|
|
93
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, 'Items must be an array');
|
|
94
|
+
}
|
|
95
|
+
if (items.length === 0) {
|
|
96
|
+
return createStepResult(step.id, StepStatus.SKIPPED, [], startedAt);
|
|
97
|
+
}
|
|
98
|
+
const batchSize = step.batchSize;
|
|
99
|
+
const pauseBetweenBatches = step.pauseBetweenBatches;
|
|
100
|
+
// If batch mode, process items in batches
|
|
101
|
+
if (batchSize && batchSize > 0) {
|
|
102
|
+
return await executeForEachBatched(step, items, batchSize, pauseBetweenBatches ?? 0, context, sdkRegistry, stepExecutor, dispatch, startedAt);
|
|
103
|
+
}
|
|
104
|
+
// Standard item-by-item execution
|
|
105
|
+
const results = [];
|
|
106
|
+
for (let i = 0; i < items.length; i++) {
|
|
107
|
+
context.variables[step.itemVariable] = items[i];
|
|
108
|
+
context.variables['loop'] = {
|
|
109
|
+
index: i,
|
|
110
|
+
first: i === 0,
|
|
111
|
+
last: i === items.length - 1,
|
|
112
|
+
length: items.length,
|
|
113
|
+
};
|
|
114
|
+
if (step.indexVariable) {
|
|
115
|
+
context.variables[step.indexVariable] = i;
|
|
116
|
+
}
|
|
117
|
+
// Execute iteration steps
|
|
118
|
+
for (const iterStep of step.steps) {
|
|
119
|
+
const result = await dispatch(iterStep, context, sdkRegistry, stepExecutor);
|
|
120
|
+
if (result.status === StepStatus.COMPLETED && iterStep.outputVariable) {
|
|
121
|
+
context.variables[iterStep.outputVariable] = result.output;
|
|
122
|
+
}
|
|
123
|
+
if (result.status === StepStatus.FAILED) {
|
|
124
|
+
const errorAction = step.errorHandling?.action ?? 'stop';
|
|
125
|
+
if (errorAction === 'stop') {
|
|
126
|
+
cleanupLoopVars();
|
|
127
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, result.error);
|
|
128
|
+
}
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
results.push(context.variables[step.itemVariable]);
|
|
133
|
+
}
|
|
134
|
+
cleanupLoopVars();
|
|
135
|
+
return createStepResult(step.id, StepStatus.COMPLETED, results, startedAt);
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
cleanupLoopVars();
|
|
139
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, error instanceof Error ? error.message : String(error));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Execute for-each in batch mode.
|
|
144
|
+
* Items are split into batches; {{ batch }} contains the current batch array.
|
|
145
|
+
*/
|
|
146
|
+
async function executeForEachBatched(step, items, batchSize, pauseBetweenBatches, context, sdkRegistry, stepExecutor, dispatch, startedAt) {
|
|
147
|
+
const results = [];
|
|
148
|
+
const totalBatches = Math.ceil(items.length / batchSize);
|
|
149
|
+
for (let batchIndex = 0; batchIndex < totalBatches; batchIndex++) {
|
|
150
|
+
const batchStart = batchIndex * batchSize;
|
|
151
|
+
const batchItems = items.slice(batchStart, batchStart + batchSize);
|
|
152
|
+
// Pause between batches (not before first batch)
|
|
153
|
+
if (batchIndex > 0 && pauseBetweenBatches > 0) {
|
|
154
|
+
await new Promise((resolve) => setTimeout(resolve, pauseBetweenBatches));
|
|
155
|
+
}
|
|
156
|
+
// Expose batch-level variables
|
|
157
|
+
context.variables['batch'] = batchItems;
|
|
158
|
+
context.variables['loop'] = {
|
|
159
|
+
index: batchIndex,
|
|
160
|
+
first: batchIndex === 0,
|
|
161
|
+
last: batchIndex === totalBatches - 1,
|
|
162
|
+
length: totalBatches,
|
|
163
|
+
batchSize,
|
|
164
|
+
batchStart,
|
|
165
|
+
totalItems: items.length,
|
|
166
|
+
};
|
|
167
|
+
// Process each item in the batch
|
|
168
|
+
for (let i = 0; i < batchItems.length; i++) {
|
|
169
|
+
const globalIndex = batchStart + i;
|
|
170
|
+
context.variables[step.itemVariable] = batchItems[i];
|
|
171
|
+
if (step.indexVariable) {
|
|
172
|
+
context.variables[step.indexVariable] = globalIndex;
|
|
173
|
+
}
|
|
174
|
+
for (const iterStep of step.steps) {
|
|
175
|
+
const result = await dispatch(iterStep, context, sdkRegistry, stepExecutor);
|
|
176
|
+
if (result.status === StepStatus.COMPLETED && iterStep.outputVariable) {
|
|
177
|
+
context.variables[iterStep.outputVariable] = result.output;
|
|
178
|
+
}
|
|
179
|
+
if (result.status === StepStatus.FAILED) {
|
|
180
|
+
const errorAction = step.errorHandling?.action ?? 'stop';
|
|
181
|
+
if (errorAction === 'stop') {
|
|
182
|
+
delete context.variables[step.itemVariable];
|
|
183
|
+
delete context.variables['loop'];
|
|
184
|
+
delete context.variables['batch'];
|
|
185
|
+
if (step.indexVariable)
|
|
186
|
+
delete context.variables[step.indexVariable];
|
|
187
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, result.error);
|
|
188
|
+
}
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
results.push(context.variables[step.itemVariable]);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
delete context.variables[step.itemVariable];
|
|
196
|
+
delete context.variables['loop'];
|
|
197
|
+
delete context.variables['batch'];
|
|
198
|
+
if (step.indexVariable)
|
|
199
|
+
delete context.variables[step.indexVariable];
|
|
200
|
+
return createStepResult(step.id, StepStatus.COMPLETED, results, startedAt);
|
|
201
|
+
}
|
|
202
|
+
// ============================================================================
|
|
203
|
+
// While Step
|
|
204
|
+
// ============================================================================
|
|
205
|
+
export async function executeWhileStep(step, context, sdkRegistry, stepExecutor, dispatch) {
|
|
206
|
+
const startedAt = new Date();
|
|
207
|
+
let iterations = 0;
|
|
208
|
+
try {
|
|
209
|
+
while (evaluateCondition(step.condition, context)) {
|
|
210
|
+
if (iterations >= step.maxIterations) {
|
|
211
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, `Max iterations (${step.maxIterations}) exceeded`);
|
|
212
|
+
}
|
|
213
|
+
// Execute iteration steps
|
|
214
|
+
for (const iterStep of step.steps) {
|
|
215
|
+
const result = await dispatch(iterStep, context, sdkRegistry, stepExecutor);
|
|
216
|
+
if (result.status === StepStatus.COMPLETED && iterStep.outputVariable) {
|
|
217
|
+
context.variables[iterStep.outputVariable] = result.output;
|
|
218
|
+
}
|
|
219
|
+
if (result.status === StepStatus.FAILED) {
|
|
220
|
+
const errorAction = step.errorHandling?.action ?? 'stop';
|
|
221
|
+
if (errorAction === 'stop') {
|
|
222
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, result.error);
|
|
223
|
+
}
|
|
224
|
+
// 'continue' - skip to next iteration
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
iterations++;
|
|
229
|
+
}
|
|
230
|
+
return createStepResult(step.id, StepStatus.COMPLETED, { iterations }, startedAt);
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, error instanceof Error ? error.message : String(error));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// ============================================================================
|
|
237
|
+
// Map Step
|
|
238
|
+
// ============================================================================
|
|
239
|
+
export async function executeMapStep(step, context) {
|
|
240
|
+
const startedAt = new Date();
|
|
241
|
+
try {
|
|
242
|
+
// Resolve items array
|
|
243
|
+
const items = resolveTemplates(step.items, context);
|
|
244
|
+
if (!Array.isArray(items)) {
|
|
245
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, 'Items must be an array');
|
|
246
|
+
}
|
|
247
|
+
// Map each item using the expression
|
|
248
|
+
const mapped = items.map((item) => {
|
|
249
|
+
context.variables[step.itemVariable] = item;
|
|
250
|
+
const result = resolveTemplates(step.expression, context);
|
|
251
|
+
delete context.variables[step.itemVariable];
|
|
252
|
+
return result;
|
|
253
|
+
});
|
|
254
|
+
return createStepResult(step.id, StepStatus.COMPLETED, mapped, startedAt);
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
delete context.variables[step.itemVariable];
|
|
258
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, error instanceof Error ? error.message : String(error));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// ============================================================================
|
|
262
|
+
// Filter Step
|
|
263
|
+
// ============================================================================
|
|
264
|
+
export async function executeFilterStep(step, context) {
|
|
265
|
+
const startedAt = new Date();
|
|
266
|
+
try {
|
|
267
|
+
// Resolve items array
|
|
268
|
+
const items = resolveTemplates(step.items, context);
|
|
269
|
+
if (!Array.isArray(items)) {
|
|
270
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, 'Items must be an array');
|
|
271
|
+
}
|
|
272
|
+
// Filter items using the condition
|
|
273
|
+
const filtered = items.filter((item) => {
|
|
274
|
+
context.variables[step.itemVariable] = item;
|
|
275
|
+
const result = evaluateCondition(step.condition, context);
|
|
276
|
+
delete context.variables[step.itemVariable];
|
|
277
|
+
return result;
|
|
278
|
+
});
|
|
279
|
+
return createStepResult(step.id, StepStatus.COMPLETED, filtered, startedAt);
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
delete context.variables[step.itemVariable];
|
|
283
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, error instanceof Error ? error.message : String(error));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// ============================================================================
|
|
287
|
+
// Reduce Step
|
|
288
|
+
// ============================================================================
|
|
289
|
+
export async function executeReduceStep(step, context) {
|
|
290
|
+
const startedAt = new Date();
|
|
291
|
+
try {
|
|
292
|
+
// Resolve items array
|
|
293
|
+
const items = resolveTemplates(step.items, context);
|
|
294
|
+
if (!Array.isArray(items)) {
|
|
295
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, 'Items must be an array');
|
|
296
|
+
}
|
|
297
|
+
// Reduce items using the expression
|
|
298
|
+
let accumulator = step.initialValue ?? null;
|
|
299
|
+
for (const item of items) {
|
|
300
|
+
context.variables[step.itemVariable] = item;
|
|
301
|
+
context.variables[step.accumulatorVariable] = accumulator;
|
|
302
|
+
accumulator = resolveTemplates(step.expression, context);
|
|
303
|
+
delete context.variables[step.itemVariable];
|
|
304
|
+
delete context.variables[step.accumulatorVariable];
|
|
305
|
+
}
|
|
306
|
+
return createStepResult(step.id, StepStatus.COMPLETED, accumulator, startedAt);
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
delete context.variables[step.itemVariable];
|
|
310
|
+
delete context.variables[step.accumulatorVariable];
|
|
311
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, error instanceof Error ? error.message : String(error));
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
// ============================================================================
|
|
315
|
+
// Parallel Step
|
|
316
|
+
// ============================================================================
|
|
317
|
+
export async function executeParallelStep(step, context, sdkRegistry, stepExecutor, dispatch, cloneContext, mergeContexts, executeConcurrentlyWithLimit) {
|
|
318
|
+
const startedAt = new Date();
|
|
319
|
+
try {
|
|
320
|
+
// Execute branches in parallel
|
|
321
|
+
const branchPromises = step.branches.map(async (branch) => {
|
|
322
|
+
// Clone context for isolation
|
|
323
|
+
const branchContext = cloneContext(context);
|
|
324
|
+
// Execute branch steps
|
|
325
|
+
const branchResults = [];
|
|
326
|
+
for (const branchStep of branch.steps) {
|
|
327
|
+
const result = await dispatch(branchStep, branchContext, sdkRegistry, stepExecutor);
|
|
328
|
+
if (result.status === StepStatus.COMPLETED && branchStep.outputVariable) {
|
|
329
|
+
branchContext.variables[branchStep.outputVariable] = result.output;
|
|
330
|
+
branchResults.push(result.output);
|
|
331
|
+
}
|
|
332
|
+
if (result.status === StepStatus.FAILED) {
|
|
333
|
+
throw new Error(`Branch ${branch.id} failed: ${errorToString(result.error)}`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return { branchId: branch.id, context: branchContext, results: branchResults };
|
|
337
|
+
});
|
|
338
|
+
// Wait for all branches (or limited concurrency)
|
|
339
|
+
const branchResults = step.maxConcurrent
|
|
340
|
+
? await executeConcurrentlyWithLimit(branchPromises, step.maxConcurrent)
|
|
341
|
+
: await Promise.all(branchPromises);
|
|
342
|
+
// Merge branch contexts back into main context
|
|
343
|
+
for (const { branchId, context: branchContext } of branchResults) {
|
|
344
|
+
mergeContexts(context, branchContext, branchId);
|
|
345
|
+
}
|
|
346
|
+
const outputs = branchResults.map((br) => br.results);
|
|
347
|
+
return createStepResult(step.id, StepStatus.COMPLETED, outputs, startedAt);
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
if (step.onError === 'continue') {
|
|
351
|
+
return createStepResult(step.id, StepStatus.COMPLETED, null, startedAt);
|
|
352
|
+
}
|
|
353
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, error instanceof Error ? error.message : String(error));
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
// ============================================================================
|
|
357
|
+
// Try Step
|
|
358
|
+
// ============================================================================
|
|
359
|
+
export async function executeTryStep(step, context, sdkRegistry, stepExecutor, dispatch) {
|
|
360
|
+
const startedAt = new Date();
|
|
361
|
+
let tryError;
|
|
362
|
+
try {
|
|
363
|
+
// Execute try block
|
|
364
|
+
for (const tryStep of step.try) {
|
|
365
|
+
const result = await dispatch(tryStep, context, sdkRegistry, stepExecutor);
|
|
366
|
+
if (result.status === StepStatus.COMPLETED && tryStep.outputVariable) {
|
|
367
|
+
context.variables[tryStep.outputVariable] = result.output;
|
|
368
|
+
}
|
|
369
|
+
if (result.status === StepStatus.FAILED) {
|
|
370
|
+
tryError = new Error(result.error ? errorToString(result.error) : 'Step failed');
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
// If error occurred and catch block exists, execute catch
|
|
375
|
+
let catchError;
|
|
376
|
+
if (tryError && step.catch) {
|
|
377
|
+
// Inject error object into context
|
|
378
|
+
context.variables['error'] = {
|
|
379
|
+
message: tryError.message,
|
|
380
|
+
step: tryError,
|
|
381
|
+
};
|
|
382
|
+
for (const catchStep of step.catch) {
|
|
383
|
+
const result = await dispatch(catchStep, context, sdkRegistry, stepExecutor);
|
|
384
|
+
if (result.status === StepStatus.COMPLETED && catchStep.outputVariable) {
|
|
385
|
+
context.variables[catchStep.outputVariable] = result.output;
|
|
386
|
+
}
|
|
387
|
+
if (result.status === StepStatus.FAILED) {
|
|
388
|
+
catchError = new Error(result.error ? errorToString(result.error) : 'Catch block failed');
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
delete context.variables['error'];
|
|
393
|
+
}
|
|
394
|
+
// Execute finally block (always runs)
|
|
395
|
+
if (step.finally) {
|
|
396
|
+
for (const finallyStep of step.finally) {
|
|
397
|
+
const result = await dispatch(finallyStep, context, sdkRegistry, stepExecutor);
|
|
398
|
+
if (result.status === StepStatus.COMPLETED && finallyStep.outputVariable) {
|
|
399
|
+
context.variables[finallyStep.outputVariable] = result.output;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
// Return success if catch handled the error, or error if not
|
|
404
|
+
if (tryError && !step.catch) {
|
|
405
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, tryError.message);
|
|
406
|
+
}
|
|
407
|
+
if (catchError) {
|
|
408
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, catchError.message);
|
|
409
|
+
}
|
|
410
|
+
return createStepResult(step.id, StepStatus.COMPLETED, null, startedAt);
|
|
411
|
+
}
|
|
412
|
+
catch (error) {
|
|
413
|
+
// Execute finally even on unexpected error
|
|
414
|
+
if (step.finally) {
|
|
415
|
+
try {
|
|
416
|
+
for (const finallyStep of step.finally) {
|
|
417
|
+
const result = await dispatch(finallyStep, context, sdkRegistry, stepExecutor);
|
|
418
|
+
if (result.status === StepStatus.COMPLETED && finallyStep.outputVariable) {
|
|
419
|
+
context.variables[finallyStep.outputVariable] = result.output;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
catch {
|
|
424
|
+
// Ignore finally errors
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, error instanceof Error ? error.message : String(error));
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// ============================================================================
|
|
431
|
+
// Script Step
|
|
432
|
+
// ============================================================================
|
|
433
|
+
export async function executeScriptStep(step, context) {
|
|
434
|
+
const startedAt = new Date();
|
|
435
|
+
try {
|
|
436
|
+
// Resolve any templates in the code
|
|
437
|
+
const resolvedInputs = resolveTemplates(step.inputs, context);
|
|
438
|
+
// Execute the script with the workflow context
|
|
439
|
+
const result = await executeScriptAsync(resolvedInputs.code, {
|
|
440
|
+
variables: context.variables,
|
|
441
|
+
inputs: context.inputs,
|
|
442
|
+
steps: context.stepMetadata,
|
|
443
|
+
}, {
|
|
444
|
+
timeout: resolvedInputs.timeout ?? 5000,
|
|
445
|
+
});
|
|
446
|
+
if (!result.success) {
|
|
447
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, result.error ?? 'Script execution failed');
|
|
448
|
+
}
|
|
449
|
+
return createStepResult(step.id, StepStatus.COMPLETED, result.value, startedAt);
|
|
450
|
+
}
|
|
451
|
+
catch (error) {
|
|
452
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, error instanceof Error ? error.message : String(error));
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
// ============================================================================
|
|
456
|
+
// Wait Step
|
|
457
|
+
// ============================================================================
|
|
458
|
+
export async function executeWaitStep(step, context, stateStore) {
|
|
459
|
+
const startedAt = new Date();
|
|
460
|
+
try {
|
|
461
|
+
switch (step.mode) {
|
|
462
|
+
case 'duration': {
|
|
463
|
+
if (!step.duration) {
|
|
464
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, 'Wait step with mode=duration requires a duration');
|
|
465
|
+
}
|
|
466
|
+
const resolvedDuration = resolveTemplates(step.duration, context);
|
|
467
|
+
const ms = parseDuration(resolvedDuration);
|
|
468
|
+
// For short durations (under 5 minutes), do in-process wait
|
|
469
|
+
if (ms <= 300000) {
|
|
470
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
471
|
+
return createStepResult(step.id, StepStatus.COMPLETED, { waited: ms }, startedAt);
|
|
472
|
+
}
|
|
473
|
+
// For longer durations, checkpoint and schedule resume
|
|
474
|
+
if (stateStore) {
|
|
475
|
+
stateStore.saveCheckpoint({
|
|
476
|
+
runId: context.runId,
|
|
477
|
+
stepIndex: context.currentStepIndex,
|
|
478
|
+
stepName: step.id,
|
|
479
|
+
status: StepStatus.COMPLETED,
|
|
480
|
+
startedAt: startedAt,
|
|
481
|
+
completedAt: new Date(),
|
|
482
|
+
inputs: { mode: 'duration', resumeAt: new Date(Date.now() + ms).toISOString() },
|
|
483
|
+
outputs: { waiting: true },
|
|
484
|
+
error: null,
|
|
485
|
+
retryCount: 0,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
const resumeAt = new Date(Date.now() + ms).toISOString();
|
|
489
|
+
return createStepResult(step.id, StepStatus.COMPLETED, {
|
|
490
|
+
waiting: true,
|
|
491
|
+
mode: 'duration',
|
|
492
|
+
resumeAt,
|
|
493
|
+
durationMs: ms,
|
|
494
|
+
}, startedAt);
|
|
495
|
+
}
|
|
496
|
+
case 'webhook': {
|
|
497
|
+
const resumeToken = crypto.randomUUID();
|
|
498
|
+
const webhookPath = step.webhookPath
|
|
499
|
+
? resolveTemplates(step.webhookPath, context)
|
|
500
|
+
: `/resume/${context.runId}/${step.id}/${resumeToken}`;
|
|
501
|
+
if (stateStore) {
|
|
502
|
+
stateStore.saveCheckpoint({
|
|
503
|
+
runId: context.runId,
|
|
504
|
+
stepIndex: context.currentStepIndex,
|
|
505
|
+
stepName: step.id,
|
|
506
|
+
status: StepStatus.COMPLETED,
|
|
507
|
+
startedAt: startedAt,
|
|
508
|
+
completedAt: new Date(),
|
|
509
|
+
inputs: { mode: 'webhook', resumeToken, webhookPath },
|
|
510
|
+
outputs: { waiting: true },
|
|
511
|
+
error: null,
|
|
512
|
+
retryCount: 0,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
return createStepResult(step.id, StepStatus.COMPLETED, {
|
|
516
|
+
waiting: true,
|
|
517
|
+
mode: 'webhook',
|
|
518
|
+
resumeToken,
|
|
519
|
+
webhookPath,
|
|
520
|
+
}, startedAt);
|
|
521
|
+
}
|
|
522
|
+
case 'form': {
|
|
523
|
+
if (!step.fields || Object.keys(step.fields).length === 0) {
|
|
524
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, 'Wait step with mode=form requires fields');
|
|
525
|
+
}
|
|
526
|
+
const resumeToken = crypto.randomUUID();
|
|
527
|
+
if (stateStore) {
|
|
528
|
+
stateStore.saveCheckpoint({
|
|
529
|
+
runId: context.runId,
|
|
530
|
+
stepIndex: context.currentStepIndex,
|
|
531
|
+
stepName: step.id,
|
|
532
|
+
status: StepStatus.COMPLETED,
|
|
533
|
+
startedAt: startedAt,
|
|
534
|
+
completedAt: new Date(),
|
|
535
|
+
inputs: { mode: 'form', resumeToken, fields: step.fields },
|
|
536
|
+
outputs: { waiting: true },
|
|
537
|
+
error: null,
|
|
538
|
+
retryCount: 0,
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
return createStepResult(step.id, StepStatus.COMPLETED, {
|
|
542
|
+
waiting: true,
|
|
543
|
+
mode: 'form',
|
|
544
|
+
resumeToken,
|
|
545
|
+
fields: step.fields,
|
|
546
|
+
formPath: `/form/${context.runId}/${step.id}/${resumeToken}`,
|
|
547
|
+
}, startedAt);
|
|
548
|
+
}
|
|
549
|
+
default:
|
|
550
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, `Unknown wait mode: ${step.mode}`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
catch (error) {
|
|
554
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, error instanceof Error ? error.message : String(error));
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
// ============================================================================
|
|
558
|
+
// Merge Step
|
|
559
|
+
// ============================================================================
|
|
560
|
+
export async function executeMergeStep(step, context) {
|
|
561
|
+
const startedAt = new Date();
|
|
562
|
+
try {
|
|
563
|
+
// Resolve all source expressions to arrays
|
|
564
|
+
const resolvedSources = [];
|
|
565
|
+
for (const source of step.sources) {
|
|
566
|
+
const resolved = resolveTemplates(source, context);
|
|
567
|
+
if (!Array.isArray(resolved)) {
|
|
568
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, `Merge source "${source}" did not resolve to an array`);
|
|
569
|
+
}
|
|
570
|
+
resolvedSources.push(resolved);
|
|
571
|
+
}
|
|
572
|
+
let result;
|
|
573
|
+
switch (step.mode) {
|
|
574
|
+
case 'append':
|
|
575
|
+
result = resolvedSources.flat();
|
|
576
|
+
break;
|
|
577
|
+
case 'match': {
|
|
578
|
+
if (!step.matchField) {
|
|
579
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, 'Merge mode "match" requires matchField');
|
|
580
|
+
}
|
|
581
|
+
const fieldSets = resolvedSources.map((source) => new Set(source.map((item) => getNestedValue(item, step.matchField))));
|
|
582
|
+
const commonKeys = fieldSets.reduce((acc, set) => new Set([...acc].filter((key) => set.has(key))));
|
|
583
|
+
result = resolvedSources[0].filter((item) => commonKeys.has(getNestedValue(item, step.matchField)));
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
case 'diff': {
|
|
587
|
+
if (!step.matchField) {
|
|
588
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, 'Merge mode "diff" requires matchField');
|
|
589
|
+
}
|
|
590
|
+
const otherKeys = new Set(resolvedSources.slice(1).flat().map((item) => getNestedValue(item, step.matchField)));
|
|
591
|
+
result = resolvedSources[0].filter((item) => !otherKeys.has(getNestedValue(item, step.matchField)));
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
case 'combine_by_field': {
|
|
595
|
+
if (!step.matchField) {
|
|
596
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, 'Merge mode "combine_by_field" requires matchField');
|
|
597
|
+
}
|
|
598
|
+
const grouped = new Map();
|
|
599
|
+
const onConflict = step.onConflict ?? 'keep_last';
|
|
600
|
+
for (const source of resolvedSources) {
|
|
601
|
+
for (const item of source) {
|
|
602
|
+
if (!item || typeof item !== 'object')
|
|
603
|
+
continue;
|
|
604
|
+
const key = getNestedValue(item, step.matchField);
|
|
605
|
+
const existing = grouped.get(key);
|
|
606
|
+
if (existing) {
|
|
607
|
+
if (onConflict === 'keep_first') {
|
|
608
|
+
for (const [k, v] of Object.entries(item)) {
|
|
609
|
+
if (!(k in existing))
|
|
610
|
+
existing[k] = v;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
else if (onConflict === 'keep_last') {
|
|
614
|
+
Object.assign(existing, item);
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
Object.assign(existing, item);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
grouped.set(key, { ...item });
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
result = Array.from(grouped.values());
|
|
626
|
+
break;
|
|
627
|
+
}
|
|
628
|
+
default:
|
|
629
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, `Unknown merge mode: ${step.mode}`);
|
|
630
|
+
}
|
|
631
|
+
return createStepResult(step.id, StepStatus.COMPLETED, result, startedAt);
|
|
632
|
+
}
|
|
633
|
+
catch (error) {
|
|
634
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, error instanceof Error ? error.message : String(error));
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Get a nested value from an object using dot notation.
|
|
639
|
+
* Used by merge step for field matching (replaces the old getField function).
|
|
640
|
+
*/
|
|
641
|
+
function getNestedValue(item, field) {
|
|
642
|
+
if (!item || typeof item !== 'object')
|
|
643
|
+
return undefined;
|
|
644
|
+
const parts = field.split('.');
|
|
645
|
+
let current = item;
|
|
646
|
+
for (const part of parts) {
|
|
647
|
+
if (!current || typeof current !== 'object')
|
|
648
|
+
return undefined;
|
|
649
|
+
current = current[part];
|
|
650
|
+
}
|
|
651
|
+
return current;
|
|
652
|
+
}
|
|
653
|
+
//# sourceMappingURL=control-flow.js.map
|