@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.
- package/.mocharc.json +1 -2
- package/README.md +123 -178
- package/dist/graph/controller.js +29 -6
- package/dist/graph/index.js +402 -0
- package/dist/index.js +22 -7
- package/dist/interfaces/index.js +15 -0
- package/dist/modules/agenda/adapters/node-cron/index.js +29 -0
- package/dist/modules/agenda/index.js +140 -0
- package/dist/{services/embedding.js → modules/embedding/adapters/ai/index.js} +24 -7
- package/dist/modules/embedding/index.js +59 -0
- package/dist/modules/memory/adapters/in-memory/index.js +210 -0
- package/dist/{memory → modules/memory}/adapters/meilisearch/index.js +97 -2
- package/dist/{memory → modules/memory}/adapters/redis/index.js +77 -15
- package/dist/modules/memory/index.js +103 -0
- package/dist/utils/{stringifiy-zod-schema.js → generate-action-schema.js} +5 -5
- package/graph/controller.ts +38 -14
- package/graph/index.ts +468 -0
- package/index.ts +25 -7
- package/interfaces/index.ts +346 -28
- package/modules/agenda/adapters/node-cron/index.ts +25 -0
- package/modules/agenda/index.ts +159 -0
- package/modules/embedding/adapters/ai/index.ts +42 -0
- package/modules/embedding/index.ts +45 -0
- package/modules/memory/adapters/in-memory/index.ts +203 -0
- package/{memory → modules/memory}/adapters/meilisearch/index.ts +114 -8
- package/modules/memory/adapters/redis/index.ts +164 -0
- package/modules/memory/index.ts +93 -0
- package/package.json +4 -4
- package/test/graph/index.test.ts +646 -0
- package/test/modules/agenda/node-cron.test.ts +286 -0
- package/test/modules/embedding/ai.test.ts +78 -0
- package/test/modules/memory/adapters/in-memory.test.ts +153 -0
- package/test/{memory → modules/memory}/adapters/meilisearch.test.ts +80 -94
- package/test/modules/memory/adapters/redis.test.ts +169 -0
- package/test/modules/memory/base.test.ts +230 -0
- package/test/services/agenda.test.ts +279 -280
- package/tsconfig.json +0 -3
- package/types/index.ts +82 -203
- package/utils/{stringifiy-zod-schema.ts → generate-action-schema.ts} +3 -3
- package/app/README.md +0 -36
- package/app/app/favicon.ico +0 -0
- package/app/app/globals.css +0 -21
- package/app/app/gun.ts +0 -0
- package/app/app/layout.tsx +0 -18
- package/app/app/page.tsx +0 -321
- package/app/eslint.config.mjs +0 -16
- package/app/next.config.ts +0 -7
- package/app/package-lock.json +0 -5912
- package/app/package.json +0 -31
- package/app/pnpm-lock.yaml +0 -4031
- package/app/postcss.config.mjs +0 -8
- package/app/public/file.svg +0 -1
- package/app/public/globe.svg +0 -1
- package/app/public/next.svg +0 -1
- package/app/public/vercel.svg +0 -1
- package/app/public/window.svg +0 -1
- package/app/tailwind.config.ts +0 -18
- package/app/tsconfig.json +0 -27
- package/dist/graph/graph.js +0 -162
- package/dist/memory/index.js +0 -9
- package/dist/services/agenda.js +0 -115
- package/dist/services/queue.js +0 -142
- package/dist/utils/experimental-graph-rag.js +0 -152
- package/dist/utils/generate-object.js +0 -111
- package/dist/utils/inject-actions.js +0 -16
- package/dist/utils/queue-item-transformer.js +0 -24
- package/dist/utils/sanitize-results.js +0 -60
- package/graph/graph.ts +0 -193
- package/memory/adapters/redis/index.ts +0 -103
- package/memory/index.ts +0 -22
- package/services/agenda.ts +0 -118
- package/services/embedding.ts +0 -26
- package/services/queue.ts +0 -145
- package/test/.env.test +0 -4
- package/test/graph/engine.test.ts +0 -533
- package/test/memory/adapters/redis.test.ts +0 -160
- package/test/memory/base.test.ts +0 -229
- package/test/services/queue.test.ts +0 -286
- package/utils/experimental-graph-rag.ts +0 -170
- package/utils/generate-object.ts +0 -117
- package/utils/inject-actions.ts +0 -19
- package/utils/queue-item-transformer.ts +0 -38
- 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
|
+
});
|