@ronaldroe/micro-flow 0.0.9

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 (110) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +527 -0
  3. package/dist/index.js +2 -0
  4. package/dist/index.js.map +7 -0
  5. package/dist/src/classes/base.js +4 -0
  6. package/dist/src/classes/base.js.map +7 -0
  7. package/dist/src/classes/events/broadcast.js +2 -0
  8. package/dist/src/classes/events/broadcast.js.map +7 -0
  9. package/dist/src/classes/events/event.js +2 -0
  10. package/dist/src/classes/events/event.js.map +7 -0
  11. package/dist/src/classes/events/index.js +2 -0
  12. package/dist/src/classes/events/index.js.map +7 -0
  13. package/dist/src/classes/events/state_event.js +2 -0
  14. package/dist/src/classes/events/state_event.js.map +7 -0
  15. package/dist/src/classes/events/step_event.js +2 -0
  16. package/dist/src/classes/events/step_event.js.map +7 -0
  17. package/dist/src/classes/events/workflow_event.js +2 -0
  18. package/dist/src/classes/events/workflow_event.js.map +7 -0
  19. package/dist/src/classes/index.js +2 -0
  20. package/dist/src/classes/index.js.map +7 -0
  21. package/dist/src/classes/state.js +2 -0
  22. package/dist/src/classes/state.js.map +7 -0
  23. package/dist/src/classes/steps/case.js +2 -0
  24. package/dist/src/classes/steps/case.js.map +7 -0
  25. package/dist/src/classes/steps/conditional_step.js +2 -0
  26. package/dist/src/classes/steps/conditional_step.js.map +7 -0
  27. package/dist/src/classes/steps/delay_step.js +2 -0
  28. package/dist/src/classes/steps/delay_step.js.map +7 -0
  29. package/dist/src/classes/steps/flow_control_step.js +2 -0
  30. package/dist/src/classes/steps/flow_control_step.js.map +7 -0
  31. package/dist/src/classes/steps/index.js +2 -0
  32. package/dist/src/classes/steps/index.js.map +7 -0
  33. package/dist/src/classes/steps/logic_step.js +2 -0
  34. package/dist/src/classes/steps/logic_step.js.map +7 -0
  35. package/dist/src/classes/steps/loop_step.js +2 -0
  36. package/dist/src/classes/steps/loop_step.js.map +7 -0
  37. package/dist/src/classes/steps/step.js +2 -0
  38. package/dist/src/classes/steps/step.js.map +7 -0
  39. package/dist/src/classes/steps/switch_step.js +2 -0
  40. package/dist/src/classes/steps/switch_step.js.map +7 -0
  41. package/dist/src/classes/workflow.js +2 -0
  42. package/dist/src/classes/workflow.js.map +7 -0
  43. package/dist/src/classes/workflow.test.js +2 -0
  44. package/dist/src/classes/workflow.test.js.map +7 -0
  45. package/dist/src/enums/base_types.js +2 -0
  46. package/dist/src/enums/base_types.js.map +7 -0
  47. package/dist/src/enums/conditional_step_comparators.js +2 -0
  48. package/dist/src/enums/conditional_step_comparators.js.map +7 -0
  49. package/dist/src/enums/delay_types.js +2 -0
  50. package/dist/src/enums/delay_types.js.map +7 -0
  51. package/dist/src/enums/errors.js +6 -0
  52. package/dist/src/enums/errors.js.map +7 -0
  53. package/dist/src/enums/flow_control_types.js +2 -0
  54. package/dist/src/enums/flow_control_types.js.map +7 -0
  55. package/dist/src/enums/index.js +2 -0
  56. package/dist/src/enums/index.js.map +7 -0
  57. package/dist/src/enums/logic_step_types.js +2 -0
  58. package/dist/src/enums/logic_step_types.js.map +7 -0
  59. package/dist/src/enums/loop_types.js +2 -0
  60. package/dist/src/enums/loop_types.js.map +7 -0
  61. package/dist/src/enums/state_event_names.js +2 -0
  62. package/dist/src/enums/state_event_names.js.map +7 -0
  63. package/dist/src/enums/step_event_names.js +2 -0
  64. package/dist/src/enums/step_event_names.js.map +7 -0
  65. package/dist/src/enums/step_statuses.js +2 -0
  66. package/dist/src/enums/step_statuses.js.map +7 -0
  67. package/dist/src/enums/step_types.js +2 -0
  68. package/dist/src/enums/step_types.js.map +7 -0
  69. package/dist/src/enums/sub_step_types.js +2 -0
  70. package/dist/src/enums/sub_step_types.js.map +7 -0
  71. package/dist/src/enums/workflow_event_names.js +2 -0
  72. package/dist/src/enums/workflow_event_names.js.map +7 -0
  73. package/dist/src/enums/workflow_statuses.js +2 -0
  74. package/dist/src/enums/workflow_statuses.js.map +7 -0
  75. package/package.json +58 -0
  76. package/src/classes/base.js +143 -0
  77. package/src/classes/events/broadcast.js +57 -0
  78. package/src/classes/events/event.js +144 -0
  79. package/src/classes/events/index.js +5 -0
  80. package/src/classes/events/state_event.js +28 -0
  81. package/src/classes/events/step_event.js +28 -0
  82. package/src/classes/events/workflow_event.js +28 -0
  83. package/src/classes/index.js +5 -0
  84. package/src/classes/state.js +301 -0
  85. package/src/classes/steps/case.js +70 -0
  86. package/src/classes/steps/conditional_step.js +90 -0
  87. package/src/classes/steps/delay_step.js +103 -0
  88. package/src/classes/steps/flow_control_step.js +70 -0
  89. package/src/classes/steps/index.js +8 -0
  90. package/src/classes/steps/logic_step.js +148 -0
  91. package/src/classes/steps/loop_step.js +147 -0
  92. package/src/classes/steps/step.js +129 -0
  93. package/src/classes/steps/switch_step.js +64 -0
  94. package/src/classes/workflow.js +383 -0
  95. package/src/enums/base_types.js +6 -0
  96. package/src/enums/conditional_step_comparators.js +57 -0
  97. package/src/enums/delay_types.js +31 -0
  98. package/src/enums/errors.js +27 -0
  99. package/src/enums/flow_control_types.js +12 -0
  100. package/src/enums/index.js +14 -0
  101. package/src/enums/logic_step_types.js +16 -0
  102. package/src/enums/loop_types.js +14 -0
  103. package/src/enums/state_event_names.js +17 -0
  104. package/src/enums/step_event_names.js +25 -0
  105. package/src/enums/step_statuses.js +17 -0
  106. package/src/enums/step_types.js +15 -0
  107. package/src/enums/sub_step_types.js +20 -0
  108. package/src/enums/workflow_event_names.js +27 -0
  109. package/src/enums/workflow_statuses.js +21 -0
  110. package/src/index.js +2 -0
@@ -0,0 +1,28 @@
1
+ import { Event } from './index.js';
2
+ import { workflow_event_names } from '../../enums/index.js';
3
+
4
+ /**
5
+ * Manages workflow-specific events by extending the base Event class.
6
+ * @class WorkflowEvent
7
+ * @extends Event
8
+ */
9
+ export default class WorkflowEvent extends Event {
10
+ event_names = workflow_event_names;
11
+
12
+ /**
13
+ * Creates a new WorkflowEvent instance and registers all workflow events.
14
+ * @constructor
15
+ */
16
+ constructor() {
17
+ super();
18
+ this.registerWorkflowEvents();
19
+ }
20
+
21
+ /**
22
+ * Registers all workflow event names defined in the workflow_event_names enum.
23
+ * @returns {void}
24
+ */
25
+ registerWorkflowEvents() {
26
+ this.registerEvents(this.event_names);
27
+ }
28
+ }
@@ -0,0 +1,5 @@
1
+ export * from './events/index.js';
2
+ export { default as Base } from './base.js';
3
+ export { default as State } from './state.js';
4
+ export { default as Workflow } from './workflow.js';
5
+ export * from './steps/index.js';
@@ -0,0 +1,301 @@
1
+ import { errors, warnings } from '../enums/errors.js';
2
+ import { StepEvent, WorkflowEvent, StateEvent } from './events/index.js';
3
+ import {
4
+ base_types,
5
+ conditional_step_comparators,
6
+ state_event_names,
7
+ step_event_names,
8
+ step_statuses,
9
+ step_types,
10
+ sub_step_types,
11
+ workflow_event_names,
12
+ workflow_statuses,
13
+ } from '../enums/index.js';
14
+
15
+ const defaultState = {
16
+ messages: {
17
+ errors,
18
+ warnings,
19
+ },
20
+ statuses: {
21
+ workflow: workflow_statuses,
22
+ step: step_statuses
23
+ },
24
+ event_names: {
25
+ workflow: workflow_event_names,
26
+ step: step_event_names,
27
+ state: state_event_names,
28
+ },
29
+ events: {
30
+ workflow: new WorkflowEvent(),
31
+ step: new StepEvent(),
32
+ state: new StateEvent(),
33
+ },
34
+ types: {
35
+ base_types,
36
+ step_types,
37
+ sub_step_types,
38
+ },
39
+ workflows: {},
40
+ conditional_step_comparators
41
+ };
42
+
43
+ let state = { ...defaultState };
44
+
45
+ // Module-level shortcuts for events and event_names
46
+ const events = state.events;
47
+ const event_names = state.event_names;
48
+
49
+ /**
50
+ * Singleton class representing the global state for workflows, steps, and processes.
51
+ * Provides methods for managing state with getter/setter functionality, nested path access,
52
+ * and immutability options. The state is shared across all workflow and step instances.
53
+ *
54
+ * @class State
55
+ */
56
+ class State {
57
+ /**
58
+ * Deletes a state property using dot-notation or bracket-notation path access.
59
+ *
60
+ * @param {string} path - The path of the state property to delete (e.g., "user.profile.email" or "users[0].email").
61
+ * @returns {void}
62
+ * @throws {Error} Throws if path is empty or invalid.
63
+ */
64
+ static delete(path) {
65
+ if (!path) {
66
+ throw new Error(errors.INVALID_STATE_PATH);
67
+ }
68
+
69
+ const parts = State.parsePath(path);
70
+ let current = state;
71
+
72
+ for (let i = 0; i < parts.length - 1; i++) {
73
+ const part = parts[i];
74
+
75
+ if (!Object.prototype.hasOwnProperty.call(current, part) || typeof current[part] !== 'object') {
76
+ return;
77
+ }
78
+
79
+ current = current[part];
80
+ }
81
+
82
+ delete current[parts[parts.length - 1]];
83
+
84
+ events.state.emit(event_names.state.DELETED, { state });
85
+ }
86
+
87
+ /**
88
+ * Iterates over a collection (array or object) located at the specified state path,
89
+ * executing a callback function for each item.
90
+ *
91
+ * @param {string} path - The path of the state property to iterate over.
92
+ * @param {Function} callback - The function to execute for each item in the collection.
93
+ * @throws {Error} Throws if the state property at the path is not an array or object.
94
+ */
95
+ static async each(path, callback) {
96
+ const collection = State.get(path);
97
+
98
+ if (Array.isArray(collection)) {
99
+ for (const [index, item] of collection.entries()) {
100
+ events.state.emit(event_names.state.EACH, { state });
101
+ await callback(item, index);
102
+ }
103
+ } else if (
104
+ typeof collection === 'object' &&
105
+ Object.prototype.toString.call(collection) === '[object Object]'
106
+ ) {
107
+ for (const key of Object.keys(collection)) {
108
+ events.state.emit(event_names.state.EACH, { state });
109
+ await callback(collection[key], key);
110
+ }
111
+ } else {
112
+ throw new Error(errors.VALUE_NOT_ITERABLE);
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Freezes the entire state object, making it immutable.
118
+ * @returns {void}
119
+ */
120
+ static freeze() {
121
+ const frozenState = Object.freeze(state);
122
+ events.state.emit(event_names.state.FROZEN, { state });
123
+ return frozenState;
124
+ }
125
+
126
+ /**
127
+ * Gets the value of a state property using dot-notation or bracket-notation path access.
128
+ *
129
+ * @param {string} path - The path of the state property to get. Supports both dot notation
130
+ * (e.g., "user.profile.name") and bracket notation (e.g., "users[0].name" or "data['key-name']").
131
+ * Special values:
132
+ * - Falsy values (null, undefined, false, ""): Returns entire state object
133
+ * - "*": Returns entire state object
134
+ * @param {*} [defaultValue=null] - Default value to return if the path doesn't exist.
135
+ * @param {string} [type='string'] - The output type to convert the value to.
136
+ * Supported types: "string", "number", "boolean".
137
+ * @returns {*} The value of the state property, or defaultValue if not found. null if not found
138
+ * and no defaultValue provided.
139
+ * @throws {Error} Throws if the value cannot be converted to the specified type.
140
+ */
141
+ static get(path, defaultValue = null, type = null) {
142
+ let gotten = state;
143
+ if (!path || ['*', ''].includes(path)) {
144
+ events.state.emit(event_names.state.GET, { state: gotten ?? defaultValue });
145
+ return gotten;
146
+ }
147
+
148
+ gotten = State.getFromPropertyPath(path, false) ?? defaultValue;
149
+
150
+ if (type) {
151
+ try {
152
+ switch (type) {
153
+ case 'string':
154
+ gotten = String(gotten);
155
+ break;
156
+ case 'number':
157
+ gotten = Number(gotten);
158
+ break;
159
+ case 'boolean':
160
+ gotten = Boolean(gotten);
161
+ break;
162
+ default:
163
+ break;
164
+ }
165
+ } catch (error) {
166
+ console.error("Error converting state value: ", error);
167
+ }
168
+ }
169
+
170
+ events.state.emit(event_names.state.GET, { state: gotten });
171
+
172
+ return gotten ?? defaultValue;
173
+ }
174
+
175
+ /**
176
+ * Resolves a nested property path within the state object.
177
+ * Supports both dot notation and bracket notation.
178
+ *
179
+ * @param {string} path - The path to the property (e.g., "user.profile.name", "users[0].name", "data['key-name']").
180
+ * @param {boolean} [emit=true] - Whether to emit the GET_FROM_PROPERTY_PATH event.
181
+ * @returns {*} The value at the specified path, or undefined if not found.
182
+ */
183
+ static getFromPropertyPath(path, emit = true) {
184
+ const parts = State.parsePath(path);
185
+ let current = state;
186
+
187
+ for (const part of parts) {
188
+ if (current && Object.prototype.hasOwnProperty.call(current, part)) {
189
+ current = current[part];
190
+ } else {
191
+ return undefined;
192
+ }
193
+ }
194
+
195
+ if (emit) {
196
+ events.state.emit(event_names.state.GET_FROM_PROPERTY_PATH, { state });
197
+ }
198
+
199
+ return current;
200
+ }
201
+
202
+ /**
203
+ * Gets the entire state object.
204
+ * @returns {Object} The entire state object.
205
+ */
206
+ static getState() {
207
+ events.state.emit(event_names.state.GET_STATE, { state });
208
+ return state;
209
+ }
210
+
211
+ /**
212
+ * Merges an object into the current State.
213
+ * @param {Object} newState - The object to merge into the current State.
214
+ * @returns {object} The updated state object.
215
+ */
216
+ static merge(newState) {
217
+ state = { ...state, ...newState };
218
+ events.state.emit(event_names.state.MERGE, { state });
219
+ return state;
220
+ }
221
+
222
+ /**
223
+ * Parses a property path string into an array of keys, supporting both dot notation
224
+ * and bracket notation.
225
+ *
226
+ * @param {string} path - The path to parse (e.g., "user.profile.name", "users[0].name", "data['key-name']").
227
+ * @returns {string[]} Array of property keys.
228
+ */
229
+ static parsePath(path) {
230
+ const matches = path.match(/[^.[\]]+|(?<=\[)([^\]]+)(?=\])/g);
231
+
232
+ if (!matches) {
233
+ return [];
234
+ }
235
+
236
+ return matches.map(part => part.replace(/^['"]|['"]$/g, ''));
237
+ }
238
+
239
+ /**
240
+ * Resets the state to its default values.
241
+ * @returns {object} The reset state object.
242
+ */
243
+ static reset() {
244
+ state = { ...defaultState };
245
+ events.state.emit(event_names.state.RESET, { state });
246
+ return state;
247
+ }
248
+
249
+ /**
250
+ * Sets the value of a state property using dot-notation or bracket-notation path access.
251
+ * Creates intermediate objects if they don't exist.
252
+ *
253
+ * @param {string} path - The path of the state property to set. Supports both dot notation
254
+ * (e.g., "user.profile.name") and bracket notation (e.g., "users[0].name" or "data['key-name']").
255
+ * @param {*} value - The value to set for the state property.
256
+ * @returns {void}
257
+ * @throws {Error} Throws if path is empty or invalid.
258
+ */
259
+ static set(path, value) {
260
+ if (!path) {
261
+ throw new Error(errors.INVALID_STATE_PATH);
262
+ }
263
+
264
+ events.state.emit(event_names.state.SET, { state });
265
+
266
+ State.setToPropertyPath(path, value, false);
267
+ }
268
+
269
+ /**
270
+ * Sets a nested property value within the state object based on a path.
271
+ * Supports both dot notation and bracket notation. Creates intermediate objects/arrays as needed.
272
+ *
273
+ * @param {string} path - The path to the property (e.g., "user.profile.name", "users[0].name", "data['key-name']").
274
+ * @param {*} value - The value to set at the specified path.
275
+ * @param {boolean} [emit=true] - Whether to emit the SET_TO_PROPERTY_PATH event.
276
+ */
277
+ static setToPropertyPath(path, value, emit = true) {
278
+ const parts = State.parsePath(path);
279
+ let current = state;
280
+
281
+ for (let i = 0; i < parts.length - 1; i++) {
282
+ const part = parts[i];
283
+ const nextPart = parts[i + 1];
284
+
285
+ if (!Object.prototype.hasOwnProperty.call(current, part) || typeof current[part] !== 'object') {
286
+ // Determine if next part is an array index (numeric)
287
+ const isNextPartNumeric = /^\d+$/.test(nextPart);
288
+ current[part] = isNextPartNumeric ? [] : {};
289
+ }
290
+ current = current[part];
291
+ }
292
+
293
+ if (emit) {
294
+ events.state.emit(event_names.state.SET_TO_PROPERTY_PATH, { state });
295
+ }
296
+
297
+ current[parts[parts.length - 1]] = value;
298
+ }
299
+ }
300
+
301
+ export default State;
@@ -0,0 +1,70 @@
1
+ import LogicStep from './logic_step.js';
2
+ import { conditional_step_comparators } from '../../enums/index.js';
3
+
4
+ /**
5
+ * Case class representing a single case in a switch statement.
6
+ * Used in conjunction with SwitchStep to create switch/case logic.
7
+ * @class Case
8
+ * @extends LogicStep
9
+ */
10
+ export default class Case extends LogicStep {
11
+ static step_name = 'case';
12
+
13
+ /**
14
+ * Creates a new Case instance.
15
+ * Note: Plain LogicStep instances can be used in place of Case, but they MUST have conditional.subject set.
16
+ * @param {Object} options - Configuration options.
17
+ * @param {string} [options.name] - Name of the case.
18
+ * @param {Object} [options.conditional] - Conditional configuration.
19
+ * @param {*} [options.conditional.subject=null] - Subject to evaluate (typically set by SwitchStep).
20
+ * @param {conditional_step_comparators|string} [options.conditional.operator=null] - Comparison operator.
21
+ * @param {*} [options.conditional.value=null] - Value to compare against.
22
+ * @param {Function|Step|Workflow} [options.callable=async () => {}] - Function, Step, or Workflow to execute when case matches.
23
+ * @param {boolean} [options.force_subject_override=false] - Force override of subject even if already set.
24
+ */
25
+ constructor({
26
+ name,
27
+ conditional = {
28
+ subject: null,
29
+ operator: null,
30
+ value: null,
31
+ },
32
+ callable = async () => {},
33
+ force_subject_override = false,
34
+ }) {
35
+ super({
36
+ name,
37
+ step_type: Case.step_name,
38
+ callable,
39
+ });
40
+
41
+ this.conditional = conditional;
42
+ this.force_subject_override = force_subject_override;
43
+
44
+ this.is_matched = false;
45
+ }
46
+
47
+ /**
48
+ * Sets the switch subject from the parent SwitchStep.
49
+ * Automatically sets the conditional subject if not already set or if force_subject_override is true.
50
+ * @param {*} subject - The subject value from the SwitchStep.
51
+ * @throws {Error} If no subject is provided and conditional.subject is not set.
52
+ * @throws {Error} If the resulting conditional configuration is invalid.
53
+ */
54
+ set switch_subject(subject) {
55
+ const subjectProvided = subject !== null && subject !== undefined;
56
+ const hasExistingSubject = this.conditional.subject !== null && this.conditional.subject !== undefined;
57
+
58
+ if (!subjectProvided && !hasExistingSubject) {
59
+ throw new Error(`No subject set for case step: ${this.name}, using default equality check`);
60
+ }
61
+
62
+ if (subjectProvided && (!hasExistingSubject || this.force_subject_override)) {
63
+ this.conditional.subject = subject;
64
+ }
65
+
66
+ if (!this.conditionalIsValid()) {
67
+ throw new Error(`Invalid conditional configuration for case step: ${this.name}`);
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,90 @@
1
+ import LogicStep from './logic_step.js';
2
+ import { conditional_step_comparators } from '../../enums/index.js';
3
+
4
+ /**
5
+ * ConditionalStep class for branching logic based on conditions.
6
+ * @class ConditionalStep
7
+ * @extends LogicStep
8
+ */
9
+ export default class ConditionalStep extends LogicStep {
10
+ static step_name = 'conditional';
11
+
12
+ /**
13
+ * Creates a new ConditionalStep instance.
14
+ * @param {Object} options - Configuration options.
15
+ * @param {string} [options.name] - Name of the step.
16
+ * @param {Object} [options.conditional] - Conditional configuration.
17
+ * @param {*} [options.conditional.subject] - Subject to evaluate.
18
+ * @param {conditional_step_comparators|string} [options.conditional.operator] - Comparison operator.
19
+ * @param {*} [options.conditional.value] - Value to compare against.
20
+ * @param {Function|Step|Workflow} [options.true_callable=async () => {}] - Callable to execute if condition is true.
21
+ * @param {Function|Step|Workflow} [options.false_callable=async () => {}] - Callable to execute if condition is false.
22
+ */
23
+ constructor({
24
+ name,
25
+ conditional = {
26
+ subject: null,
27
+ operator: null,
28
+ value: null,
29
+ },
30
+ true_callable = async () => {},
31
+ false_callable = async () => {},
32
+ }) {
33
+ super({
34
+ name,
35
+ conditional
36
+ });
37
+
38
+ this.true_callable = true_callable;
39
+ this.false_callable = false_callable;
40
+
41
+ this.callable = this.conditional.bind(this);
42
+ }
43
+
44
+ /**
45
+ * Executes the appropriate branch based on the condition evaluation.
46
+ * @async
47
+ * @returns {Promise<*>} The result of the executed branch.
48
+ */
49
+ async conditional() {
50
+ const true_callable = this.true_callable;
51
+ true_callable.parentWorkflowId = this.parentWorkflowId;
52
+
53
+ const false_callable = this.false_callable;
54
+ false_callable.parentWorkflowId = this.parentWorkflowId;
55
+
56
+ let result = null;
57
+
58
+ if (this.checkCondition()) {
59
+ this.log(
60
+ this.getState('events.step.event_names.CONDITIONAL_TRUE_BRANCH_EXECUTED'),
61
+ `Condition met for step: ${this.name}, executing true branch`
62
+ );
63
+
64
+ // Normally, the callable is automatically set according to its type
65
+ // Here, they are set separately, so we need to account for types
66
+ if (typeof true_callable === 'function') {
67
+ result = await true_callable();
68
+ } else {
69
+ true_callable.parentWorkflowId = this.parentWorkflowId;
70
+ result = await true_callable.execute();
71
+ }
72
+ } else {
73
+ this.log(
74
+ this.getState('events.step.event_names.CONDITIONAL_FALSE_BRANCH_EXECUTED'),
75
+ `Condition not met for step: ${this.name}, executing false branch`
76
+ );
77
+
78
+ // Normally, the callable is automatically set according to its type
79
+ // Here, they are set separately, so we need to account for types
80
+ if (typeof false_callable === 'function') {
81
+ result = await false_callable();
82
+ } else {
83
+ false_callable.parentWorkflowId = this.parentWorkflowId;
84
+ result = await false_callable.execute();
85
+ }
86
+ }
87
+
88
+ return { message: `Conditional step ${this.name} completed`, result };
89
+ }
90
+ }
@@ -0,0 +1,103 @@
1
+ import Step from './step.js';
2
+ import { delay_types, step_types } from '../../enums/index.js';
3
+ import schedule from 'node-schedule';
4
+ import { addMilliseconds } from 'date-fns';
5
+
6
+ /**
7
+ * DelayStep class for introducing delays in workflow execution.
8
+ * Supports both absolute and relative delays.
9
+ * @class DelayStep
10
+ * @extends Step
11
+ */
12
+ export default class DelayStep extends Step {
13
+ static step_name = 'delay';
14
+
15
+ /**
16
+ * Creates a new DelayStep instance.
17
+ * @param {Object} options - Configuration options.
18
+ * @param {string} [options.name] - Name of the step.
19
+ * @param {Date|string} [options.absolute_timestamp=new Date()] - Absolute timestamp to delay until.
20
+ * @param {number} [options.relative_delay_ms=0] - Relative delay in milliseconds.
21
+ * @param {string} [options.delay_type=delay_types.RELATIVE] - Type of delay ('absolute' or 'relative').
22
+ */
23
+ constructor({
24
+ name,
25
+ absolute_timestamp = new Date(),
26
+ relative_delay_ms = 0,
27
+ delay_type = delay_types.RELATIVE
28
+ }) {
29
+ super({
30
+ name,
31
+ step_type: step_types.DELAY,
32
+ });
33
+
34
+ this.delay_type = delay_type;
35
+ this.absolute_timestamp = new Date(absolute_timestamp);
36
+ this.relative_delay_ms = relative_delay_ms;
37
+
38
+ this.callable = this[delay_type].bind(this);
39
+ }
40
+
41
+ /**
42
+ * Executes an absolute delay until the specified timestamp. If the timestamp is in the past, it continues immediately.
43
+ * @returns {Promise<Object>} Resolves with a message object when delay completes.
44
+ */
45
+ async absolute() {
46
+ const now = new Date();
47
+
48
+ if (this.absolute_timestamp.getTime() <= now.getTime()) {
49
+ this.log(
50
+ this.getState('events.step.event_names.DELAY_STEP_ABSOLUTE_COMPLETE'),
51
+ `No delay for step: ${this.name}. Continuing.`
52
+ );
53
+ return this;
54
+ }
55
+
56
+ return this.delay(this.absolute_timestamp);
57
+ }
58
+
59
+ /** Schedules a delay until the specified date and time.
60
+ * @param {Date} delay_until - The date and time to delay until.
61
+ * @returns {Promise<DelayStep>} Resolves with the DelayStep instance when delay completes.
62
+ */
63
+ async delay(delay_until) {
64
+ return new Promise((resolve) => {
65
+ this.log(
66
+ this.getState(
67
+ `events.step.event_names.DELAY_STEP_${this.delay_type.toUpperCase()}_SCHEDULED`
68
+ ),
69
+ `Delay scheduled for step: ${this.name} until ${delay_until.toISOString()}`
70
+ );
71
+
72
+ const job = schedule.scheduleJob(delay_until, () => {
73
+ this.log(
74
+ this.getState(
75
+ `events.step.event_names.DELAY_STEP_${this.delay_type.toUpperCase()}_COMPLETE`
76
+ ),
77
+ `Delay complete for step: ${this.name}. Continuing.`
78
+ );
79
+ resolve(this);
80
+ });
81
+
82
+ this.scheduled_job = job;
83
+ });
84
+ }
85
+
86
+ /**
87
+ * Executes a relative delay for the specified duration. If the delay duration is zero or negative, it continues immediately.
88
+ * @returns {Promise<Object>} Resolves with a message object when delay completes.
89
+ */
90
+ async relative() {
91
+ if (this.relative_delay_ms <= 0) {
92
+ this.log(
93
+ this.getState('events.step.event_names.DELAY_STEP_RELATIVE_COMPLETE'),
94
+ `No delay for step: ${this.name}. Continuing.`
95
+ );
96
+ return this;
97
+ }
98
+
99
+ const delay_until = addMilliseconds(new Date(), this.relative_delay_ms);
100
+
101
+ return this.delay(delay_until);
102
+ }
103
+ }
@@ -0,0 +1,70 @@
1
+ import { LogicStep } from './index.js';
2
+ import flow_control_types from '../../enums/flow_control_types.js';
3
+ import { conditional_step_comparators } from '../../enums/index.js';
4
+
5
+ /**
6
+ * FlowControlStep class for controlling workflow execution flow (break, continue, skip, pause).
7
+ * @class FlowControlStep
8
+ * @extends LogicStep
9
+ */
10
+ export default class FlowControlStep extends LogicStep {
11
+ static step_name = 'flow_control';
12
+
13
+ /**
14
+ * Creates a new FlowControlStep instance.
15
+ * @param {Object} options - Configuration options.
16
+ * @param {Object} [options.conditional] - Conditional configuration.
17
+ * @param {*} [options.conditional.subject] - Subject to evaluate.
18
+ * @param {conditional_step_comparators|string} [options.conditional.operator] - Comparison operator.
19
+ * @param {*} [options.conditional.value] - Value to compare against.
20
+ * @param {string} [options.name] - Name of the step.
21
+ * @param {string} [options.flow_control_type=flow_control_types.BREAK] - Type of flow control.
22
+ * @throws {Error} Throws if flow_control_type is invalid.
23
+ */
24
+ constructor({
25
+ conditional = {
26
+ subject: null,
27
+ operator: null,
28
+ value: null,
29
+ },
30
+ name,
31
+ flow_control_type = flow_control_types.BREAK,
32
+ }) {
33
+ super({
34
+ name,
35
+ conditional
36
+ });
37
+
38
+ if (!Object.values(flow_control_types).includes(flow_control_type)) {
39
+ throw new Error(`Invalid flow control type: ${flow_control_type}`);
40
+ }
41
+
42
+ this.flow_control_type = flow_control_type;
43
+ this.callable = this.shouldFlowControl.bind(this);
44
+ }
45
+
46
+ /**
47
+ * Evaluates the condition and sets the appropriate flow control flag.
48
+ * @async
49
+ * @returns {Promise<boolean>} True if the flow control should be activated.
50
+ */
51
+ async shouldFlowControl() {
52
+ if (this.checkCondition()) {
53
+ this.log(
54
+ this.getState('events.step.event_names.CONDITIONAL_TRUE_BRANCH_EXECUTED'),
55
+ `Break condition met for step: ${this.name}`
56
+ );
57
+ this.setParentWorkflowValue(this.parentWorkflowId, `should_${this.flow_control_type}`, true);
58
+
59
+ return true;
60
+ } else {
61
+ this.log(
62
+ this.getState('events.step.event_names.CONDITIONAL_FALSE_BRANCH_EXECUTED'),
63
+ `Break condition not met for step: ${this.name}`
64
+ );
65
+ this.setParentWorkflowValue(this.parentWorkflowId, `should_${this.flow_control_type}`, false);
66
+
67
+ return false;
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,8 @@
1
+ export { default as Case } from './case.js';
2
+ export { default as ConditionalStep } from './conditional_step.js';
3
+ export { default as DelayStep } from './delay_step.js';
4
+ export { default as FlowControlStep } from './flow_control_step.js';
5
+ export { default as LogicStep } from './logic_step.js';
6
+ export { default as LoopStep } from './loop_step.js';
7
+ export { default as Step } from './step.js';
8
+ export { default as SwitchStep } from './switch_step.js';