@ai.ntellect/core 0.6.16 → 0.6.19

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 (83) hide show
  1. package/.mocharc.json +1 -2
  2. package/README.md +123 -178
  3. package/dist/graph/controller.js +29 -6
  4. package/dist/graph/index.js +402 -0
  5. package/dist/index.js +22 -7
  6. package/dist/interfaces/index.js +15 -0
  7. package/dist/modules/agenda/adapters/node-cron/index.js +29 -0
  8. package/dist/modules/agenda/index.js +140 -0
  9. package/dist/{services/embedding.js → modules/embedding/adapters/ai/index.js} +24 -7
  10. package/dist/modules/embedding/index.js +59 -0
  11. package/dist/modules/memory/adapters/in-memory/index.js +210 -0
  12. package/dist/{memory → modules/memory}/adapters/meilisearch/index.js +97 -2
  13. package/dist/{memory → modules/memory}/adapters/redis/index.js +77 -15
  14. package/dist/modules/memory/index.js +103 -0
  15. package/dist/utils/{stringifiy-zod-schema.js → generate-action-schema.js} +5 -5
  16. package/graph/controller.ts +38 -14
  17. package/graph/index.ts +468 -0
  18. package/index.ts +25 -7
  19. package/interfaces/index.ts +346 -28
  20. package/modules/agenda/adapters/node-cron/index.ts +25 -0
  21. package/modules/agenda/index.ts +159 -0
  22. package/modules/embedding/adapters/ai/index.ts +42 -0
  23. package/modules/embedding/index.ts +45 -0
  24. package/modules/memory/adapters/in-memory/index.ts +203 -0
  25. package/{memory → modules/memory}/adapters/meilisearch/index.ts +114 -8
  26. package/modules/memory/adapters/redis/index.ts +164 -0
  27. package/modules/memory/index.ts +93 -0
  28. package/package.json +4 -4
  29. package/test/graph/index.test.ts +646 -0
  30. package/test/modules/agenda/node-cron.test.ts +286 -0
  31. package/test/modules/embedding/ai.test.ts +78 -0
  32. package/test/modules/memory/adapters/in-memory.test.ts +153 -0
  33. package/test/{memory → modules/memory}/adapters/meilisearch.test.ts +80 -94
  34. package/test/modules/memory/adapters/redis.test.ts +169 -0
  35. package/test/modules/memory/base.test.ts +230 -0
  36. package/test/services/agenda.test.ts +279 -280
  37. package/tsconfig.json +0 -3
  38. package/types/index.ts +82 -203
  39. package/utils/{stringifiy-zod-schema.ts → generate-action-schema.ts} +3 -3
  40. package/app/README.md +0 -36
  41. package/app/app/favicon.ico +0 -0
  42. package/app/app/globals.css +0 -21
  43. package/app/app/gun.ts +0 -0
  44. package/app/app/layout.tsx +0 -18
  45. package/app/app/page.tsx +0 -321
  46. package/app/eslint.config.mjs +0 -16
  47. package/app/next.config.ts +0 -7
  48. package/app/package-lock.json +0 -5912
  49. package/app/package.json +0 -31
  50. package/app/pnpm-lock.yaml +0 -4031
  51. package/app/postcss.config.mjs +0 -8
  52. package/app/public/file.svg +0 -1
  53. package/app/public/globe.svg +0 -1
  54. package/app/public/next.svg +0 -1
  55. package/app/public/vercel.svg +0 -1
  56. package/app/public/window.svg +0 -1
  57. package/app/tailwind.config.ts +0 -18
  58. package/app/tsconfig.json +0 -27
  59. package/dist/graph/graph.js +0 -162
  60. package/dist/memory/index.js +0 -9
  61. package/dist/services/agenda.js +0 -115
  62. package/dist/services/queue.js +0 -142
  63. package/dist/utils/experimental-graph-rag.js +0 -152
  64. package/dist/utils/generate-object.js +0 -111
  65. package/dist/utils/inject-actions.js +0 -16
  66. package/dist/utils/queue-item-transformer.js +0 -24
  67. package/dist/utils/sanitize-results.js +0 -60
  68. package/graph/graph.ts +0 -193
  69. package/memory/adapters/redis/index.ts +0 -103
  70. package/memory/index.ts +0 -22
  71. package/services/agenda.ts +0 -118
  72. package/services/embedding.ts +0 -26
  73. package/services/queue.ts +0 -145
  74. package/test/.env.test +0 -4
  75. package/test/graph/engine.test.ts +0 -533
  76. package/test/memory/adapters/redis.test.ts +0 -160
  77. package/test/memory/base.test.ts +0 -229
  78. package/test/services/queue.test.ts +0 -286
  79. package/utils/experimental-graph-rag.ts +0 -170
  80. package/utils/generate-object.ts +0 -117
  81. package/utils/inject-actions.ts +0 -19
  82. package/utils/queue-item-transformer.ts +0 -38
  83. package/utils/sanitize-results.ts +0 -66
@@ -0,0 +1,646 @@
1
+ import { expect } from "chai";
2
+ import EventEmitter from "events";
3
+ import sinon from "sinon";
4
+ import { z } from "zod";
5
+ import { GraphFlow } from "../../graph";
6
+ import { GraphDefinition, Node } from "../../types";
7
+
8
+ /**
9
+ * ✅ Define a valid schema using Zod.
10
+ */
11
+ const TestSchema = z.object({
12
+ value: z.number().default(0),
13
+ });
14
+
15
+ /**
16
+ * ✅ Define the schema type for TypeScript inference.
17
+ */
18
+ type TestSchema = typeof TestSchema;
19
+
20
+ describe("Graph", function () {
21
+ let graph: GraphFlow<TestSchema>;
22
+ let eventEmitter: EventEmitter;
23
+
24
+ beforeEach(() => {
25
+ eventEmitter = new EventEmitter();
26
+ graph = new GraphFlow("TestGraph", {
27
+ name: "TestGraph",
28
+ nodes: [],
29
+ context: { value: 0 },
30
+ schema: TestSchema,
31
+ eventEmitter: eventEmitter,
32
+ });
33
+ });
34
+
35
+ /**
36
+ * ✅ Ensure a simple node executes and updates the context correctly.
37
+ */
38
+ it("should execute a simple node and update the context", async function () {
39
+ const simpleNode: Node<TestSchema> = {
40
+ name: "simpleNode",
41
+ execute: async (context) => {
42
+ context.value += 1;
43
+ },
44
+ next: [],
45
+ };
46
+
47
+ graph.addNode(simpleNode);
48
+ await graph.execute("simpleNode");
49
+
50
+ const context = graph.getContext();
51
+ expect(context.value).to.equal(1);
52
+ });
53
+
54
+ /**
55
+ * ✅ Verify that `nodeStarted` and `nodeCompleted` events are triggered.
56
+ */
57
+ it("should trigger `nodeStarted` and `nodeCompleted` events", async function () {
58
+ const nodeStartedSpy = sinon.spy();
59
+ const nodeCompletedSpy = sinon.spy();
60
+
61
+ graph.on("nodeStarted", nodeStartedSpy);
62
+ graph.on("nodeCompleted", nodeCompletedSpy);
63
+
64
+ const testNode: Node<TestSchema> = {
65
+ name: "testNode",
66
+ execute: async (context) => {
67
+ context.value += 1;
68
+ },
69
+ next: [],
70
+ };
71
+
72
+ graph.addNode(testNode);
73
+ await graph.execute("testNode");
74
+
75
+ expect(nodeStartedSpy.calledOnce).to.be.true;
76
+ expect(nodeCompletedSpy.calledOnce).to.be.true;
77
+ });
78
+
79
+ /**
80
+ * ✅ Ensure an error is thrown when a node fails and `nodeError` event is triggered.
81
+ */
82
+ it("should handle errors and trigger `nodeError` event", async function () {
83
+ const errorNode: Node<TestSchema> = {
84
+ name: "errorNode",
85
+ execute: async () => {
86
+ throw new Error("Test error");
87
+ },
88
+ next: [],
89
+ };
90
+
91
+ graph.addNode(errorNode);
92
+ const nodeErrorSpy = sinon.spy();
93
+ graph.on("nodeError", nodeErrorSpy);
94
+
95
+ try {
96
+ await graph.execute("errorNode");
97
+ } catch (error) {
98
+ expect((error as Error).message).to.equal("Test error");
99
+ }
100
+
101
+ expect(nodeErrorSpy.calledOnce).to.be.true;
102
+ });
103
+
104
+ /**
105
+ * ✅ Ensure a node requiring user confirmation waits before execution.
106
+ */
107
+ it("should execute a node requiring user confirmation", async function () {
108
+ const confirmationNode: Node<TestSchema> = {
109
+ name: "waitUserConfirmation",
110
+ execute: async (context) => {
111
+ return new Promise<void>((resolve) => {
112
+ graph.on("userConfirmed", () => {
113
+ context.value += 1;
114
+ resolve();
115
+ });
116
+ });
117
+ },
118
+ next: [],
119
+ };
120
+
121
+ graph.addNode(confirmationNode);
122
+ const executionPromise = graph.execute("waitUserConfirmation");
123
+
124
+ setTimeout(() => {
125
+ graph.emit("userConfirmed");
126
+ }, 100);
127
+
128
+ await executionPromise;
129
+ const context = graph.getContext();
130
+ expect(context.value).to.equal(1);
131
+ });
132
+
133
+ /**
134
+ * ✅ Ensure that context validation using Zod works correctly.
135
+ */
136
+ it("should validate context with Zod", async function () {
137
+ const invalidContext = { value: "invalid_string" };
138
+
139
+ try {
140
+ const simpleNode: Node<TestSchema> = {
141
+ name: "simpleNode",
142
+ execute: async (context) => {
143
+ context.value += 1;
144
+ },
145
+ next: [],
146
+ };
147
+
148
+ graph.addNode(simpleNode);
149
+ await graph.execute("simpleNode", invalidContext as any);
150
+ } catch (error) {
151
+ expect((error as Error & { errors: any[] }).errors[0].message).to.include(
152
+ "Expected number"
153
+ );
154
+ }
155
+ });
156
+
157
+ /**
158
+ * ✅ Ensure a node with validated inputs and outputs executes correctly.
159
+ */
160
+ it("should execute a node with validated inputs and outputs", async function () {
161
+ const paramNode: Node<TestSchema> = {
162
+ name: "paramNode",
163
+ inputs: z.object({
164
+ increment: z.number(),
165
+ }),
166
+ outputs: z.object({
167
+ value: z.number().min(5),
168
+ }),
169
+ execute: async (context, inputs: { increment: number }) => {
170
+ context.value += inputs.increment;
171
+ },
172
+ next: [],
173
+ };
174
+
175
+ graph.addNode(paramNode);
176
+ await graph.execute("paramNode", {}, { increment: 5 });
177
+
178
+ const context = graph.getContext();
179
+ expect(context.value).to.equal(5);
180
+ });
181
+
182
+ /**
183
+ * ✅ Ensure a node does not execute if a condition is not met.
184
+ */
185
+ it("should not execute a node when condition is false", async function () {
186
+ const conditionalNode: Node<TestSchema> = {
187
+ name: "conditionalNode",
188
+ condition: (context) => context.value > 0,
189
+ execute: async (context) => {
190
+ context.value += 10;
191
+ },
192
+ next: [],
193
+ };
194
+
195
+ graph.addNode(conditionalNode);
196
+ await graph.execute("conditionalNode");
197
+
198
+ const context = graph.getContext();
199
+ expect(context.value).to.equal(0);
200
+ });
201
+
202
+ /**
203
+ * ✅ Ensure that a node retries execution when it fails.
204
+ */
205
+ it("should retry a node execution when it fails", async function () {
206
+ let attemptCount = 0;
207
+ const retryNode: Node<TestSchema> = {
208
+ name: "retryNode",
209
+ retry: { maxAttempts: 3, delay: 0 },
210
+ execute: async (context) => {
211
+ attemptCount++;
212
+ if (attemptCount < 3) {
213
+ throw new Error("Temporary failure");
214
+ }
215
+ context.value += 1;
216
+ },
217
+ next: [],
218
+ };
219
+
220
+ graph.addNode(retryNode);
221
+ await graph.execute("retryNode");
222
+
223
+ const context = graph.getContext();
224
+ expect(context.value).to.equal(1);
225
+ expect(attemptCount).to.equal(3);
226
+ });
227
+
228
+ /**
229
+ * ✅ Ensure dynamic event-based execution works via `emit`.
230
+ */
231
+ it("should trigger a node execution from an event", async function () {
232
+ this.timeout(5000); // Ensure we have enough time to complete the test
233
+
234
+ const eventNode: Node<TestSchema> = {
235
+ name: "eventNode",
236
+ events: ["customEvent"],
237
+ execute: async (context) => {
238
+ context.value += 1;
239
+ },
240
+ next: [],
241
+ };
242
+
243
+ graph.addNode(eventNode);
244
+
245
+ // Use a promise to ensure the event is properly handled
246
+ await new Promise<void>((resolve, reject) => {
247
+ const timeout = setTimeout(
248
+ () => reject(new Error("Event did not trigger")),
249
+ 1500
250
+ );
251
+
252
+ graph.on("nodeCompleted", ({ name }) => {
253
+ if (name === "eventNode") {
254
+ clearTimeout(timeout);
255
+ resolve();
256
+ }
257
+ });
258
+
259
+ graph.emit("customEvent").catch(reject);
260
+ });
261
+
262
+ const context = graph.getContext();
263
+ expect(context.value).to.equal(1);
264
+ });
265
+
266
+ /**
267
+ * ✅ Ensure that removing a node works correctly.
268
+ */
269
+ it("should remove a node from the graph", function () {
270
+ const testNode: Node<TestSchema> = {
271
+ name: "testNode",
272
+ execute: async () => {},
273
+ };
274
+ graph.addNode(testNode);
275
+ graph.removeNode("testNode");
276
+
277
+ expect(graph.getNodes().length).to.equal(0);
278
+ });
279
+
280
+ it("should clear and reload the graph using `load`", function () {
281
+ const nodeA: Node<TestSchema> = {
282
+ name: "A",
283
+ execute: async () => {},
284
+ };
285
+ const nodeB: Node<TestSchema> = {
286
+ name: "B",
287
+ execute: async () => {},
288
+ };
289
+
290
+ const newDefinition: GraphDefinition<TestSchema> = {
291
+ name: "TestGraph",
292
+ entryNode: "A",
293
+ nodes: [nodeA, nodeB],
294
+ context: { value: 0 },
295
+ schema: TestSchema,
296
+ };
297
+
298
+ graph.load(newDefinition);
299
+ expect(graph.getNodes().length).to.equal(2);
300
+ expect(graph.getNodes().map((n) => n.name)).to.include.members(["A", "B"]);
301
+ });
302
+
303
+ /**
304
+ * ✅ Test input validation failure
305
+ */
306
+ it("should throw error when node input validation fails", async function () {
307
+ const nodeWithInput: Node<TestSchema> = {
308
+ name: "inputNode",
309
+ inputs: z.object({
310
+ amount: z.number().min(0),
311
+ }),
312
+ execute: async (context, inputs: { amount: number }) => {
313
+ context.value += inputs.amount;
314
+ },
315
+ next: [],
316
+ };
317
+
318
+ graph.addNode(nodeWithInput);
319
+
320
+ try {
321
+ await graph.execute("inputNode", {}, { amount: -1 });
322
+ expect.fail("Should have thrown an error");
323
+ } catch (error) {
324
+ expect((error as Error).message).to.include(
325
+ "Number must be greater than or equal to 0"
326
+ );
327
+ }
328
+ });
329
+
330
+ /**
331
+ * ✅ Test output validation failure
332
+ */
333
+ it("should throw error when node output validation fails", async function () {
334
+ const nodeWithOutput: Node<TestSchema> = {
335
+ name: "outputNode",
336
+ outputs: z.object({
337
+ value: z.number().max(10),
338
+ }),
339
+ execute: async (context) => {
340
+ context.value = 20; // This will fail output validation
341
+ },
342
+ next: [],
343
+ };
344
+
345
+ graph.addNode(nodeWithOutput);
346
+
347
+ try {
348
+ await graph.execute("outputNode");
349
+ expect.fail("Should have thrown an error");
350
+ } catch (error) {
351
+ expect((error as Error).message).to.include(
352
+ "Number must be less than or equal to 10"
353
+ );
354
+ }
355
+ });
356
+
357
+ /**
358
+ * ✅ Test successful input and output validation
359
+ */
360
+ it("should successfully validate both inputs and outputs", async function () {
361
+ const validatedNode: Node<TestSchema> = {
362
+ name: "validatedNode",
363
+ inputs: z.object({
364
+ increment: z.number().min(0).max(5),
365
+ }),
366
+ outputs: z.object({
367
+ value: z.number().min(0).max(10),
368
+ }),
369
+ execute: async (context, inputs: { increment: number }) => {
370
+ context.value += inputs.increment;
371
+ },
372
+ next: [],
373
+ };
374
+
375
+ graph.addNode(validatedNode);
376
+
377
+ // Test with valid input that produces valid output
378
+ await graph.execute("validatedNode", {}, { increment: 3 });
379
+ expect(graph.getContext().value).to.equal(3);
380
+
381
+ // Test with valid input that would produce invalid output
382
+ try {
383
+ await graph.execute("validatedNode", { value: 7 }, { increment: 5 });
384
+ expect.fail("Should have thrown an error");
385
+ } catch (error) {
386
+ expect((error as Error).message).to.include(
387
+ "Number must be less than or equal to 10"
388
+ );
389
+ }
390
+ });
391
+
392
+ /**
393
+ * ✅ Test missing required inputs
394
+ */
395
+ it("should throw error when required inputs are missing", async function () {
396
+ const nodeWithRequiredInput: Node<TestSchema> = {
397
+ name: "requiredInputNode",
398
+ inputs: z.object({
399
+ required: z.string(),
400
+ }),
401
+ execute: async () => {},
402
+ next: [],
403
+ };
404
+
405
+ graph.addNode(nodeWithRequiredInput);
406
+
407
+ try {
408
+ await graph.execute("requiredInputNode");
409
+ expect.fail("Should have thrown an error");
410
+ } catch (error) {
411
+ expect((error as Error).message).to.include("Inputs required for node");
412
+ }
413
+ });
414
+
415
+ /**
416
+ * ✅ Test complex workflow with multiple branches
417
+ */
418
+ it("should execute a complex workflow with multiple branches", async function () {
419
+ const nodeA: Node<TestSchema> = {
420
+ name: "nodeA",
421
+ execute: async (context) => {
422
+ context.value += 1;
423
+ },
424
+ next: ["nodeB1", "nodeB2"],
425
+ };
426
+
427
+ const nodeB1: Node<TestSchema> = {
428
+ name: "nodeB1",
429
+ execute: async (context) => {
430
+ context.value *= 2;
431
+ },
432
+ next: ["nodeC"],
433
+ };
434
+
435
+ const nodeB2: Node<TestSchema> = {
436
+ name: "nodeB2",
437
+ execute: async (context) => {
438
+ context.value += 3;
439
+ },
440
+ next: ["nodeC"],
441
+ };
442
+
443
+ const nodeC: Node<TestSchema> = {
444
+ name: "nodeC",
445
+ execute: async (context) => {
446
+ // Créer une copie du contexte pour éviter les modifications concurrentes
447
+ const newValue = context.value + 5;
448
+ context.value = newValue;
449
+ },
450
+ };
451
+
452
+ [nodeA, nodeB1, nodeB2, nodeC].forEach((node) => graph.addNode(node));
453
+
454
+ await graph.execute("nodeA");
455
+ expect(graph.getContext().value).to.equal(9);
456
+ });
457
+
458
+ /**
459
+ * ✅ Test conditional workflow branching
460
+ */
461
+ it("should execute different branches based on conditions", async function () {
462
+ const startNode: Node<TestSchema> = {
463
+ name: "start",
464
+ execute: async (context) => {
465
+ context.value = 5;
466
+ },
467
+ next: ["branchA", "branchB"],
468
+ };
469
+
470
+ const branchA: Node<TestSchema> = {
471
+ name: "branchA",
472
+ condition: (context) => context.value < 10,
473
+ execute: async (context) => {
474
+ context.value *= 2;
475
+ },
476
+ next: ["end"],
477
+ };
478
+
479
+ const branchB: Node<TestSchema> = {
480
+ name: "branchB",
481
+ condition: (context) => context.value >= 10,
482
+ execute: async (context) => {
483
+ context.value += 10;
484
+ },
485
+ next: ["end"],
486
+ };
487
+
488
+ const endNode: Node<TestSchema> = {
489
+ name: "end",
490
+ execute: async (context) => {
491
+ context.value = context.value + 1;
492
+ },
493
+ };
494
+
495
+ [startNode, branchA, branchB, endNode].forEach((node) =>
496
+ graph.addNode(node)
497
+ );
498
+
499
+ await graph.execute("start");
500
+ expect(graph.getContext().value).to.equal(11);
501
+ });
502
+
503
+ /**
504
+ * ✅ Test complex event-driven workflow
505
+ */
506
+ it("should handle complex event-driven workflows", async function () {
507
+ this.timeout(5000); // Augmenter le timeout pour les tests asynchrones
508
+ const eventCounter = { count: 0 };
509
+
510
+ const startNode: Node<TestSchema> = {
511
+ name: "start",
512
+ events: ["startWorkflow"],
513
+ execute: async (context) => {
514
+ context.value = 1;
515
+ },
516
+ next: ["process"],
517
+ };
518
+
519
+ const processNode: Node<TestSchema> = {
520
+ name: "process",
521
+ events: ["processData"],
522
+ execute: async (context) => {
523
+ context.value *= 2;
524
+ },
525
+ next: ["finalize"],
526
+ };
527
+
528
+ const finalizeNode: Node<TestSchema> = {
529
+ name: "finalize",
530
+ events: ["complete"],
531
+ execute: async (context) => {
532
+ context.value += 3;
533
+ eventCounter.count++;
534
+ },
535
+ };
536
+
537
+ [startNode, processNode, finalizeNode].forEach((node) =>
538
+ graph.addNode(node)
539
+ );
540
+
541
+ // Test sequential event triggering
542
+ await graph.emit("startWorkflow");
543
+ await graph.emit("processData");
544
+ await graph.emit("complete");
545
+
546
+ expect(graph.getContext().value).to.equal(5); // (1 * 2) + 3
547
+ expect(eventCounter.count).to.equal(1);
548
+
549
+ // Reset context for concurrent test
550
+ graph.load({
551
+ name: "TestGraph",
552
+ nodes: [startNode, processNode, finalizeNode],
553
+ context: { value: 0 },
554
+ schema: TestSchema,
555
+ });
556
+
557
+ // Test concurrent event handling
558
+ await Promise.all([
559
+ graph.emit("startWorkflow"),
560
+ graph.emit("processData"),
561
+ graph.emit("complete"),
562
+ ]);
563
+
564
+ expect(eventCounter.count).to.equal(2);
565
+ });
566
+
567
+ /**
568
+ * ✅ Test cyclic workflow with conditional exit
569
+ */
570
+ it("should handle cyclic workflows with conditional exit", async function () {
571
+ const iterationCount = { count: 0 };
572
+
573
+ const cycleNode: Node<TestSchema> = {
574
+ name: "cycle",
575
+ execute: async (context) => {
576
+ context.value += 1;
577
+ iterationCount.count++;
578
+ },
579
+ next: ["checkExit"],
580
+ };
581
+
582
+ const checkExitNode: Node<TestSchema> = {
583
+ name: "checkExit",
584
+ execute: async (context) => {},
585
+ condition: (context) => context.value < 5,
586
+ next: ["cycle"],
587
+ };
588
+
589
+ [cycleNode, checkExitNode].forEach((node) => graph.addNode(node));
590
+
591
+ await graph.execute("cycle");
592
+
593
+ expect(graph.getContext().value).to.equal(5);
594
+ expect(iterationCount.count).to.equal(5);
595
+ });
596
+
597
+ /**
598
+ * ✅ Test executing entire graph when triggered by event
599
+ */
600
+ it("should execute entire graph when triggered by event", async function () {
601
+ const nodeA: Node<TestSchema> = {
602
+ name: "nodeA",
603
+ execute: async (context) => {
604
+ context.value += 1;
605
+ },
606
+ next: ["nodeB"],
607
+ };
608
+
609
+ const nodeB: Node<TestSchema> = {
610
+ name: "nodeB",
611
+ execute: async (context) => {
612
+ context.value *= 2;
613
+ },
614
+ next: [],
615
+ };
616
+
617
+ const graphDefinition: GraphDefinition<TestSchema> = {
618
+ name: "EventGraph",
619
+ nodes: [nodeA, nodeB],
620
+ context: { value: 0 },
621
+ schema: TestSchema,
622
+ entryNode: "nodeA",
623
+ events: ["startGraph"],
624
+ };
625
+
626
+ graph.load(graphDefinition);
627
+
628
+ // Use a promise to ensure the event is properly handled
629
+ await new Promise<void>((resolve, reject) => {
630
+ const timeout = setTimeout(
631
+ () => reject(new Error("Graph event did not complete")),
632
+ 1500
633
+ );
634
+
635
+ graph.on("graphCompleted", () => {
636
+ clearTimeout(timeout);
637
+ resolve();
638
+ });
639
+
640
+ graph.emit("startGraph").catch(reject);
641
+ });
642
+
643
+ const context = graph.getContext();
644
+ expect(context.value).to.equal(2); // (0 + 1) * 2
645
+ });
646
+ });