@omega-flow/engine 0.1.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/LICENSE +21 -0
- package/dist/index.d.mts +997 -0
- package/dist/index.d.ts +997 -0
- package/dist/index.js +1399 -0
- package/dist/index.mjs +1362 -0
- package/package.json +34 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1399 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __typeError = (msg) => {
|
|
9
|
+
throw TypeError(msg);
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
33
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
34
|
+
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
35
|
+
|
|
36
|
+
// src/index.ts
|
|
37
|
+
var index_exports = {};
|
|
38
|
+
__export(index_exports, {
|
|
39
|
+
InMemoryWorkflowMemory: () => InMemoryWorkflowMemory,
|
|
40
|
+
InMemoryWorkflowScheduler: () => InMemoryWorkflowScheduler,
|
|
41
|
+
InMemoryWorkflowStore: () => InMemoryWorkflowStore,
|
|
42
|
+
NodeModel: () => NodeModel_default,
|
|
43
|
+
WorkflowManager: () => WorkflowManager,
|
|
44
|
+
WorkflowModel: () => WorkflowModel_default,
|
|
45
|
+
defaultNodeModels: () => nodes_default
|
|
46
|
+
});
|
|
47
|
+
module.exports = __toCommonJS(index_exports);
|
|
48
|
+
|
|
49
|
+
// src/engine/WorkflowModel.ts
|
|
50
|
+
var import_ajv = __toESM(require("ajv"));
|
|
51
|
+
var import_types = require("@omega-flow/types");
|
|
52
|
+
|
|
53
|
+
// src/engine/EdgeModel.ts
|
|
54
|
+
var EdgeModel = class {
|
|
55
|
+
/**
|
|
56
|
+
* Creates a new EdgeModel instance.
|
|
57
|
+
* @param edge - The edge definition from the workflow
|
|
58
|
+
* @throws Error if edge is missing, has no id, or lacks source/target
|
|
59
|
+
*/
|
|
60
|
+
constructor(edge) {
|
|
61
|
+
if (!edge || !edge.id) {
|
|
62
|
+
throw new Error("Edge must have an id");
|
|
63
|
+
}
|
|
64
|
+
if (!edge.source || !edge.target) {
|
|
65
|
+
throw new Error("Edge must have a source and target");
|
|
66
|
+
}
|
|
67
|
+
this.edge = edge;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Gets the unique identifier of this edge.
|
|
71
|
+
* @returns The edge's ID as a string
|
|
72
|
+
*/
|
|
73
|
+
getId() {
|
|
74
|
+
return String(this.edge.id);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Gets the ID of the source (origin) node.
|
|
78
|
+
* @returns The source node's ID as a string
|
|
79
|
+
*/
|
|
80
|
+
getSourceNodeId() {
|
|
81
|
+
return String(this.edge.source);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Gets the source handle identifier (output port).
|
|
85
|
+
* Handles allow nodes to have multiple outputs (e.g., "true"/"false" for conditions).
|
|
86
|
+
* @returns The source handle name, or "_" if not specified
|
|
87
|
+
*/
|
|
88
|
+
getSourceHandle() {
|
|
89
|
+
return String(this.edge.sourceHandle || "_");
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Gets the ID of the target (destination) node.
|
|
93
|
+
* @returns The target node's ID as a string
|
|
94
|
+
*/
|
|
95
|
+
getTargetNodeId() {
|
|
96
|
+
return String(this.edge.target);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Gets the target handle identifier (input port).
|
|
100
|
+
* @returns The target handle name, or "_" if not specified
|
|
101
|
+
*/
|
|
102
|
+
getTargetHandle() {
|
|
103
|
+
return String(this.edge.targetHandle || "_");
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
var EdgeModel_default = EdgeModel;
|
|
107
|
+
|
|
108
|
+
// src/engine/WorkflowModel.ts
|
|
109
|
+
var _WorkflowModel_instances, startWorkflow_fn, moveToNode_fn, completeWorkflow_fn, log_fn;
|
|
110
|
+
var WorkflowModel = class {
|
|
111
|
+
/**
|
|
112
|
+
* Creates a new WorkflowModel instance from a workflow definition.
|
|
113
|
+
* @param workflow - The workflow definition to execute
|
|
114
|
+
* @param nodeModels - Map of node type names to their NodeModel classes
|
|
115
|
+
* @throws Error if workflow is invalid or missing required nodes
|
|
116
|
+
*/
|
|
117
|
+
constructor(workflow, nodeModels, services) {
|
|
118
|
+
__privateAdd(this, _WorkflowModel_instances);
|
|
119
|
+
/** Array of instantiated node models for this workflow */
|
|
120
|
+
this.nodes = [];
|
|
121
|
+
/** Array of edge models connecting the nodes */
|
|
122
|
+
this.edges = [];
|
|
123
|
+
/** The node currently waiting for events (null before start or after completion) */
|
|
124
|
+
this.currentNode = null;
|
|
125
|
+
/** Execution history recording workflow transitions */
|
|
126
|
+
this.history = [];
|
|
127
|
+
/** Current execution status of the workflow */
|
|
128
|
+
this.status = import_types.WorkflowStatus.Idle;
|
|
129
|
+
/** Unique identifier for this workflow instance */
|
|
130
|
+
this.instanceId = "";
|
|
131
|
+
/** Unix timestamp (ms) when this workflow instance was started */
|
|
132
|
+
this.startedAt = 0;
|
|
133
|
+
const ajv = new import_ajv.default();
|
|
134
|
+
const validate = ajv.compile(import_types.WorkflowSchema);
|
|
135
|
+
if (!validate(workflow)) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
"Invalid workflow data: " + ajv.errorsText(validate.errors)
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
this.workflow = workflow;
|
|
141
|
+
function nodeHasType(node) {
|
|
142
|
+
return node.type !== void 0;
|
|
143
|
+
}
|
|
144
|
+
this.nodes = this.workflow.flow.nodes.filter(nodeHasType).map((node) => {
|
|
145
|
+
const model = nodeModels[node.type];
|
|
146
|
+
if (!model) {
|
|
147
|
+
throw new Error(`Node type ${node.type} not found`);
|
|
148
|
+
}
|
|
149
|
+
const instance = model.create(node);
|
|
150
|
+
if (services) {
|
|
151
|
+
instance.services = services;
|
|
152
|
+
}
|
|
153
|
+
return instance;
|
|
154
|
+
});
|
|
155
|
+
this.edges = this.workflow.flow.edges.map((e) => {
|
|
156
|
+
const edge = new EdgeModel_default(e);
|
|
157
|
+
const sourceNode = this.getNode(edge.getSourceNodeId());
|
|
158
|
+
if (!sourceNode) {
|
|
159
|
+
throw new Error(`Source node ${edge.getSourceNodeId()} not found`);
|
|
160
|
+
}
|
|
161
|
+
const targetNode = this.getNode(edge.getTargetNodeId());
|
|
162
|
+
if (!targetNode) {
|
|
163
|
+
throw new Error(`Target node ${edge.getTargetNodeId()} not found`);
|
|
164
|
+
}
|
|
165
|
+
sourceNode.connect(targetNode, edge);
|
|
166
|
+
return edge;
|
|
167
|
+
});
|
|
168
|
+
if (!this.getStartNode()) {
|
|
169
|
+
throw new Error("Workflow does not have a start node");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// PUBLIC METHODS
|
|
173
|
+
/**
|
|
174
|
+
* Restores workflow state from a previously saved context.
|
|
175
|
+
* Use this to resume a workflow that was interrupted or to restore from persistence.
|
|
176
|
+
* After calling setContext, you must call start() to begin processing events.
|
|
177
|
+
* @param context - The context to restore from
|
|
178
|
+
* @throws Error if workflow is already running
|
|
179
|
+
* @throws Error if context is invalid or doesn't match this workflow
|
|
180
|
+
* @throws Error if the current node in context doesn't exist in the workflow
|
|
181
|
+
*/
|
|
182
|
+
setContext(context) {
|
|
183
|
+
if (this.status !== import_types.WorkflowStatus.Idle) {
|
|
184
|
+
throw new Error("Workflow is already running");
|
|
185
|
+
}
|
|
186
|
+
const ajv = new import_ajv.default();
|
|
187
|
+
const validate = ajv.compile(import_types.ContextSchema);
|
|
188
|
+
if (!validate(context)) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
"Invalid context data: " + ajv.errorsText(validate.errors)
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
if (this.workflow.id !== context.workflowId) {
|
|
194
|
+
throw new Error("Workflow id does not match");
|
|
195
|
+
}
|
|
196
|
+
this.currentNode = this.getNode(context.currentNodeId);
|
|
197
|
+
if (!this.currentNode) {
|
|
198
|
+
throw new Error("Current node does not exist in workflow");
|
|
199
|
+
}
|
|
200
|
+
this.nodes.forEach((node) => {
|
|
201
|
+
node.setState(context.nodeState[node.getId()] || {});
|
|
202
|
+
});
|
|
203
|
+
this.history = context.history || [];
|
|
204
|
+
this.instanceId = context.instanceId;
|
|
205
|
+
this.startedAt = context.startedAt;
|
|
206
|
+
if (context.isCompleted) {
|
|
207
|
+
this.status = import_types.WorkflowStatus.Completed;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Exports the current workflow state as a Context object.
|
|
212
|
+
* The context contains all information needed to restore the workflow later.
|
|
213
|
+
* @returns A Context object containing workflow state
|
|
214
|
+
*/
|
|
215
|
+
getContext() {
|
|
216
|
+
return {
|
|
217
|
+
workflowId: this.workflow.id,
|
|
218
|
+
instanceId: this.instanceId,
|
|
219
|
+
currentNodeId: this.currentNode && this.currentNode.getId(),
|
|
220
|
+
nodeState: this.nodes.reduce((acc, node) => {
|
|
221
|
+
acc[node.getId()] = node.getState();
|
|
222
|
+
return acc;
|
|
223
|
+
}, {}),
|
|
224
|
+
history: this.history,
|
|
225
|
+
isCompleted: this.status === import_types.WorkflowStatus.Completed,
|
|
226
|
+
startedAt: this.startedAt
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Gets the current execution status of the workflow.
|
|
231
|
+
* @returns The current WorkflowStatus
|
|
232
|
+
*/
|
|
233
|
+
getStatus() {
|
|
234
|
+
return this.status;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Starts or resumes the workflow execution.
|
|
238
|
+
* If no context was set, starts from the beginning at the start node.
|
|
239
|
+
* If a context was set via setContext(), resumes from the saved position.
|
|
240
|
+
* @throws Error if workflow is already running or completed
|
|
241
|
+
* @throws Error if workflow has no start node
|
|
242
|
+
*/
|
|
243
|
+
start() {
|
|
244
|
+
if (this.status === import_types.WorkflowStatus.Completed) {
|
|
245
|
+
throw new Error("Workflow is already completed");
|
|
246
|
+
}
|
|
247
|
+
if (this.status !== import_types.WorkflowStatus.Idle) {
|
|
248
|
+
throw new Error("Workflow is already running");
|
|
249
|
+
}
|
|
250
|
+
if (!this.currentNode) {
|
|
251
|
+
this.currentNode = this.getStartNode();
|
|
252
|
+
if (this.instanceId === "") {
|
|
253
|
+
this.instanceId = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
254
|
+
}
|
|
255
|
+
if (this.startedAt === 0) {
|
|
256
|
+
this.startedAt = Date.now();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (!this.currentNode) {
|
|
260
|
+
throw new Error("Workflow does not have a start node");
|
|
261
|
+
}
|
|
262
|
+
this.status = import_types.WorkflowStatus.Waiting;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Processes an incoming event through the workflow.
|
|
266
|
+
*
|
|
267
|
+
* This is the main event processing method. It passes the event to the current node
|
|
268
|
+
* and handles workflow transitions. The method recursively processes the event through
|
|
269
|
+
* subsequent nodes until a node doesn't accept the event or the workflow completes.
|
|
270
|
+
*
|
|
271
|
+
* @param event - The event to process
|
|
272
|
+
* @throws Error if workflow is not in waiting status
|
|
273
|
+
* @throws Error if event is invalid
|
|
274
|
+
* @throws Error if current node is not set
|
|
275
|
+
*/
|
|
276
|
+
async acceptEvent(event) {
|
|
277
|
+
if (this.status != import_types.WorkflowStatus.Waiting) {
|
|
278
|
+
throw new Error(
|
|
279
|
+
`Workflow cannot accept events in current status: ${this.status}`
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
const ajv = new import_ajv.default();
|
|
283
|
+
const validate = ajv.compile(import_types.EventSchema);
|
|
284
|
+
if (!validate(event)) {
|
|
285
|
+
throw new Error("Invalid event data: " + ajv.errorsText(validate.errors));
|
|
286
|
+
}
|
|
287
|
+
const currentNode = this.getCurrentNode();
|
|
288
|
+
if (!currentNode) {
|
|
289
|
+
throw new Error("Current node not set");
|
|
290
|
+
}
|
|
291
|
+
const processed = await currentNode.acceptEvent(event);
|
|
292
|
+
if (!processed) {
|
|
293
|
+
this.status = import_types.WorkflowStatus.Waiting;
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
this.status = import_types.WorkflowStatus.Processing;
|
|
297
|
+
const nextNode = await currentNode.nextNode(event);
|
|
298
|
+
if (currentNode.equals(nextNode)) {
|
|
299
|
+
this.status = import_types.WorkflowStatus.Waiting;
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (currentNode.equals(this.getStartNode())) {
|
|
303
|
+
__privateMethod(this, _WorkflowModel_instances, startWorkflow_fn).call(this, event);
|
|
304
|
+
}
|
|
305
|
+
if (!nextNode) {
|
|
306
|
+
__privateMethod(this, _WorkflowModel_instances, completeWorkflow_fn).call(this, event);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
this.status = import_types.WorkflowStatus.Transforming;
|
|
310
|
+
__privateMethod(this, _WorkflowModel_instances, moveToNode_fn).call(this, nextNode, event);
|
|
311
|
+
this.status = import_types.WorkflowStatus.Waiting;
|
|
312
|
+
return await this.acceptEvent(event);
|
|
313
|
+
}
|
|
314
|
+
// HELPER METHODS
|
|
315
|
+
/**
|
|
316
|
+
* Gets the current node that is waiting for events.
|
|
317
|
+
* @returns The current NodeModel
|
|
318
|
+
* @throws Error if workflow is not running (still idle)
|
|
319
|
+
*/
|
|
320
|
+
getCurrentNode() {
|
|
321
|
+
if (this.status === import_types.WorkflowStatus.Idle) {
|
|
322
|
+
throw new Error("Workflow is not running");
|
|
323
|
+
}
|
|
324
|
+
return this.currentNode;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Finds the start node of the workflow.
|
|
328
|
+
* The start node is the node with no incoming edges.
|
|
329
|
+
* @returns The start NodeModel, or null if not found
|
|
330
|
+
*/
|
|
331
|
+
getStartNode() {
|
|
332
|
+
const startNode = this.nodes.find((node) => {
|
|
333
|
+
return !this.edges.some(
|
|
334
|
+
(edge) => String(edge.getTargetNodeId()) === String(node.getId())
|
|
335
|
+
);
|
|
336
|
+
});
|
|
337
|
+
return startNode || null;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Finds a node by its ID.
|
|
341
|
+
* @param nodeId - The ID of the node to find (can be null)
|
|
342
|
+
* @returns The NodeModel if found, or null if not found or nodeId is null
|
|
343
|
+
*/
|
|
344
|
+
getNode(nodeId) {
|
|
345
|
+
if (!nodeId) {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
const findNode = this.nodes.find(
|
|
349
|
+
(node) => String(node.getId()) === String(nodeId)
|
|
350
|
+
);
|
|
351
|
+
return findNode || null;
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
_WorkflowModel_instances = new WeakSet();
|
|
355
|
+
// PRIVATE METHODS
|
|
356
|
+
/**
|
|
357
|
+
* Records workflow start in history.
|
|
358
|
+
* Called when the workflow transitions from the start node.
|
|
359
|
+
* @param _event - The triggering event (unused, kept for consistency)
|
|
360
|
+
*/
|
|
361
|
+
startWorkflow_fn = function(_event) {
|
|
362
|
+
this.history.push({
|
|
363
|
+
time: Date.now(),
|
|
364
|
+
type: "started",
|
|
365
|
+
fromNodeId: null,
|
|
366
|
+
toNodeId: this.currentNode && this.currentNode.getId()
|
|
367
|
+
});
|
|
368
|
+
__privateMethod(this, _WorkflowModel_instances, log_fn).call(this);
|
|
369
|
+
};
|
|
370
|
+
/**
|
|
371
|
+
* Moves the workflow to a new node and records the transition.
|
|
372
|
+
* @param nextNode - The node to move to
|
|
373
|
+
* @param _event - The triggering event (unused, kept for consistency)
|
|
374
|
+
*/
|
|
375
|
+
moveToNode_fn = function(nextNode, _event) {
|
|
376
|
+
this.history.push({
|
|
377
|
+
time: Date.now(),
|
|
378
|
+
type: "step",
|
|
379
|
+
fromNodeId: this.currentNode && this.currentNode.getId(),
|
|
380
|
+
toNodeId: nextNode.getId()
|
|
381
|
+
});
|
|
382
|
+
this.currentNode = nextNode;
|
|
383
|
+
__privateMethod(this, _WorkflowModel_instances, log_fn).call(this);
|
|
384
|
+
};
|
|
385
|
+
/**
|
|
386
|
+
* Marks the workflow as completed and records the completion.
|
|
387
|
+
* @param _event - The triggering event (unused, kept for consistency)
|
|
388
|
+
*/
|
|
389
|
+
completeWorkflow_fn = function(_event) {
|
|
390
|
+
this.history.push({
|
|
391
|
+
time: Date.now(),
|
|
392
|
+
type: "completed",
|
|
393
|
+
fromNodeId: this.currentNode && this.currentNode.getId(),
|
|
394
|
+
toNodeId: null
|
|
395
|
+
});
|
|
396
|
+
this.status = import_types.WorkflowStatus.Completed;
|
|
397
|
+
__privateMethod(this, _WorkflowModel_instances, log_fn).call(this);
|
|
398
|
+
};
|
|
399
|
+
/**
|
|
400
|
+
* Internal logging helper for debugging workflow transitions.
|
|
401
|
+
*/
|
|
402
|
+
log_fn = function() {
|
|
403
|
+
};
|
|
404
|
+
var WorkflowModel_default = WorkflowModel;
|
|
405
|
+
|
|
406
|
+
// src/engine/NodeModel.ts
|
|
407
|
+
var NodeModel = class _NodeModel {
|
|
408
|
+
/**
|
|
409
|
+
* Creates a new NodeModel instance.
|
|
410
|
+
* @param node - The node definition from the workflow
|
|
411
|
+
* @throws Error if node is missing or doesn't have an id
|
|
412
|
+
*/
|
|
413
|
+
constructor(node) {
|
|
414
|
+
/** Services available to nodes (scheduler, etc.) */
|
|
415
|
+
this.services = {};
|
|
416
|
+
if (!node || !node.id) {
|
|
417
|
+
throw new Error("Node must have an id");
|
|
418
|
+
}
|
|
419
|
+
this.node = node;
|
|
420
|
+
this.connections = [];
|
|
421
|
+
this.state = {};
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Factory method to create a new NodeModel instance.
|
|
425
|
+
* Subclasses should override this method to perform type validation.
|
|
426
|
+
* @param node - The node definition from the workflow
|
|
427
|
+
* @returns A new NodeModel instance
|
|
428
|
+
*/
|
|
429
|
+
static create(node) {
|
|
430
|
+
return new this(node);
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Gets the unique identifier of this node.
|
|
434
|
+
* @returns The node's ID as a string
|
|
435
|
+
*/
|
|
436
|
+
getId() {
|
|
437
|
+
return String(this.node.id);
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Gets the data payload associated with this node.
|
|
441
|
+
* This typically contains node-specific configuration like parameters.
|
|
442
|
+
* @returns The node's data object, or an empty object if not defined
|
|
443
|
+
*/
|
|
444
|
+
getData() {
|
|
445
|
+
return this.node.data || {};
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Checks if this node is equal to another node by comparing IDs.
|
|
449
|
+
* @param node - The node to compare against (can be null)
|
|
450
|
+
* @returns True if the nodes have the same ID, false otherwise
|
|
451
|
+
*/
|
|
452
|
+
equals(node) {
|
|
453
|
+
if (!node) {
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
if (node instanceof _NodeModel) {
|
|
457
|
+
return this.getId() === node.getId();
|
|
458
|
+
}
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Gets the current internal state of the node.
|
|
463
|
+
* State is used to share data between `acceptEvent` and `nextNode` methods.
|
|
464
|
+
* @returns The current state object
|
|
465
|
+
*/
|
|
466
|
+
getState() {
|
|
467
|
+
return this.state;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Replaces the entire internal state of the node.
|
|
471
|
+
* @param state - The new state object
|
|
472
|
+
*/
|
|
473
|
+
setState(state) {
|
|
474
|
+
this.state = state;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Merges changes into the existing state (shallow merge).
|
|
478
|
+
* @param changes - Object containing state properties to update
|
|
479
|
+
*/
|
|
480
|
+
updateState(changes) {
|
|
481
|
+
this.state = {
|
|
482
|
+
...this.state,
|
|
483
|
+
...changes
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Connects this node to a target node via an edge.
|
|
488
|
+
* This node becomes the source node of the connection.
|
|
489
|
+
* @param targetNode - The node to connect to
|
|
490
|
+
* @param edge - The edge defining the connection
|
|
491
|
+
* @throws Error if attempting to connect node to itself
|
|
492
|
+
* @throws Error if connection already exists with the same source handle
|
|
493
|
+
*/
|
|
494
|
+
connect(targetNode, edge) {
|
|
495
|
+
if (this.getId() === targetNode.getId()) {
|
|
496
|
+
throw new Error("Cannot connect node to itself");
|
|
497
|
+
}
|
|
498
|
+
const sameConnection = this.connections.find(
|
|
499
|
+
(c) => c.targetNode.getId() === targetNode.getId()
|
|
500
|
+
);
|
|
501
|
+
if (sameConnection) {
|
|
502
|
+
const prevEdge = sameConnection.edge;
|
|
503
|
+
if (prevEdge.getSourceHandle() === edge.getSourceHandle()) {
|
|
504
|
+
throw new Error("Connection already exists");
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
this.connections.push({
|
|
508
|
+
targetNode,
|
|
509
|
+
edge
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Returns all outgoing connections from this node.
|
|
514
|
+
* @returns Array of Connection objects containing targetNode and edge
|
|
515
|
+
*/
|
|
516
|
+
getConnections() {
|
|
517
|
+
return this.connections;
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Returns all source handle identifiers (output labels) for this node.
|
|
521
|
+
* Source handles define the different output paths from this node.
|
|
522
|
+
* @returns Array of source handle strings
|
|
523
|
+
*/
|
|
524
|
+
getSourceHandles() {
|
|
525
|
+
return this.connections.map((c) => c.edge.getSourceHandle());
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Finds the target node connected to a specific source handle.
|
|
529
|
+
* @param sourceHandle - The source handle (output) identifier to look up
|
|
530
|
+
* @returns The connected NodeModel, or null if no connection exists for the handle
|
|
531
|
+
*/
|
|
532
|
+
getTargetNodeFromSourceHandle(sourceHandle) {
|
|
533
|
+
const connection = this.connections.find(
|
|
534
|
+
(c) => c.edge.getSourceHandle() === sourceHandle
|
|
535
|
+
);
|
|
536
|
+
if (connection) {
|
|
537
|
+
return connection.targetNode;
|
|
538
|
+
}
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Returns the target node connected to the first source handle, or null
|
|
543
|
+
* if this node has no outgoing connections.
|
|
544
|
+
*
|
|
545
|
+
* Most pass-through nodes (single output) implement `nextNode` as
|
|
546
|
+
* `return this.getDefaultNext();`. Use it instead of repeating the
|
|
547
|
+
* `getSourceHandles()[0]` lookup by hand.
|
|
548
|
+
*/
|
|
549
|
+
getDefaultNext() {
|
|
550
|
+
const handle = this.getSourceHandles()[0];
|
|
551
|
+
if (!handle) {
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
return this.getTargetNodeFromSourceHandle(handle);
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Accepts and processes an incoming event.
|
|
558
|
+
*
|
|
559
|
+
* This method must be overridden by subclasses to define how the node
|
|
560
|
+
* handles events. The method can be called multiple times for the same
|
|
561
|
+
* logical operation (e.g., start wait, then complete wait).
|
|
562
|
+
*
|
|
563
|
+
* @param event - The event to process
|
|
564
|
+
* @returns Promise resolving to true if event is accepted and processing is complete,
|
|
565
|
+
* false if the node is still waiting or doesn't accept the event
|
|
566
|
+
* @throws Error if not implemented by subclass
|
|
567
|
+
*/
|
|
568
|
+
async acceptEvent(_event) {
|
|
569
|
+
throw new Error("acceptEvent method not implemented");
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Determines the next node to execute after processing an event.
|
|
573
|
+
*
|
|
574
|
+
* This method must be overridden by subclasses. It is called after
|
|
575
|
+
* `acceptEvent` returns true to determine where the workflow should go next.
|
|
576
|
+
*
|
|
577
|
+
* @param event - The event that was processed
|
|
578
|
+
* @returns Promise resolving to the next NodeModel to execute,
|
|
579
|
+
* or null if the workflow should end
|
|
580
|
+
* @throws Error if not implemented by subclass
|
|
581
|
+
*/
|
|
582
|
+
async nextNode(_event) {
|
|
583
|
+
throw new Error("nextNode method not implemented");
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
var NodeModel_default = NodeModel;
|
|
587
|
+
|
|
588
|
+
// src/nodes/ActionModel.ts
|
|
589
|
+
var ActionModel = class extends NodeModel_default {
|
|
590
|
+
/**
|
|
591
|
+
* Creates a new ActionModel instance.
|
|
592
|
+
* @param node - The node definition from the workflow
|
|
593
|
+
*/
|
|
594
|
+
constructor(node) {
|
|
595
|
+
super(node);
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Factory method to create an ActionModel with type validation.
|
|
599
|
+
* @param node - The node definition from the workflow
|
|
600
|
+
* @returns A new ActionModel instance
|
|
601
|
+
* @throws Error if node type is not "Action"
|
|
602
|
+
*/
|
|
603
|
+
static create(node) {
|
|
604
|
+
if (node.type !== "Action") {
|
|
605
|
+
throw new Error("Node type must be Action");
|
|
606
|
+
}
|
|
607
|
+
return new this(node);
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Accepts all events immediately.
|
|
611
|
+
* Actions don't filter events - they execute whenever reached.
|
|
612
|
+
* @param _event - The event being processed (unused)
|
|
613
|
+
* @returns Always returns true (event accepted)
|
|
614
|
+
*/
|
|
615
|
+
async acceptEvent(_event) {
|
|
616
|
+
return true;
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Returns the next node connected to this action's output.
|
|
620
|
+
* @param _event - The event being processed (unused)
|
|
621
|
+
* @returns The next NodeModel, or null if no connection exists
|
|
622
|
+
*/
|
|
623
|
+
async nextNode(_event) {
|
|
624
|
+
return this.getDefaultNext();
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
// src/nodes/conditionEvaluator.ts
|
|
629
|
+
function evaluateConditions(conditions, facts) {
|
|
630
|
+
if (!conditions || !Array.isArray(conditions.groups)) return false;
|
|
631
|
+
return conditions.groups.some((group) => evaluateGroup(group, facts));
|
|
632
|
+
}
|
|
633
|
+
function evaluateGroup(group, facts) {
|
|
634
|
+
const rules = group.conditions ?? [];
|
|
635
|
+
if (group.operator === "all") {
|
|
636
|
+
return rules.every((rule) => evaluateRule(rule, facts));
|
|
637
|
+
}
|
|
638
|
+
return rules.some((rule) => evaluateRule(rule, facts));
|
|
639
|
+
}
|
|
640
|
+
function evaluateRule(rule, facts) {
|
|
641
|
+
const factValue = resolvePath(facts, rule.fact);
|
|
642
|
+
return applyOperator(rule.operator, factValue, rule.value);
|
|
643
|
+
}
|
|
644
|
+
function resolvePath(source, path) {
|
|
645
|
+
if (!path) return void 0;
|
|
646
|
+
const parts = path.split(".");
|
|
647
|
+
let current = source;
|
|
648
|
+
for (const part of parts) {
|
|
649
|
+
if (current == null || typeof current !== "object") return void 0;
|
|
650
|
+
current = current[part];
|
|
651
|
+
}
|
|
652
|
+
return current;
|
|
653
|
+
}
|
|
654
|
+
function applyOperator(operator, factValue, ruleValue) {
|
|
655
|
+
switch (operator) {
|
|
656
|
+
case "equal":
|
|
657
|
+
return factValue === ruleValue;
|
|
658
|
+
case "notEqual":
|
|
659
|
+
return factValue !== ruleValue;
|
|
660
|
+
case "greaterThan":
|
|
661
|
+
return isNumber(factValue) && isNumber(ruleValue) && factValue > ruleValue;
|
|
662
|
+
case "greaterThanInclusive":
|
|
663
|
+
return isNumber(factValue) && isNumber(ruleValue) && factValue >= ruleValue;
|
|
664
|
+
case "lessThan":
|
|
665
|
+
return isNumber(factValue) && isNumber(ruleValue) && factValue < ruleValue;
|
|
666
|
+
case "lessThanInclusive":
|
|
667
|
+
return isNumber(factValue) && isNumber(ruleValue) && factValue <= ruleValue;
|
|
668
|
+
case "in":
|
|
669
|
+
return Array.isArray(ruleValue) && ruleValue.includes(factValue);
|
|
670
|
+
case "notIn":
|
|
671
|
+
return Array.isArray(ruleValue) && !ruleValue.includes(factValue);
|
|
672
|
+
case "contains":
|
|
673
|
+
if (Array.isArray(factValue)) return factValue.includes(ruleValue);
|
|
674
|
+
if (typeof factValue === "string" && typeof ruleValue === "string") {
|
|
675
|
+
return factValue.includes(ruleValue);
|
|
676
|
+
}
|
|
677
|
+
return false;
|
|
678
|
+
case "doesNotContain":
|
|
679
|
+
if (Array.isArray(factValue)) return !factValue.includes(ruleValue);
|
|
680
|
+
if (typeof factValue === "string" && typeof ruleValue === "string") {
|
|
681
|
+
return !factValue.includes(ruleValue);
|
|
682
|
+
}
|
|
683
|
+
return false;
|
|
684
|
+
default:
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
function isNumber(value) {
|
|
689
|
+
return typeof value === "number" && !Number.isNaN(value);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// src/nodes/ConditionModel.ts
|
|
693
|
+
var ConditionModel = class extends NodeModel_default {
|
|
694
|
+
constructor(node) {
|
|
695
|
+
super(node);
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Factory method to create a ConditionModel with type validation.
|
|
699
|
+
*/
|
|
700
|
+
static create(node) {
|
|
701
|
+
if (node.type !== "Condition") {
|
|
702
|
+
throw new Error("Node type must be Condition");
|
|
703
|
+
}
|
|
704
|
+
return new this(node);
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Evaluates the condition against the event data using the built-in
|
|
708
|
+
* evaluator. The result is stored in state for use by nextNode().
|
|
709
|
+
*/
|
|
710
|
+
async acceptEvent(event) {
|
|
711
|
+
const conditions = this.getData().conditions;
|
|
712
|
+
const facts = event.data ?? {};
|
|
713
|
+
const conditionResult = evaluateConditions(conditions, facts);
|
|
714
|
+
this.setState({ conditionResult });
|
|
715
|
+
return true;
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Routes to "true" handle if condition passed, "false" handle otherwise.
|
|
719
|
+
*/
|
|
720
|
+
async nextNode(_event) {
|
|
721
|
+
const conditionTrue = this.getState().conditionResult;
|
|
722
|
+
if (conditionTrue) {
|
|
723
|
+
return this.getTargetNodeFromSourceHandle("true");
|
|
724
|
+
} else {
|
|
725
|
+
return this.getTargetNodeFromSourceHandle("false");
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
// src/nodes/ExitModel.ts
|
|
731
|
+
var ExitModel = class extends NodeModel_default {
|
|
732
|
+
/**
|
|
733
|
+
* Creates a new ExitModel instance.
|
|
734
|
+
* @param node - The node definition from the workflow
|
|
735
|
+
*/
|
|
736
|
+
constructor(node) {
|
|
737
|
+
super(node);
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Factory method to create an ExitModel with type validation.
|
|
741
|
+
* @param node - The node definition from the workflow
|
|
742
|
+
* @returns A new ExitModel instance
|
|
743
|
+
* @throws Error if node type is not "Exit"
|
|
744
|
+
*/
|
|
745
|
+
static create(node) {
|
|
746
|
+
if (node.type !== "Exit") {
|
|
747
|
+
throw new Error("Node type must be Exit");
|
|
748
|
+
}
|
|
749
|
+
return new this(node);
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Accepts all events immediately.
|
|
753
|
+
* Exit nodes don't filter events - they terminate whenever reached.
|
|
754
|
+
* @param _event - The event being processed (unused)
|
|
755
|
+
* @returns Always returns true (event accepted)
|
|
756
|
+
*/
|
|
757
|
+
async acceptEvent(_event) {
|
|
758
|
+
return true;
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Always returns null to signal workflow termination.
|
|
762
|
+
* @param _event - The event being processed (unused)
|
|
763
|
+
* @returns Always null, signaling workflow completion
|
|
764
|
+
*/
|
|
765
|
+
async nextNode(_event) {
|
|
766
|
+
return null;
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
// src/nodes/TriggerModel.ts
|
|
771
|
+
var TriggerModel = class extends NodeModel_default {
|
|
772
|
+
/**
|
|
773
|
+
* Creates a new TriggerModel instance.
|
|
774
|
+
* @param node - The node definition from the workflow
|
|
775
|
+
*/
|
|
776
|
+
constructor(node) {
|
|
777
|
+
super(node);
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Factory method to create a TriggerModel with type validation.
|
|
781
|
+
* @param node - The node definition from the workflow
|
|
782
|
+
* @returns A new TriggerModel instance
|
|
783
|
+
* @throws Error if node type is not "Trigger"
|
|
784
|
+
*/
|
|
785
|
+
static create(node) {
|
|
786
|
+
if (node.type !== "Trigger") {
|
|
787
|
+
throw new Error("Node type must be Trigger");
|
|
788
|
+
}
|
|
789
|
+
return new this(node);
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Accepts events that match the configured trigger event type.
|
|
793
|
+
* @param event - The event being processed
|
|
794
|
+
* @returns True if event.type matches the configured params.event, false otherwise
|
|
795
|
+
*/
|
|
796
|
+
async acceptEvent(event) {
|
|
797
|
+
const nodeData = this.getData();
|
|
798
|
+
return event.type === nodeData.params.event;
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Returns the next node connected to this trigger's output.
|
|
802
|
+
* @param _event - The event being processed (unused)
|
|
803
|
+
* @returns The next NodeModel, or null if no connection exists
|
|
804
|
+
*/
|
|
805
|
+
async nextNode(_event) {
|
|
806
|
+
return this.getDefaultNext();
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
|
|
810
|
+
// src/nodes/WaitModel.ts
|
|
811
|
+
var WaitModel = class extends NodeModel_default {
|
|
812
|
+
/**
|
|
813
|
+
* Creates a new WaitModel instance.
|
|
814
|
+
* @param node - The node definition from the workflow
|
|
815
|
+
*/
|
|
816
|
+
constructor(node) {
|
|
817
|
+
super(node);
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Factory method to create a WaitModel with type validation.
|
|
821
|
+
* @param node - The node definition from the workflow
|
|
822
|
+
* @returns A new WaitModel instance
|
|
823
|
+
* @throws Error if node type is not "Wait"
|
|
824
|
+
*/
|
|
825
|
+
static create(node) {
|
|
826
|
+
if (node.type !== "Wait") {
|
|
827
|
+
throw new Error("Node type must be of type Wait");
|
|
828
|
+
}
|
|
829
|
+
return new this(node);
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Checks if the node is currently in a waiting state.
|
|
833
|
+
* @returns True if waiting has been started but not completed
|
|
834
|
+
*/
|
|
835
|
+
isWaiting() {
|
|
836
|
+
const state = this.getState();
|
|
837
|
+
return state && state.waitStartsAt !== void 0;
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Starts the waiting period by recording the start time and scheduling a timeout event.
|
|
841
|
+
* @param time - The timestamp when waiting begins (usually event.time)
|
|
842
|
+
* @param eventData - Optional event data to include in the scheduled timeout (for routing)
|
|
843
|
+
*/
|
|
844
|
+
async startWaiting(time, eventData) {
|
|
845
|
+
this.updateState({ waitStartsAt: time });
|
|
846
|
+
if (this.services.scheduler) {
|
|
847
|
+
const duration = this.getData().params.duration || 0;
|
|
848
|
+
const timeoutEvent = {
|
|
849
|
+
id: `timeout_${this.getId()}_${Date.now()}`,
|
|
850
|
+
type: "system:timeout",
|
|
851
|
+
time: time + duration,
|
|
852
|
+
data: eventData
|
|
853
|
+
};
|
|
854
|
+
await this.services.scheduler.schedule(timeoutEvent, duration);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Stops the waiting period by recording the end time.
|
|
859
|
+
* @param time - The timestamp when waiting ends
|
|
860
|
+
*/
|
|
861
|
+
stopWaiting(time) {
|
|
862
|
+
this.updateState({ waitEndsAt: time });
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Checks if the configured wait duration has elapsed.
|
|
866
|
+
* @param currentTime - The current timestamp to check against
|
|
867
|
+
* @returns True if waiting and the duration has passed, false otherwise
|
|
868
|
+
*/
|
|
869
|
+
isWaitComplete(currentTime) {
|
|
870
|
+
if (this.isWaiting()) {
|
|
871
|
+
const state = this.getState();
|
|
872
|
+
const nodeData = this.getData();
|
|
873
|
+
const waitDuration = nodeData.params.duration || 0;
|
|
874
|
+
return currentTime >= state.waitStartsAt + waitDuration;
|
|
875
|
+
}
|
|
876
|
+
return false;
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Processes events during the waiting period.
|
|
880
|
+
* - First event: Starts waiting and returns false
|
|
881
|
+
* - Subsequent events: Checks if wait is complete
|
|
882
|
+
* - If complete: Stops waiting and returns true
|
|
883
|
+
* - If not complete: Returns false (still waiting)
|
|
884
|
+
* @param event - The event being processed
|
|
885
|
+
* @returns True if wait is complete and event is accepted, false if still waiting
|
|
886
|
+
*/
|
|
887
|
+
async acceptEvent(event) {
|
|
888
|
+
if (this.isWaiting()) {
|
|
889
|
+
if (this.isWaitComplete(event.time)) {
|
|
890
|
+
this.stopWaiting(event.time);
|
|
891
|
+
return true;
|
|
892
|
+
}
|
|
893
|
+
return false;
|
|
894
|
+
} else {
|
|
895
|
+
await this.startWaiting(event.time, event.data);
|
|
896
|
+
return false;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Returns the next node connected to this wait node's output.
|
|
901
|
+
* @param _event - The event being processed (unused)
|
|
902
|
+
* @returns The next NodeModel, or null if no connection exists
|
|
903
|
+
*/
|
|
904
|
+
async nextNode(_event) {
|
|
905
|
+
return this.getDefaultNext();
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
|
|
909
|
+
// src/nodes/TriggerOrTimeout.ts
|
|
910
|
+
var TriggerOrTimeoutModel = class extends WaitModel {
|
|
911
|
+
/**
|
|
912
|
+
* Creates a new TriggerOrTimeoutModel instance.
|
|
913
|
+
* @param node - The node definition from the workflow
|
|
914
|
+
*/
|
|
915
|
+
constructor(node) {
|
|
916
|
+
super(node);
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Factory method to create a TriggerOrTimeoutModel with type validation.
|
|
920
|
+
* @param node - The node definition from the workflow
|
|
921
|
+
* @returns A new TriggerOrTimeoutModel instance
|
|
922
|
+
* @throws Error if node type is not "TriggerOrTimeout"
|
|
923
|
+
*/
|
|
924
|
+
static create(node) {
|
|
925
|
+
if (node.type !== "TriggerOrTimeout") {
|
|
926
|
+
throw new Error("Node type must be TriggerOrTimeout");
|
|
927
|
+
}
|
|
928
|
+
return new this(node);
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Processes events, accepting either a matching trigger event or timeout completion.
|
|
932
|
+
* - If event type matches params.event: Records `resolvedBy: "trigger"` and accepts
|
|
933
|
+
* - If already waiting and timeout elapsed: Records `resolvedBy: "timeout"` and accepts
|
|
934
|
+
* - If not waiting: Starts waiting and returns false
|
|
935
|
+
* @param event - The event being processed
|
|
936
|
+
* @returns True if event matches trigger or timeout completed, false if still waiting
|
|
937
|
+
*/
|
|
938
|
+
async acceptEvent(event) {
|
|
939
|
+
const nodeData = this.getData();
|
|
940
|
+
if (event.type === nodeData.params.event) {
|
|
941
|
+
this.stopWaiting(event.time);
|
|
942
|
+
this.updateState({ resolvedBy: "trigger" });
|
|
943
|
+
return true;
|
|
944
|
+
} else if (this.isWaiting()) {
|
|
945
|
+
if (this.isWaitComplete(event.time)) {
|
|
946
|
+
this.stopWaiting(event.time);
|
|
947
|
+
this.updateState({ resolvedBy: "timeout" });
|
|
948
|
+
return true;
|
|
949
|
+
}
|
|
950
|
+
return false;
|
|
951
|
+
} else {
|
|
952
|
+
await this.startWaiting(event.time, event.data);
|
|
953
|
+
return false;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Routes execution to the `trigger` source handle if the matching event
|
|
958
|
+
* resolved the wait, or the `timeout` source handle if the duration elapsed.
|
|
959
|
+
* @param _event - The event being processed (unused)
|
|
960
|
+
* @returns The next NodeModel, or null if no connection exists for the resolved handle
|
|
961
|
+
*/
|
|
962
|
+
async nextNode(_event) {
|
|
963
|
+
const { resolvedBy } = this.getState();
|
|
964
|
+
return this.getTargetNodeFromSourceHandle(resolvedBy);
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
|
|
968
|
+
// src/nodes/index.ts
|
|
969
|
+
var nodes_default = {
|
|
970
|
+
Action: ActionModel,
|
|
971
|
+
Condition: ConditionModel,
|
|
972
|
+
Exit: ExitModel,
|
|
973
|
+
Trigger: TriggerModel,
|
|
974
|
+
Wait: WaitModel,
|
|
975
|
+
TriggerOrTimeout: TriggerOrTimeoutModel
|
|
976
|
+
};
|
|
977
|
+
|
|
978
|
+
// src/manager/WorkflowManager.ts
|
|
979
|
+
var import_types2 = require("@omega-flow/types");
|
|
980
|
+
var WorkflowManager = class {
|
|
981
|
+
/**
|
|
982
|
+
* Creates a new WorkflowManager instance.
|
|
983
|
+
* @param config - Configuration options including storage backends and node models
|
|
984
|
+
*/
|
|
985
|
+
constructor(config) {
|
|
986
|
+
this.workflowStore = config.workflowStore;
|
|
987
|
+
this.workflowMemory = config.workflowMemory;
|
|
988
|
+
this.workflowScheduler = config.workflowScheduler;
|
|
989
|
+
this.nodeModels = config.nodeModels;
|
|
990
|
+
this.eventExtractor = config.eventExtractor;
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Process an event by routing it to appropriate workflow instances
|
|
994
|
+
* @param event - The event to process
|
|
995
|
+
*/
|
|
996
|
+
async processEvent(event) {
|
|
997
|
+
const [domain, subjectId] = this.eventExtractor(event);
|
|
998
|
+
const workflows = await this.workflowStore.getAllWorkflows(domain);
|
|
999
|
+
for (const workflowDef of workflows) {
|
|
1000
|
+
try {
|
|
1001
|
+
await this.processWorkflowForEvent(
|
|
1002
|
+
workflowDef,
|
|
1003
|
+
event,
|
|
1004
|
+
domain,
|
|
1005
|
+
subjectId
|
|
1006
|
+
);
|
|
1007
|
+
} catch (error) {
|
|
1008
|
+
console.error(
|
|
1009
|
+
`Error processing workflow ${workflowDef.id} for domain ${domain}, subject ${subjectId}:`,
|
|
1010
|
+
error
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Process a specific workflow definition for an event
|
|
1017
|
+
* @param workflowDef - The workflow definition
|
|
1018
|
+
* @param event - The event to process
|
|
1019
|
+
* @param domain - The domain identifier
|
|
1020
|
+
* @param subjectId - The subject ID extracted from the event
|
|
1021
|
+
*/
|
|
1022
|
+
async processWorkflowForEvent(workflowDef, event, domain, subjectId) {
|
|
1023
|
+
const contexts = await this.workflowMemory.getContexts(
|
|
1024
|
+
domain,
|
|
1025
|
+
workflowDef.id,
|
|
1026
|
+
subjectId
|
|
1027
|
+
);
|
|
1028
|
+
const activeContexts = contexts.filter((ctx) => !ctx.isCompleted);
|
|
1029
|
+
const completedContexts = contexts.filter((ctx) => ctx.isCompleted);
|
|
1030
|
+
for (const context of activeContexts) {
|
|
1031
|
+
await this.resumeWorkflowInstance(
|
|
1032
|
+
workflowDef,
|
|
1033
|
+
context,
|
|
1034
|
+
event,
|
|
1035
|
+
domain,
|
|
1036
|
+
subjectId
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
const canStartNew = this.canStartNewInstance(
|
|
1040
|
+
workflowDef,
|
|
1041
|
+
activeContexts,
|
|
1042
|
+
completedContexts
|
|
1043
|
+
);
|
|
1044
|
+
if (canStartNew) {
|
|
1045
|
+
await this.tryStartNewWorkflowInstance(
|
|
1046
|
+
workflowDef,
|
|
1047
|
+
event,
|
|
1048
|
+
domain,
|
|
1049
|
+
subjectId
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
/**
|
|
1054
|
+
* Check if a new workflow instance can be started based on frequency option
|
|
1055
|
+
* @param workflowDef - The workflow definition
|
|
1056
|
+
* @param activeContexts - Currently active workflow instances
|
|
1057
|
+
* @param completedContexts - Completed workflow instances
|
|
1058
|
+
* @returns true if a new instance can be started
|
|
1059
|
+
*/
|
|
1060
|
+
canStartNewInstance(workflowDef, activeContexts, completedContexts) {
|
|
1061
|
+
const frequency = workflowDef.options.frequency;
|
|
1062
|
+
if (!frequency) {
|
|
1063
|
+
return activeContexts.length === 0 && completedContexts.length === 0;
|
|
1064
|
+
}
|
|
1065
|
+
if (frequency.type === "one_time") {
|
|
1066
|
+
return activeContexts.length === 0 && completedContexts.length === 0;
|
|
1067
|
+
}
|
|
1068
|
+
if (frequency.type === "every_rematch") {
|
|
1069
|
+
if (activeContexts.length > 0) {
|
|
1070
|
+
return false;
|
|
1071
|
+
}
|
|
1072
|
+
if (frequency.interval && completedContexts.length > 0) {
|
|
1073
|
+
const mostRecentCompleted = completedContexts.reduce(
|
|
1074
|
+
(latest, ctx) => ctx.startedAt > latest.startedAt ? ctx : latest
|
|
1075
|
+
);
|
|
1076
|
+
const intervalMs = frequency.interval * 1e3;
|
|
1077
|
+
const timeSinceLastStart = Date.now() - mostRecentCompleted.startedAt;
|
|
1078
|
+
return timeSinceLastStart >= intervalMs;
|
|
1079
|
+
}
|
|
1080
|
+
return true;
|
|
1081
|
+
}
|
|
1082
|
+
return false;
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Try to start a new workflow instance for a subject
|
|
1086
|
+
* Only saves context if the workflow actually started (start node accepted the event)
|
|
1087
|
+
* @param workflowDef - The workflow definition
|
|
1088
|
+
* @param event - The triggering event
|
|
1089
|
+
* @param domain - The domain identifier
|
|
1090
|
+
* @param subjectId - The subject ID
|
|
1091
|
+
*/
|
|
1092
|
+
async tryStartNewWorkflowInstance(workflowDef, event, domain, subjectId) {
|
|
1093
|
+
const workflow = new WorkflowModel_default(workflowDef, this.nodeModels, { scheduler: this.workflowScheduler });
|
|
1094
|
+
workflow.start();
|
|
1095
|
+
const startNode = workflow.getStartNode();
|
|
1096
|
+
try {
|
|
1097
|
+
await workflow.acceptEvent(event);
|
|
1098
|
+
} catch (error) {
|
|
1099
|
+
console.error(
|
|
1100
|
+
`Error accepting event in workflow ${workflowDef.id}:`,
|
|
1101
|
+
error
|
|
1102
|
+
);
|
|
1103
|
+
throw error;
|
|
1104
|
+
}
|
|
1105
|
+
if (workflow.getStatus() === import_types2.WorkflowStatus.Waiting && workflow.getCurrentNode()?.equals(startNode)) {
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
const context = workflow.getContext();
|
|
1109
|
+
await this.workflowMemory.saveContext(
|
|
1110
|
+
domain,
|
|
1111
|
+
workflowDef.id,
|
|
1112
|
+
subjectId,
|
|
1113
|
+
context
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Resume an existing workflow instance and process an event
|
|
1118
|
+
* @param workflowDef - The workflow definition
|
|
1119
|
+
* @param context - The existing context
|
|
1120
|
+
* @param event - The event to process
|
|
1121
|
+
* @param domain - The domain identifier
|
|
1122
|
+
* @param subjectId - The subject ID
|
|
1123
|
+
*/
|
|
1124
|
+
async resumeWorkflowInstance(workflowDef, context, event, domain, subjectId) {
|
|
1125
|
+
const workflow = new WorkflowModel_default(workflowDef, this.nodeModels, { scheduler: this.workflowScheduler });
|
|
1126
|
+
workflow.setContext(context);
|
|
1127
|
+
workflow.start();
|
|
1128
|
+
try {
|
|
1129
|
+
await workflow.acceptEvent(event);
|
|
1130
|
+
} catch (error) {
|
|
1131
|
+
console.error(
|
|
1132
|
+
`Error accepting event in workflow ${workflowDef.id}:`,
|
|
1133
|
+
error
|
|
1134
|
+
);
|
|
1135
|
+
throw error;
|
|
1136
|
+
}
|
|
1137
|
+
const updatedContext = workflow.getContext();
|
|
1138
|
+
await this.workflowMemory.saveContext(
|
|
1139
|
+
domain,
|
|
1140
|
+
workflowDef.id,
|
|
1141
|
+
subjectId,
|
|
1142
|
+
updatedContext
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Get the workflow scheduler instance
|
|
1147
|
+
* Useful for nodes that need to schedule future events
|
|
1148
|
+
* @returns The workflow scheduler
|
|
1149
|
+
*/
|
|
1150
|
+
getScheduler() {
|
|
1151
|
+
return this.workflowScheduler;
|
|
1152
|
+
}
|
|
1153
|
+
};
|
|
1154
|
+
|
|
1155
|
+
// src/manager/stores/InMemoryWorkflowStore.ts
|
|
1156
|
+
var InMemoryWorkflowStore = class {
|
|
1157
|
+
/**
|
|
1158
|
+
* Creates a new InMemoryWorkflowStore.
|
|
1159
|
+
* @param workflows - Optional array of workflows to initialize the store with
|
|
1160
|
+
*/
|
|
1161
|
+
constructor(workflows = []) {
|
|
1162
|
+
this.workflows = /* @__PURE__ */ new Map();
|
|
1163
|
+
for (const { domain, workflow } of workflows) {
|
|
1164
|
+
let domainWorkflows = this.workflows.get(domain);
|
|
1165
|
+
if (!domainWorkflows) {
|
|
1166
|
+
domainWorkflows = /* @__PURE__ */ new Map();
|
|
1167
|
+
this.workflows.set(domain, domainWorkflows);
|
|
1168
|
+
}
|
|
1169
|
+
domainWorkflows.set(workflow.id, workflow);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Retrieve a workflow definition by its ID
|
|
1174
|
+
* @param domain - The domain identifier
|
|
1175
|
+
* @param workflowId - The unique identifier of the workflow
|
|
1176
|
+
* @returns Promise resolving to the workflow definition, or null if not found
|
|
1177
|
+
*/
|
|
1178
|
+
async getWorkflow(domain, workflowId) {
|
|
1179
|
+
const domainWorkflows = this.workflows.get(domain);
|
|
1180
|
+
return domainWorkflows?.get(workflowId) ?? null;
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* Add or update a workflow definition in the store
|
|
1184
|
+
* @param domain - The domain identifier
|
|
1185
|
+
* @param workflow - The workflow definition to add or update
|
|
1186
|
+
*/
|
|
1187
|
+
async setWorkflow(domain, workflow) {
|
|
1188
|
+
let domainWorkflows = this.workflows.get(domain);
|
|
1189
|
+
if (!domainWorkflows) {
|
|
1190
|
+
domainWorkflows = /* @__PURE__ */ new Map();
|
|
1191
|
+
this.workflows.set(domain, domainWorkflows);
|
|
1192
|
+
}
|
|
1193
|
+
domainWorkflows.set(workflow.id, workflow);
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* Remove a workflow definition from the store
|
|
1197
|
+
* @param domain - The domain identifier
|
|
1198
|
+
* @param workflowId - The unique identifier of the workflow to remove
|
|
1199
|
+
*/
|
|
1200
|
+
async deleteWorkflow(domain, workflowId) {
|
|
1201
|
+
const domainWorkflows = this.workflows.get(domain);
|
|
1202
|
+
if (domainWorkflows) {
|
|
1203
|
+
domainWorkflows.delete(workflowId);
|
|
1204
|
+
if (domainWorkflows.size === 0) {
|
|
1205
|
+
this.workflows.delete(domain);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Get all workflow definitions in the store for a specific domain
|
|
1211
|
+
* @param domain - The domain identifier
|
|
1212
|
+
* @returns Promise resolving to an array of all workflows in the domain
|
|
1213
|
+
*/
|
|
1214
|
+
async getAllWorkflows(domain) {
|
|
1215
|
+
const domainWorkflows = this.workflows.get(domain);
|
|
1216
|
+
if (!domainWorkflows) {
|
|
1217
|
+
return [];
|
|
1218
|
+
}
|
|
1219
|
+
return Array.from(domainWorkflows.values());
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
1222
|
+
|
|
1223
|
+
// src/manager/memories/InMemoryWorkflowMemory.ts
|
|
1224
|
+
var InMemoryWorkflowMemory = class {
|
|
1225
|
+
/**
|
|
1226
|
+
* Creates a new InMemoryWorkflowMemory with empty storage.
|
|
1227
|
+
*/
|
|
1228
|
+
constructor() {
|
|
1229
|
+
this.contexts = /* @__PURE__ */ new Map();
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Retrieve all workflow contexts for a specific subject in a domain
|
|
1233
|
+
* @param domain - The domain identifier
|
|
1234
|
+
* @param workflowId - The unique identifier of the workflow
|
|
1235
|
+
* @param subjectId - The unique identifier of the subject
|
|
1236
|
+
* @returns Promise resolving to an array of contexts
|
|
1237
|
+
*/
|
|
1238
|
+
async getContexts(domain, workflowId, subjectId) {
|
|
1239
|
+
const domainContexts = this.contexts.get(domain);
|
|
1240
|
+
if (!domainContexts) {
|
|
1241
|
+
return [];
|
|
1242
|
+
}
|
|
1243
|
+
const workflowContexts = domainContexts.get(workflowId);
|
|
1244
|
+
if (!workflowContexts) {
|
|
1245
|
+
return [];
|
|
1246
|
+
}
|
|
1247
|
+
const subjectContexts = workflowContexts.get(subjectId);
|
|
1248
|
+
if (!subjectContexts) {
|
|
1249
|
+
return [];
|
|
1250
|
+
}
|
|
1251
|
+
return Array.from(subjectContexts.values());
|
|
1252
|
+
}
|
|
1253
|
+
/**
|
|
1254
|
+
* Save workflow context for a specific subject in a domain
|
|
1255
|
+
* @param domain - The domain identifier
|
|
1256
|
+
* @param workflowId - The unique identifier of the workflow
|
|
1257
|
+
* @param subjectId - The unique identifier of the subject
|
|
1258
|
+
* @param context - The context to save (identified by context.instanceId)
|
|
1259
|
+
*/
|
|
1260
|
+
async saveContext(domain, workflowId, subjectId, context) {
|
|
1261
|
+
let domainContexts = this.contexts.get(domain);
|
|
1262
|
+
if (!domainContexts) {
|
|
1263
|
+
domainContexts = /* @__PURE__ */ new Map();
|
|
1264
|
+
this.contexts.set(domain, domainContexts);
|
|
1265
|
+
}
|
|
1266
|
+
let workflowContexts = domainContexts.get(workflowId);
|
|
1267
|
+
if (!workflowContexts) {
|
|
1268
|
+
workflowContexts = /* @__PURE__ */ new Map();
|
|
1269
|
+
domainContexts.set(workflowId, workflowContexts);
|
|
1270
|
+
}
|
|
1271
|
+
let subjectContexts = workflowContexts.get(subjectId);
|
|
1272
|
+
if (!subjectContexts) {
|
|
1273
|
+
subjectContexts = /* @__PURE__ */ new Map();
|
|
1274
|
+
workflowContexts.set(subjectId, subjectContexts);
|
|
1275
|
+
}
|
|
1276
|
+
subjectContexts.set(context.instanceId, context);
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Delete workflow context for a specific subject and instance in a domain
|
|
1280
|
+
* @param domain - The domain identifier
|
|
1281
|
+
* @param workflowId - The unique identifier of the workflow
|
|
1282
|
+
* @param subjectId - The unique identifier of the subject
|
|
1283
|
+
* @param instanceId - The unique identifier of the workflow instance
|
|
1284
|
+
*/
|
|
1285
|
+
async deleteContext(domain, workflowId, subjectId, instanceId) {
|
|
1286
|
+
const domainContexts = this.contexts.get(domain);
|
|
1287
|
+
if (!domainContexts) {
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
const workflowContexts = domainContexts.get(workflowId);
|
|
1291
|
+
if (!workflowContexts) {
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
const subjectContexts = workflowContexts.get(subjectId);
|
|
1295
|
+
if (!subjectContexts) {
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
subjectContexts.delete(instanceId);
|
|
1299
|
+
if (subjectContexts.size === 0) {
|
|
1300
|
+
workflowContexts.delete(subjectId);
|
|
1301
|
+
}
|
|
1302
|
+
if (workflowContexts.size === 0) {
|
|
1303
|
+
domainContexts.delete(workflowId);
|
|
1304
|
+
}
|
|
1305
|
+
if (domainContexts.size === 0) {
|
|
1306
|
+
this.contexts.delete(domain);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
/**
|
|
1310
|
+
* Clear all contexts from memory
|
|
1311
|
+
*/
|
|
1312
|
+
async clear() {
|
|
1313
|
+
this.contexts.clear();
|
|
1314
|
+
}
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1317
|
+
// src/manager/schedulers/InMemoryWorkflowScheduler.ts
|
|
1318
|
+
var InMemoryWorkflowScheduler = class {
|
|
1319
|
+
/**
|
|
1320
|
+
* Creates a new InMemoryWorkflowScheduler.
|
|
1321
|
+
* Call setWorkflowManager() before scheduling any events.
|
|
1322
|
+
*/
|
|
1323
|
+
constructor() {
|
|
1324
|
+
this.schedules = /* @__PURE__ */ new Map();
|
|
1325
|
+
this.scheduleIdCounter = 0;
|
|
1326
|
+
this.workflowManager = null;
|
|
1327
|
+
}
|
|
1328
|
+
/**
|
|
1329
|
+
* Set the workflow manager reference
|
|
1330
|
+
* The scheduler will call processEvent on the manager when scheduled events are due
|
|
1331
|
+
* @param manager - The workflow manager instance
|
|
1332
|
+
*/
|
|
1333
|
+
setWorkflowManager(manager) {
|
|
1334
|
+
this.workflowManager = manager;
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* Schedule an event to be delivered after a delay
|
|
1338
|
+
* When the delay expires, the event will be passed to the workflow manager
|
|
1339
|
+
* @param event - The event to schedule
|
|
1340
|
+
* @param delayMs - Delay in milliseconds before the event is delivered
|
|
1341
|
+
* @returns Promise resolving to a schedule ID that can be used to cancel
|
|
1342
|
+
*/
|
|
1343
|
+
async schedule(event, delayMs) {
|
|
1344
|
+
if (!this.workflowManager) {
|
|
1345
|
+
throw new Error(
|
|
1346
|
+
"WorkflowManager not set. Call setWorkflowManager() before scheduling events."
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1349
|
+
const scheduleId = `schedule_${++this.scheduleIdCounter}`;
|
|
1350
|
+
const timeoutHandle = setTimeout(async () => {
|
|
1351
|
+
this.schedules.delete(scheduleId);
|
|
1352
|
+
if (this.workflowManager) {
|
|
1353
|
+
await this.workflowManager.processEvent(event);
|
|
1354
|
+
}
|
|
1355
|
+
}, delayMs);
|
|
1356
|
+
this.schedules.set(scheduleId, timeoutHandle);
|
|
1357
|
+
return scheduleId;
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Cancel a scheduled event
|
|
1361
|
+
* @param scheduleId - The ID of the schedule to cancel
|
|
1362
|
+
* @returns Promise resolving to true if canceled, false if not found
|
|
1363
|
+
*/
|
|
1364
|
+
async cancel(scheduleId) {
|
|
1365
|
+
const timeoutHandle = this.schedules.get(scheduleId);
|
|
1366
|
+
if (!timeoutHandle) {
|
|
1367
|
+
return false;
|
|
1368
|
+
}
|
|
1369
|
+
clearTimeout(timeoutHandle);
|
|
1370
|
+
this.schedules.delete(scheduleId);
|
|
1371
|
+
return true;
|
|
1372
|
+
}
|
|
1373
|
+
/**
|
|
1374
|
+
* Get the number of currently scheduled events
|
|
1375
|
+
* @returns The count of active schedules
|
|
1376
|
+
*/
|
|
1377
|
+
getScheduleCount() {
|
|
1378
|
+
return this.schedules.size;
|
|
1379
|
+
}
|
|
1380
|
+
/**
|
|
1381
|
+
* Cancel all scheduled events
|
|
1382
|
+
*/
|
|
1383
|
+
async cancelAll() {
|
|
1384
|
+
for (const timeoutHandle of this.schedules.values()) {
|
|
1385
|
+
clearTimeout(timeoutHandle);
|
|
1386
|
+
}
|
|
1387
|
+
this.schedules.clear();
|
|
1388
|
+
}
|
|
1389
|
+
};
|
|
1390
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1391
|
+
0 && (module.exports = {
|
|
1392
|
+
InMemoryWorkflowMemory,
|
|
1393
|
+
InMemoryWorkflowScheduler,
|
|
1394
|
+
InMemoryWorkflowStore,
|
|
1395
|
+
NodeModel,
|
|
1396
|
+
WorkflowManager,
|
|
1397
|
+
WorkflowModel,
|
|
1398
|
+
defaultNodeModels
|
|
1399
|
+
});
|