@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.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
+ });