@ronaldroe/micro-flow 1.3.9 → 3.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 +51 -10
- 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/instance_state.js +2 -0
- package/dist/src/classes/instance_state.js.map +7 -0
- 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 +3 -3
- package/dist/src/classes/steps/flow_control_step.js +1 -1
- package/dist/src/classes/steps/flow_control_step.js.map +3 -3
- 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 -10
- package/src/classes/callable_registry.js +82 -0
- package/src/classes/events/event.js +3 -3
- package/src/classes/index.js +2 -0
- package/src/classes/instance_state.js +277 -0
- package/src/classes/state.js +20 -49
- package/src/classes/steps/case.js +34 -4
- package/src/classes/steps/conditional_step.js +72 -5
- package/src/classes/steps/delay_step.js +23 -8
- package/src/classes/steps/flow_control_step.js +21 -4
- package/src/classes/steps/logic_step.js +63 -45
- package/src/classes/steps/loop_step.js +88 -3
- package/src/classes/steps/step.js +238 -20
- package/src/classes/steps/switch_step.js +68 -9
- package/src/classes/workflow.js +280 -62
- 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
package/src/classes/workflow.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
2
|
import Base from './base.js';
|
|
3
|
+
import CallableRegistry from './callable_registry.js';
|
|
4
|
+
import Step from './steps/step.js';
|
|
5
|
+
import State from './state.js';
|
|
6
|
+
import { statuses, event_names, events, types, conditional_step_comparators, messages } from './instance_state.js';
|
|
3
7
|
import { base_types } from '../enums/index.js';
|
|
4
8
|
|
|
5
9
|
/**
|
|
@@ -12,30 +16,48 @@ export default class Workflow extends Base {
|
|
|
12
16
|
* Creates a new Workflow instance.
|
|
13
17
|
* @param {Object} options - Configuration options.
|
|
14
18
|
* @param {string} [options.name] - Name of the workflow.
|
|
19
|
+
* @param {CallableRegistry|null} [options.callable_registry=null] - Registry for callable objects.
|
|
15
20
|
* @param {boolean} [options.exit_on_error=false] - Whether to exit on error.
|
|
16
21
|
* @param {Array<Step>} [options.steps=[]] - Array of steps to add to the workflow.
|
|
17
22
|
* @param {boolean} [options.throw_on_empty=false] - Whether to throw error if workflow is empty.
|
|
23
|
+
* @param {boolean} [options.use_state_singleton=false] - Deprecated. When true, this workflow (and every
|
|
24
|
+
* `Step` it owns) reads/writes `getState`/`setState`/`deleteState` calls through the process-wide `State`
|
|
25
|
+
* singleton instead of this workflow's own state.
|
|
18
26
|
*/
|
|
19
27
|
constructor({
|
|
20
28
|
name,
|
|
29
|
+
callable_registry = null,
|
|
21
30
|
exit_on_error = false,
|
|
31
|
+
result_per_step = false,
|
|
32
|
+
result_per_step_function = null,
|
|
22
33
|
steps = [],
|
|
23
|
-
throw_on_empty = false
|
|
34
|
+
throw_on_empty = false,
|
|
35
|
+
use_state_singleton = false,
|
|
24
36
|
}) {
|
|
25
|
-
super({ name, base_type: base_types.WORKFLOW });
|
|
26
|
-
|
|
27
|
-
this.initializeWorkflowState();
|
|
28
|
-
|
|
29
|
-
this.addSteps(steps);
|
|
37
|
+
super({ name, base_type: base_types.WORKFLOW, use_state_singleton });
|
|
30
38
|
|
|
39
|
+
this.callable_registry = callable_registry ?? new CallableRegistry();
|
|
40
|
+
this.current_session_id = null;
|
|
31
41
|
this.exit_on_error = exit_on_error;
|
|
32
|
-
this.
|
|
42
|
+
this.result_per_step = result_per_step;
|
|
33
43
|
this.sessions = {};
|
|
34
|
-
this.
|
|
44
|
+
this.throw_on_empty = throw_on_empty;
|
|
45
|
+
this.result_per_step_function = result_per_step_function;
|
|
46
|
+
|
|
47
|
+
// _steps/steps_by_id must exist before initializeWorkflowState(): it reads this._steps
|
|
48
|
+
// (to set current_step) and logs, which serializes `this` - both need this._steps to
|
|
49
|
+
// already be an array, even when no steps are passed (addSteps([]) never calls addStep,
|
|
50
|
+
// so it wouldn't otherwise get initialized).
|
|
51
|
+
this._steps = [];
|
|
52
|
+
this.steps_by_id = {};
|
|
53
|
+
this.addSteps(steps);
|
|
54
|
+
this.initializeWorkflowState();
|
|
35
55
|
}
|
|
36
56
|
|
|
37
57
|
/**
|
|
38
58
|
* Executes the workflow by running all steps in sequence.
|
|
59
|
+
* If the workflow is currently `paused`, resumes from the step after the one
|
|
60
|
+
* that was running when it paused, rather than starting over from the beginning.
|
|
39
61
|
* @async
|
|
40
62
|
* @returns {Promise<Workflow>} The workflow instance with execution results.
|
|
41
63
|
* @throws {Error} Throws if workflow is empty and throw_on_empty is true.
|
|
@@ -51,21 +73,25 @@ export default class Workflow extends Base {
|
|
|
51
73
|
}
|
|
52
74
|
|
|
53
75
|
this.markAsComplete();
|
|
54
|
-
this.prepareResult('Workflow is empty', null);
|
|
76
|
+
await this.prepareResult('Workflow is empty', null);
|
|
55
77
|
return this;
|
|
56
78
|
}
|
|
57
|
-
|
|
79
|
+
|
|
80
|
+
const is_resuming = this.status === statuses.workflow.PAUSED;
|
|
81
|
+
const paused_at_index = this._steps.findIndex(step => step.id === this.current_step);
|
|
82
|
+
const start_index = is_resuming ? paused_at_index + 1 : 0;
|
|
83
|
+
|
|
58
84
|
this.markAsRunning();
|
|
59
85
|
|
|
60
|
-
for (let i =
|
|
86
|
+
for (let i = start_index; i < this._steps.length; i++) {
|
|
61
87
|
if (this.should_break) {
|
|
62
|
-
this.log(
|
|
88
|
+
this.log(event_names.workflow.WORKFLOW_BREAK_EXECUTED, `Workflow "${this.name}" execution broken at step ${this._steps[i].name} - ${this._steps[i].id}.`);
|
|
63
89
|
break;
|
|
64
90
|
}
|
|
65
91
|
|
|
66
92
|
if (this.should_skip) {
|
|
67
93
|
this.log(
|
|
68
|
-
|
|
94
|
+
event_names.workflow.WORKFLOW_STEP_SKIPPED,
|
|
69
95
|
`Workflow "${this.name}" skipping step ${this._steps[i].name} - ${this._steps[i].id}.`
|
|
70
96
|
);
|
|
71
97
|
this.should_skip = false;
|
|
@@ -76,10 +102,10 @@ export default class Workflow extends Base {
|
|
|
76
102
|
|
|
77
103
|
try {
|
|
78
104
|
const step_result = await this.step();
|
|
79
|
-
this.prepareResult('Success', step_result);
|
|
105
|
+
await this.prepareResult('Success', step_result);
|
|
80
106
|
} catch (error) {
|
|
81
107
|
this.markAsFailed();
|
|
82
|
-
this.prepareResult(`Workflow execution failed at step ${this.steps_by_id[this.current_step].name} - ${this.current_step}`, { error });
|
|
108
|
+
await this.prepareResult(`Workflow execution failed at step ${this.steps_by_id[this.current_step].name} - ${this.current_step}`, { error });
|
|
83
109
|
|
|
84
110
|
if (this.exit_on_error) {
|
|
85
111
|
return this;
|
|
@@ -94,7 +120,7 @@ export default class Workflow extends Base {
|
|
|
94
120
|
}
|
|
95
121
|
|
|
96
122
|
this.markAsComplete();
|
|
97
|
-
return this;
|
|
123
|
+
return this.prepareForSerialization();
|
|
98
124
|
}
|
|
99
125
|
|
|
100
126
|
/**
|
|
@@ -106,8 +132,8 @@ export default class Workflow extends Base {
|
|
|
106
132
|
this.should_pause = false;
|
|
107
133
|
this.timing.resume_time = new Date();
|
|
108
134
|
|
|
109
|
-
|
|
110
|
-
|
|
135
|
+
events.workflow.emit(
|
|
136
|
+
event_names.workflow.WORKFLOW_RESUMED,
|
|
111
137
|
this.getState()
|
|
112
138
|
);
|
|
113
139
|
return this.execute();
|
|
@@ -121,10 +147,9 @@ export default class Workflow extends Base {
|
|
|
121
147
|
async step() {
|
|
122
148
|
const step = this.steps_by_id[this.current_step];
|
|
123
149
|
|
|
124
|
-
step.parentWorkflowId = this.id;
|
|
125
150
|
const result = await step.execute();
|
|
126
151
|
|
|
127
|
-
if (step.status ===
|
|
152
|
+
if (step.status === statuses.step.FAILED) {
|
|
128
153
|
throw step.errors[step.errors.length - 1] ?? new Error(`Step "${step.name}" failed`);
|
|
129
154
|
}
|
|
130
155
|
|
|
@@ -137,8 +162,10 @@ export default class Workflow extends Base {
|
|
|
137
162
|
* @throws {Error} Throws if step is not a valid Step instance.
|
|
138
163
|
*/
|
|
139
164
|
addStep(step) {
|
|
165
|
+
// This check only ensures that the getCallableType method exists,
|
|
166
|
+
// which is a characteristic of Step instances
|
|
140
167
|
if (typeof step.getCallableType !== 'function') {
|
|
141
|
-
throw new Error('Invalid
|
|
168
|
+
throw new Error('Invalid input. Must be an instance of Step.');
|
|
142
169
|
}
|
|
143
170
|
|
|
144
171
|
if (!Array.isArray(this._steps)) {
|
|
@@ -151,7 +178,10 @@ export default class Workflow extends Base {
|
|
|
151
178
|
|
|
152
179
|
this.steps_by_id[step.id] = step;
|
|
153
180
|
|
|
154
|
-
step.
|
|
181
|
+
step.parent_workflow_id = this.id;
|
|
182
|
+
step.parent_workflow = this.prepareForSerialization();
|
|
183
|
+
step.use_state_singleton = this.use_state_singleton;
|
|
184
|
+
step.state = this.state;
|
|
155
185
|
this._steps.push(step);
|
|
156
186
|
}
|
|
157
187
|
|
|
@@ -166,7 +196,10 @@ export default class Workflow extends Base {
|
|
|
166
196
|
}
|
|
167
197
|
|
|
168
198
|
this.steps_by_id[step.id] = step;
|
|
169
|
-
step.
|
|
199
|
+
step.parent_workflow_id = this.id;
|
|
200
|
+
step.parent_workflow = this.prepareForSerialization();
|
|
201
|
+
step.use_state_singleton = this.use_state_singleton;
|
|
202
|
+
step.state = this.state;
|
|
170
203
|
this._steps.splice(index, 0, step);
|
|
171
204
|
}
|
|
172
205
|
|
|
@@ -175,6 +208,10 @@ export default class Workflow extends Base {
|
|
|
175
208
|
* @param {Step[]} steps - Array of steps to add.
|
|
176
209
|
*/
|
|
177
210
|
addSteps(steps) {
|
|
211
|
+
if (!Array.isArray(steps)) {
|
|
212
|
+
throw new Error('Invalid input. Must be an array of Step instances.');
|
|
213
|
+
}
|
|
214
|
+
|
|
178
215
|
steps.forEach(step => this.addStep(step));
|
|
179
216
|
}
|
|
180
217
|
|
|
@@ -183,6 +220,7 @@ export default class Workflow extends Base {
|
|
|
183
220
|
*/
|
|
184
221
|
clearSteps() {
|
|
185
222
|
this._steps = [];
|
|
223
|
+
this.steps_by_id = {};
|
|
186
224
|
}
|
|
187
225
|
|
|
188
226
|
/**
|
|
@@ -207,6 +245,10 @@ export default class Workflow extends Base {
|
|
|
207
245
|
* @param {string} stepId - The ID of the step to delete.
|
|
208
246
|
*/
|
|
209
247
|
deleteStep(stepId) {
|
|
248
|
+
if (!Array.isArray(this._steps)) {
|
|
249
|
+
this._steps = [];
|
|
250
|
+
}
|
|
251
|
+
|
|
210
252
|
this._steps = this._steps.filter(step => step.id !== stepId);
|
|
211
253
|
}
|
|
212
254
|
|
|
@@ -215,36 +257,63 @@ export default class Workflow extends Base {
|
|
|
215
257
|
* @param {number} index - The index of the step to delete.
|
|
216
258
|
*/
|
|
217
259
|
deleteStepByIndex(index) {
|
|
260
|
+
if (!Array.isArray(this._steps)) {
|
|
261
|
+
this._steps = [];
|
|
262
|
+
}
|
|
263
|
+
|
|
218
264
|
this._steps.splice(index, 1);
|
|
219
265
|
}
|
|
220
266
|
|
|
267
|
+
/**
|
|
268
|
+
* Resolves a nested property path within this workflow's own state - the low-level counterpart
|
|
269
|
+
* to `getState()`. Falls back to the deprecated `State` singleton's resolver when
|
|
270
|
+
* `use_state_singleton` is `true`.
|
|
271
|
+
* @param {string} path - Path to the state property.
|
|
272
|
+
* @param {boolean} [emit=true] - Only meaningful when `use_state_singleton` is `true`; whether
|
|
273
|
+
* to emit the singleton's `GET_FROM_PROPERTY_PATH` state event.
|
|
274
|
+
* @returns {*} The value at the specified path, or undefined if not found.
|
|
275
|
+
*/
|
|
276
|
+
getStateFromPropertyPath(path, emit = true) {
|
|
277
|
+
if (this.use_state_singleton) {
|
|
278
|
+
console.warn('The state singleton has been deprecated. Use the .prepareForSerialization() method on the workflow instance instead.');
|
|
279
|
+
return State.getFromPropertyPath(path, emit);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return this.state.getStateFromPropertyPath(path);
|
|
283
|
+
}
|
|
284
|
+
|
|
221
285
|
/**
|
|
222
286
|
* Initializes the workflow state with default values.
|
|
223
287
|
*/
|
|
224
288
|
initializeWorkflowState() {
|
|
225
|
-
this.
|
|
226
|
-
this.
|
|
227
|
-
this.
|
|
228
|
-
this.should_break = false;
|
|
229
|
-
this.should_continue = false;
|
|
230
|
-
this.should_pause = false;
|
|
231
|
-
this.should_skip = false;
|
|
232
|
-
this.status = this.
|
|
233
|
-
this._steps = [];
|
|
234
|
-
this.throw_on_empty = this.throw_on_empty;
|
|
289
|
+
this.current_step = ! this.isEmpty() ? this._steps[0].id : null;
|
|
290
|
+
this.results = this.results ?? [];
|
|
291
|
+
this.sessions = this.sessions ?? {};
|
|
292
|
+
this.should_break = this.should_break ?? false;
|
|
293
|
+
this.should_continue = this.should_continue ?? false;
|
|
294
|
+
this.should_pause = this.should_pause ?? false;
|
|
295
|
+
this.should_skip = this.should_skip ?? false;
|
|
296
|
+
this.status = this.status ?? statuses.workflow.CREATED;
|
|
235
297
|
this.timing = {
|
|
236
298
|
...this.timing,
|
|
237
|
-
create_time: new Date(),
|
|
238
|
-
pause_time: null,
|
|
239
|
-
resume_time: null,
|
|
299
|
+
create_time: this.timing?.create_time ?? new Date(),
|
|
300
|
+
pause_time: this.timing?.pause_time ?? null,
|
|
301
|
+
resume_time: this.timing?.resume_time ?? null,
|
|
240
302
|
}
|
|
241
303
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
304
|
+
if (this.use_state_singleton) {
|
|
305
|
+
// The deprecated `State` singleton is shared by every workflow that opts into it, so it
|
|
306
|
+
// still needs an id-keyed registry (unlike per-instance state, which only ever has one
|
|
307
|
+
// workflow to represent and can just reference it directly - see the else branch).
|
|
308
|
+
const workflows = this.getState('workflows');
|
|
309
|
+
workflows[this.id] = this;
|
|
310
|
+
this.setState('workflows', workflows);
|
|
311
|
+
} else {
|
|
312
|
+
this.setState('workflow', this);
|
|
313
|
+
}
|
|
245
314
|
|
|
246
315
|
this.log(
|
|
247
|
-
|
|
316
|
+
event_names.workflow.WORKFLOW_CREATED,
|
|
248
317
|
`Workflow "${this.name}" initialized.`
|
|
249
318
|
);
|
|
250
319
|
}
|
|
@@ -254,7 +323,7 @@ export default class Workflow extends Base {
|
|
|
254
323
|
* @returns {boolean} True if the workflow is empty.
|
|
255
324
|
*/
|
|
256
325
|
isEmpty() {
|
|
257
|
-
return !this._steps || !this._steps.length
|
|
326
|
+
return !Array.isArray(this._steps) || !this._steps.length;
|
|
258
327
|
}
|
|
259
328
|
|
|
260
329
|
/**
|
|
@@ -273,11 +342,11 @@ export default class Workflow extends Base {
|
|
|
273
342
|
this.timing.create_time = new Date();
|
|
274
343
|
|
|
275
344
|
this.log(
|
|
276
|
-
|
|
345
|
+
event_names.workflow.WORKFLOW_CREATED,
|
|
277
346
|
`Workflow "${this.name}" created.`
|
|
278
347
|
);
|
|
279
348
|
|
|
280
|
-
return
|
|
349
|
+
return statuses.workflow.CREATED;
|
|
281
350
|
}
|
|
282
351
|
|
|
283
352
|
/**
|
|
@@ -293,23 +362,23 @@ export default class Workflow extends Base {
|
|
|
293
362
|
*/
|
|
294
363
|
markAsPaused() {
|
|
295
364
|
this.timing.pause_time = new Date();
|
|
296
|
-
this.status =
|
|
365
|
+
this.status = statuses.workflow.PAUSED;
|
|
297
366
|
|
|
298
|
-
|
|
299
|
-
|
|
367
|
+
events.workflow.emit(
|
|
368
|
+
event_names.workflow.WORKFLOW_PAUSED,
|
|
300
369
|
this.getState()
|
|
301
370
|
);
|
|
302
371
|
}
|
|
303
|
-
|
|
372
|
+
|
|
304
373
|
/**
|
|
305
374
|
* Marks the workflow as resumed.
|
|
306
375
|
*/
|
|
307
376
|
markAsResumed() {
|
|
308
377
|
this.timing.resume_time = new Date();
|
|
309
|
-
this.status =
|
|
378
|
+
this.status = statuses.workflow.RUNNING;
|
|
310
379
|
|
|
311
|
-
|
|
312
|
-
|
|
380
|
+
events.workflow.emit(
|
|
381
|
+
event_names.workflow.WORKFLOW_RESUMED,
|
|
313
382
|
this.getState()
|
|
314
383
|
);
|
|
315
384
|
}
|
|
@@ -323,12 +392,22 @@ export default class Workflow extends Base {
|
|
|
323
392
|
const [step] = this._steps.splice(fromIndex, 1);
|
|
324
393
|
this._steps.splice(toIndex, 0, step);
|
|
325
394
|
|
|
326
|
-
|
|
327
|
-
|
|
395
|
+
events.workflow.emit(
|
|
396
|
+
event_names.workflow.WORKFLOW_STEP_MOVED,
|
|
328
397
|
this.getState()
|
|
329
398
|
);
|
|
330
399
|
}
|
|
331
400
|
|
|
401
|
+
/**
|
|
402
|
+
* Parses a property path string into an array of keys, supporting both dot notation and
|
|
403
|
+
* bracket notation (e.g. `"users[0].name"`). Pure utility - not affected by `use_state_singleton`.
|
|
404
|
+
* @param {string} path - The path to parse.
|
|
405
|
+
* @returns {string[]} Array of property keys.
|
|
406
|
+
*/
|
|
407
|
+
parseStatePath(path) {
|
|
408
|
+
return this.use_state_singleton ? State.parsePath(path) : this.state.parseStatePath(path);
|
|
409
|
+
}
|
|
410
|
+
|
|
332
411
|
/**
|
|
333
412
|
* Pauses the workflow execution.
|
|
334
413
|
*/
|
|
@@ -336,8 +415,8 @@ export default class Workflow extends Base {
|
|
|
336
415
|
this.should_pause = true;
|
|
337
416
|
this.timing.pause_time = new Date();
|
|
338
417
|
|
|
339
|
-
|
|
340
|
-
|
|
418
|
+
events.workflow.emit(
|
|
419
|
+
event_names.workflow.WORKFLOW_PAUSED,
|
|
341
420
|
this.getState()
|
|
342
421
|
);
|
|
343
422
|
}
|
|
@@ -350,13 +429,41 @@ export default class Workflow extends Base {
|
|
|
350
429
|
return this._steps.pop();
|
|
351
430
|
}
|
|
352
431
|
|
|
432
|
+
/**
|
|
433
|
+
* Inserts safely serializable properties of the workflow into a new object for serialization.
|
|
434
|
+
* @returns {Object} An object containing the workflow's properties ready for serialization.
|
|
435
|
+
*/
|
|
436
|
+
prepareForSerialization() {
|
|
437
|
+
const serialized_workflow = {
|
|
438
|
+
id: this.id,
|
|
439
|
+
current_session_id: this.current_session_id,
|
|
440
|
+
current_step: this.current_step,
|
|
441
|
+
exit_on_error: this.exit_on_error,
|
|
442
|
+
name: this.name,
|
|
443
|
+
sessions: this.sessions,
|
|
444
|
+
status: this.status,
|
|
445
|
+
steps: this._steps.map(step => step.prepareForSerialization()),
|
|
446
|
+
throw_on_empty: this.throw_on_empty,
|
|
447
|
+
timing: this.timing,
|
|
448
|
+
results: this.results,
|
|
449
|
+
use_state_singleton: this.use_state_singleton,
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
return serialized_workflow;
|
|
453
|
+
}
|
|
454
|
+
|
|
353
455
|
/**
|
|
354
456
|
* Prepares a result object and adds it to the results array.
|
|
355
457
|
* @param {string} message - Result message.
|
|
356
458
|
* @param {*} data - Result data.
|
|
357
459
|
*/
|
|
358
|
-
prepareResult(message, data) {
|
|
359
|
-
this.
|
|
460
|
+
async prepareResult(message, data) {
|
|
461
|
+
if (this.result_per_step && typeof this.result_per_step_function === 'function') {
|
|
462
|
+
await this.result_per_step_function(this.prepareForSerialization());
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const result = { message, data };
|
|
466
|
+
this.results.push(result);
|
|
360
467
|
}
|
|
361
468
|
|
|
362
469
|
/**
|
|
@@ -375,6 +482,33 @@ export default class Workflow extends Base {
|
|
|
375
482
|
steps.forEach(step => this.addStep(step));
|
|
376
483
|
}
|
|
377
484
|
|
|
485
|
+
/**
|
|
486
|
+
* Serializes the workflow into a JSON string.
|
|
487
|
+
* @returns {string} The JSON string representation of the workflow.
|
|
488
|
+
*/
|
|
489
|
+
serialize() {
|
|
490
|
+
return JSON.stringify(this.prepareForSerialization());
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Sets a nested property value within this workflow's own state, creating intermediate
|
|
495
|
+
* objects/arrays as needed - the low-level counterpart to `setState()`. Falls back to the
|
|
496
|
+
* deprecated `State` singleton's setter when `use_state_singleton` is `true`.
|
|
497
|
+
* @param {string} path - Path to the state property.
|
|
498
|
+
* @param {*} value - The value to set at the specified path.
|
|
499
|
+
* @param {boolean} [emit=true] - Only meaningful when `use_state_singleton` is `true`; whether
|
|
500
|
+
* to emit the singleton's `SET_TO_PROPERTY_PATH` state event.
|
|
501
|
+
*/
|
|
502
|
+
setStateToPropertyPath(path, value, emit = true) {
|
|
503
|
+
if (this.use_state_singleton) {
|
|
504
|
+
console.warn('The state singleton has been deprecated. Use the .prepareForSerialization() method on the workflow instance instead.');
|
|
505
|
+
State.setToPropertyPath(path, value, emit);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
this.state.setStateToPropertyPath(path, value);
|
|
510
|
+
}
|
|
511
|
+
|
|
378
512
|
/**
|
|
379
513
|
* Removes and returns the first step from the workflow.
|
|
380
514
|
* @returns {Step} The first step.
|
|
@@ -399,10 +533,20 @@ export default class Workflow extends Base {
|
|
|
399
533
|
|
|
400
534
|
this.steps_by_id[step.id] = step;
|
|
401
535
|
|
|
402
|
-
step.
|
|
536
|
+
step.parent_workflow_id = this.id;
|
|
537
|
+
step.use_state_singleton = this.use_state_singleton;
|
|
538
|
+
step.state = this.state;
|
|
403
539
|
this._steps.unshift(step);
|
|
404
540
|
}
|
|
405
541
|
|
|
542
|
+
/**
|
|
543
|
+
* Custom JSON serializer
|
|
544
|
+
* @returns {Object} The JSON representation of the workflow.
|
|
545
|
+
*/
|
|
546
|
+
toJSON() {
|
|
547
|
+
return this.prepareForSerialization();
|
|
548
|
+
}
|
|
549
|
+
|
|
406
550
|
/**
|
|
407
551
|
* Gets the array of steps in the workflow.
|
|
408
552
|
* @returns {Step[]} Array of steps.
|
|
@@ -416,12 +560,86 @@ export default class Workflow extends Base {
|
|
|
416
560
|
* @param {Step[]} steps - Array of steps to add.
|
|
417
561
|
*/
|
|
418
562
|
set steps(steps) {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
563
|
+
this.addSteps(steps);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Deserializes a JSON string into a Workflow instance and hydrates it.
|
|
568
|
+
* @param {string} serialized_workflow - The JSON string representation of the workflow.
|
|
569
|
+
* @param {CallableRegistry|null} [callable_registry] - Registry used to resolve function callables in the workflow's steps.
|
|
570
|
+
* @returns {Workflow} The hydrated Workflow instance.
|
|
571
|
+
* @throws {Error} Throws if the serialized workflow is not a string.
|
|
572
|
+
*/
|
|
573
|
+
static hydrateSerialized(serialized_workflow, callable_registry = null) {
|
|
574
|
+
// TODO: Validate structure of serialized workflow
|
|
575
|
+
if (typeof serialized_workflow !== 'string') {
|
|
576
|
+
throw new Error('Invalid serialized workflow. Must be a string.');
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const parsed = JSON.parse(serialized_workflow);
|
|
580
|
+
|
|
581
|
+
return Workflow.hydrate(parsed, callable_registry);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Hydrates a parsed workflow object into a Workflow instance.
|
|
586
|
+
* @param {Object} parsed_workflow - The parsed workflow object.
|
|
587
|
+
* @param {CallableRegistry|null} [callable_registry] - Registry used to resolve function callables in the workflow's steps.
|
|
588
|
+
* @returns {Workflow} The hydrated Workflow instance.
|
|
589
|
+
* @throws {Error} Throws if the parsed workflow is not a valid object.
|
|
590
|
+
*/
|
|
591
|
+
static hydrate(parsed_workflow, callable_registry = null) {
|
|
592
|
+
// TODO: Validate structure of serialized workflow
|
|
593
|
+
// TODO: Use event system to handle errors?
|
|
594
|
+
if (typeof parsed_workflow !== 'object' || parsed_workflow === null) {
|
|
595
|
+
throw new Error('Invalid parsed workflow. Must be a valid object.');
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const hydrated_workflow = new Workflow({
|
|
599
|
+
name: parsed_workflow.name,
|
|
600
|
+
callable_registry,
|
|
601
|
+
exit_on_error: parsed_workflow.exit_on_error,
|
|
602
|
+
steps: parsed_workflow.steps.map(step => Step.hydrateAny(step, callable_registry)),
|
|
603
|
+
throw_on_empty: parsed_workflow.throw_on_empty,
|
|
604
|
+
use_state_singleton: parsed_workflow.use_state_singleton ?? false,
|
|
423
605
|
});
|
|
424
606
|
|
|
425
|
-
|
|
607
|
+
// The constructor above (via Base) always generates a fresh id, and addStep() has
|
|
608
|
+
// already stamped that fresh id onto each step's parent_workflow_id and (in singleton mode)
|
|
609
|
+
// registered the workflow under it in the singleton's `workflows` registry. Restoring the
|
|
610
|
+
// real id below would otherwise leave both referencing a discarded id, so fix them up here
|
|
611
|
+
// too. Per-instance state needs no such fixup for its `workflow` key - it's the same live
|
|
612
|
+
// object, so the id mutation below is reflected automatically.
|
|
613
|
+
const stale_id = hydrated_workflow.id;
|
|
614
|
+
hydrated_workflow.id = parsed_workflow.id;
|
|
615
|
+
|
|
616
|
+
if (hydrated_workflow.use_state_singleton) {
|
|
617
|
+
const workflows = hydrated_workflow.getState('workflows');
|
|
618
|
+
delete workflows[stale_id];
|
|
619
|
+
workflows[hydrated_workflow.id] = hydrated_workflow;
|
|
620
|
+
hydrated_workflow.setState('workflows', workflows);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
hydrated_workflow.steps.forEach(step => {
|
|
624
|
+
step.parent_workflow_id = hydrated_workflow.id;
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
hydrated_workflow.current_session_id = parsed_workflow.current_session_id;
|
|
628
|
+
hydrated_workflow.current_step = parsed_workflow.current_step ?? hydrated_workflow.current_step;
|
|
629
|
+
hydrated_workflow.sessions = parsed_workflow.sessions ?? {};
|
|
630
|
+
hydrated_workflow.status = parsed_workflow.status;
|
|
631
|
+
hydrated_workflow.timing = parsed_workflow.timing;
|
|
632
|
+
hydrated_workflow.results = parsed_workflow.results;
|
|
633
|
+
|
|
634
|
+
return hydrated_workflow;
|
|
426
635
|
}
|
|
427
636
|
}
|
|
637
|
+
|
|
638
|
+
// Framework constants, exposed as static members rather than duplicated into every
|
|
639
|
+
// Workflow's own state (see instance_state.js, the canonical source for these values).
|
|
640
|
+
Workflow.statuses = statuses;
|
|
641
|
+
Workflow.event_names = event_names;
|
|
642
|
+
Workflow.events = events;
|
|
643
|
+
Workflow.types = types;
|
|
644
|
+
Workflow.conditional_step_comparators = conditional_step_comparators;
|
|
645
|
+
Workflow.messages = messages;
|
package/src/enums/delay_types.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @enum {string}
|
|
6
6
|
* @readonly
|
|
7
7
|
*/
|
|
8
|
-
const
|
|
8
|
+
const logic_step_types = {
|
|
9
9
|
CONDITIONAL: 'conditional',
|
|
10
10
|
LOOP: 'loop',
|
|
11
11
|
FLOW_CONTROL: 'flow_control',
|
|
@@ -13,4 +13,4 @@ const LogicStepTypes = {
|
|
|
13
13
|
SKIP: 'skip'
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
export default
|
|
16
|
+
export default logic_step_types;
|
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
* @type {Object.<string, string>}
|
|
4
4
|
* @readonly
|
|
5
5
|
* @example
|
|
6
|
-
* console.log(sub_step_types.
|
|
7
|
-
* console.log(sub_step_types.
|
|
6
|
+
* console.log(sub_step_types.step); // "step"
|
|
7
|
+
* console.log(sub_step_types.conditional_step); // "conditional"
|
|
8
8
|
*/
|
|
9
9
|
const sub_step_types = {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
step: 'step',
|
|
11
|
+
logic_step: 'logic',
|
|
12
|
+
conditional_step: 'conditional',
|
|
13
|
+
flow_control_step: 'flow_control',
|
|
14
|
+
loop_step: 'loop',
|
|
15
|
+
switch_step: 'switch',
|
|
16
|
+
case: 'case',
|
|
17
|
+
delay_step: 'delay',
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
export default sub_step_types;
|