@ronaldroe/micro-flow 2.0.0 → 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.
Files changed (34) hide show
  1. package/dist/src/classes/base.js +2 -2
  2. package/dist/src/classes/base.js.map +2 -2
  3. package/dist/src/classes/index.js +1 -1
  4. package/dist/src/classes/index.js.map +2 -2
  5. package/dist/src/classes/instance_state.js +2 -0
  6. package/dist/src/classes/instance_state.js.map +7 -0
  7. package/dist/src/classes/state.js +1 -1
  8. package/dist/src/classes/state.js.map +3 -3
  9. package/dist/src/classes/steps/conditional_step.js +1 -1
  10. package/dist/src/classes/steps/conditional_step.js.map +3 -3
  11. package/dist/src/classes/steps/delay_step.js +1 -1
  12. package/dist/src/classes/steps/delay_step.js.map +3 -3
  13. package/dist/src/classes/steps/flow_control_step.js +1 -1
  14. package/dist/src/classes/steps/flow_control_step.js.map +3 -3
  15. package/dist/src/classes/steps/logic_step.js +1 -1
  16. package/dist/src/classes/steps/logic_step.js.map +3 -3
  17. package/dist/src/classes/steps/step.js +1 -1
  18. package/dist/src/classes/steps/step.js.map +2 -2
  19. package/dist/src/classes/steps/switch_step.js +1 -1
  20. package/dist/src/classes/steps/switch_step.js.map +3 -3
  21. package/dist/src/classes/workflow.js +1 -1
  22. package/dist/src/classes/workflow.js.map +3 -3
  23. package/package.json +1 -1
  24. package/src/classes/base.js +2 -1
  25. package/src/classes/index.js +2 -1
  26. package/src/classes/instance_state.js +277 -0
  27. package/src/classes/state.js +14 -313
  28. package/src/classes/steps/conditional_step.js +3 -2
  29. package/src/classes/steps/delay_step.js +5 -8
  30. package/src/classes/steps/flow_control_step.js +3 -2
  31. package/src/classes/steps/logic_step.js +38 -38
  32. package/src/classes/steps/step.js +12 -6
  33. package/src/classes/steps/switch_step.js +2 -1
  34. package/src/classes/workflow.js +52 -31
@@ -1,6 +1,7 @@
1
1
  import LogicStep from './logic_step.js';
2
2
  import flow_control_types from '../../enums/flow_control_types.js';
3
3
  import { conditional_step_comparators } from '../../enums/index.js';
4
+ import { event_names } from '../instance_state.js';
4
5
 
5
6
  /**
6
7
  * FlowControlStep class for controlling workflow execution flow (break or skip).
@@ -51,7 +52,7 @@ export default class FlowControlStep extends LogicStep {
51
52
  async shouldFlowControl() {
52
53
  if (this.checkCondition()) {
53
54
  this.log(
54
- this.getState('events.step.event_names.CONDITIONAL_TRUE_BRANCH_EXECUTED'),
55
+ event_names.step.CONDITIONAL_TRUE_BRANCH_EXECUTED,
55
56
  `Break condition met for step: ${this.name}`
56
57
  );
57
58
  this.setParentWorkflowValue(this.parent_workflow_id, `should_${this.flow_control_type}`, true);
@@ -59,7 +60,7 @@ export default class FlowControlStep extends LogicStep {
59
60
  return true;
60
61
  } else {
61
62
  this.log(
62
- this.getState('events.step.event_names.CONDITIONAL_FALSE_BRANCH_EXECUTED'),
63
+ event_names.step.CONDITIONAL_FALSE_BRANCH_EXECUTED,
63
64
  `Break condition not met for step: ${this.name}`
64
65
  );
65
66
  this.setParentWorkflowValue(this.parent_workflow_id, `should_${this.flow_control_type}`, false);
@@ -55,77 +55,77 @@ export default class LogicStep extends Step {
55
55
  const subject = typeof raw_subject === 'function' ? raw_subject() : raw_subject;
56
56
 
57
57
  // Don't resolve value for CUSTOM_FUNCTION - the value IS the function to call
58
- const is_custom_function = operator === this.getState('conditional_step_comparators.CUSTOM_FUNCTION');
58
+ const is_custom_function = operator === conditional_step_comparators.CUSTOM_FUNCTION;
59
59
  const value = (!is_custom_function && typeof raw_value === 'function') ? raw_value() : raw_value;
60
60
 
61
61
  switch (operator) {
62
- case this.getState('conditional_step_comparators.STRICT_EQUALS'):
63
- case this.getState('conditional_step_comparators.SIGN_STRICT_EQUALS'):
62
+ case conditional_step_comparators.STRICT_EQUALS:
63
+ case conditional_step_comparators.SIGN_STRICT_EQUALS:
64
64
  return subject === value;
65
- case this.getState('conditional_step_comparators.SIGN_EQUALS'):
66
- case this.getState('conditional_step_comparators.EQUALS'):
65
+ case conditional_step_comparators.SIGN_EQUALS:
66
+ case conditional_step_comparators.EQUALS:
67
67
  return subject == value;
68
- case this.getState('conditional_step_comparators.NOT_EQUALS'):
69
- case this.getState('conditional_step_comparators.SIGN_NOT_EQUALS'):
68
+ case conditional_step_comparators.NOT_EQUALS:
69
+ case conditional_step_comparators.SIGN_NOT_EQUALS:
70
70
  return subject != value;
71
- case this.getState('conditional_step_comparators.STRICT_NOT_EQUALS'):
72
- case this.getState('conditional_step_comparators.SIGN_STRICT_NOT_EQUALS'):
71
+ case conditional_step_comparators.STRICT_NOT_EQUALS:
72
+ case conditional_step_comparators.SIGN_STRICT_NOT_EQUALS:
73
73
  return subject !== value;
74
- case this.getState('conditional_step_comparators.GREATER_THAN'):
75
- case this.getState('conditional_step_comparators.SIGN_GREATER_THAN'):
74
+ case conditional_step_comparators.GREATER_THAN:
75
+ case conditional_step_comparators.SIGN_GREATER_THAN:
76
76
  return subject > value;
77
- case this.getState('conditional_step_comparators.LESS_THAN'):
78
- case this.getState('conditional_step_comparators.SIGN_LESS_THAN'):
77
+ case conditional_step_comparators.LESS_THAN:
78
+ case conditional_step_comparators.SIGN_LESS_THAN:
79
79
  return subject < value;
80
- case this.getState('conditional_step_comparators.GREATER_THAN_OR_EQUAL'):
81
- case this.getState('conditional_step_comparators.SIGN_GREATER_THAN_OR_EQUAL'):
80
+ case conditional_step_comparators.GREATER_THAN_OR_EQUAL:
81
+ case conditional_step_comparators.SIGN_GREATER_THAN_OR_EQUAL:
82
82
  return subject >= value;
83
- case this.getState('conditional_step_comparators.LESS_THAN_OR_EQUAL'):
84
- case this.getState('conditional_step_comparators.SIGN_LESS_THAN_OR_EQUAL'):
83
+ case conditional_step_comparators.LESS_THAN_OR_EQUAL:
84
+ case conditional_step_comparators.SIGN_LESS_THAN_OR_EQUAL:
85
85
  return subject <= value;
86
- case this.getState('conditional_step_comparators.STRING_CONTAINS'):
87
- case this.getState('conditional_step_comparators.STRING_INCLUDES'):
88
- case this.getState('conditional_step_comparators.ARRAY_CONTAINS'):
89
- case this.getState('conditional_step_comparators.ARRAY_INCLUDES'):
86
+ case conditional_step_comparators.STRING_CONTAINS:
87
+ case conditional_step_comparators.STRING_INCLUDES:
88
+ case conditional_step_comparators.ARRAY_CONTAINS:
89
+ case conditional_step_comparators.ARRAY_INCLUDES:
90
90
  return (Array.isArray(subject) || typeof subject === 'string') && subject.includes(value);
91
- case this.getState('conditional_step_comparators.IN'):
91
+ case conditional_step_comparators.IN:
92
92
  return (Array.isArray(value) || typeof value === 'string') && value.includes(subject);
93
- case this.getState('conditional_step_comparators.STRING_NOT_CONTAINS'):
94
- case this.getState('conditional_step_comparators.STRING_NOT_INCLUDES'):
95
- case this.getState('conditional_step_comparators.ARRAY_NOT_CONTAINS'):
96
- case this.getState('conditional_step_comparators.ARRAY_NOT_INCLUDES'):
93
+ case conditional_step_comparators.STRING_NOT_CONTAINS:
94
+ case conditional_step_comparators.STRING_NOT_INCLUDES:
95
+ case conditional_step_comparators.ARRAY_NOT_CONTAINS:
96
+ case conditional_step_comparators.ARRAY_NOT_INCLUDES:
97
97
  return (Array.isArray(subject) || typeof subject === 'string') && !subject.includes(value);
98
- case this.getState('conditional_step_comparators.NOT_IN'):
98
+ case conditional_step_comparators.NOT_IN:
99
99
  return (Array.isArray(value) || typeof value === 'string') && !value.includes(subject);
100
- case this.getState('conditional_step_comparators.EMPTY'):
100
+ case conditional_step_comparators.EMPTY:
101
101
  return subject === '' || subject === null || subject === undefined || subject.length === 0;
102
- case this.getState('conditional_step_comparators.NOT_EMPTY'):
102
+ case conditional_step_comparators.NOT_EMPTY:
103
103
  return subject !== '' && subject !== null && subject !== undefined && subject.length > 0;
104
- case this.getState('conditional_step_comparators.REGEX_MATCH'):
104
+ case conditional_step_comparators.REGEX_MATCH:
105
105
  if (typeof value !== 'string') {
106
106
  throw new Error(`Regex input must be a string.`);
107
107
  }
108
108
  const regex = new RegExp(value);
109
109
  return regex.test(subject);
110
- case this.getState('conditional_step_comparators.REGEX_NOT_MATCH'):
110
+ case conditional_step_comparators.REGEX_NOT_MATCH:
111
111
  if (typeof value !== 'string') {
112
112
  throw new Error(`Regex input must be a string.`);
113
113
  }
114
114
  const not_match_regex = new RegExp(value);
115
115
  return !not_match_regex.test(subject);
116
- case this.getState('conditional_step_comparators.STRING_STARTS_WITH'):
116
+ case conditional_step_comparators.STRING_STARTS_WITH:
117
117
  return typeof subject === 'string' && typeof value === 'string' && subject.startsWith(value);
118
- case this.getState('conditional_step_comparators.STRING_ENDS_WITH'):
118
+ case conditional_step_comparators.STRING_ENDS_WITH:
119
119
  return typeof subject === 'string' && typeof value === 'string' && subject.endsWith(value);
120
- case this.getState('conditional_step_comparators.NULLISH'):
120
+ case conditional_step_comparators.NULLISH:
121
121
  return subject === null || subject === undefined;
122
- case this.getState('conditional_step_comparators.NOT_NULLISH'):
122
+ case conditional_step_comparators.NOT_NULLISH:
123
123
  return subject !== null && subject !== undefined;
124
- case this.getState('conditional_step_comparators.IS_TYPE'):
124
+ case conditional_step_comparators.IS_TYPE:
125
125
  return typeof subject === value;
126
- case this.getState('conditional_step_comparators.IS_NOT_TYPE'):
126
+ case conditional_step_comparators.IS_NOT_TYPE:
127
127
  return typeof subject !== value;
128
- case this.getState('conditional_step_comparators.CUSTOM_FUNCTION'):
128
+ case conditional_step_comparators.CUSTOM_FUNCTION:
129
129
  if (typeof value !== 'function') {
130
130
  throw new Error(`Invalid custom function: ${value}`);
131
131
  }
@@ -101,7 +101,7 @@ export default class Step extends Base {
101
101
  }
102
102
  }
103
103
 
104
- const { FAILED, COMPLETE } = this.getState('statuses')[this.base_type];
104
+ const { FAILED, COMPLETE } = Workflow.statuses[this.base_type];
105
105
 
106
106
  if (![FAILED, COMPLETE].includes(this.status)) {
107
107
  this.markAsComplete();
@@ -264,16 +264,22 @@ export default class Step extends Base {
264
264
  }
265
265
 
266
266
  /**
267
- * Sets a value in the parent workflow's state.
267
+ * Sets a value on the parent workflow instance itself. Normally this is the live object
268
+ * shared via this step's own state under the `workflow` key (see `initializeWorkflowState()`
269
+ * in `workflow.js`); when `use_state_singleton` is `true`, several unrelated workflows can
270
+ * share the same process-wide state, so it's looked up by id in the singleton's `workflows`
271
+ * registry instead.
268
272
  * @param {string} workflow_id - ID of the parent workflow.
269
- * @param {string} path - Path in the workflow state to set.
273
+ * @param {string} path - Property name to set on the workflow instance.
270
274
  * @param {*} value - Value to set at the specified path.
271
- * @throws {Error} Throws if parent workflow is not found.
275
+ * @throws {Error} Throws if the parent workflow is not found.
272
276
  */
273
277
  setParentWorkflowValue(workflow_id, path, value) {
274
- const parent_workflow = this.getState('workflows')[workflow_id];
278
+ const parent_workflow = this.use_state_singleton
279
+ ? this.getState('workflows')[workflow_id]
280
+ : this.getState('workflow');
275
281
 
276
- if (!parent_workflow) {
282
+ if (!parent_workflow || parent_workflow.id !== workflow_id) {
277
283
  throw new Error(`Parent workflow with ID ${workflow_id} not found.`);
278
284
  }
279
285
 
@@ -1,4 +1,5 @@
1
1
  import Step from './step.js';
2
+ import { event_names } from '../instance_state.js';
2
3
 
3
4
  /**
4
5
  * SwitchStep class for implementing switch/case logic in workflows.
@@ -67,7 +68,7 @@ export default class SwitchStep extends Step {
67
68
 
68
69
  if (is_matched) {
69
70
  this.log(
70
- this.getState('events.step.event_names.SWITCH_CASE_MATCHED'),
71
+ event_names.step.SWITCH_CASE_MATCHED,
71
72
  `Case matched for step: ${this.name}, executing case callable`
72
73
  );
73
74
 
@@ -3,6 +3,7 @@ import Base from './base.js';
3
3
  import CallableRegistry from './callable_registry.js';
4
4
  import Step from './steps/step.js';
5
5
  import State from './state.js';
6
+ import { statuses, event_names, events, types, conditional_step_comparators, messages } from './instance_state.js';
6
7
  import { base_types } from '../enums/index.js';
7
8
 
8
9
  /**
@@ -76,7 +77,7 @@ export default class Workflow extends Base {
76
77
  return this;
77
78
  }
78
79
 
79
- const is_resuming = this.status === this.getState('statuses.workflow').PAUSED;
80
+ const is_resuming = this.status === statuses.workflow.PAUSED;
80
81
  const paused_at_index = this._steps.findIndex(step => step.id === this.current_step);
81
82
  const start_index = is_resuming ? paused_at_index + 1 : 0;
82
83
 
@@ -84,13 +85,13 @@ export default class Workflow extends Base {
84
85
 
85
86
  for (let i = start_index; i < this._steps.length; i++) {
86
87
  if (this.should_break) {
87
- this.log(this.getState('event_names.workflow').WORKFLOW_BREAK_EXECUTED, `Workflow "${this.name}" execution broken at step ${this._steps[i].name} - ${this._steps[i].id}.`);
88
+ this.log(event_names.workflow.WORKFLOW_BREAK_EXECUTED, `Workflow "${this.name}" execution broken at step ${this._steps[i].name} - ${this._steps[i].id}.`);
88
89
  break;
89
90
  }
90
91
 
91
92
  if (this.should_skip) {
92
93
  this.log(
93
- this.getState('events.workflow.event_names.WORKFLOW_STEP_SKIPPED'),
94
+ event_names.workflow.WORKFLOW_STEP_SKIPPED,
94
95
  `Workflow "${this.name}" skipping step ${this._steps[i].name} - ${this._steps[i].id}.`
95
96
  );
96
97
  this.should_skip = false;
@@ -131,8 +132,8 @@ export default class Workflow extends Base {
131
132
  this.should_pause = false;
132
133
  this.timing.resume_time = new Date();
133
134
 
134
- this.getState('events.workflow').emit(
135
- this.getState('event_names.workflow').WORKFLOW_RESUMED,
135
+ events.workflow.emit(
136
+ event_names.workflow.WORKFLOW_RESUMED,
136
137
  this.getState()
137
138
  );
138
139
  return this.execute();
@@ -148,7 +149,7 @@ export default class Workflow extends Base {
148
149
 
149
150
  const result = await step.execute();
150
151
 
151
- if (step.status === this.getState('statuses.step.FAILED')) {
152
+ if (step.status === statuses.step.FAILED) {
152
153
  throw step.errors[step.errors.length - 1] ?? new Error(`Step "${step.name}" failed`);
153
154
  }
154
155
 
@@ -292,7 +293,7 @@ export default class Workflow extends Base {
292
293
  this.should_continue = this.should_continue ?? false;
293
294
  this.should_pause = this.should_pause ?? false;
294
295
  this.should_skip = this.should_skip ?? false;
295
- this.status = this.status ?? this.getState('statuses.workflow').CREATED;
296
+ this.status = this.status ?? statuses.workflow.CREATED;
296
297
  this.timing = {
297
298
  ...this.timing,
298
299
  create_time: this.timing?.create_time ?? new Date(),
@@ -300,12 +301,19 @@ export default class Workflow extends Base {
300
301
  resume_time: this.timing?.resume_time ?? null,
301
302
  }
302
303
 
303
- const workflows = this.getState('workflows');
304
- workflows[this.id] = this;
305
- this.setState('workflows', workflows);
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
+ }
306
314
 
307
315
  this.log(
308
- this.getState('event_names.workflow').WORKFLOW_CREATED,
316
+ event_names.workflow.WORKFLOW_CREATED,
309
317
  `Workflow "${this.name}" initialized.`
310
318
  );
311
319
  }
@@ -334,11 +342,11 @@ export default class Workflow extends Base {
334
342
  this.timing.create_time = new Date();
335
343
 
336
344
  this.log(
337
- this.getState('event_names.workflow').WORKFLOW_CREATED,
345
+ event_names.workflow.WORKFLOW_CREATED,
338
346
  `Workflow "${this.name}" created.`
339
347
  );
340
348
 
341
- return this.getState('statuses.workflow').CREATED;
349
+ return statuses.workflow.CREATED;
342
350
  }
343
351
 
344
352
  /**
@@ -354,23 +362,23 @@ export default class Workflow extends Base {
354
362
  */
355
363
  markAsPaused() {
356
364
  this.timing.pause_time = new Date();
357
- this.status = this.getState('statuses.workflow').PAUSED;
365
+ this.status = statuses.workflow.PAUSED;
358
366
 
359
- this.getState('events.workflow').emit(
360
- this.getState('event_names.workflow').WORKFLOW_PAUSED,
367
+ events.workflow.emit(
368
+ event_names.workflow.WORKFLOW_PAUSED,
361
369
  this.getState()
362
370
  );
363
371
  }
364
-
372
+
365
373
  /**
366
374
  * Marks the workflow as resumed.
367
375
  */
368
376
  markAsResumed() {
369
377
  this.timing.resume_time = new Date();
370
- this.status = this.getState('statuses.workflow').RUNNING;
378
+ this.status = statuses.workflow.RUNNING;
371
379
 
372
- this.getState('events.workflow').emit(
373
- this.getState('event_names.workflow').WORKFLOW_RESUMED,
380
+ events.workflow.emit(
381
+ event_names.workflow.WORKFLOW_RESUMED,
374
382
  this.getState()
375
383
  );
376
384
  }
@@ -384,8 +392,8 @@ export default class Workflow extends Base {
384
392
  const [step] = this._steps.splice(fromIndex, 1);
385
393
  this._steps.splice(toIndex, 0, step);
386
394
 
387
- this.getState('events.workflow').emit(
388
- this.getState('event_names.workflow').WORKFLOW_STEP_MOVED,
395
+ events.workflow.emit(
396
+ event_names.workflow.WORKFLOW_STEP_MOVED,
389
397
  this.getState()
390
398
  );
391
399
  }
@@ -407,8 +415,8 @@ export default class Workflow extends Base {
407
415
  this.should_pause = true;
408
416
  this.timing.pause_time = new Date();
409
417
 
410
- this.getState('events.workflow').emit(
411
- this.getState('event_names.workflow').WORKFLOW_PAUSED,
418
+ events.workflow.emit(
419
+ event_names.workflow.WORKFLOW_PAUSED,
412
420
  this.getState()
413
421
  );
414
422
  }
@@ -597,16 +605,20 @@ export default class Workflow extends Base {
597
605
  });
598
606
 
599
607
  // The constructor above (via Base) always generates a fresh id, and addStep() has
600
- // already stamped that fresh id onto each step's parent_workflow_id and registered
601
- // the workflow under it in its own state's `workflows` registry. Restoring the real id
602
- // below would otherwise leave both of those referencing a discarded id, so fix them up here too.
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.
603
613
  const stale_id = hydrated_workflow.id;
604
614
  hydrated_workflow.id = parsed_workflow.id;
605
615
 
606
- const workflows = hydrated_workflow.getState('workflows');
607
- delete workflows[stale_id];
608
- workflows[hydrated_workflow.id] = hydrated_workflow;
609
- hydrated_workflow.setState('workflows', workflows);
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
+ }
610
622
 
611
623
  hydrated_workflow.steps.forEach(step => {
612
624
  step.parent_workflow_id = hydrated_workflow.id;
@@ -622,3 +634,12 @@ export default class Workflow extends Base {
622
634
  return hydrated_workflow;
623
635
  }
624
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;