@ai.ntellect/core 0.6.20 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/.mocharc.json +2 -1
  2. package/README.md +87 -148
  3. package/graph/controller.ts +1 -1
  4. package/graph/event-manager.ts +288 -0
  5. package/graph/index.ts +152 -384
  6. package/graph/logger.ts +70 -0
  7. package/graph/node.ts +398 -0
  8. package/graph/observer.ts +361 -0
  9. package/interfaces/index.ts +102 -1
  10. package/modules/agenda/index.ts +3 -16
  11. package/modules/embedding/index.ts +3 -3
  12. package/package.json +12 -20
  13. package/test/graph/index.test.ts +296 -154
  14. package/test/graph/observer.test.ts +398 -0
  15. package/test/modules/agenda/node-cron.test.ts +37 -16
  16. package/test/modules/memory/adapters/in-memory.test.ts +2 -2
  17. package/test/modules/memory/adapters/meilisearch.test.ts +28 -24
  18. package/test/modules/memory/base.test.ts +3 -3
  19. package/tsconfig.json +4 -2
  20. package/types/index.ts +23 -2
  21. package/utils/generate-action-schema.ts +8 -7
  22. package/.env.example +0 -2
  23. package/dist/graph/controller.js +0 -75
  24. package/dist/graph/index.js +0 -402
  25. package/dist/index.js +0 -41
  26. package/dist/interfaces/index.js +0 -17
  27. package/dist/modules/agenda/adapters/node-cron/index.js +0 -29
  28. package/dist/modules/agenda/index.js +0 -140
  29. package/dist/modules/embedding/adapters/ai/index.js +0 -57
  30. package/dist/modules/embedding/index.js +0 -59
  31. package/dist/modules/memory/adapters/in-memory/index.js +0 -210
  32. package/dist/modules/memory/adapters/meilisearch/index.js +0 -320
  33. package/dist/modules/memory/adapters/redis/index.js +0 -158
  34. package/dist/modules/memory/index.js +0 -103
  35. package/dist/types/index.js +0 -2
  36. package/dist/utils/generate-action-schema.js +0 -42
  37. package/dist/utils/header-builder.js +0 -34
  38. package/graph.ts +0 -74
  39. package/test/modules/embedding/ai.test.ts +0 -78
  40. package/test/modules/memory/adapters/redis.test.ts +0 -169
  41. package/test/services/agenda.test.ts +0 -279
@@ -2,41 +2,63 @@ import { expect } from "chai";
2
2
  import EventEmitter from "events";
3
3
  import sinon from "sinon";
4
4
  import { z } from "zod";
5
+ import { GraphFlowController } from "../../graph/controller";
5
6
  import { GraphFlow } from "../../graph/index";
6
- import { GraphDefinition, Node } from "../../types";
7
+ import { GraphContext, GraphDefinition, Node } from "../../types";
8
+
7
9
  /**
8
- * Define a valid schema using Zod.
10
+ * Test schema definition using Zod for graph context validation
11
+ * Defines a schema with:
12
+ * - value: numeric value for tracking state changes
13
+ * - eventPayload: optional object containing transaction metadata
9
14
  */
10
15
  const TestSchema = z.object({
11
16
  value: z.number().default(0),
17
+ eventPayload: z
18
+ .object({
19
+ transactionId: z.string().optional(),
20
+ status: z.string().optional(),
21
+ })
22
+ .optional(),
12
23
  });
13
24
 
14
- /**
15
- * ✅ Define the schema type for TypeScript inference.
16
- */
17
25
  type TestSchema = typeof TestSchema;
18
26
 
27
+ /**
28
+ * Test suite for the Graph Flow implementation
29
+ * This suite validates the core functionality of the graph-based workflow system:
30
+ * - Node execution and state management through context
31
+ * - Event handling (emission, correlation, waiting)
32
+ * - Error handling and retry mechanisms
33
+ * - Input/Output validation using Zod schemas
34
+ * - Complex workflows with multiple branches and conditions
35
+ * - Parallel and sequential execution patterns
36
+ *
37
+ * The tests use a simple numeric value-based context to demonstrate state changes
38
+ * and a transaction-based event payload for testing event correlation.
39
+ */
19
40
  describe("Graph", function () {
20
41
  let graph: GraphFlow<TestSchema>;
21
42
  let eventEmitter: EventEmitter;
22
43
 
23
44
  beforeEach(() => {
24
45
  eventEmitter = new EventEmitter();
25
- graph = new GraphFlow(
26
- "TestGraph",
27
- {
28
- name: "TestGraph",
29
- nodes: [],
30
- context: { value: 0 },
31
- schema: TestSchema,
32
- eventEmitter: eventEmitter,
33
- },
34
- { verbose: true }
35
- );
46
+ graph = new GraphFlow("TestGraph", {
47
+ name: "TestGraph",
48
+ nodes: [],
49
+ context: { value: 0 },
50
+ schema: TestSchema,
51
+ eventEmitter: eventEmitter,
52
+ });
36
53
  });
37
54
 
38
55
  /**
39
- * Ensure a simple node executes and updates the context correctly.
56
+ * Tests basic node execution and context update functionality
57
+ * Validates that:
58
+ * - A node can be added to the graph
59
+ * - The node's execute function is called
60
+ * - The context is properly updated
61
+ * - The updated context is accessible after execution
40
62
  */
41
63
  it("should execute a simple node and update the context", async function () {
42
64
  const simpleNode: Node<TestSchema> = {
@@ -55,7 +77,11 @@ describe("Graph", function () {
55
77
  });
56
78
 
57
79
  /**
58
- * Verify that `nodeStarted` and `nodeCompleted` events are triggered.
80
+ * Tests event emission for node lifecycle events
81
+ * Validates that the graph properly emits events for:
82
+ * - Node execution start (nodeStarted)
83
+ * - Node execution completion (nodeCompleted)
84
+ * This is crucial for monitoring and debugging workflow execution
59
85
  */
60
86
  it("should trigger `nodeStarted` and `nodeCompleted` events", async function () {
61
87
  const nodeStartedSpy = sinon.spy();
@@ -80,7 +106,12 @@ describe("Graph", function () {
80
106
  });
81
107
 
82
108
  /**
83
- * Ensure an error is thrown when a node fails and `nodeError` event is triggered.
109
+ * Tests error handling and error event emission
110
+ * Validates that:
111
+ * - Errors in node execution are properly caught
112
+ * - The nodeError event is emitted
113
+ * - The error message is preserved
114
+ * This ensures robust error handling in the workflow
84
115
  */
85
116
  it("should handle errors and trigger `nodeError` event", async function () {
86
117
  const errorNode: Node<TestSchema> = {
@@ -105,7 +136,12 @@ describe("Graph", function () {
105
136
  });
106
137
 
107
138
  /**
108
- * Ensure that context validation using Zod works correctly.
139
+ * Tests context validation using Zod schema
140
+ * Validates that:
141
+ * - Invalid context values are rejected
142
+ * - Proper error messages are generated
143
+ * - Type safety is maintained during execution
144
+ * This ensures data integrity throughout the workflow
109
145
  */
110
146
  it("should validate context with Zod", async function () {
111
147
  const invalidContext = { value: "invalid_string" };
@@ -129,7 +165,12 @@ describe("Graph", function () {
129
165
  });
130
166
 
131
167
  /**
132
- * Ensure a node with validated inputs and outputs executes correctly.
168
+ * Tests node execution with input/output validation
169
+ * Demonstrates:
170
+ * - Input parameter validation
171
+ * - Output state validation
172
+ * - Integration between node execution and validation
173
+ * Ensures type safety and data consistency in node interactions
133
174
  */
134
175
  it("should execute a node with validated inputs and outputs", async function () {
135
176
  const paramNode: Node<TestSchema, { increment: number }> = {
@@ -154,7 +195,12 @@ describe("Graph", function () {
154
195
  });
155
196
 
156
197
  /**
157
- * Ensure a node does not execute if a condition is not met.
198
+ * Tests conditional node execution
199
+ * Validates that:
200
+ * - Nodes can have conditional execution logic
201
+ * - Conditions are evaluated against current context
202
+ * - Nodes are skipped when conditions are not met
203
+ * This enables dynamic workflow paths based on state
158
204
  */
159
205
  it("should not execute a node when condition is false", async function () {
160
206
  const conditionalNode: Node<TestSchema> = {
@@ -174,7 +220,13 @@ describe("Graph", function () {
174
220
  });
175
221
 
176
222
  /**
177
- * Ensure that a node retries execution when it fails.
223
+ * Tests node retry functionality
224
+ * Validates the retry mechanism:
225
+ * - Maximum attempt limits
226
+ * - Retry delays
227
+ * - Success after retry
228
+ * - Context preservation between attempts
229
+ * Essential for handling transient failures in workflows
178
230
  */
179
231
  it("should retry a node execution when it fails", async function () {
180
232
  let attemptCount = 0;
@@ -200,45 +252,7 @@ describe("Graph", function () {
200
252
  });
201
253
 
202
254
  /**
203
- * Ensure dynamic event-based execution works via `emit`.
204
- */
205
- it("should trigger a node execution from an event", async function () {
206
- this.timeout(5000); // Ensure we have enough time to complete the test
207
-
208
- const eventNode: Node<TestSchema> = {
209
- name: "eventNode",
210
- events: ["customEvent"],
211
- execute: async (context) => {
212
- context.value = (context.value ?? 0) + 1;
213
- },
214
- next: [],
215
- };
216
-
217
- graph.addNode(eventNode);
218
-
219
- // Use a promise to ensure the event is properly handled
220
- await new Promise<void>((resolve, reject) => {
221
- const timeout = setTimeout(
222
- () => reject(new Error("Event did not trigger")),
223
- 1500
224
- );
225
-
226
- graph.on("nodeCompleted", ({ name }) => {
227
- if (name === "eventNode") {
228
- clearTimeout(timeout);
229
- resolve();
230
- }
231
- });
232
-
233
- graph.emit("customEvent").catch(reject);
234
- });
235
-
236
- const context = graph.getContext();
237
- expect(context.value).to.equal(1);
238
- });
239
-
240
- /**
241
- * ✅ Ensure that removing a node works correctly.
255
+ * Tests node removal functionality
242
256
  */
243
257
  it("should remove a node from the graph", function () {
244
258
  const testNode: Node<TestSchema> = {
@@ -251,6 +265,9 @@ describe("Graph", function () {
251
265
  expect(graph.getNodes().length).to.equal(0);
252
266
  });
253
267
 
268
+ /**
269
+ * Tests graph reloading functionality
270
+ */
254
271
  it("should clear and reload the graph using `load`", function () {
255
272
  const nodeA: Node<TestSchema> = {
256
273
  name: "A",
@@ -275,7 +292,7 @@ describe("Graph", function () {
275
292
  });
276
293
 
277
294
  /**
278
- * Test input validation failure
295
+ * Tests input validation error handling
279
296
  */
280
297
  it("should throw error when node input validation fails", async function () {
281
298
  const nodeWithInput: Node<TestSchema, { amount: number }> = {
@@ -302,7 +319,7 @@ describe("Graph", function () {
302
319
  });
303
320
 
304
321
  /**
305
- * Test output validation failure
322
+ * Tests output validation error handling
306
323
  */
307
324
  it("should throw error when node output validation fails", async function () {
308
325
  const nodeWithOutput: Node<TestSchema> = {
@@ -329,7 +346,7 @@ describe("Graph", function () {
329
346
  });
330
347
 
331
348
  /**
332
- * Test successful input and output validation
349
+ * Tests successful input/output validation flow
333
350
  */
334
351
  it("should successfully validate both inputs and outputs", async function () {
335
352
  const validatedNode: Node<TestSchema, { increment: number }> = {
@@ -364,7 +381,7 @@ describe("Graph", function () {
364
381
  });
365
382
 
366
383
  /**
367
- * Test missing required inputs
384
+ * Tests handling of missing required inputs
368
385
  */
369
386
  it("should throw error when required inputs are missing", async function () {
370
387
  const nodeWithRequiredInput: Node<TestSchema, { required: string }> = {
@@ -387,9 +404,15 @@ describe("Graph", function () {
387
404
  });
388
405
 
389
406
  /**
390
- * Test complex workflow with multiple branches
407
+ * Tests complex workflow execution with multiple branches
408
+ * Demonstrates:
409
+ * - Multiple execution paths
410
+ * - Node chaining
411
+ * - Parallel branch execution
412
+ * - Context accumulation across branches
413
+ * This validates the graph's ability to handle complex business processes
391
414
  */
392
- it("should execute a complex workflow with multiple branches", async function () {
415
+ it("should execute a complex workflow with multiple nodes and accumulate the value", async function () {
393
416
  const nodeA: Node<TestSchema> = {
394
417
  name: "nodeA",
395
418
  execute: async (context) => {
@@ -417,20 +440,18 @@ describe("Graph", function () {
417
440
  const nodeC: Node<TestSchema> = {
418
441
  name: "nodeC",
419
442
  execute: async (context) => {
420
- // Créer une copie du contexte pour éviter les modifications concurrentes
421
- const newValue = (context.value ?? 0) + 5;
422
- context.value = newValue;
443
+ context.value = (context.value ?? 0) + 5;
423
444
  },
424
445
  };
425
446
 
426
447
  [nodeA, nodeB1, nodeB2, nodeC].forEach((node) => graph.addNode(node));
427
448
 
428
449
  await graph.execute("nodeA");
429
- expect(graph.getContext().value).to.equal(9);
450
+ expect(graph.getContext().value).to.equal(15);
430
451
  });
431
452
 
432
453
  /**
433
- * Test conditional workflow branching
454
+ * Tests conditional branching in workflows
434
455
  */
435
456
  it("should execute different branches based on conditions", async function () {
436
457
  const startNode: Node<TestSchema> = {
@@ -438,141 +459,262 @@ describe("Graph", function () {
438
459
  execute: async (context) => {
439
460
  context.value = (context.value ?? 0) + 5;
440
461
  },
441
- next: ["branchA", "branchB"],
462
+ next: ["end"],
442
463
  };
443
464
 
444
- const branchA: Node<TestSchema> = {
445
- name: "branchA",
446
- condition: (context) => (context.value ?? 0) < 10,
465
+ const endNode: Node<TestSchema> = {
466
+ name: "end",
447
467
  execute: async (context) => {
448
- context.value = (context.value ?? 0) * 2;
468
+ if ((context.value ?? 0) < 10) {
469
+ context.value = (context.value ?? 0) * 2;
470
+ } else {
471
+ context.value = (context.value ?? 0) + 1;
472
+ }
449
473
  },
450
- next: ["end"],
451
474
  };
452
475
 
453
- const branchB: Node<TestSchema> = {
454
- name: "branchB",
455
- condition: (context) => (context.value ?? 0) >= 10,
476
+ [startNode, endNode].forEach((node) => graph.addNode(node));
477
+
478
+ await graph.execute("start");
479
+ expect(graph.getContext().value).to.equal(10);
480
+ });
481
+
482
+ /**
483
+ * Tests parallel workflow execution using GraphController
484
+ * Validates:
485
+ * - Multiple graph execution in parallel
486
+ * - Independent context maintenance
487
+ * - Proper result aggregation
488
+ * - Concurrency control
489
+ * Essential for scaling workflow processing
490
+ */
491
+ it("should handle parallel workflows using GraphController", async function () {
492
+ // Graph 1
493
+ const graph1 = new GraphFlow("Graph1", {
494
+ name: "Graph1",
495
+ nodes: [],
496
+ context: { value: 0 },
497
+ schema: TestSchema,
498
+ });
499
+
500
+ const processNode1: Node<TestSchema> = {
501
+ name: "process1",
456
502
  execute: async (context) => {
457
- context.value = (context.value ?? 0) + 10;
503
+ context.value = 1;
458
504
  },
459
- next: ["end"],
505
+ next: ["finalize1"],
460
506
  };
461
507
 
462
- const endNode: Node<TestSchema> = {
463
- name: "end",
508
+ const finalizeNode1: Node<TestSchema> = {
509
+ name: "finalize1",
464
510
  execute: async (context) => {
465
- context.value = (context.value ?? 0) + 1;
511
+ context.value = (context.value ?? 0) * 2;
466
512
  },
467
513
  };
468
514
 
469
- [startNode, branchA, branchB, endNode].forEach((node) =>
470
- graph.addNode(node)
471
- );
472
-
473
- await graph.load({
474
- name: "TestGraph",
475
- nodes: [startNode, branchA, branchB, endNode],
515
+ // Graph 2
516
+ const graph2 = new GraphFlow("Graph2", {
517
+ name: "Graph2",
518
+ nodes: [],
476
519
  context: { value: 0 },
477
520
  schema: TestSchema,
478
521
  });
479
522
 
480
- await graph.execute("start");
481
- expect(graph.getContext().value).to.equal(11);
523
+ const processNode2: Node<TestSchema> = {
524
+ name: "process2",
525
+ execute: async (context) => {
526
+ context.value = 2;
527
+ },
528
+ next: ["finalize2"],
529
+ };
530
+
531
+ const finalizeNode2: Node<TestSchema> = {
532
+ name: "finalize2",
533
+ execute: async (context) => {
534
+ context.value = (context.value ?? 0) + 3;
535
+ },
536
+ };
537
+
538
+ graph1.addNode(processNode1);
539
+ graph1.addNode(finalizeNode1);
540
+ graph2.addNode(processNode2);
541
+ graph2.addNode(finalizeNode2);
542
+
543
+ const results = await GraphFlowController.executeParallel(
544
+ [graph1, graph2],
545
+ ["process1", "process2"],
546
+ 2,
547
+ [{}, {}]
548
+ );
549
+
550
+ expect(results[0].value).to.equal(2); // Graph1: 1 * 2
551
+ expect(results[1].value).to.equal(5); // Graph2: 2 + 3
482
552
  });
483
553
 
484
554
  /**
485
- * Test complex event-driven workflow
555
+ * Tests sequential workflow execution using GraphController
486
556
  */
487
- it("should handle complex event-driven workflows", async function () {
488
- this.timeout(5000); // Augmenter le timeout pour les tests asynchrones
489
- const eventCounter = { count: 0 };
557
+ it("should handle sequential workflows using GraphController", async function () {
558
+ // Graph 1
559
+ const graph1 = new GraphFlow("Graph1", {
560
+ name: "Graph1",
561
+ nodes: [],
562
+ context: { value: 1 },
563
+ schema: TestSchema,
564
+ });
490
565
 
491
- const startNode: Node<TestSchema> = {
492
- name: "start",
493
- events: ["startWorkflow"],
566
+ const startNode1: Node<TestSchema> = {
567
+ name: "start1",
494
568
  execute: async (context) => {
495
- context.value = 1;
569
+ context.value = (context.value ?? 0) * 2;
496
570
  },
497
- next: ["process"],
498
571
  };
499
572
 
500
- const processNode: Node<TestSchema> = {
501
- name: "process",
502
- events: ["processData"],
573
+ // Graph 2
574
+ const graph2 = new GraphFlow("Graph2", {
575
+ name: "Graph2",
576
+ nodes: [],
577
+ context: { value: 3 },
578
+ schema: TestSchema,
579
+ });
580
+
581
+ const startNode2: Node<TestSchema> = {
582
+ name: "start2",
503
583
  execute: async (context) => {
504
- context.value = (context.value ?? 0) * 2;
584
+ context.value = (context.value ?? 0) + 2;
505
585
  },
506
- next: ["finalize"],
507
586
  };
508
587
 
509
- const finalizeNode: Node<TestSchema> = {
510
- name: "finalize",
511
- events: ["complete"],
512
- execute: async (context) => {
513
- context.value = (context.value ?? 0) + 3;
514
- eventCounter.count++;
588
+ graph1.addNode(startNode1);
589
+ graph2.addNode(startNode2);
590
+
591
+ const results = await GraphFlowController.executeSequential(
592
+ [graph1, graph2],
593
+ ["start1", "start2"],
594
+ [{}, {}]
595
+ );
596
+
597
+ expect(results[0].value).to.equal(2); // Graph1: 1 * 2
598
+ expect(results[1].value).to.equal(5); // Graph2: 3 + 2
599
+ });
600
+
601
+ /**
602
+ * Tests event correlation functionality
603
+ * Demonstrates:
604
+ * - Event correlation based on transaction ID
605
+ * - Timeout handling
606
+ * - Multiple event synchronization
607
+ * - Context updates after correlation
608
+ * Critical for integrating with external event sources
609
+ */
610
+ it("should handle correlated events correctly", async function () {
611
+ this.timeout(10000);
612
+ const graph = new GraphFlow("test", {
613
+ name: "test",
614
+ nodes: [],
615
+ context: { value: 0 },
616
+ schema: TestSchema,
617
+ eventEmitter: new EventEmitter(),
618
+ });
619
+
620
+ let eventsReceived = 0;
621
+ const node = {
622
+ name: "testNode",
623
+ waitForEvents: {
624
+ events: ["eventA", "eventB"],
625
+ timeout: 5000,
626
+ strategy: "all" as const,
627
+ },
628
+ execute: async (context: GraphContext<typeof TestSchema>) => {
629
+ eventsReceived = 2;
630
+ context.value = 42;
515
631
  },
516
632
  };
517
633
 
518
- [startNode, processNode, finalizeNode].forEach((node) =>
519
- graph.addNode(node)
520
- );
634
+ graph.addNode(node);
521
635
 
522
- // Test sequential event triggering
523
- await graph.emit("startWorkflow");
524
- await graph.emit("processData");
525
- await graph.emit("complete");
636
+ graph.execute("testNode");
526
637
 
527
- expect(graph.getContext().value).to.equal(5); // (1 * 2) + 3
528
- expect(eventCounter.count).to.equal(1);
638
+ await new Promise((resolve) => setTimeout(resolve, 500));
529
639
 
530
- // Reset context for concurrent test
531
- graph.load({
532
- name: "TestGraph",
533
- nodes: [startNode, processNode, finalizeNode],
640
+ await graph.emit("eventA", { eventPayload: { status: "A" } });
641
+ await new Promise((resolve) => setTimeout(resolve, 100));
642
+ await graph.emit("eventB", { eventPayload: { status: "B" } });
643
+
644
+ await new Promise((resolve) => setTimeout(resolve, 100));
645
+
646
+ expect(eventsReceived).to.equal(2);
647
+ expect(graph.getContext().value).to.equal(42);
648
+ });
649
+
650
+ /**
651
+ * Tests multiple event waiting functionality
652
+ */
653
+ it("should wait for multiple events before continuing", async function () {
654
+ this.timeout(10000);
655
+ const graph = new GraphFlow("test", {
656
+ name: "test",
657
+ nodes: [],
534
658
  context: { value: 0 },
535
659
  schema: TestSchema,
660
+ eventEmitter: new EventEmitter(),
536
661
  });
537
662
 
538
- // Test concurrent event handling
539
- await Promise.all([
540
- graph.emit("startWorkflow"),
541
- graph.emit("processData"),
542
- graph.emit("complete"),
543
- ]);
663
+ const node = {
664
+ name: "testNode",
665
+ waitForEvents: {
666
+ events: ["event1", "event2"],
667
+ timeout: 5000,
668
+ strategy: "all" as const,
669
+ },
670
+ execute: async (context: GraphContext<typeof TestSchema>) => {
671
+ context.value = 42; // Ajouter une modification du contexte
672
+ },
673
+ };
674
+
675
+ graph.addNode(node);
676
+ graph.execute("testNode");
544
677
 
545
- expect(eventCounter.count).to.equal(2);
678
+ await new Promise((resolve) => setTimeout(resolve, 500));
679
+ await graph.emit("event1", { eventPayload: { status: "1" } });
680
+ await new Promise((resolve) => setTimeout(resolve, 100));
681
+ await graph.emit("event2", { eventPayload: { status: "2" } });
682
+ expect(graph.getContext().value).to.equal(42);
546
683
  });
547
684
 
548
685
  /**
549
- * Test cyclic workflow with conditional exit
686
+ * Tests single event waiting functionality
550
687
  */
551
- it("should handle cyclic workflows with conditional exit", async function () {
552
- const iterationCount = { count: 0 };
688
+ it("should wait for a single event before continuing", async function () {
689
+ this.timeout(5000);
553
690
 
554
- const cycleNode: Node<TestSchema> = {
555
- name: "cycle",
556
- execute: async (context) => {
557
- context.value = (context.value ?? 0) + 1;
558
- iterationCount.count++;
691
+ const waitingNode: Node<TestSchema> = {
692
+ name: "waitingNode",
693
+ execute: async (context: GraphContext<typeof TestSchema>) => {
694
+ context.value = 1;
559
695
  },
560
- next: ["checkExit"],
696
+ waitForEvent: true,
697
+ next: ["finalNode"],
561
698
  };
562
699
 
563
- const checkExitNode: Node<TestSchema> = {
564
- name: "checkExit",
565
- execute: async () => {},
566
- next: (context) => {
567
- return (context.value ?? 0) < 5 ? ["cycle"] : [];
700
+ const finalNode: Node<TestSchema> = {
701
+ name: "finalNode",
702
+ execute: async (context: GraphContext<typeof TestSchema>) => {
703
+ context.value = (context.value ?? 0) + 5;
568
704
  },
569
705
  };
570
706
 
571
- [cycleNode, checkExitNode].forEach((node) => graph.addNode(node));
707
+ [waitingNode, finalNode].forEach((node) => graph.addNode(node));
708
+
709
+ const resultPromise = graph.execute("waitingNode");
710
+
711
+ // Wait a bit to ensure the node is ready
712
+ await new Promise((resolve) => setTimeout(resolve, 500));
572
713
 
573
- await graph.execute("cycle");
714
+ // Emit the event
715
+ await graph.emit("someEvent");
574
716
 
575
- expect(graph.getContext().value).to.equal(5);
576
- expect(iterationCount.count).to.equal(5);
717
+ const result = await resultPromise;
718
+ expect(result.value).to.equal(6); // 1 (waitingNode) + 5 (finalNode)
577
719
  });
578
720
  });