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