@ai.ntellect/core 0.6.0 → 0.6.1

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/graph/engine.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Persistence, RealTimeNotifier } from "@/interfaces";
2
- import { GraphDefinition, Node, NodeRelationship, SharedState } from "@/types";
2
+ import { GraphDefinition, Node, SharedState } from "@/types";
3
3
  import { configDotenv } from "dotenv";
4
4
  import EventEmitter from "events";
5
5
  import { z } from "zod";
@@ -13,44 +13,43 @@ interface GraphOptions<T> {
13
13
  }
14
14
 
15
15
  /**
16
- * Represents a directed worflow structure capable of executing nodes in sequence or parallel.
17
- * The worflow can handle state management, event emissions, and conditional execution paths.
16
+ * Représente un workflow dirigé capable d’exécuter des noeuds en séquence ou en parallèle.
18
17
  *
19
- * @template T - The type of data stored in the worflow's context
18
+ * @template T - Le type de données stockées dans le contexte du workflow
20
19
  */
21
20
  export class GraphEngine<T> {
22
- /** Stores global context data accessible to all nodes */
21
+ /** Données globales accessibles à tous les nœuds */
23
22
  public globalContext: Map<string, any>;
24
23
 
25
- /** Event emitter for handling worflow-wide events */
24
+ /** Event emitter pour gérer les événements du workflow */
26
25
  private eventEmitter: EventEmitter;
27
26
 
28
- /** Map of all nodes in the worflow */
27
+ /** Map de tous les nœuds du workflow */
29
28
  public nodes: Map<string, Node<T>>;
30
29
 
31
- /** Set of nodes that have been executed */
30
+ /** Ensemble des nœuds déjà exécutés */
32
31
  public executedNodes: Set<string>;
33
32
 
34
- /** Name identifier for the worflow */
33
+ /** Nom du workflow */
35
34
  public name: string;
36
35
 
37
- /** Optional persistence layer for saving worflow state */
36
+ /** Couche de persistance optionnelle pour sauvegarder l'état du workflow */
38
37
  private persistence: Persistence<T> | null;
39
38
 
40
- /** Optional notifier for real-time updates */
39
+ /** Notifier en temps réel optionnel */
41
40
  private notifier: RealTimeNotifier | null;
42
41
 
42
+ /** Schéma global Zod pour valider l’état ou le contexte du workflow */
43
43
  private schema?: z.ZodSchema<T>;
44
44
 
45
+ /** État interne actuel du workflow */
45
46
  private currentState: SharedState<T>;
46
47
 
47
48
  /**
48
- * Creates a new Graph instance.
49
+ * Crée une nouvelle instance de GraphEngine.
49
50
  *
50
- * @param {GraphDefinition<T>} [definition] - Initial worflow structure and configuration
51
- * @param {Object} [config] - Additional configuration options
52
- * @param {boolean} [config.autoDetectCycles] - Whether to check for cycles during initialization
53
- * @throws {Error} If cycles are detected when autoDetectCycles is true
51
+ * @param {GraphDefinition<T>} [definition] - La définition initiale du workflow
52
+ * @param {GraphOptions<T>} [options] - Options de configuration
54
53
  */
55
54
  constructor(definition?: GraphDefinition<T>, options?: GraphOptions<T>) {
56
55
  this.name = definition?.name || "anonymous";
@@ -61,14 +60,14 @@ export class GraphEngine<T> {
61
60
  this.persistence = null;
62
61
  this.notifier = null;
63
62
  this.schema = options?.schema;
64
- this.currentState = { context: {} } as SharedState<T>;
63
+ this.currentState = {} as SharedState<T>;
65
64
 
66
65
  if (definition) {
67
66
  this.loadFromDefinition(definition);
68
67
  }
69
68
 
70
69
  if (options?.autoDetectCycles && this.checkForCycles()) {
71
- throw new Error("Cycle detected in the workflow");
70
+ throw new Error("Cycle détecté dans le workflow");
72
71
  }
73
72
 
74
73
  if (options?.initialState) {
@@ -77,68 +76,63 @@ export class GraphEngine<T> {
77
76
  }
78
77
 
79
78
  /**
80
- * Adds a value to the global context.
81
- * @param {string} key - The key to store the value under
82
- * @param {any} value - The value to store
79
+ * Ajoute un élément au contexte global.
80
+ * @param {string} key - La clé
81
+ * @param {any} value - La valeur
83
82
  */
84
83
  addToContext(key: string, value: any): void {
85
84
  this.globalContext.set(key, value);
86
85
  }
87
86
 
88
87
  /**
89
- * Retrieves a value from the global context.
90
- * @param {string} key - The key to retrieve
91
- * @returns {any} The stored value, or undefined if not found
88
+ * Récupère un élément du contexte global.
89
+ * @param {string} key - La clé
92
90
  */
93
91
  getContext(key: string): any {
94
92
  return this.globalContext.get(key);
95
93
  }
96
94
 
97
95
  /**
98
- * Removes a value from the global context.
99
- * @param {string} key - The key to remove
96
+ * Supprime un élément du contexte global.
97
+ * @param {string} key - La clé
100
98
  */
101
99
  removeFromContext(key: string): void {
102
100
  this.globalContext.delete(key);
103
101
  }
104
102
 
105
103
  /**
106
- * Sets the persistence layer for the worflow.
107
- * @param {Persistence<T>} persistence - The persistence implementation
104
+ * Définit la couche de persistance.
105
+ * @param {Persistence<T>} persistence
108
106
  */
109
107
  setPersistence(persistence: Persistence<T>): void {
110
108
  this.persistence = persistence;
111
109
  }
112
110
 
113
111
  /**
114
- * Sets the real-time notifier for the worflow.
115
- * @param {RealTimeNotifier} notifier - The notifier implementation
112
+ * Définit le notifier en temps réel.
113
+ * @param {RealTimeNotifier} notifier
116
114
  */
117
115
  setNotifier(notifier: RealTimeNotifier): void {
118
116
  this.notifier = notifier;
119
117
  }
120
118
 
121
119
  /**
122
- * Loads a worflow structure from a definition object.
120
+ * Charge un workflow à partir d'une définition.
123
121
  * @private
124
- * @param {GraphDefinition<T>} definition - The worflow definition
122
+ * @param {GraphDefinition<T>} definition
125
123
  */
126
124
  private loadFromDefinition(definition: GraphDefinition<T>): void {
127
125
  Object.entries(definition.nodes).forEach(([_, nodeConfig]) => {
128
- this.addNode(nodeConfig, {
129
- condition: nodeConfig.condition,
130
- relationships: nodeConfig.relationships,
131
- });
126
+ this.addNode(nodeConfig);
132
127
  });
133
128
  }
134
129
 
135
130
  /**
136
- * Recursively checks if a node is part of a cycle.
137
- * @private
138
- * @param {string} nodeName - The name of the node to check
139
- * @param {Set<string>} visited - Set of visited nodes
140
- * @param {Set<string>} recStack - Set of nodes in the current recursion stack
141
- * @returns {boolean} True if a cycle is detected, false otherwise
131
+ * Vérifie récursivement s’il existe un cycle dans le workflow.
132
+ * @param {string} nodeName
133
+ * @param {Set<string>} visited
134
+ * @param {Set<string>} recStack
135
+ * @returns {boolean}
142
136
  */
143
137
  private isCyclic(
144
138
  nodeName: string,
@@ -169,8 +163,8 @@ export class GraphEngine<T> {
169
163
  }
170
164
 
171
165
  /**
172
- * Checks if the worflow contains any cycles.
173
- * @returns {boolean} True if cycles are detected, false otherwise
166
+ * Vérifie si le workflow contient des cycles.
167
+ * @returns {boolean}
174
168
  */
175
169
  public checkForCycles(): boolean {
176
170
  const visited = new Set<string>();
@@ -185,30 +179,18 @@ export class GraphEngine<T> {
185
179
  }
186
180
 
187
181
  /**
188
- * Adds a new node to the worflow.
189
- * @param {Node<T>} node - The node to add
190
- * @param {Object} options - Node configuration options
191
- * @param {Function} [options.condition] - Condition function for node execution
192
- * @param {string[]} [options.relations] - Array of relations node names
193
- * @param {string[]} [options.events] - Array of event names to listen for
182
+ * Ajoute un nouveau nœud au workflow.
183
+ * @param {Node<T>} node
194
184
  */
195
- addNode(
196
- node: Node<T>,
197
- {
198
- condition,
199
- relationships,
200
- events,
201
- }: {
202
- condition?: (state: SharedState<T>) => boolean;
203
- relationships?: NodeRelationship[];
204
- events?: string[];
185
+ addNode(node: Node<T>): void {
186
+ if (node.relationships) {
187
+ node.relationships.forEach((relationship) => {
188
+ this.nodes.get(relationship.name)?.relationships?.push(relationship);
189
+ });
205
190
  }
206
- ): void {
207
- node.relationships = relationships;
208
- node.condition = condition;
209
191
 
210
- if (events) {
211
- events.forEach((event) => {
192
+ if (node.events) {
193
+ node.events.forEach((event) => {
212
194
  this.eventEmitter.on(event, async (data) => {
213
195
  const state = data.state || {};
214
196
  await this.execute(state, node.name);
@@ -220,19 +202,19 @@ export class GraphEngine<T> {
220
202
  }
221
203
 
222
204
  /**
223
- * Emits an event to the worflow's event emitter.
224
- * @param {string} eventName - Name of the event to emit
225
- * @param {any} data - Data to pass with the event
205
+ * Émet un événement sur l'event emitter du workflow.
206
+ * @param {string} eventName
207
+ * @param {any} data
226
208
  */
227
209
  public emit(eventName: string, data: any): void {
228
210
  this.eventEmitter.emit(eventName, data);
229
211
  }
230
212
 
231
213
  /**
232
- * Adds a subworflow as a node in the current worflow.
233
- * @param {Graph<T>} subGraph - The subworflow to add
234
- * @param {string} entryNode - The entry node name in the subworflow
235
- * @param {string} name - The name for the subworflow node
214
+ * Ajoute un sous-graph (GraphEngine) comme un nœud dans le workflow courant.
215
+ * @param {GraphEngine<T>} subGraph
216
+ * @param {string} entryNode - Le nom du nœud de démarrage dans le sous-graph
217
+ * @param {string} name - Le nom symbolique à donner au sous-graph
236
218
  */
237
219
  addSubGraph(subGraph: GraphEngine<T>, entryNode: string, name: string): void {
238
220
  const subGraphNode: Node<T> = {
@@ -246,25 +228,26 @@ export class GraphEngine<T> {
246
228
  }
247
229
 
248
230
  /**
249
- * Executes the worflow starting from a specific node.
250
- * @param {SharedState<T>} state - The initial state
251
- * @param {string} startNode - The name of the starting node
252
- * @param {Function} [onStream] - Callback for streaming state updates
253
- * @param {Function} [onError] - Callback for handling errors
231
+ * Exécute le workflow à partir d’un nœud donné.
232
+ * @param {SharedState<T>} state
233
+ * @param {string} startNode
234
+ * @param {(state: SharedState<T>) => void} [onStream] - Callback sur l’évolution de l’état
235
+ * @param {(error: Error, nodeName: string, state: SharedState<T>) => void} [onError] - Callback sur erreur
254
236
  */
255
237
  async execute(
256
238
  state: SharedState<T>,
257
239
  startNode: string,
258
- onStream?: (state: SharedState<T>) => void,
240
+ onStream?: (graph: GraphEngine<T>) => void,
259
241
  onError?: (error: Error, nodeName: string, state: SharedState<T>) => void
260
242
  ): Promise<SharedState<T>> {
261
243
  try {
244
+ // Valide l'état initial via le schéma global (si défini)
262
245
  if (this.schema) {
263
246
  try {
264
- this.schema.parse(state.context);
247
+ this.schema.parse(state);
265
248
  } catch (error) {
266
249
  const validationError = new Error(
267
- `Initial state validation failed: ${
250
+ `Échec de la validation de l'état initial: ${
268
251
  error instanceof Error ? error.message : error
269
252
  }`
270
253
  );
@@ -279,8 +262,11 @@ export class GraphEngine<T> {
279
262
  while (currentNodeName) {
280
263
  this.executedNodes.add(currentNodeName);
281
264
  const currentNode = this.nodes.get(currentNodeName);
282
- if (!currentNode) throw new Error(`Node ${currentNodeName} not found.`);
265
+ if (!currentNode) {
266
+ throw new Error(`Nœud ${currentNodeName} introuvable.`);
267
+ }
283
268
 
269
+ // Vérification de condition (si présente)
284
270
  if (
285
271
  currentNode.condition &&
286
272
  !currentNode.condition(this.currentState)
@@ -289,6 +275,7 @@ export class GraphEngine<T> {
289
275
  }
290
276
 
291
277
  try {
278
+ // Notifier : début d'exécution du nœud
292
279
  if (this.notifier) {
293
280
  this.notifier.notify("nodeExecutionStarted", {
294
281
  workflow: this.name,
@@ -304,9 +291,10 @@ export class GraphEngine<T> {
304
291
 
305
292
  if (newState) {
306
293
  this.setState(newState);
307
- if (onStream) onStream(this.currentState);
294
+ if (onStream) onStream(this);
308
295
  }
309
296
 
297
+ // Sauvegarde via la persistence (optionnel)
310
298
  if (this.persistence) {
311
299
  await this.persistence.saveState(
312
300
  this.name,
@@ -315,6 +303,7 @@ export class GraphEngine<T> {
315
303
  );
316
304
  }
317
305
 
306
+ // Notifier : fin d'exécution du nœud
318
307
  if (this.notifier) {
319
308
  await this.notifier.notify("nodeExecutionCompleted", {
320
309
  workflow: this.name,
@@ -323,8 +312,9 @@ export class GraphEngine<T> {
323
312
  });
324
313
  }
325
314
  } catch (error) {
326
- if (onError)
315
+ if (onError) {
327
316
  onError(error as Error, currentNodeName, this.currentState);
317
+ }
328
318
  if (this.notifier) {
329
319
  this.notifier.notify("nodeExecutionFailed", {
330
320
  workflow: this.name,
@@ -336,15 +326,19 @@ export class GraphEngine<T> {
336
326
  break;
337
327
  }
338
328
 
329
+ // Gestion des relations (branchements)
339
330
  const relationsNodes = currentNode.relationships || [];
340
331
  if (relationsNodes.length > 1) {
332
+ // Exécution parallèle des branches
341
333
  await Promise.all(
342
334
  relationsNodes.map((relation) =>
343
335
  this.execute(this.currentState, relation.name, onStream, onError)
344
336
  )
345
337
  );
338
+ // Après exécution en parallèle, on arrête la boucle
346
339
  break;
347
340
  } else {
341
+ // Cas normal : un seul chemin
348
342
  currentNodeName = relationsNodes[0]?.name || "";
349
343
  }
350
344
  }
@@ -359,18 +353,16 @@ export class GraphEngine<T> {
359
353
  }
360
354
 
361
355
  /**
362
- * Executes multiple nodes in parallel with a concurrency limit.
363
- * @param {SharedState<T>} state - The shared state
364
- * @param {string[]} nodeNames - Array of node names to execute
365
- * @param {number} [concurrencyLimit=5] - Maximum number of concurrent executions
366
- * @param {Function} [onStream] - Callback for streaming state updates
367
- * @param {Function} [onError] - Callback for handling errors
356
+ * Exécute plusieurs nœuds en parallèle au sein du même workflow, avec une limite de concurrence.
357
+ * @param {SharedState<T>} state
358
+ * @param {string[]} nodeNames
359
+ * @param {number} [concurrencyLimit=5]
368
360
  */
369
361
  async executeParallel(
370
362
  state: SharedState<T>,
371
363
  nodeNames: string[],
372
364
  concurrencyLimit: number = 5,
373
- onStream?: (state: SharedState<T>) => void,
365
+ onStream?: (graph: GraphEngine<T>) => void,
374
366
  onError?: (error: Error, nodeName: string, state: SharedState<T>) => void
375
367
  ): Promise<void> {
376
368
  const executeWithLimit = async (nodeName: string) => {
@@ -388,8 +380,8 @@ export class GraphEngine<T> {
388
380
  }
389
381
 
390
382
  /**
391
- * Updates the worflow structure with a new definition.
392
- * @param {GraphDefinition<T>} definition - The new worflow definition
383
+ * Met à jour le workflow avec une nouvelle définition (mise à jour des nœuds existants ou ajout de nouveaux).
384
+ * @param {GraphDefinition<T>} definition
393
385
  */
394
386
  updateGraph(definition: GraphDefinition<T>): void {
395
387
  Object.entries(definition.nodes).forEach(([_, nodeConfig]) => {
@@ -398,18 +390,16 @@ export class GraphEngine<T> {
398
390
  existingNode.relationships =
399
391
  nodeConfig.relationships || existingNode.relationships;
400
392
  existingNode.condition = nodeConfig.condition || existingNode.condition;
393
+ existingNode.events = nodeConfig.events || existingNode.events;
401
394
  } else {
402
- this.addNode(nodeConfig, {
403
- condition: nodeConfig.condition,
404
- relationships: nodeConfig.relationships,
405
- });
395
+ this.addNode(nodeConfig);
406
396
  }
407
397
  });
408
398
  }
409
399
 
410
400
  /**
411
- * Replace the worflow with a new definition.
412
- * @param {GraphDefinition<T>} definition - The new worflow definition
401
+ * Remplace complètement le workflow par une nouvelle définition.
402
+ * @param {GraphDefinition<T>} definition
413
403
  */
414
404
  replaceGraph(definition: GraphDefinition<T>): void {
415
405
  this.nodes.clear();
@@ -417,14 +407,9 @@ export class GraphEngine<T> {
417
407
  }
418
408
 
419
409
  /**
420
- * Generates a visual representation of the worflow using Mermaid diagram syntax.
421
- * The diagram shows all nodes and their connections, with special highlighting for:
422
- * - Entry nodes (green)
423
- * - Event nodes (yellow)
424
- * - Conditional nodes (orange)
425
- *
426
- * @param {string} [title] - Optional title for the diagram
427
- * @returns {string} Mermaid diagram syntax representing the worflow
410
+ * Génère un diagramme Mermaid pour visualiser le workflow.
411
+ * @param {string} [title]
412
+ * @returns {string}
428
413
  */
429
414
  generateMermaidDiagram(title?: string): string {
430
415
  const lines: string[] = ["flowchart TD"];
@@ -433,48 +418,46 @@ export class GraphEngine<T> {
433
418
  lines.push(` subgraph ${title}`);
434
419
  }
435
420
 
436
- // Add nodes with styling
421
+ // Ajout des nœuds
437
422
  this.nodes.forEach((node, nodeName) => {
438
423
  const hasEvents = node.events && node.events.length > 0;
439
424
  const hasCondition = !!node.condition;
440
425
 
441
- // Style nodes based on their properties
426
+ // Style selon les propriétés
442
427
  let style = "";
443
428
  if (hasEvents) {
444
- style = "style " + nodeName + " fill:#FFD700,stroke:#DAA520"; // Yellow for event nodes
429
+ style = "style " + nodeName + " fill:#FFD700,stroke:#DAA520"; // Jaune pour event
445
430
  } else if (hasCondition) {
446
- style = "style " + nodeName + " fill:#FFA500,stroke:#FF8C00"; // Orange for conditional nodes
431
+ style = "style " + nodeName + " fill:#FFA500,stroke:#FF8C00"; // Orange pour condition
447
432
  }
448
433
 
449
- // Add node definition
450
434
  lines.push(` ${nodeName}[${nodeName}]`);
451
435
  if (style) {
452
436
  lines.push(` ${style}`);
453
437
  }
454
438
  });
455
439
 
456
- // Add connections
440
+ // Ajout des connexions
457
441
  this.nodes.forEach((node, nodeName) => {
458
442
  if (node.relationships) {
459
443
  node.relationships.forEach((relationsNode) => {
460
444
  let connectionStyle = "";
461
445
  if (node.condition) {
462
- connectionStyle = "---|condition|"; // Add label for conditional connections
446
+ connectionStyle = "---|condition|";
463
447
  } else {
464
- connectionStyle = "-->"; // Normal connection
448
+ connectionStyle = "-->";
465
449
  }
466
450
  lines.push(` ${nodeName} ${connectionStyle} ${relationsNode}`);
467
451
  });
468
452
  }
469
453
 
470
- // Add event connections if any
454
+ // Gestion des events
471
455
  if (node.events && node.events.length > 0) {
472
456
  node.events.forEach((event: string) => {
473
457
  const eventNodeId = `${event}_event`;
474
458
  lines.push(` ${eventNodeId}((${event})):::event`);
475
459
  lines.push(` ${eventNodeId} -.->|trigger| ${nodeName}`);
476
460
  });
477
- // Add style class for event nodes
478
461
  lines.push(" classDef event fill:#FFD700,stroke:#DAA520");
479
462
  }
480
463
  });
@@ -487,26 +470,29 @@ export class GraphEngine<T> {
487
470
  }
488
471
 
489
472
  /**
490
- * Renders the worflow visualization using Mermaid syntax.
491
- * This method can be used to visualize the worflow structure in supported environments.
492
- *
493
- * @param {string} [title] - Optional title for the visualization
473
+ * Affiche le diagramme Mermaid dans la console.
474
+ * @param {string} [title]
494
475
  */
495
476
  visualize(title?: string): void {
496
477
  const diagram = this.generateMermaidDiagram(title);
497
478
  console.log(
498
- "To visualize this worflow, use a Mermaid-compatible renderer with this syntax:"
479
+ "Pour visualiser ce workflow, utilisez un rendu compatible Mermaid avec la syntaxe suivante :"
499
480
  );
500
481
  console.log("\n```mermaid");
501
482
  console.log(diagram);
502
483
  console.log("```\n");
503
484
  }
504
485
 
505
- exportGraphToJson<T>(worflow: GraphDefinition<T>): string {
486
+ /**
487
+ * Exporte la définition du workflow au format JSON (pour debug ou documentation).
488
+ * @param {GraphDefinition<T>} workflow
489
+ * @returns {string} JSON string
490
+ */
491
+ exportGraphToJson(workflow: GraphDefinition<T>): string {
506
492
  const result = {
507
- worflowName: worflow.name,
508
- entryNode: worflow.entryNode,
509
- nodes: Object.entries(worflow.nodes).reduce((acc, [key, node]) => {
493
+ workflowName: workflow.name,
494
+ entryNode: workflow.entryNode,
495
+ nodes: Object.entries(workflow.nodes).reduce((acc, [key, node]) => {
510
496
  acc[key] = {
511
497
  name: node.name,
512
498
  description: node.description || "No description provided",
@@ -521,10 +507,8 @@ export class GraphEngine<T> {
521
507
  }
522
508
 
523
509
  /**
524
- * Generates a visual representation of the workflow schema.
525
- * Displays the structure of the data expected for each node.
526
- *
527
- * @returns {string} A formatted string describing the workflow schema
510
+ * Génère une représentation textuelle (console) du schéma du workflow.
511
+ * @returns {string}
528
512
  */
529
513
  visualizeSchema(): string {
530
514
  const output: string[] = [];
@@ -532,6 +516,7 @@ export class GraphEngine<T> {
532
516
  output.push(`📋 Graph: ${this.name}`);
533
517
  output.push("=".repeat(50));
534
518
 
519
+ // Schéma global
535
520
  if (this.schema) {
536
521
  output.push("🔷 Global Schema:");
537
522
  output.push("-".repeat(30));
@@ -547,6 +532,7 @@ export class GraphEngine<T> {
547
532
  output.push("");
548
533
  }
549
534
 
535
+ // Détails des nœuds
550
536
  output.push("🔷 Nodes:");
551
537
  output.push("-".repeat(30));
552
538
 
@@ -557,7 +543,8 @@ export class GraphEngine<T> {
557
543
  );
558
544
 
559
545
  if (node.relationships && node.relationships.length > 0) {
560
- output.push(`Next nodes: ${node.relationships.join(", ")}`);
546
+ const rels = node.relationships.map((r) => r.name).join(", ");
547
+ output.push(`Next nodes: ${rels}`);
561
548
  }
562
549
 
563
550
  output.push("");
@@ -567,7 +554,10 @@ export class GraphEngine<T> {
567
554
  }
568
555
 
569
556
  /**
570
- * Recursively describes a Zod type.
557
+ * Décrit récursivement un type Zod pour l'affichage.
558
+ * @param {z.ZodType} type
559
+ * @param {number} indent
560
+ * @returns {string}
571
561
  */
572
562
  public describeZodType(type: z.ZodType, indent: number = 0): string {
573
563
  const padding = " ".repeat(indent);
@@ -623,38 +613,35 @@ export class GraphEngine<T> {
623
613
  }
624
614
 
625
615
  /**
626
- * Updates the state of a node.
627
- * @param {SharedState<T>} state - The current state
628
- * @param {Partial<T>} updates - The updates to apply
629
- * @returns {SharedState<T>} The updated state
616
+ * Met à jour le contexte du workflow pour un nœud, en renvoyant un nouvel état.
617
+ * @param {SharedState<T>} state
618
+ * @param {Partial<T>} updates
619
+ * @returns {SharedState<T>}
630
620
  */
631
621
  protected updateNodeState(state: SharedState<T>, updates: Partial<T>) {
632
622
  return {
633
623
  ...state,
634
- context: {
635
- ...(state.context || {}),
636
- ...updates,
637
- },
624
+ ...updates,
638
625
  };
639
626
  }
640
627
 
641
628
  /**
642
- * Retrieves the current state of the workflow.
643
- * @returns {SharedState<T>} The current state
629
+ * Récupère l'état courant du workflow.
630
+ * @returns {SharedState<T>}
644
631
  */
645
632
  public getState(): SharedState<T> {
646
633
  return this.currentState;
647
634
  }
648
635
 
649
636
  /**
650
- * Sets the state of the workflow.
651
- * @param {Partial<SharedState<T>>} state - The new state
637
+ * Définit le nouvel état courant du workflow et met à jour le contexte global.
638
+ * @param {Partial<SharedState<T>>} state
652
639
  */
653
640
  public setState(state: Partial<SharedState<T>>): void {
654
641
  this.currentState = this.mergeStates(this.currentState, state);
655
642
 
656
- if (state.context) {
657
- Object.entries(state.context).forEach(([key, value]) => {
643
+ if (state) {
644
+ Object.entries(state).forEach(([key, value]) => {
658
645
  this.globalContext.set(key, value);
659
646
  });
660
647
  }
@@ -664,17 +651,17 @@ export class GraphEngine<T> {
664
651
  if (node) {
665
652
  node.state = {
666
653
  ...(node.state || {}),
667
- ...(state.context || {}),
654
+ ...(state || {}),
668
655
  };
669
656
  }
670
657
  }
671
658
  }
672
659
 
673
660
  /**
674
- * Merges two states.
675
- * @param {SharedState<T>} currentState - The current state
676
- * @param {Partial<SharedState<T>>} newState - The new state
677
- * @returns {SharedState<T>} The merged state
661
+ * Fusionne deux états.
662
+ * @param {SharedState<T>} currentState
663
+ * @param {Partial<SharedState<T>>} newState
664
+ * @returns {SharedState<T>}
678
665
  */
679
666
  private mergeStates(
680
667
  currentState: SharedState<T>,
@@ -682,28 +669,122 @@ export class GraphEngine<T> {
682
669
  ): SharedState<T> {
683
670
  return {
684
671
  ...currentState,
685
- context: {
686
- ...(currentState.context || {}),
687
- ...(newState.context || {}),
688
- },
672
+ ...(newState || {}),
689
673
  };
690
674
  }
691
675
 
692
676
  /**
693
- * Updates the state of the workflow.
694
- * @param {Partial<SharedState<T>>} updates - The updates to apply
695
- * @returns {SharedState<T>} The updated state
677
+ * Met à jour l'état courant et le renvoie.
678
+ * @param {Partial<T>} updates
679
+ * @returns {SharedState<T>}
696
680
  */
697
- public updateState(updates: Partial<SharedState<T>>): SharedState<T> {
681
+ public updateState(updates: Partial<T>): SharedState<T> {
698
682
  const currentState = this.getState();
699
- const newState = {
683
+ const newState: SharedState<T> = {
700
684
  ...currentState,
701
- context: {
702
- ...currentState.context,
703
- ...(updates.context || {}),
704
- },
685
+ ...updates,
705
686
  };
706
687
  this.setState(newState);
707
688
  return newState;
708
689
  }
690
+
691
+ /* =============================================
692
+ = MÉTHODES STATIQUES POUR PLUSIEURS GRAPHES =
693
+ ============================================= */
694
+
695
+ /**
696
+ * Exécute plusieurs GraphEngine en **séquence** (l'un après l'autre).
697
+ * @param graphs Liste des graphes à exécuter
698
+ * @param startNodes Noms des nœuds de départ correspondants
699
+ * @param initialStates États initiaux correspondants
700
+ * @param onStream Callback d'avancement
701
+ * @param onError Callback d'erreur
702
+ * @returns Tableau des états finaux de chaque graphe
703
+ */
704
+ public static async executeGraphsInSequence<U>(
705
+ graphs: GraphEngine<U>[],
706
+ startNodes: string[],
707
+ initialStates: SharedState<U>[],
708
+ onStream?: (graph: GraphEngine<U>) => void,
709
+ onError?: (error: Error, nodeName: string, state: SharedState<U>) => void
710
+ ): Promise<SharedState<U>[]> {
711
+ const finalStates: SharedState<U>[] = [];
712
+
713
+ for (let i = 0; i < graphs.length; i++) {
714
+ const graph = graphs[i];
715
+ const startNode = startNodes[i];
716
+ const initialState = initialStates[i];
717
+ const result = await graph.execute(
718
+ initialState,
719
+ startNode,
720
+ onStream,
721
+ onError
722
+ );
723
+ finalStates.push(result);
724
+ }
725
+
726
+ return finalStates;
727
+ }
728
+
729
+ /**
730
+ * Exécute plusieurs GraphEngine en **parallèle** (sans limite de concurrence).
731
+ * @param graphs Liste des graphes
732
+ * @param startNodes Noms des nœuds de départ
733
+ * @param initialStates États initiaux
734
+ * @param onStream Callback d'avancement
735
+ * @param onError Callback d'erreur
736
+ * @returns Tableau des états finaux de chaque graphe
737
+ */
738
+ public static async executeGraphsInParallel<U>(
739
+ graphs: GraphEngine<U>[],
740
+ startNodes: string[],
741
+ initialStates: SharedState<U>[],
742
+ onStream?: (graph: GraphEngine<U>) => void,
743
+ onError?: (error: Error, nodeName: string, state: SharedState<U>) => void
744
+ ): Promise<SharedState<U>[]> {
745
+ const promises = graphs.map((graph, index) =>
746
+ graph.execute(initialStates[index], startNodes[index], onStream, onError)
747
+ );
748
+ return Promise.all(promises);
749
+ }
750
+
751
+ /**
752
+ * Exécute plusieurs GraphEngine en parallèle **avec une limite de concurrence**.
753
+ * @param graphs Liste des graphes
754
+ * @param startNodes Noms des nœuds de départ
755
+ * @param initialStates États initiaux
756
+ * @param concurrencyLimit Limite de concurrence
757
+ * @param onStream Callback d'avancement
758
+ * @param onError Callback d'erreur
759
+ * @returns Tableau des états finaux de chaque graphe
760
+ */
761
+ public static async executeGraphsWithConcurrencyLimit<U>(
762
+ graphs: GraphEngine<U>[],
763
+ startNodes: string[],
764
+ initialStates: SharedState<U>[],
765
+ concurrencyLimit: number,
766
+ onStream?: (graph: GraphEngine<U>) => void,
767
+ onError?: (error: Error, nodeName: string, state: SharedState<U>) => void
768
+ ): Promise<SharedState<U>[]> {
769
+ const results: SharedState<U>[] = [];
770
+
771
+ for (let i = 0; i < graphs.length; i += concurrencyLimit) {
772
+ const chunkGraphs = graphs.slice(i, i + concurrencyLimit);
773
+ const chunkStartNodes = startNodes.slice(i, i + concurrencyLimit);
774
+ const chunkInitialStates = initialStates.slice(i, i + concurrencyLimit);
775
+
776
+ const chunkPromises = chunkGraphs.map((graph, index) => {
777
+ return graph.execute(
778
+ chunkInitialStates[index],
779
+ chunkStartNodes[index],
780
+ onStream,
781
+ onError
782
+ );
783
+ });
784
+ const chunkResults = await Promise.all(chunkPromises);
785
+ results.push(...chunkResults);
786
+ }
787
+
788
+ return results;
789
+ }
709
790
  }