@ronaldroe/micro-flow 1.3.8 → 2.0.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 +53 -12
- package/dist/src/classes/base.js +2 -2
- package/dist/src/classes/base.js.map +3 -3
- package/dist/src/classes/callable_registry.js +1 -1
- package/dist/src/classes/callable_registry.js.map +2 -2
- package/dist/src/classes/events/event.js +1 -1
- package/dist/src/classes/events/event.js.map +3 -3
- package/dist/src/classes/index.js +1 -1
- package/dist/src/classes/index.js.map +3 -3
- package/dist/src/classes/state.js +1 -1
- package/dist/src/classes/state.js.map +3 -3
- package/dist/src/classes/steps/case.js +1 -1
- package/dist/src/classes/steps/case.js.map +3 -3
- package/dist/src/classes/steps/conditional_step.js +1 -1
- package/dist/src/classes/steps/conditional_step.js.map +3 -3
- package/dist/src/classes/steps/delay_step.js +1 -1
- package/dist/src/classes/steps/delay_step.js.map +2 -2
- package/dist/src/classes/steps/flow_control_step.js +1 -1
- package/dist/src/classes/steps/flow_control_step.js.map +2 -2
- package/dist/src/classes/steps/logic_step.js +1 -1
- package/dist/src/classes/steps/logic_step.js.map +3 -3
- package/dist/src/classes/steps/loop_step.js +1 -1
- package/dist/src/classes/steps/loop_step.js.map +3 -3
- package/dist/src/classes/steps/step.js +1 -1
- package/dist/src/classes/steps/step.js.map +3 -3
- package/dist/src/classes/steps/switch_step.js +1 -1
- package/dist/src/classes/steps/switch_step.js.map +3 -3
- package/dist/src/classes/workflow.js +1 -1
- package/dist/src/classes/workflow.js.map +3 -3
- package/dist/src/enums/delay_types.js.map +1 -1
- package/dist/src/enums/logic_step_types.js.map +3 -3
- package/dist/src/enums/sub_step_types.js +1 -1
- package/dist/src/enums/sub_step_types.js.map +2 -2
- package/package.json +1 -1
- package/src/classes/base.js +42 -11
- package/src/classes/callable_registry.js +82 -0
- package/src/classes/events/event.js +3 -3
- package/src/classes/index.js +2 -1
- package/src/classes/state.js +278 -8
- package/src/classes/steps/case.js +34 -4
- package/src/classes/steps/conditional_step.js +69 -3
- package/src/classes/steps/delay_step.js +18 -0
- package/src/classes/steps/flow_control_step.js +18 -2
- package/src/classes/steps/logic_step.js +26 -8
- package/src/classes/steps/loop_step.js +88 -3
- package/src/classes/steps/step.js +228 -16
- package/src/classes/steps/switch_step.js +66 -8
- package/src/classes/workflow.js +237 -40
- package/src/enums/delay_types.js +1 -1
- package/src/enums/logic_step_types.js +2 -2
- package/src/enums/sub_step_types.js +10 -10
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import Step from './step.js';
|
|
1
2
|
import LogicStep from './logic_step.js';
|
|
2
3
|
import { conditional_step_comparators } from '../../enums/index.js';
|
|
3
4
|
|
|
@@ -19,6 +20,8 @@ export default class ConditionalStep extends LogicStep {
|
|
|
19
20
|
* @param {*|Function} [options.conditional.value] - Value to compare against. Can be a function that returns the value.
|
|
20
21
|
* @param {Function|Step|Workflow} [options.true_callable=async () => {}] - Callable to execute if condition is true.
|
|
21
22
|
* @param {Function|Step|Workflow} [options.false_callable=async () => {}] - Callable to execute if condition is false.
|
|
23
|
+
* @param {string|null} [options.true_callable_registry_key=null] - Optional key to reference true_callable to be rehydrated after serialization.
|
|
24
|
+
* @param {string|null} [options.false_callable_registry_key=null] - Optional key to reference false_callable to be rehydrated after serialization.
|
|
22
25
|
*/
|
|
23
26
|
constructor({
|
|
24
27
|
name,
|
|
@@ -29,12 +32,23 @@ export default class ConditionalStep extends LogicStep {
|
|
|
29
32
|
},
|
|
30
33
|
true_callable = async () => {},
|
|
31
34
|
false_callable = async () => {},
|
|
35
|
+
true_callable_registry_key = null,
|
|
36
|
+
false_callable_registry_key = null,
|
|
32
37
|
}) {
|
|
33
38
|
super({
|
|
34
39
|
name,
|
|
35
40
|
conditional
|
|
36
41
|
});
|
|
37
42
|
|
|
43
|
+
// Optional keys to reference true_callable/false_callable to be rehydrated after serialization.
|
|
44
|
+
this.true_callable_registry_key = true_callable_registry_key;
|
|
45
|
+
this.false_callable_registry_key = false_callable_registry_key;
|
|
46
|
+
|
|
47
|
+
// Keep the raw (unbound) originals for serialization - binding renames a function
|
|
48
|
+
// (e.g. "falseBranch" -> "bound falseBranch"), which would break registry lookups on hydrate.
|
|
49
|
+
this._true_callable_raw = true_callable;
|
|
50
|
+
this._false_callable_raw = false_callable;
|
|
51
|
+
|
|
38
52
|
// Bind function callables to this step instance for state access
|
|
39
53
|
if (typeof true_callable === 'function') {
|
|
40
54
|
this.true_callable = true_callable.bind(this);
|
|
@@ -52,7 +66,11 @@ export default class ConditionalStep extends LogicStep {
|
|
|
52
66
|
}
|
|
53
67
|
|
|
54
68
|
/**
|
|
55
|
-
* Executes the appropriate branch based on the condition evaluation.
|
|
69
|
+
* Executes the appropriate branch based on the condition evaluation. When the executed branch
|
|
70
|
+
* is a `Step`/`Workflow` (not a plain function), it is stamped with this step's own
|
|
71
|
+
* `parent_workflow_id`/`use_state_singleton`/`state` first - true/false_callable are never
|
|
72
|
+
* added to the parent workflow via `addStep()`, so this is the only way they end up sharing
|
|
73
|
+
* its state instead of their own, independent one.
|
|
56
74
|
* @async
|
|
57
75
|
* @returns {Promise<*>} The result of the executed branch.
|
|
58
76
|
*/
|
|
@@ -71,7 +89,9 @@ export default class ConditionalStep extends LogicStep {
|
|
|
71
89
|
if (typeof true_callable === 'function') {
|
|
72
90
|
result = await true_callable();
|
|
73
91
|
} else {
|
|
74
|
-
true_callable.
|
|
92
|
+
true_callable.parent_workflow_id = this.parent_workflow_id;
|
|
93
|
+
true_callable.use_state_singleton = this.use_state_singleton;
|
|
94
|
+
true_callable.state = this.state;
|
|
75
95
|
result = await true_callable.execute();
|
|
76
96
|
}
|
|
77
97
|
} else {
|
|
@@ -83,11 +103,57 @@ export default class ConditionalStep extends LogicStep {
|
|
|
83
103
|
if (typeof false_callable === 'function') {
|
|
84
104
|
result = await false_callable();
|
|
85
105
|
} else {
|
|
86
|
-
false_callable.
|
|
106
|
+
false_callable.parent_workflow_id = this.parent_workflow_id;
|
|
107
|
+
false_callable.use_state_singleton = this.use_state_singleton;
|
|
108
|
+
false_callable.state = this.state;
|
|
87
109
|
result = await false_callable.execute();
|
|
88
110
|
}
|
|
89
111
|
}
|
|
90
112
|
|
|
91
113
|
return { message: `Conditional step ${this.name} completed`, result };
|
|
92
114
|
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Inserts safely serializable properties of the step into a new object for serialization.
|
|
118
|
+
* @returns {Object} An object containing the step's properties ready for serialization.
|
|
119
|
+
*/
|
|
120
|
+
prepareForSerialization() {
|
|
121
|
+
return {
|
|
122
|
+
...super.prepareForSerialization(),
|
|
123
|
+
// The base `callable` is an internal wiring detail (the bound `conditional` method) -
|
|
124
|
+
// ConditionalStep's constructor doesn't take a callable, so it isn't real data to persist.
|
|
125
|
+
callable: null,
|
|
126
|
+
true_callable: this.true_callable_registry_key
|
|
127
|
+
? { type: Step.callable_types.FUNCTION, value: this.true_callable_registry_key }
|
|
128
|
+
: Step.serializeCallableField(this._true_callable_raw),
|
|
129
|
+
false_callable: this.false_callable_registry_key
|
|
130
|
+
? { type: Step.callable_types.FUNCTION, value: this.false_callable_registry_key }
|
|
131
|
+
: Step.serializeCallableField(this._false_callable_raw),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Hydrates a parsed step object into a ConditionalStep instance, resolving the true/false branch callables.
|
|
137
|
+
* @param {Object} parsed_step - The parsed step object.
|
|
138
|
+
* @param {import('../callable_registry.js').default|null} [callable_registry] - Registry used to resolve function callables.
|
|
139
|
+
* @returns {ConditionalStep} The hydrated ConditionalStep instance.
|
|
140
|
+
*/
|
|
141
|
+
static hydrate(parsed_step, callable_registry = null) {
|
|
142
|
+
const true_descriptor = parsed_step.true_callable;
|
|
143
|
+
const false_descriptor = parsed_step.false_callable;
|
|
144
|
+
|
|
145
|
+
return super.hydrate({
|
|
146
|
+
...parsed_step,
|
|
147
|
+
true_callable: Step.hydrateCallableField(true_descriptor, callable_registry),
|
|
148
|
+
false_callable: Step.hydrateCallableField(false_descriptor, callable_registry),
|
|
149
|
+
true_callable_registry_key: true_descriptor?.type === Step.callable_types.FUNCTION
|
|
150
|
+
? true_descriptor.value
|
|
151
|
+
: null,
|
|
152
|
+
false_callable_registry_key: false_descriptor?.type === Step.callable_types.FUNCTION
|
|
153
|
+
? false_descriptor.value
|
|
154
|
+
: null,
|
|
155
|
+
}, callable_registry);
|
|
156
|
+
}
|
|
93
157
|
}
|
|
158
|
+
|
|
159
|
+
ConditionalStep.registerStepClass(ConditionalStep);
|
|
@@ -100,4 +100,22 @@ export default class DelayStep extends Step {
|
|
|
100
100
|
|
|
101
101
|
return this.delay(delay_until);
|
|
102
102
|
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Inserts safely serializable properties of the step into a new object for serialization.
|
|
106
|
+
* @returns {Object} An object containing the step's properties ready for serialization.
|
|
107
|
+
*/
|
|
108
|
+
prepareForSerialization() {
|
|
109
|
+
return {
|
|
110
|
+
...super.prepareForSerialization(),
|
|
111
|
+
// The base `callable` is an internal wiring detail (the bound absolute/relative method) -
|
|
112
|
+
// DelayStep's constructor doesn't take a callable, so it isn't real data to persist.
|
|
113
|
+
callable: null,
|
|
114
|
+
delay_type: this.delay_type,
|
|
115
|
+
absolute_timestamp: this.absolute_timestamp,
|
|
116
|
+
relative_delay_ms: this.relative_delay_ms,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
103
119
|
}
|
|
120
|
+
|
|
121
|
+
DelayStep.registerStepClass(DelayStep);
|
|
@@ -54,7 +54,7 @@ export default class FlowControlStep extends LogicStep {
|
|
|
54
54
|
this.getState('events.step.event_names.CONDITIONAL_TRUE_BRANCH_EXECUTED'),
|
|
55
55
|
`Break condition met for step: ${this.name}`
|
|
56
56
|
);
|
|
57
|
-
this.setParentWorkflowValue(this.
|
|
57
|
+
this.setParentWorkflowValue(this.parent_workflow_id, `should_${this.flow_control_type}`, true);
|
|
58
58
|
|
|
59
59
|
return true;
|
|
60
60
|
} else {
|
|
@@ -62,9 +62,25 @@ export default class FlowControlStep extends LogicStep {
|
|
|
62
62
|
this.getState('events.step.event_names.CONDITIONAL_FALSE_BRANCH_EXECUTED'),
|
|
63
63
|
`Break condition not met for step: ${this.name}`
|
|
64
64
|
);
|
|
65
|
-
this.setParentWorkflowValue(this.
|
|
65
|
+
this.setParentWorkflowValue(this.parent_workflow_id, `should_${this.flow_control_type}`, false);
|
|
66
66
|
|
|
67
67
|
return false;
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Inserts safely serializable properties of the step into a new object for serialization.
|
|
73
|
+
* @returns {Object} An object containing the step's properties ready for serialization.
|
|
74
|
+
*/
|
|
75
|
+
prepareForSerialization() {
|
|
76
|
+
return {
|
|
77
|
+
...super.prepareForSerialization(),
|
|
78
|
+
// The base `callable` is an internal wiring detail (the bound `shouldFlowControl` method) -
|
|
79
|
+
// FlowControlStep's constructor doesn't take a callable, so it isn't real data to persist.
|
|
80
|
+
callable: null,
|
|
81
|
+
flow_control_type: this.flow_control_type,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
70
84
|
}
|
|
85
|
+
|
|
86
|
+
FlowControlStep.registerStepClass(FlowControlStep);
|
|
@@ -18,10 +18,12 @@ export default class LogicStep extends Step {
|
|
|
18
18
|
* @param {conditional_step_comparators|string} [options.conditional.operator] - Comparison operator.
|
|
19
19
|
* @param {*|Function, optional} [options.conditional.value] - Value to compare against. Can be a function that returns the value.
|
|
20
20
|
* @param {Function} [options.callable=async () => {}] - Function to execute.
|
|
21
|
+
* @param {string|null} [options.callable_registry_key=null] - Optional key to reference the callable to be rehydrated after serialization.
|
|
21
22
|
*/
|
|
22
23
|
constructor({
|
|
23
24
|
name,
|
|
24
25
|
callable = async () => {},
|
|
26
|
+
callable_registry_key = null,
|
|
25
27
|
conditional = {
|
|
26
28
|
operator: null,
|
|
27
29
|
subject: null,
|
|
@@ -31,7 +33,8 @@ export default class LogicStep extends Step {
|
|
|
31
33
|
super({
|
|
32
34
|
name,
|
|
33
35
|
step_type: step_types.LOGIC,
|
|
34
|
-
callable
|
|
36
|
+
callable,
|
|
37
|
+
callable_registry_key,
|
|
35
38
|
});
|
|
36
39
|
|
|
37
40
|
this.setConditional(conditional);
|
|
@@ -44,16 +47,16 @@ export default class LogicStep extends Step {
|
|
|
44
47
|
* @throws {Error} Throws if operator is unknown.
|
|
45
48
|
*/
|
|
46
49
|
checkCondition() {
|
|
47
|
-
const
|
|
48
|
-
const
|
|
50
|
+
const raw_subject = this.conditional_config.subject;
|
|
51
|
+
const raw_value = this.conditional_config.value;
|
|
49
52
|
const operator = this.conditional_config.operator;
|
|
50
53
|
|
|
51
54
|
// Resolve subject - call it if it's a function
|
|
52
|
-
const subject = typeof
|
|
55
|
+
const subject = typeof raw_subject === 'function' ? raw_subject() : raw_subject;
|
|
53
56
|
|
|
54
57
|
// Don't resolve value for CUSTOM_FUNCTION - the value IS the function to call
|
|
55
|
-
const
|
|
56
|
-
const value = (!
|
|
58
|
+
const is_custom_function = operator === this.getState('conditional_step_comparators.CUSTOM_FUNCTION');
|
|
59
|
+
const value = (!is_custom_function && typeof raw_value === 'function') ? raw_value() : raw_value;
|
|
57
60
|
|
|
58
61
|
switch (operator) {
|
|
59
62
|
case this.getState('conditional_step_comparators.STRICT_EQUALS'):
|
|
@@ -108,8 +111,8 @@ export default class LogicStep extends Step {
|
|
|
108
111
|
if (typeof value !== 'string') {
|
|
109
112
|
throw new Error(`Regex input must be a string.`);
|
|
110
113
|
}
|
|
111
|
-
const
|
|
112
|
-
return !
|
|
114
|
+
const not_match_regex = new RegExp(value);
|
|
115
|
+
return !not_match_regex.test(subject);
|
|
113
116
|
case this.getState('conditional_step_comparators.STRING_STARTS_WITH'):
|
|
114
117
|
return typeof subject === 'string' && typeof value === 'string' && subject.startsWith(value);
|
|
115
118
|
case this.getState('conditional_step_comparators.STRING_ENDS_WITH'):
|
|
@@ -157,4 +160,19 @@ export default class LogicStep extends Step {
|
|
|
157
160
|
setConditional(conditional) {
|
|
158
161
|
this.conditional_config = { subject: conditional.subject, operator: conditional.operator, value: conditional.value };
|
|
159
162
|
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Inserts safely serializable properties of the step into a new object for serialization.
|
|
166
|
+
* Note: function-valued subject/value are not persisted - there's no registry for them,
|
|
167
|
+
* only the plain-callable field supports registry-based rehydration.
|
|
168
|
+
* @returns {Object} An object containing the step's properties ready for serialization.
|
|
169
|
+
*/
|
|
170
|
+
prepareForSerialization() {
|
|
171
|
+
return {
|
|
172
|
+
...super.prepareForSerialization(),
|
|
173
|
+
conditional: { ...this.conditional_config },
|
|
174
|
+
};
|
|
175
|
+
}
|
|
160
176
|
}
|
|
177
|
+
|
|
178
|
+
LogicStep.registerStepClass(LogicStep);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { loop_types, step_types } from '../../enums/index.js';
|
|
2
|
+
import Step from './step.js';
|
|
2
3
|
import LogicStep from './logic_step.js';
|
|
3
4
|
import { conditional_step_comparators } from '../../enums/index.js';
|
|
4
5
|
|
|
@@ -23,6 +24,7 @@ export default class LoopStep extends LogicStep {
|
|
|
23
24
|
* @param {string} [options.loop_type=loop_types.FOR_EACH] - Type of loop ('for', 'for_each', 'while', or 'generator').
|
|
24
25
|
* @param {number} [options.iterations=0] - Number of iterations to execute. Only used for 'for' loops.
|
|
25
26
|
* @param {number} [options.max_iterations=1000] - Maximum number of iterations to prevent infinite loops.
|
|
27
|
+
* @param {string|null} [options.loop_callable_registry_key=null] - Optional key to reference the per-iteration callable to be rehydrated after serialization.
|
|
26
28
|
*/
|
|
27
29
|
constructor({
|
|
28
30
|
name,
|
|
@@ -36,6 +38,7 @@ export default class LoopStep extends LogicStep {
|
|
|
36
38
|
loop_type = loop_types.FOR_EACH,
|
|
37
39
|
iterations = 0,
|
|
38
40
|
max_iterations = 1000,
|
|
41
|
+
loop_callable_registry_key = null,
|
|
39
42
|
}) {
|
|
40
43
|
super({ name, conditional });
|
|
41
44
|
this.iterable = iterable;
|
|
@@ -45,16 +48,38 @@ export default class LoopStep extends LogicStep {
|
|
|
45
48
|
this.results = [];
|
|
46
49
|
this.current_item = null;
|
|
47
50
|
|
|
51
|
+
// Optional key to reference the per-iteration callable to be rehydrated after serialization.
|
|
52
|
+
this.loop_callable_registry_key = loop_callable_registry_key;
|
|
53
|
+
|
|
48
54
|
// Store the user's callable separately so loop methods can invoke it.
|
|
49
55
|
// this._callable will be set to the loop method by the setter below.
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
// The raw object is kept too (distinct from the bound version) so serialization
|
|
57
|
+
// can recover the original function/Step/Workflow instead of the loop-runner method.
|
|
58
|
+
this._loop_callable_type = this.getCallableType(callable);
|
|
59
|
+
this._loop_callable_object = callable;
|
|
60
|
+
this._loop_callable = this._loop_callable_type === 'function'
|
|
52
61
|
? callable.bind(this)
|
|
53
62
|
: callable.execute.bind(callable);
|
|
54
63
|
|
|
55
64
|
this.callable = this[`${loop_type}_loop`].bind(this);
|
|
56
65
|
}
|
|
57
66
|
|
|
67
|
+
/**
|
|
68
|
+
* When the per-iteration callable is a `Step`/`Workflow` (not a plain function), stamps it with
|
|
69
|
+
* this loop step's own `parent_workflow_id`/`use_state_singleton`/`state` right before the loop
|
|
70
|
+
* runs - mirrors what `Workflow.addStep()` does for top-level steps, since a loop callable is
|
|
71
|
+
* never added to the workflow directly.
|
|
72
|
+
*/
|
|
73
|
+
propagateStateToLoopCallable() {
|
|
74
|
+
if (this._loop_callable_type === 'function') {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this._loop_callable_object.parent_workflow_id = this.parent_workflow_id;
|
|
79
|
+
this._loop_callable_object.use_state_singleton = this.use_state_singleton;
|
|
80
|
+
this._loop_callable_object.state = this.state;
|
|
81
|
+
}
|
|
82
|
+
|
|
58
83
|
/**
|
|
59
84
|
* Executes a generator/async generator and appends yielded values to results.
|
|
60
85
|
* @throws {Error} If the callable is not a generator or async generator function.
|
|
@@ -65,6 +90,8 @@ export default class LoopStep extends LogicStep {
|
|
|
65
90
|
throw new Error('Iterable must be a generator function for generator loops');
|
|
66
91
|
}
|
|
67
92
|
|
|
93
|
+
this.propagateStateToLoopCallable();
|
|
94
|
+
|
|
68
95
|
let iterations = 0;
|
|
69
96
|
// Use for await...of to handle both sync and async generators
|
|
70
97
|
for await (const item of this._loop_callable()) {
|
|
@@ -75,6 +102,8 @@ export default class LoopStep extends LogicStep {
|
|
|
75
102
|
}
|
|
76
103
|
}
|
|
77
104
|
|
|
105
|
+
this.iterations = iterations;
|
|
106
|
+
|
|
78
107
|
return {
|
|
79
108
|
message: `Generator loop ${this.name} completed after ${iterations} iterations`,
|
|
80
109
|
result: this.results
|
|
@@ -86,11 +115,16 @@ export default class LoopStep extends LogicStep {
|
|
|
86
115
|
* @returns {Object} - An object containing a message and the results of the loop.
|
|
87
116
|
*/
|
|
88
117
|
async for_loop() {
|
|
118
|
+
this.propagateStateToLoopCallable();
|
|
119
|
+
|
|
120
|
+
const target = this.iterations;
|
|
89
121
|
let i = 0;
|
|
90
|
-
for (; i <
|
|
122
|
+
for (; i < target; i++) {
|
|
91
123
|
this.results.push(await this._loop_callable());
|
|
92
124
|
}
|
|
93
125
|
|
|
126
|
+
this.iterations = i;
|
|
127
|
+
|
|
94
128
|
return {
|
|
95
129
|
message: `For loop ${this.name} completed after ${i} iterations`,
|
|
96
130
|
result: this.results
|
|
@@ -107,6 +141,8 @@ export default class LoopStep extends LogicStep {
|
|
|
107
141
|
throw new Error('Iterable is required for for_each loops');
|
|
108
142
|
}
|
|
109
143
|
|
|
144
|
+
this.propagateStateToLoopCallable();
|
|
145
|
+
|
|
110
146
|
if (typeof this.iterable === 'function') {
|
|
111
147
|
this.iterable = this.iterable();
|
|
112
148
|
}
|
|
@@ -118,6 +154,8 @@ export default class LoopStep extends LogicStep {
|
|
|
118
154
|
this.results.push(await this._loop_callable());
|
|
119
155
|
}
|
|
120
156
|
|
|
157
|
+
this.iterations = iterations;
|
|
158
|
+
|
|
121
159
|
return {
|
|
122
160
|
message: `For each loop ${this.name} completed after ${iterations} iterations`,
|
|
123
161
|
result: this.results
|
|
@@ -134,15 +172,62 @@ export default class LoopStep extends LogicStep {
|
|
|
134
172
|
throw new Error('Valid conditional is required for while loops');
|
|
135
173
|
}
|
|
136
174
|
|
|
175
|
+
this.propagateStateToLoopCallable();
|
|
176
|
+
|
|
137
177
|
let iterations = 0;
|
|
138
178
|
while (this.checkCondition() && iterations < this.max_iterations) {
|
|
139
179
|
iterations++;
|
|
140
180
|
this.results.push(await this._loop_callable());
|
|
141
181
|
}
|
|
142
182
|
|
|
183
|
+
this.iterations = iterations;
|
|
184
|
+
|
|
143
185
|
return {
|
|
144
186
|
message: `While loop ${this.name} completed after ${iterations} iterations`,
|
|
145
187
|
result: this.results
|
|
146
188
|
};
|
|
147
189
|
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Inserts safely serializable properties of the step into a new object for serialization.
|
|
193
|
+
* Note: a function-valued `iterable` is not persisted, since there's no registry for it.
|
|
194
|
+
* @returns {Object} An object containing the step's properties ready for serialization.
|
|
195
|
+
*/
|
|
196
|
+
prepareForSerialization() {
|
|
197
|
+
return {
|
|
198
|
+
...super.prepareForSerialization(),
|
|
199
|
+
callable: this.loop_callable_registry_key
|
|
200
|
+
? { type: Step.callable_types.FUNCTION, value: this.loop_callable_registry_key }
|
|
201
|
+
: Step.serializeCallableField(this._loop_callable_object),
|
|
202
|
+
loop_type: this.loop_type,
|
|
203
|
+
iterations: this.iterations,
|
|
204
|
+
max_iterations: this.max_iterations,
|
|
205
|
+
iterable: Array.isArray(this.iterable) ? this.iterable : null,
|
|
206
|
+
results: this.results,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Hydrates a parsed step object into a LoopStep instance, resolving the per-iteration callable.
|
|
212
|
+
* @param {Object} parsed_step - The parsed step object.
|
|
213
|
+
* @param {import('../callable_registry.js').default|null} [callable_registry] - Registry used to resolve function callables.
|
|
214
|
+
* @returns {LoopStep} The hydrated LoopStep instance.
|
|
215
|
+
*/
|
|
216
|
+
static hydrate(parsed_step, callable_registry = null) {
|
|
217
|
+
const callable_descriptor = parsed_step.callable;
|
|
218
|
+
|
|
219
|
+
const instance = super.hydrate({
|
|
220
|
+
...parsed_step,
|
|
221
|
+
callable: Step.hydrateCallableField(callable_descriptor, callable_registry),
|
|
222
|
+
loop_callable_registry_key: callable_descriptor?.type === Step.callable_types.FUNCTION
|
|
223
|
+
? callable_descriptor.value
|
|
224
|
+
: null,
|
|
225
|
+
}, callable_registry);
|
|
226
|
+
|
|
227
|
+
instance.results = parsed_step.results ?? [];
|
|
228
|
+
|
|
229
|
+
return instance;
|
|
230
|
+
}
|
|
148
231
|
}
|
|
232
|
+
|
|
233
|
+
LoopStep.registerStepClass(LoopStep);
|