@ai.ntellect/core 0.7.7 → 1.0.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/README.md +158 -81
- package/index.ts +462 -22
- package/package copy.json +21 -0
- package/package.json +9 -44
- package/tsconfig.json +108 -22
- package/types.ts +62 -0
- package/utils/executor.ts +42 -0
- package/.mocharc.json +0 -5
- package/dist/graph/controller.d.ts +0 -31
- package/dist/graph/controller.d.ts.map +0 -1
- package/dist/graph/controller.js +0 -71
- package/dist/graph/controller.js.map +0 -1
- package/dist/graph/event-manager.d.ts +0 -92
- package/dist/graph/event-manager.d.ts.map +0 -1
- package/dist/graph/event-manager.js +0 -244
- package/dist/graph/event-manager.js.map +0 -1
- package/dist/graph/index.d.ts +0 -159
- package/dist/graph/index.d.ts.map +0 -1
- package/dist/graph/index.js +0 -303
- package/dist/graph/index.js.map +0 -1
- package/dist/graph/logger.d.ts +0 -46
- package/dist/graph/logger.d.ts.map +0 -1
- package/dist/graph/logger.js +0 -69
- package/dist/graph/logger.js.map +0 -1
- package/dist/graph/node.d.ts +0 -92
- package/dist/graph/node.d.ts.map +0 -1
- package/dist/graph/node.js +0 -249
- package/dist/graph/node.js.map +0 -1
- package/dist/graph/observer.d.ts +0 -113
- package/dist/graph/observer.d.ts.map +0 -1
- package/dist/graph/observer.js +0 -198
- package/dist/graph/observer.js.map +0 -1
- package/dist/index.d.ts +0 -26
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -42
- package/dist/index.js.map +0 -1
- package/dist/interfaces/index.d.ts +0 -447
- package/dist/interfaces/index.d.ts.map +0 -1
- package/dist/interfaces/index.js +0 -75
- package/dist/interfaces/index.js.map +0 -1
- package/dist/modules/agenda/adapters/node-cron/index.d.ts +0 -17
- package/dist/modules/agenda/adapters/node-cron/index.d.ts.map +0 -1
- package/dist/modules/agenda/adapters/node-cron/index.js +0 -30
- package/dist/modules/agenda/adapters/node-cron/index.js.map +0 -1
- package/dist/modules/agenda/index.d.ts +0 -63
- package/dist/modules/agenda/index.d.ts.map +0 -1
- package/dist/modules/agenda/index.js +0 -141
- package/dist/modules/agenda/index.js.map +0 -1
- package/dist/modules/embedding/adapters/ai/index.d.ts +0 -29
- package/dist/modules/embedding/adapters/ai/index.d.ts.map +0 -1
- package/dist/modules/embedding/adapters/ai/index.js +0 -58
- package/dist/modules/embedding/adapters/ai/index.js.map +0 -1
- package/dist/modules/embedding/index.d.ts +0 -36
- package/dist/modules/embedding/index.d.ts.map +0 -1
- package/dist/modules/embedding/index.js +0 -60
- package/dist/modules/embedding/index.js.map +0 -1
- package/dist/modules/memory/adapters/in-memory/index.d.ts +0 -120
- package/dist/modules/memory/adapters/in-memory/index.d.ts.map +0 -1
- package/dist/modules/memory/adapters/in-memory/index.js +0 -211
- package/dist/modules/memory/adapters/in-memory/index.js.map +0 -1
- package/dist/modules/memory/adapters/meilisearch/index.d.ts +0 -110
- package/dist/modules/memory/adapters/meilisearch/index.d.ts.map +0 -1
- package/dist/modules/memory/adapters/meilisearch/index.js +0 -321
- package/dist/modules/memory/adapters/meilisearch/index.js.map +0 -1
- package/dist/modules/memory/adapters/redis/index.d.ts +0 -82
- package/dist/modules/memory/adapters/redis/index.d.ts.map +0 -1
- package/dist/modules/memory/adapters/redis/index.js +0 -159
- package/dist/modules/memory/adapters/redis/index.js.map +0 -1
- package/dist/modules/memory/index.d.ts +0 -67
- package/dist/modules/memory/index.d.ts.map +0 -1
- package/dist/modules/memory/index.js +0 -104
- package/dist/modules/memory/index.js.map +0 -1
- package/dist/types/index.d.ts +0 -166
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -3
- package/dist/types/index.js.map +0 -1
- package/dist/utils/generate-action-schema.d.ts +0 -5
- package/dist/utils/generate-action-schema.d.ts.map +0 -1
- package/dist/utils/generate-action-schema.js +0 -44
- package/dist/utils/generate-action-schema.js.map +0 -1
- package/dist/utils/header-builder.d.ts +0 -12
- package/dist/utils/header-builder.d.ts.map +0 -1
- package/dist/utils/header-builder.js +0 -35
- package/dist/utils/header-builder.js.map +0 -1
- package/graph/controller.ts +0 -74
- package/graph/event-manager.ts +0 -295
- package/graph/index.ts +0 -397
- package/graph/logger.ts +0 -70
- package/graph/node.ts +0 -305
- package/graph/observer.ts +0 -368
- package/interfaces/index.ts +0 -545
- package/modules/agenda/adapters/node-cron/index.ts +0 -25
- package/modules/agenda/index.ts +0 -146
- package/modules/embedding/adapters/ai/index.ts +0 -42
- package/modules/embedding/index.ts +0 -45
- package/modules/memory/adapters/in-memory/index.ts +0 -207
- package/modules/memory/adapters/meilisearch/index.ts +0 -361
- package/modules/memory/adapters/redis/index.ts +0 -164
- package/modules/memory/index.ts +0 -93
- package/test/graph/controller.test.ts +0 -187
- package/test/graph/event-manager.test.ts +0 -72
- package/test/graph/index.test.ts +0 -768
- package/test/graph/node.test.ts +0 -510
- package/test/graph/observer.test.ts +0 -398
- package/test/modules/agenda/node-cron.test.ts +0 -307
- package/test/modules/memory/adapters/in-memory.test.ts +0 -153
- package/test/modules/memory/adapters/meilisearch.test.ts +0 -287
- package/test/modules/memory/base.test.ts +0 -230
- package/types/index.ts +0 -184
- package/utils/generate-action-schema.ts +0 -46
- package/utils/header-builder.ts +0 -40
package/test/graph/index.test.ts
DELETED
@@ -1,768 +0,0 @@
|
|
1
|
-
import { expect, use } from "chai";
|
2
|
-
import chaiAsPromised from "chai-as-promised";
|
3
|
-
import EventEmitter from "events";
|
4
|
-
import sinon from "sinon";
|
5
|
-
import { z } from "zod";
|
6
|
-
import { GraphController } from "../../graph/controller";
|
7
|
-
import { GraphFlow } from "../../graph/index";
|
8
|
-
import { NodeParams } from "../../graph/node";
|
9
|
-
import { GraphContext, GraphDefinition, Node } from "../../types";
|
10
|
-
|
11
|
-
use(chaiAsPromised);
|
12
|
-
|
13
|
-
/**
|
14
|
-
* Test schema definition using Zod for graph context validation
|
15
|
-
* Defines a schema with:
|
16
|
-
* - value: numeric value for tracking state changes
|
17
|
-
* - counter: numeric value for tracking state changes
|
18
|
-
* - message: string for tracking state changes
|
19
|
-
* - eventPayload: optional object containing transaction metadata
|
20
|
-
*/
|
21
|
-
const TestSchema = z.object({
|
22
|
-
value: z.number().default(0),
|
23
|
-
counter: z.number().default(0),
|
24
|
-
message: z.string().default(""),
|
25
|
-
eventPayload: z
|
26
|
-
.object({
|
27
|
-
transactionId: z.string().optional(),
|
28
|
-
status: z.string().optional(),
|
29
|
-
})
|
30
|
-
.optional(),
|
31
|
-
});
|
32
|
-
|
33
|
-
type TestSchema = typeof TestSchema;
|
34
|
-
|
35
|
-
/**
|
36
|
-
* Test suite for the Graph Flow implementation
|
37
|
-
* This suite validates the core functionality of the graph-based workflow system:
|
38
|
-
* - Node execution and state management through context
|
39
|
-
* - Event handling (emission, correlation, waiting)
|
40
|
-
* - Error handling and retry mechanisms
|
41
|
-
* - Input/Output validation using Zod schemas
|
42
|
-
* - Complex workflows with multiple branches and conditions
|
43
|
-
* - Parallel and sequential execution patterns
|
44
|
-
*
|
45
|
-
* The tests use a simple numeric value-based context to demonstrate state changes
|
46
|
-
* and a transaction-based event payload for testing event correlation.
|
47
|
-
*/
|
48
|
-
describe("GraphFlow", function () {
|
49
|
-
let graph: GraphFlow<TestSchema>;
|
50
|
-
let eventEmitter: EventEmitter;
|
51
|
-
|
52
|
-
beforeEach(() => {
|
53
|
-
eventEmitter = new EventEmitter();
|
54
|
-
graph = new GraphFlow("TestGraph", {
|
55
|
-
name: "TestGraph",
|
56
|
-
nodes: [],
|
57
|
-
context: { value: 0 },
|
58
|
-
schema: TestSchema,
|
59
|
-
eventEmitter: eventEmitter,
|
60
|
-
});
|
61
|
-
});
|
62
|
-
|
63
|
-
/**
|
64
|
-
* Tests basic node execution and context update functionality
|
65
|
-
* Validates that:
|
66
|
-
* - A node can be added to the graph
|
67
|
-
* - The node's execute function is called
|
68
|
-
* - The context is properly updated
|
69
|
-
* - The updated context is accessible after execution
|
70
|
-
*/
|
71
|
-
it("should execute a simple node and update the context", async function () {
|
72
|
-
const simpleNode: Node<TestSchema> = {
|
73
|
-
name: "simpleNode",
|
74
|
-
execute: async (context) => {
|
75
|
-
context.value = (context.value ?? 0) + 1;
|
76
|
-
},
|
77
|
-
next: [],
|
78
|
-
};
|
79
|
-
|
80
|
-
graph.addNode(simpleNode);
|
81
|
-
await graph.execute("simpleNode");
|
82
|
-
|
83
|
-
const context = graph.getContext();
|
84
|
-
expect(context.value).to.equal(1);
|
85
|
-
});
|
86
|
-
|
87
|
-
/**
|
88
|
-
* Tests event emission for node lifecycle events
|
89
|
-
* Validates that the graph properly emits events for:
|
90
|
-
* - Node execution start (nodeStarted)
|
91
|
-
* - Node execution completion (nodeCompleted)
|
92
|
-
* This is crucial for monitoring and debugging workflow execution
|
93
|
-
*/
|
94
|
-
it("should trigger `nodeStarted` and `nodeCompleted` events", async function () {
|
95
|
-
const nodeStartedSpy = sinon.spy();
|
96
|
-
const nodeCompletedSpy = sinon.spy();
|
97
|
-
|
98
|
-
graph.on("nodeStarted", nodeStartedSpy);
|
99
|
-
graph.on("nodeCompleted", nodeCompletedSpy);
|
100
|
-
|
101
|
-
const testNode: Node<TestSchema> = {
|
102
|
-
name: "testNode",
|
103
|
-
execute: async (context) => {
|
104
|
-
context.value = (context.value ?? 0) + 1;
|
105
|
-
},
|
106
|
-
next: [],
|
107
|
-
};
|
108
|
-
|
109
|
-
graph.addNode(testNode);
|
110
|
-
await graph.execute("testNode");
|
111
|
-
|
112
|
-
expect(nodeStartedSpy.calledOnce).to.be.true;
|
113
|
-
expect(nodeCompletedSpy.calledOnce).to.be.true;
|
114
|
-
});
|
115
|
-
|
116
|
-
/**
|
117
|
-
* Tests error handling and error event emission
|
118
|
-
* Validates that:
|
119
|
-
* - Errors in node execution are properly caught
|
120
|
-
* - The nodeError event is emitted
|
121
|
-
* - The error message is preserved
|
122
|
-
* This ensures robust error handling in the workflow
|
123
|
-
*/
|
124
|
-
it("should handle errors and trigger `nodeError` event", async function () {
|
125
|
-
const errorNode: Node<TestSchema> = {
|
126
|
-
name: "errorNode",
|
127
|
-
execute: async () => {
|
128
|
-
throw new Error("Test error");
|
129
|
-
},
|
130
|
-
next: [],
|
131
|
-
};
|
132
|
-
|
133
|
-
graph.addNode(errorNode);
|
134
|
-
const nodeErrorSpy = sinon.spy();
|
135
|
-
graph.on("nodeError", nodeErrorSpy);
|
136
|
-
|
137
|
-
try {
|
138
|
-
await graph.execute("errorNode");
|
139
|
-
} catch (error) {
|
140
|
-
expect((error as Error).message).to.equal("Test error");
|
141
|
-
}
|
142
|
-
|
143
|
-
expect(nodeErrorSpy.calledOnce).to.be.true;
|
144
|
-
});
|
145
|
-
|
146
|
-
/**
|
147
|
-
* Tests context validation using Zod schema
|
148
|
-
* Validates that:
|
149
|
-
* - Invalid context values are rejected
|
150
|
-
* - Proper error messages are generated
|
151
|
-
* - Type safety is maintained during execution
|
152
|
-
* This ensures data integrity throughout the workflow
|
153
|
-
*/
|
154
|
-
it("should validate context with Zod", async function () {
|
155
|
-
const invalidContext = { value: "invalid_string" };
|
156
|
-
|
157
|
-
try {
|
158
|
-
const simpleNode: Node<TestSchema> = {
|
159
|
-
name: "simpleNode",
|
160
|
-
execute: async (context) => {
|
161
|
-
context.value = (context.value ?? 0) + 1;
|
162
|
-
},
|
163
|
-
next: [],
|
164
|
-
};
|
165
|
-
|
166
|
-
graph.addNode(simpleNode);
|
167
|
-
await graph.execute("simpleNode", invalidContext as any);
|
168
|
-
} catch (error) {
|
169
|
-
expect((error as Error & { errors: any[] }).errors[0].message).to.include(
|
170
|
-
"Expected number"
|
171
|
-
);
|
172
|
-
}
|
173
|
-
});
|
174
|
-
|
175
|
-
/**
|
176
|
-
* Tests node execution with input/output validation
|
177
|
-
* Demonstrates:
|
178
|
-
* - Input parameter validation
|
179
|
-
* - Output state validation
|
180
|
-
* - Integration between node execution and validation
|
181
|
-
* Ensures type safety and data consistency in node interactions
|
182
|
-
*/
|
183
|
-
it("should execute a node with validated params and outputs", async function () {
|
184
|
-
const paramNode: Node<TestSchema, { increment: number }> = {
|
185
|
-
name: "paramNode",
|
186
|
-
params: z.object({
|
187
|
-
increment: z.number(),
|
188
|
-
}),
|
189
|
-
execute: async (context, params?: { increment: number }) => {
|
190
|
-
if (!params) throw new Error("params required");
|
191
|
-
context.value = (context.value ?? 0) + params.increment;
|
192
|
-
},
|
193
|
-
next: [],
|
194
|
-
};
|
195
|
-
|
196
|
-
graph.addNode(paramNode);
|
197
|
-
await graph.execute("paramNode", { increment: 5 });
|
198
|
-
|
199
|
-
const context = graph.getContext();
|
200
|
-
expect(context.value).to.equal(5);
|
201
|
-
});
|
202
|
-
|
203
|
-
/**
|
204
|
-
* Tests conditional node execution
|
205
|
-
* Validates that:
|
206
|
-
* - Nodes can have conditional execution logic
|
207
|
-
* - Conditions are evaluated against current context
|
208
|
-
* - Nodes are skipped when conditions are not met
|
209
|
-
* This enables dynamic workflow paths based on state
|
210
|
-
*/
|
211
|
-
it("should not execute a node when condition is false", async function () {
|
212
|
-
const conditionalNode: Node<TestSchema> = {
|
213
|
-
name: "conditionalNode",
|
214
|
-
condition: (context) => (context.value ?? 0) > 0,
|
215
|
-
execute: async (context) => {
|
216
|
-
context.value = (context.value ?? 0) + 10;
|
217
|
-
},
|
218
|
-
next: [],
|
219
|
-
};
|
220
|
-
|
221
|
-
graph.addNode(conditionalNode);
|
222
|
-
await graph.execute("conditionalNode");
|
223
|
-
|
224
|
-
const context = graph.getContext();
|
225
|
-
expect(context.value).to.equal(0);
|
226
|
-
});
|
227
|
-
|
228
|
-
/**
|
229
|
-
* Tests node retry functionality
|
230
|
-
* Validates the retry mechanism:
|
231
|
-
* - Maximum attempt limits
|
232
|
-
* - Retry delays
|
233
|
-
* - Success after retry
|
234
|
-
* - Context preservation between attempts
|
235
|
-
* Essential for handling transient failures in workflows
|
236
|
-
*/
|
237
|
-
it("should retry a node execution when it fails", async () => {
|
238
|
-
let attempts = 0;
|
239
|
-
const retryNode: Node<TestSchema> = {
|
240
|
-
name: "retryNode",
|
241
|
-
execute: async (context) => {
|
242
|
-
attempts++;
|
243
|
-
if (attempts < 3) {
|
244
|
-
throw new Error("Temporary failure");
|
245
|
-
}
|
246
|
-
context.value = 42;
|
247
|
-
},
|
248
|
-
retry: {
|
249
|
-
maxAttempts: 3,
|
250
|
-
delay: 100,
|
251
|
-
},
|
252
|
-
};
|
253
|
-
|
254
|
-
const graph = new GraphFlow("test", {
|
255
|
-
name: "test",
|
256
|
-
schema: TestSchema,
|
257
|
-
context: { value: 0 },
|
258
|
-
nodes: [retryNode],
|
259
|
-
});
|
260
|
-
|
261
|
-
await graph.execute("retryNode");
|
262
|
-
expect(attempts).to.equal(3);
|
263
|
-
expect(graph.getContext().value).to.equal(42);
|
264
|
-
});
|
265
|
-
|
266
|
-
/**
|
267
|
-
* Tests node removal functionality
|
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
|
-
/**
|
281
|
-
* Tests graph reloading functionality
|
282
|
-
*/
|
283
|
-
it("should clear and reload the graph using `load`", function () {
|
284
|
-
const nodeA: Node<TestSchema> = {
|
285
|
-
name: "A",
|
286
|
-
execute: async () => {},
|
287
|
-
};
|
288
|
-
const nodeB: Node<TestSchema> = {
|
289
|
-
name: "B",
|
290
|
-
execute: async () => {},
|
291
|
-
};
|
292
|
-
|
293
|
-
const newDefinition: GraphDefinition<TestSchema> = {
|
294
|
-
name: "TestGraph",
|
295
|
-
entryNode: "A",
|
296
|
-
nodes: [nodeA, nodeB],
|
297
|
-
context: { value: 0 },
|
298
|
-
schema: TestSchema,
|
299
|
-
};
|
300
|
-
|
301
|
-
graph.load(newDefinition);
|
302
|
-
expect(graph.getNodes().length).to.equal(2);
|
303
|
-
expect(graph.getNodes().map((n) => n.name)).to.include.members(["A", "B"]);
|
304
|
-
});
|
305
|
-
|
306
|
-
/**
|
307
|
-
* Tests input validation error handling
|
308
|
-
*/
|
309
|
-
it("should throw error when node input validation fails", async () => {
|
310
|
-
const node: Node<TestSchema> = {
|
311
|
-
name: "test",
|
312
|
-
params: z.object({
|
313
|
-
value: z.number().min(0),
|
314
|
-
}),
|
315
|
-
execute: async (context, params) => {
|
316
|
-
if (!params) throw new Error("params required");
|
317
|
-
context.value = params.value;
|
318
|
-
},
|
319
|
-
};
|
320
|
-
|
321
|
-
const graph = new GraphFlow("test", {
|
322
|
-
name: "test",
|
323
|
-
schema: TestSchema,
|
324
|
-
context: { value: 0 },
|
325
|
-
nodes: [node],
|
326
|
-
});
|
327
|
-
|
328
|
-
try {
|
329
|
-
await graph.execute("test", { value: -1 });
|
330
|
-
expect.fail("Should have thrown an error");
|
331
|
-
} catch (error: any) {
|
332
|
-
expect(error.message).to.include("Number must be greater than or equal");
|
333
|
-
}
|
334
|
-
});
|
335
|
-
/**
|
336
|
-
* Tests successful input/output validation flow
|
337
|
-
*/
|
338
|
-
it("should successfully validate both params and outputs", async function () {
|
339
|
-
const graph = new GraphFlow("test", {
|
340
|
-
name: "test",
|
341
|
-
nodes: [
|
342
|
-
{
|
343
|
-
name: "validatedNode",
|
344
|
-
params: z.object({
|
345
|
-
increment: z.number().min(0).max(5),
|
346
|
-
}),
|
347
|
-
execute: async (
|
348
|
-
context: GraphContext<TestSchema>,
|
349
|
-
params?: NodeParams
|
350
|
-
) => {
|
351
|
-
if (!params) throw new Error("params required");
|
352
|
-
context.value = (context.value ?? 0) + params.increment;
|
353
|
-
},
|
354
|
-
},
|
355
|
-
],
|
356
|
-
schema: TestSchema,
|
357
|
-
context: { value: 0, counter: 0, message: "" },
|
358
|
-
});
|
359
|
-
|
360
|
-
// Test avec valeur valide
|
361
|
-
await graph.execute("validatedNode", { increment: 3 });
|
362
|
-
expect(graph.getContext().value).to.equal(3);
|
363
|
-
|
364
|
-
// Test avec valeur invalide
|
365
|
-
await expect(
|
366
|
-
graph.execute("validatedNode", { increment: 10 })
|
367
|
-
).to.be.rejectedWith("Number must be less than or equal to 5");
|
368
|
-
});
|
369
|
-
|
370
|
-
/**
|
371
|
-
* Tests handling of missing required params
|
372
|
-
*/
|
373
|
-
it("should throw error when required params are missing", async function () {
|
374
|
-
const graph = new GraphFlow("test", {
|
375
|
-
name: "test",
|
376
|
-
nodes: [
|
377
|
-
{
|
378
|
-
name: "requiredInputNode",
|
379
|
-
params: z.object({
|
380
|
-
value: z.number(),
|
381
|
-
}),
|
382
|
-
execute: async (
|
383
|
-
context: GraphContext<TestSchema>,
|
384
|
-
params?: NodeParams
|
385
|
-
) => {
|
386
|
-
context.counter = params?.value || 0;
|
387
|
-
},
|
388
|
-
},
|
389
|
-
],
|
390
|
-
schema: TestSchema,
|
391
|
-
context: { value: 0, counter: 0, message: "" },
|
392
|
-
});
|
393
|
-
|
394
|
-
await expect(graph.execute("requiredInputNode")).to.be.rejectedWith(
|
395
|
-
"Params required for node"
|
396
|
-
);
|
397
|
-
});
|
398
|
-
|
399
|
-
/**
|
400
|
-
* Tests complex workflow execution with multiple branches
|
401
|
-
* Demonstrates:
|
402
|
-
* - Multiple execution paths
|
403
|
-
* - Node chaining
|
404
|
-
* - Parallel branch execution
|
405
|
-
* - Context accumulation across branches
|
406
|
-
* This validates the graph's ability to handle complex business processes
|
407
|
-
*/
|
408
|
-
it("should execute a complex workflow with multiple nodes and accumulate the value", async function () {
|
409
|
-
const nodeA: Node<TestSchema> = {
|
410
|
-
name: "nodeA",
|
411
|
-
execute: async (context) => {
|
412
|
-
context.value = (context.value ?? 0) + 1;
|
413
|
-
},
|
414
|
-
next: ["nodeB1", "nodeB2"],
|
415
|
-
};
|
416
|
-
|
417
|
-
const nodeB1: Node<TestSchema> = {
|
418
|
-
name: "nodeB1",
|
419
|
-
execute: async (context) => {
|
420
|
-
context.value = (context.value ?? 0) * 2;
|
421
|
-
},
|
422
|
-
next: ["nodeC"],
|
423
|
-
};
|
424
|
-
|
425
|
-
const nodeB2: Node<TestSchema> = {
|
426
|
-
name: "nodeB2",
|
427
|
-
execute: async (context) => {
|
428
|
-
context.value = (context.value ?? 0) + 3;
|
429
|
-
},
|
430
|
-
next: ["nodeC"],
|
431
|
-
};
|
432
|
-
|
433
|
-
const nodeC: Node<TestSchema> = {
|
434
|
-
name: "nodeC",
|
435
|
-
execute: async (context) => {
|
436
|
-
context.value = (context.value ?? 0) + 5;
|
437
|
-
},
|
438
|
-
};
|
439
|
-
|
440
|
-
[nodeA, nodeB1, nodeB2, nodeC].forEach((node) => graph.addNode(node));
|
441
|
-
|
442
|
-
await graph.execute("nodeA");
|
443
|
-
expect(graph.getContext().value).to.equal(15);
|
444
|
-
});
|
445
|
-
|
446
|
-
/**
|
447
|
-
* Tests conditional branching in workflows
|
448
|
-
*/
|
449
|
-
it("should execute different branches based on conditions", async function () {
|
450
|
-
const startNode: Node<TestSchema> = {
|
451
|
-
name: "start",
|
452
|
-
execute: async (context) => {
|
453
|
-
context.value = (context.value ?? 0) + 5;
|
454
|
-
},
|
455
|
-
next: ["end"],
|
456
|
-
};
|
457
|
-
|
458
|
-
const endNode: Node<TestSchema> = {
|
459
|
-
name: "end",
|
460
|
-
execute: async (context) => {
|
461
|
-
if ((context.value ?? 0) < 10) {
|
462
|
-
context.value = (context.value ?? 0) * 2;
|
463
|
-
} else {
|
464
|
-
context.value = (context.value ?? 0) + 1;
|
465
|
-
}
|
466
|
-
},
|
467
|
-
};
|
468
|
-
|
469
|
-
[startNode, endNode].forEach((node) => graph.addNode(node));
|
470
|
-
|
471
|
-
await graph.execute("start");
|
472
|
-
expect(graph.getContext().value).to.equal(10);
|
473
|
-
});
|
474
|
-
|
475
|
-
/**
|
476
|
-
* Tests parallel workflow execution using GraphController
|
477
|
-
* Validates:
|
478
|
-
* - Multiple graph execution in parallel
|
479
|
-
* - Independent context maintenance
|
480
|
-
* - Proper result aggregation
|
481
|
-
* - Concurrency control
|
482
|
-
* Essential for scaling workflow processing
|
483
|
-
*/
|
484
|
-
it("should handle parallel workflows using GraphController", async function () {
|
485
|
-
// Graph 1
|
486
|
-
const graph1 = new GraphFlow("Graph1", {
|
487
|
-
name: "Graph1",
|
488
|
-
nodes: [],
|
489
|
-
context: { value: 0 },
|
490
|
-
schema: TestSchema,
|
491
|
-
});
|
492
|
-
|
493
|
-
const processNode1: Node<TestSchema> = {
|
494
|
-
name: "process1",
|
495
|
-
execute: async (context) => {
|
496
|
-
context.value = 1;
|
497
|
-
},
|
498
|
-
next: ["finalize1"],
|
499
|
-
};
|
500
|
-
|
501
|
-
const finalizeNode1: Node<TestSchema> = {
|
502
|
-
name: "finalize1",
|
503
|
-
execute: async (context) => {
|
504
|
-
context.value = (context.value ?? 0) * 2;
|
505
|
-
},
|
506
|
-
};
|
507
|
-
|
508
|
-
// Graph 2
|
509
|
-
const graph2 = new GraphFlow("Graph2", {
|
510
|
-
name: "Graph2",
|
511
|
-
nodes: [],
|
512
|
-
context: { value: 0 },
|
513
|
-
schema: TestSchema,
|
514
|
-
});
|
515
|
-
|
516
|
-
const processNode2: Node<TestSchema> = {
|
517
|
-
name: "process2",
|
518
|
-
execute: async (context) => {
|
519
|
-
context.value = 2;
|
520
|
-
},
|
521
|
-
next: ["finalize2"],
|
522
|
-
};
|
523
|
-
|
524
|
-
const finalizeNode2: Node<TestSchema> = {
|
525
|
-
name: "finalize2",
|
526
|
-
execute: async (context) => {
|
527
|
-
context.value = (context.value ?? 0) + 3;
|
528
|
-
},
|
529
|
-
};
|
530
|
-
|
531
|
-
graph1.addNode(processNode1);
|
532
|
-
graph1.addNode(finalizeNode1);
|
533
|
-
graph2.addNode(processNode2);
|
534
|
-
graph2.addNode(finalizeNode2);
|
535
|
-
|
536
|
-
const results = await GraphController.executeParallel(
|
537
|
-
[graph1, graph2],
|
538
|
-
["process1", "process2"],
|
539
|
-
2,
|
540
|
-
[{}, {}]
|
541
|
-
);
|
542
|
-
|
543
|
-
expect(results[0].value).to.equal(2); // Graph1: 1 * 2
|
544
|
-
expect(results[1].value).to.equal(5); // Graph2: 2 + 3
|
545
|
-
});
|
546
|
-
|
547
|
-
/**
|
548
|
-
* Tests sequential workflow execution using GraphController
|
549
|
-
*/
|
550
|
-
it("should handle sequential workflows using GraphController", async function () {
|
551
|
-
// Graph 1
|
552
|
-
const graph1 = new GraphFlow("Graph1", {
|
553
|
-
name: "Graph1",
|
554
|
-
nodes: [],
|
555
|
-
context: { value: 1 },
|
556
|
-
schema: TestSchema,
|
557
|
-
});
|
558
|
-
|
559
|
-
const startNode1: Node<TestSchema> = {
|
560
|
-
name: "start1",
|
561
|
-
execute: async (context) => {
|
562
|
-
context.value = (context.value ?? 0) * 2;
|
563
|
-
},
|
564
|
-
};
|
565
|
-
|
566
|
-
// Graph 2
|
567
|
-
const graph2 = new GraphFlow("Graph2", {
|
568
|
-
name: "Graph2",
|
569
|
-
nodes: [],
|
570
|
-
context: { value: 3 },
|
571
|
-
schema: TestSchema,
|
572
|
-
});
|
573
|
-
|
574
|
-
const startNode2: Node<TestSchema> = {
|
575
|
-
name: "start2",
|
576
|
-
execute: async (context) => {
|
577
|
-
context.value = (context.value ?? 0) + 2;
|
578
|
-
},
|
579
|
-
};
|
580
|
-
|
581
|
-
graph1.addNode(startNode1);
|
582
|
-
graph2.addNode(startNode2);
|
583
|
-
|
584
|
-
const results = await GraphController.executeSequential(
|
585
|
-
[graph1, graph2],
|
586
|
-
["start1", "start2"],
|
587
|
-
[{}, {}]
|
588
|
-
);
|
589
|
-
|
590
|
-
expect(results[0].value).to.equal(2); // Graph1: 1 * 2
|
591
|
-
expect(results[1].value).to.equal(5); // Graph2: 3 + 2
|
592
|
-
});
|
593
|
-
|
594
|
-
/**
|
595
|
-
* Tests event correlation functionality
|
596
|
-
* Demonstrates:
|
597
|
-
* - Event correlation based on transaction ID
|
598
|
-
* - Timeout handling
|
599
|
-
* - Multiple event synchronization
|
600
|
-
* - Context updates after correlation
|
601
|
-
* Critical for integrating with external event sources
|
602
|
-
*/
|
603
|
-
it("should handle correlated events correctly", async function () {
|
604
|
-
this.timeout(10000);
|
605
|
-
const graph = new GraphFlow("test", {
|
606
|
-
name: "test",
|
607
|
-
nodes: [],
|
608
|
-
context: { value: 0 },
|
609
|
-
schema: TestSchema,
|
610
|
-
eventEmitter: new EventEmitter(),
|
611
|
-
});
|
612
|
-
|
613
|
-
let eventsReceived = 0;
|
614
|
-
const node = {
|
615
|
-
name: "testNode",
|
616
|
-
waitForEvents: {
|
617
|
-
events: ["eventA", "eventB"],
|
618
|
-
timeout: 5000,
|
619
|
-
strategy: "all" as const,
|
620
|
-
},
|
621
|
-
execute: async (context: GraphContext<typeof TestSchema>) => {
|
622
|
-
eventsReceived = 2;
|
623
|
-
context.value = 42;
|
624
|
-
},
|
625
|
-
};
|
626
|
-
|
627
|
-
graph.addNode(node);
|
628
|
-
|
629
|
-
graph.execute("testNode");
|
630
|
-
|
631
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
632
|
-
|
633
|
-
await graph.emit("eventA", { eventPayload: { status: "A" } });
|
634
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
635
|
-
await graph.emit("eventB", { eventPayload: { status: "B" } });
|
636
|
-
|
637
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
638
|
-
|
639
|
-
expect(eventsReceived).to.equal(2);
|
640
|
-
expect(graph.getContext().value).to.equal(42);
|
641
|
-
});
|
642
|
-
|
643
|
-
/**
|
644
|
-
* Tests multiple event waiting functionality
|
645
|
-
*/
|
646
|
-
it("should wait for multiple events before continuing", async function () {
|
647
|
-
this.timeout(10000);
|
648
|
-
const graph = new GraphFlow("test", {
|
649
|
-
name: "test",
|
650
|
-
nodes: [],
|
651
|
-
context: { value: 0 },
|
652
|
-
schema: TestSchema,
|
653
|
-
eventEmitter: new EventEmitter(),
|
654
|
-
});
|
655
|
-
|
656
|
-
const node = {
|
657
|
-
name: "testNode",
|
658
|
-
waitForEvents: {
|
659
|
-
events: ["event1", "event2"],
|
660
|
-
timeout: 5000,
|
661
|
-
strategy: "all" as const,
|
662
|
-
},
|
663
|
-
execute: async (context: GraphContext<typeof TestSchema>) => {
|
664
|
-
context.value = 42; // Ajouter une modification du contexte
|
665
|
-
},
|
666
|
-
};
|
667
|
-
|
668
|
-
graph.addNode(node);
|
669
|
-
graph.execute("testNode");
|
670
|
-
|
671
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
672
|
-
await graph.emit("event1", { eventPayload: { status: "1" } });
|
673
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
674
|
-
await graph.emit("event2", { eventPayload: { status: "2" } });
|
675
|
-
expect(graph.getContext().value).to.equal(42);
|
676
|
-
});
|
677
|
-
|
678
|
-
/**
|
679
|
-
* Tests single event waiting functionality
|
680
|
-
*/
|
681
|
-
it("should wait for a single event before continuing", async function () {
|
682
|
-
this.timeout(5000);
|
683
|
-
|
684
|
-
const waitingNode: Node<TestSchema> = {
|
685
|
-
name: "waitingNode",
|
686
|
-
execute: async (context: GraphContext<typeof TestSchema>) => {
|
687
|
-
context.value = 1;
|
688
|
-
},
|
689
|
-
waitForEvent: true,
|
690
|
-
next: ["finalNode"],
|
691
|
-
};
|
692
|
-
|
693
|
-
const finalNode: Node<TestSchema> = {
|
694
|
-
name: "finalNode",
|
695
|
-
execute: async (context: GraphContext<typeof TestSchema>) => {
|
696
|
-
context.value = (context.value ?? 0) + 5;
|
697
|
-
},
|
698
|
-
};
|
699
|
-
|
700
|
-
[waitingNode, finalNode].forEach((node) => graph.addNode(node));
|
701
|
-
|
702
|
-
const resultPromise = graph.execute("waitingNode");
|
703
|
-
|
704
|
-
// Wait a bit to ensure the node is ready
|
705
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
706
|
-
|
707
|
-
// Emit the event
|
708
|
-
await graph.emit("someEvent");
|
709
|
-
|
710
|
-
const result = await resultPromise;
|
711
|
-
expect(result.value).to.equal(6); // 1 (waitingNode) + 5 (finalNode)
|
712
|
-
});
|
713
|
-
|
714
|
-
// Test de validation des paramètres
|
715
|
-
it("should successfully validate params", async () => {
|
716
|
-
const graph = new GraphFlow("test", {
|
717
|
-
name: "test",
|
718
|
-
nodes: [
|
719
|
-
{
|
720
|
-
name: "validationNode",
|
721
|
-
params: z.object({
|
722
|
-
value: z.number().max(10),
|
723
|
-
}),
|
724
|
-
execute: async (
|
725
|
-
context: GraphContext<TestSchema>,
|
726
|
-
params?: NodeParams
|
727
|
-
) => {
|
728
|
-
context.counter = params?.value || 0;
|
729
|
-
},
|
730
|
-
},
|
731
|
-
],
|
732
|
-
schema: TestSchema,
|
733
|
-
context: { value: 0, counter: 0, message: "" },
|
734
|
-
});
|
735
|
-
|
736
|
-
// Test avec valeur invalide
|
737
|
-
await expect(
|
738
|
-
graph.execute("validationNode", { value: 20 })
|
739
|
-
).to.be.rejectedWith("Number must be less than or equal to 10");
|
740
|
-
});
|
741
|
-
|
742
|
-
// Test des paramètres requis
|
743
|
-
it("should throw error when required params are missing", async () => {
|
744
|
-
const graph = new GraphFlow("test", {
|
745
|
-
name: "test",
|
746
|
-
nodes: [
|
747
|
-
{
|
748
|
-
name: "requiredInputNode",
|
749
|
-
params: z.object({
|
750
|
-
value: z.number(),
|
751
|
-
}),
|
752
|
-
execute: async (
|
753
|
-
context: GraphContext<TestSchema>,
|
754
|
-
params?: NodeParams
|
755
|
-
) => {
|
756
|
-
context.counter = params?.value || 0;
|
757
|
-
},
|
758
|
-
},
|
759
|
-
],
|
760
|
-
schema: TestSchema,
|
761
|
-
context: { value: 0, counter: 0, message: "" },
|
762
|
-
});
|
763
|
-
|
764
|
-
await expect(graph.execute("requiredInputNode")).to.be.rejectedWith(
|
765
|
-
"Params required for node"
|
766
|
-
);
|
767
|
-
});
|
768
|
-
});
|