foliko 2.0.30 → 2.0.32

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.
@@ -1,74 +1,74 @@
1
- /**
2
- * choice stage executor
3
- * Conditional branching (if/else/switch)
4
- */
5
-
6
- const { evaluateInSandbox } = require('../../../../src/utils/sandbox');
7
-
8
- class ChoiceStage {
9
- constructor(config) {
10
- this.name = config.name;
11
- this.choice = config.choice || []; // Array of { when, do } or { case, do }
12
- this.default = config.default; // Default stage name
13
- this._log = require('../../../../src/common/logger').logger.child('ChoiceStage');
14
- }
15
-
16
- /**
17
- * Execute the choice stage
18
- * @param {FlowContext} context
19
- * @returns {Promise<string|null>} next stage name
20
- */
21
- async execute(context) {
22
- this._log.debug(`Evaluating choice: ${this.name}`);
23
-
24
- // Try each when/case condition
25
- for (const branch of this.choice) {
26
- const condition = branch.when || branch.case;
27
- if (!condition) continue;
28
-
29
- try {
30
- // Resolve condition with variable interpolation
31
- const resolvedCondition = context.resolveValue(condition);
32
-
33
- // Evaluate condition
34
- let matched = false;
35
- if (typeof resolvedCondition === 'boolean') {
36
- matched = resolvedCondition;
37
- } else if (typeof resolvedCondition === 'string') {
38
- // Use sandbox to evaluate the condition expression
39
- // Extract stage results to top level so they can be accessed directly
40
- // e.g., "init.value > 100" can access init from stage results
41
- // Also spread context.data so each-loop variables (e.g. "fruit") work
42
- // in choice conditions.
43
- const stageResults = Object.fromEntries(context._results);
44
- const evalContext = {
45
- ...stageResults, // Spread stage results to top level
46
- ...context.data, // Spread data so loop variables are reachable
47
- input: context.input,
48
- data: context.data,
49
- previousResult: context._sentData,
50
- };
51
- matched = evaluateInSandbox(resolvedCondition, evalContext, { timeout: 5000 });
52
- }
53
-
54
- if (matched) {
55
- this._log.debug(`Choice matched: ${condition} -> ${branch.do}`);
56
- return branch.do; // Return next stage name
57
- }
58
- } catch (err) {
59
- this._log.warn(`Condition evaluation error for "${condition}": ${err.message}`);
60
- }
61
- }
62
-
63
- // No condition matched, use default
64
- if (this.default) {
65
- this._log.debug(`No choice matched, using default: ${this.default}`);
66
- return this.default;
67
- }
68
-
69
- this._log.debug(`No choice matched and no default for: ${this.name}`);
70
- return null;
71
- }
72
- }
73
-
74
- module.exports = { ChoiceStage };
1
+ /**
2
+ * choice stage executor
3
+ * Conditional branching (if/else/switch)
4
+ */
5
+
6
+ const { evaluateInSandbox } = require('../../../../src/utils/sandbox');
7
+
8
+ class ChoiceStage {
9
+ constructor(config) {
10
+ this.name = config.name;
11
+ this.choice = config.choice || []; // Array of { when, do } or { case, do }
12
+ this.default = config.default; // Default stage name
13
+ this._log = require('../../../../src/common/logger').logger.child('ChoiceStage');
14
+ }
15
+
16
+ /**
17
+ * Execute the choice stage
18
+ * @param {FlowContext} context
19
+ * @returns {Promise<string|null>} next stage name
20
+ */
21
+ async execute(context) {
22
+ this._log.debug(`Evaluating choice: ${this.name}`);
23
+
24
+ // Try each when/case condition
25
+ for (const branch of this.choice) {
26
+ const condition = branch.when || branch.case;
27
+ if (!condition) continue;
28
+
29
+ try {
30
+ // Resolve condition with variable interpolation
31
+ const resolvedCondition = context.resolveValue(condition);
32
+
33
+ // Evaluate condition
34
+ let matched = false;
35
+ if (typeof resolvedCondition === 'boolean') {
36
+ matched = resolvedCondition;
37
+ } else if (typeof resolvedCondition === 'string') {
38
+ // Use sandbox to evaluate the condition expression
39
+ // Extract stage results to top level so they can be accessed directly
40
+ // e.g., "init.value > 100" can access init from stage results
41
+ // Also spread context.data so each-loop variables (e.g. "fruit") work
42
+ // in choice conditions.
43
+ const stageResults = Object.fromEntries(context._results);
44
+ const evalContext = {
45
+ ...stageResults, // Spread stage results to top level
46
+ ...context.data, // Spread data so loop variables are reachable
47
+ input: context.input,
48
+ data: context.data,
49
+ previousResult: context._sentData,
50
+ };
51
+ matched = evaluateInSandbox(resolvedCondition, evalContext, { timeout: 5000 });
52
+ }
53
+
54
+ if (matched) {
55
+ this._log.debug(`Choice matched: ${condition} -> ${branch.do}`);
56
+ return branch.do; // Return next stage name
57
+ }
58
+ } catch (err) {
59
+ this._log.warn(`Condition evaluation error for "${condition}": ${err.message}`);
60
+ }
61
+ }
62
+
63
+ // No condition matched, use default
64
+ if (this.default) {
65
+ this._log.debug(`No choice matched, using default: ${this.default}`);
66
+ return this.default;
67
+ }
68
+
69
+ this._log.debug(`No choice matched and no default for: ${this.name}`);
70
+ return null;
71
+ }
72
+ }
73
+
74
+ module.exports = { ChoiceStage };
@@ -1,123 +1,123 @@
1
- /**
2
- * each stage executor
3
- * Loop over items
4
- */
5
-
6
- class EachStage {
7
- constructor(config) {
8
- this.name = config.name;
9
- this.each = config.each; // Array to iterate over
10
- this.as = config.as || 'item'; // Item variable name
11
- this.index = config.index || 'index'; // Index variable name
12
- this.stages = config.stages || []; // Child stages to execute for each item
13
- this.maxIterations = config.maxIterations || 100;
14
- this.until = config.until; // Early exit condition
15
- this._log = require('../../../../src/common/logger').logger.child('EachStage');
16
- }
17
-
18
- /**
19
- * Execute the each stage
20
- * @param {FlowContext} context
21
- * @returns {Promise<Array>} results for each iteration
22
- */
23
- async execute(context) {
24
- this._log.debug(`Executing each loop: ${this.name}`);
25
-
26
- // Resolve the array to iterate
27
- let array;
28
- if (typeof this.each === 'string') {
29
- // Strip optional surrounding {{ }} so users can write either
30
- // "each": "items" (bare identifier)
31
- // "each": "{{items}}" (templated)
32
- // "each": "{{init.items}}" (stage.field)
33
- let path = this.each.trim();
34
- const m = path.match(/^\{\{([^}]+)\}\}$/);
35
- if (m) path = m[1].trim();
36
-
37
- // Use the low-level _resolvePath so we get the actual array/object back
38
- // (String.prototype.replace in resolveValue would coerce non-strings.)
39
- let resolved = context._resolvePath(path);
40
- if (resolved === undefined && path.startsWith('[')) {
41
- // Support inline array literal: each: "['a','b']"
42
- try { resolved = JSON.parse(path); } catch {}
43
- }
44
- if (Array.isArray(resolved)) {
45
- array = resolved;
46
- } else if (resolved && typeof resolved === 'object') {
47
- // Convert object to entries (so {{k, v}} style iteration works)
48
- array = Object.entries(resolved);
49
- } else {
50
- throw new Error(`each "${this.each}" resolved to non-iterable: ${typeof resolved}`);
51
- }
52
- } else if (Array.isArray(this.each)) {
53
- array = this.each;
54
- } else {
55
- throw new Error(`each stage "${this.name}" requires an array to iterate`);
56
- }
57
-
58
- const results = [];
59
- const maxIter = Math.min(this.maxIterations, array.length);
60
-
61
- for (let i = 0; i < maxIter; i++) {
62
- // Set loop variables
63
- context.data[this.as] = array[i];
64
- context.data[this.index] = i;
65
- context.data.loopIndex = i; // Legacy alias
66
-
67
- this._log.debug(`Each iteration ${i}: ${this.as}=`, array[i]);
68
-
69
- // Execute child stages sequentially
70
- let stageResults = [];
71
- for (const stageConfig of this.stages) {
72
- const stageExecutor = context._stageExecutor;
73
- if (stageExecutor) {
74
- const result = await stageExecutor.executeStage(stageConfig, context);
75
- stageResults.push(result);
76
-
77
- // Check for early exit
78
- if (context._destroyed) {
79
- this._log.debug('Flow destroyed, stopping each loop');
80
- break;
81
- }
82
- }
83
- }
84
- results.push(stageResults);
85
-
86
- // Check until condition
87
- if (this.until) {
88
- try {
89
- const { evaluateInSandbox } = require('../../../../../src/utils/sandbox');
90
- const evalContext = {
91
- input: context.input,
92
- data: context.data,
93
- variables: Object.fromEntries(context._results),
94
- previousResult: context._sentData,
95
- item: array[i],
96
- index: i,
97
- };
98
- const shouldStop = evaluateInSandbox(this.until, evalContext, { timeout: 5000 });
99
- if (shouldStop) {
100
- this._log.debug(`Each loop terminated at iteration ${i} by until condition`);
101
- break;
102
- }
103
- } catch (err) {
104
- this._log.warn(`until condition error: ${err.message}`);
105
- }
106
- }
107
- }
108
-
109
- // Cleanup loop variables
110
- delete context.data[this.as];
111
- delete context.data[this.index];
112
- delete context.data.loopIndex;
113
-
114
- // Store results by name if this stage has a name
115
- if (this.name) {
116
- context.setResult(this.name, results);
117
- }
118
-
119
- return results;
120
- }
121
- }
122
-
123
- module.exports = { EachStage };
1
+ /**
2
+ * each stage executor
3
+ * Loop over items
4
+ */
5
+
6
+ class EachStage {
7
+ constructor(config) {
8
+ this.name = config.name;
9
+ this.each = config.each; // Array to iterate over
10
+ this.as = config.as || 'item'; // Item variable name
11
+ this.index = config.index || 'index'; // Index variable name
12
+ this.stages = config.stages || []; // Child stages to execute for each item
13
+ this.maxIterations = config.maxIterations || 100;
14
+ this.until = config.until; // Early exit condition
15
+ this._log = require('../../../../src/common/logger').logger.child('EachStage');
16
+ }
17
+
18
+ /**
19
+ * Execute the each stage
20
+ * @param {FlowContext} context
21
+ * @returns {Promise<Array>} results for each iteration
22
+ */
23
+ async execute(context) {
24
+ this._log.debug(`Executing each loop: ${this.name}`);
25
+
26
+ // Resolve the array to iterate
27
+ let array;
28
+ if (typeof this.each === 'string') {
29
+ // Strip optional surrounding {{ }} so users can write either
30
+ // "each": "items" (bare identifier)
31
+ // "each": "{{items}}" (templated)
32
+ // "each": "{{init.items}}" (stage.field)
33
+ let path = this.each.trim();
34
+ const m = path.match(/^\{\{([^}]+)\}\}$/);
35
+ if (m) path = m[1].trim();
36
+
37
+ // Use the low-level _resolvePath so we get the actual array/object back
38
+ // (String.prototype.replace in resolveValue would coerce non-strings.)
39
+ let resolved = context._resolvePath(path);
40
+ if (resolved === undefined && path.startsWith('[')) {
41
+ // Support inline array literal: each: "['a','b']"
42
+ try { resolved = JSON.parse(path); } catch {}
43
+ }
44
+ if (Array.isArray(resolved)) {
45
+ array = resolved;
46
+ } else if (resolved && typeof resolved === 'object') {
47
+ // Convert object to entries (so {{k, v}} style iteration works)
48
+ array = Object.entries(resolved);
49
+ } else {
50
+ throw new Error(`each "${this.each}" resolved to non-iterable: ${typeof resolved}`);
51
+ }
52
+ } else if (Array.isArray(this.each)) {
53
+ array = this.each;
54
+ } else {
55
+ throw new Error(`each stage "${this.name}" requires an array to iterate`);
56
+ }
57
+
58
+ const results = [];
59
+ const maxIter = Math.min(this.maxIterations, array.length);
60
+
61
+ for (let i = 0; i < maxIter; i++) {
62
+ // Set loop variables
63
+ context.data[this.as] = array[i];
64
+ context.data[this.index] = i;
65
+ context.data.loopIndex = i; // Legacy alias
66
+
67
+ this._log.debug(`Each iteration ${i}: ${this.as}=`, array[i]);
68
+
69
+ // Execute child stages sequentially
70
+ let stageResults = [];
71
+ for (const stageConfig of this.stages) {
72
+ const stageExecutor = context._stageExecutor;
73
+ if (stageExecutor) {
74
+ const result = await stageExecutor.executeStage(stageConfig, context);
75
+ stageResults.push(result);
76
+
77
+ // Check for early exit
78
+ if (context._destroyed) {
79
+ this._log.debug('Flow destroyed, stopping each loop');
80
+ break;
81
+ }
82
+ }
83
+ }
84
+ results.push(stageResults);
85
+
86
+ // Check until condition
87
+ if (this.until) {
88
+ try {
89
+ const { evaluateInSandbox } = require('../../../../../src/utils/sandbox');
90
+ const evalContext = {
91
+ input: context.input,
92
+ data: context.data,
93
+ variables: Object.fromEntries(context._results),
94
+ previousResult: context._sentData,
95
+ item: array[i],
96
+ index: i,
97
+ };
98
+ const shouldStop = evaluateInSandbox(this.until, evalContext, { timeout: 5000 });
99
+ if (shouldStop) {
100
+ this._log.debug(`Each loop terminated at iteration ${i} by until condition`);
101
+ break;
102
+ }
103
+ } catch (err) {
104
+ this._log.warn(`until condition error: ${err.message}`);
105
+ }
106
+ }
107
+ }
108
+
109
+ // Cleanup loop variables
110
+ delete context.data[this.as];
111
+ delete context.data[this.index];
112
+ delete context.data.loopIndex;
113
+
114
+ // Store results by name if this stage has a name
115
+ if (this.name) {
116
+ context.setResult(this.name, results);
117
+ }
118
+
119
+ return results;
120
+ }
121
+ }
122
+
123
+ module.exports = { EachStage };
@@ -1,69 +1,69 @@
1
- /**
2
- * parallel stage executor
3
- * Run stages concurrently
4
- */
5
-
6
- class ParallelStage {
7
- constructor(config) {
8
- this.name = config.name;
9
- this.parallel = config.parallel || []; // Array of stages to run in parallel
10
- this._log = require('../../../../src/common/logger').logger.child('ParallelStage');
11
- }
12
-
13
- /**
14
- * Execute the parallel stage
15
- * @param {FlowContext} context
16
- * @returns {Promise<Array>} results from all parallel stages
17
- */
18
- async execute(context) {
19
- this._log.debug(`Executing parallel: ${this.name}`);
20
-
21
- if (!this.parallel || this.parallel.length === 0) {
22
- return [];
23
- }
24
-
25
- const stageExecutor = context._stageExecutor;
26
- if (!stageExecutor) {
27
- throw new Error('Stage executor not set in context');
28
- }
29
-
30
- // Execute all stages concurrently
31
- const promises = this.parallel.map(async (stageConfig) => {
32
- try {
33
- return await stageExecutor.executeStage(stageConfig, context);
34
- } catch (err) {
35
- this._log.warn(`Parallel stage error: ${err.message}`);
36
- return { error: err.message };
37
- }
38
- });
39
-
40
- const results = await Promise.all(promises);
41
-
42
- // Build result object keyed by stage name (not plain array)
43
- const resultByName = {};
44
- for (let i = 0; i < this.parallel.length; i++) {
45
- const stageConfig = this.parallel[i];
46
- const name = stageConfig.name || String(i);
47
- resultByName[name] = results[i];
48
- }
49
-
50
- // Store named results in context
51
- // If the parallel block itself has a name, store the keyed object
52
- if (this.name) {
53
- context.setResult(this.name, resultByName);
54
- }
55
-
56
- // Also store individual named results for access via {{stageName}}
57
- for (let i = 0; i < this.parallel.length; i++) {
58
- const stageConfig = this.parallel[i];
59
- if (stageConfig.name) {
60
- context.setResult(stageConfig.name, results[i]);
61
- }
62
- }
63
-
64
- this._log.debug(`Parallel completed: ${results.length} stages`);
65
- return resultByName;
66
- }
67
- }
68
-
69
- module.exports = { ParallelStage };
1
+ /**
2
+ * parallel stage executor
3
+ * Run stages concurrently
4
+ */
5
+
6
+ class ParallelStage {
7
+ constructor(config) {
8
+ this.name = config.name;
9
+ this.parallel = config.parallel || []; // Array of stages to run in parallel
10
+ this._log = require('../../../../src/common/logger').logger.child('ParallelStage');
11
+ }
12
+
13
+ /**
14
+ * Execute the parallel stage
15
+ * @param {FlowContext} context
16
+ * @returns {Promise<Array>} results from all parallel stages
17
+ */
18
+ async execute(context) {
19
+ this._log.debug(`Executing parallel: ${this.name}`);
20
+
21
+ if (!this.parallel || this.parallel.length === 0) {
22
+ return [];
23
+ }
24
+
25
+ const stageExecutor = context._stageExecutor;
26
+ if (!stageExecutor) {
27
+ throw new Error('Stage executor not set in context');
28
+ }
29
+
30
+ // Execute all stages concurrently
31
+ const promises = this.parallel.map(async (stageConfig) => {
32
+ try {
33
+ return await stageExecutor.executeStage(stageConfig, context);
34
+ } catch (err) {
35
+ this._log.warn(`Parallel stage error: ${err.message}`);
36
+ return { error: err.message };
37
+ }
38
+ });
39
+
40
+ const results = await Promise.all(promises);
41
+
42
+ // Build result object keyed by stage name (not plain array)
43
+ const resultByName = {};
44
+ for (let i = 0; i < this.parallel.length; i++) {
45
+ const stageConfig = this.parallel[i];
46
+ const name = stageConfig.name || String(i);
47
+ resultByName[name] = results[i];
48
+ }
49
+
50
+ // Store named results in context
51
+ // If the parallel block itself has a name, store the keyed object
52
+ if (this.name) {
53
+ context.setResult(this.name, resultByName);
54
+ }
55
+
56
+ // Also store individual named results for access via {{stageName}}
57
+ for (let i = 0; i < this.parallel.length; i++) {
58
+ const stageConfig = this.parallel[i];
59
+ if (stageConfig.name) {
60
+ context.setResult(stageConfig.name, results[i]);
61
+ }
62
+ }
63
+
64
+ this._log.debug(`Parallel completed: ${results.length} stages`);
65
+ return resultByName;
66
+ }
67
+ }
68
+
69
+ module.exports = { ParallelStage };