@ai.ntellect/core 0.6.20 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/.mocharc.json +2 -1
  2. package/README.md +87 -148
  3. package/graph/controller.ts +1 -1
  4. package/graph/event-manager.ts +288 -0
  5. package/graph/index.ts +152 -384
  6. package/graph/logger.ts +70 -0
  7. package/graph/node.ts +398 -0
  8. package/graph/observer.ts +361 -0
  9. package/interfaces/index.ts +102 -1
  10. package/modules/agenda/index.ts +3 -16
  11. package/modules/embedding/index.ts +3 -3
  12. package/package.json +12 -20
  13. package/test/graph/index.test.ts +296 -154
  14. package/test/graph/observer.test.ts +398 -0
  15. package/test/modules/agenda/node-cron.test.ts +37 -16
  16. package/test/modules/memory/adapters/in-memory.test.ts +2 -2
  17. package/test/modules/memory/adapters/meilisearch.test.ts +28 -24
  18. package/test/modules/memory/base.test.ts +3 -3
  19. package/tsconfig.json +4 -2
  20. package/types/index.ts +23 -2
  21. package/utils/generate-action-schema.ts +8 -7
  22. package/.env.example +0 -2
  23. package/dist/graph/controller.js +0 -75
  24. package/dist/graph/index.js +0 -402
  25. package/dist/index.js +0 -41
  26. package/dist/interfaces/index.js +0 -17
  27. package/dist/modules/agenda/adapters/node-cron/index.js +0 -29
  28. package/dist/modules/agenda/index.js +0 -140
  29. package/dist/modules/embedding/adapters/ai/index.js +0 -57
  30. package/dist/modules/embedding/index.js +0 -59
  31. package/dist/modules/memory/adapters/in-memory/index.js +0 -210
  32. package/dist/modules/memory/adapters/meilisearch/index.js +0 -320
  33. package/dist/modules/memory/adapters/redis/index.js +0 -158
  34. package/dist/modules/memory/index.js +0 -103
  35. package/dist/types/index.js +0 -2
  36. package/dist/utils/generate-action-schema.js +0 -42
  37. package/dist/utils/header-builder.js +0 -34
  38. package/graph.ts +0 -74
  39. package/test/modules/embedding/ai.test.ts +0 -78
  40. package/test/modules/memory/adapters/redis.test.ts +0 -169
  41. package/test/services/agenda.test.ts +0 -279
package/graph/index.ts CHANGED
@@ -1,7 +1,12 @@
1
1
  import { EventEmitter } from "events";
2
- import { IEventEmitter } from "interfaces";
2
+ import { BehaviorSubject, Subject } from "rxjs";
3
3
  import { ZodSchema } from "zod";
4
- import { GraphContext, GraphDefinition, Node } from "../types";
4
+ import { GraphObservable, IEventEmitter } from "../interfaces";
5
+ import { GraphContext, GraphDefinition, GraphEvent, Node } from "../types";
6
+ import { GraphEventManager } from "./event-manager";
7
+ import { GraphLogger } from "./logger";
8
+ import { GraphNode } from "./node";
9
+ import { GraphObserver } from "./observer";
5
10
 
6
11
  /**
7
12
  * @module GraphFlow
@@ -17,15 +22,23 @@ import { GraphContext, GraphDefinition, Node } from "../types";
17
22
  * @template T - Extends ZodSchema for type validation
18
23
  */
19
24
  export class GraphFlow<T extends ZodSchema> {
20
- private nodes: Map<string, Node<T, any>>;
21
25
  private context: GraphContext<T>;
22
26
  public validator?: T;
23
27
  private eventEmitter: IEventEmitter;
24
28
  private globalErrorHandler?: (error: Error, context: GraphContext<T>) => void;
25
29
  private graphEvents?: string[];
26
30
  private entryNode?: string;
27
- private logs: string[] = [];
28
31
  private verbose: boolean = false;
32
+ public nodes: Map<string, Node<T, any>>;
33
+
34
+ private eventSubject: Subject<GraphEvent<T>> = new Subject();
35
+ private stateSubject: BehaviorSubject<GraphContext<T>>;
36
+ private destroySubject: Subject<void> = new Subject();
37
+
38
+ public observer: GraphObserver<T>;
39
+ private logger: GraphLogger;
40
+ private eventManager: GraphEventManager<T>;
41
+ private nodeExecutor: GraphNode<T>;
29
42
 
30
43
  /**
31
44
  * Creates a new instance of GraphFlow
@@ -49,17 +62,36 @@ export class GraphFlow<T extends ZodSchema> {
49
62
  this.graphEvents = config.events;
50
63
  this.verbose = options.verbose ?? false;
51
64
 
65
+ this.stateSubject = new BehaviorSubject<GraphContext<T>>(this.context);
66
+
67
+ this.logger = new GraphLogger(name, options.verbose);
68
+ this.eventManager = new GraphEventManager(
69
+ this.eventEmitter,
70
+ this.nodes,
71
+ name,
72
+ this.context,
73
+ config.events,
74
+ config.entryNode,
75
+ config.onError
76
+ );
77
+ this.nodeExecutor = new GraphNode(
78
+ this.nodes,
79
+ this.logger,
80
+ this.eventManager,
81
+ this.eventSubject,
82
+ this.stateSubject
83
+ );
84
+
85
+ this.setupEventStreams();
52
86
  this.setupEventListeners();
53
87
  this.setupGraphEventListeners();
54
- }
55
88
 
56
- /**
57
- * Creates a new context for execution
58
- * @private
59
- * @returns {GraphContext<T>} A cloned context to prevent pollution during parallel execution
60
- */
61
- private createNewContext(): GraphContext<T> {
62
- return structuredClone(this.context);
89
+ this.observer = new GraphObserver(
90
+ this,
91
+ this.eventSubject,
92
+ this.stateSubject,
93
+ this.destroySubject
94
+ );
63
95
  }
64
96
 
65
97
  /**
@@ -67,77 +99,49 @@ export class GraphFlow<T extends ZodSchema> {
67
99
  * @private
68
100
  * @description Attaches all node-based event triggers while preserving external listeners
69
101
  */
70
- private setupEventListeners(): void {
71
- // First remove only the existing node-based listeners that we might have created previously
72
- // We do NOT remove, for example, "nodeStarted" or "nodeCompleted" listeners that test code added.
73
- for (const [eventName, listener] of this.eventEmitter
74
- .rawListeners("*")
75
- .entries()) {
76
- // This can be tricky—EventEmitter doesn't directly let you remove by "type" of listener.
77
- // Alternatively, we can store references in a separate structure.
78
- // For simplicity, let's do a full removeAllListeners() on node-specified events (only),
79
- // then re-add them below, but keep the test-based events like "nodeStarted" or "nodeCompleted".
80
- }
81
-
82
- // The simplest approach: removeAllListeners for each event that is declared as a node event
83
- // so we don't stack up duplicates:
84
- const allEvents = new Set<string>();
85
- for (const node of this.nodes.values()) {
86
- if (node.events) {
87
- node.events.forEach((evt) => allEvents.add(evt));
88
- }
89
- }
90
- for (const evt of allEvents) {
91
- // remove only those events that are used by nodes
92
- this.eventEmitter.removeAllListeners(evt);
93
- }
102
+ private setupEventStreams(): void {
103
+ this.eventManager.on("nodeStarted", (data) => {
104
+ this.addLog(`Event: Node "${data.name}" started`);
105
+ });
94
106
 
95
- // Now re-add the node-based event triggers
96
- for (const node of this.nodes.values()) {
97
- if (node.events && node.events.length > 0) {
98
- node.events.forEach((event) => {
99
- this.eventEmitter.on(
100
- event,
101
- async (data?: Partial<GraphContext<T>>) => {
102
- const freshContext = this.createNewContext();
103
- if (data) Object.assign(freshContext, data);
107
+ this.eventManager.on("nodeCompleted", (data) => {
108
+ this.addLog(`Event: Node "${data.name}" completed`);
109
+ });
104
110
 
105
- // If triggered by an event, we pass "true" so event-driven node will skip `next`.
106
- await this.executeNode(
107
- node.name,
108
- freshContext,
109
- undefined,
110
- /* triggeredByEvent= */ true
111
- );
112
- }
113
- );
114
- });
111
+ this.eventManager.on("nodeError", (data) => {
112
+ let errorMessage = "Unknown error";
113
+ if (data.error) {
114
+ errorMessage =
115
+ data.error instanceof Error
116
+ ? data.error.message
117
+ : data.error.errors?.[0]?.message ||
118
+ data.error.message ||
119
+ "Unknown error";
115
120
  }
116
- }
117
- }
121
+ this.addLog(`Event: Node "${data.name}" error: ${errorMessage}`);
122
+ });
118
123
 
119
- private addLog(message: string): void {
120
- const logMessage = `[${new Date().toISOString()}] ${message}`;
121
- this.logs.push(logMessage);
122
- if (this.verbose) {
123
- console.log(`[${this.name}] ${message}`);
124
- }
124
+ this.eventManager.on("nodeStateChanged", (data) => {
125
+ this.addLog(`Event: Node "${data.name}" state changed`);
126
+ });
125
127
  }
126
128
 
127
129
  /**
128
- * Enable or disable verbose logging
129
- * @param {boolean} enabled - Whether to enable verbose logging
130
+ * Sets up event listeners for node-based events
131
+ * @private
132
+ * @description Attaches all node-based event triggers while preserving external listeners
130
133
  */
131
- public setVerbose(enabled: boolean): void {
132
- this.verbose = enabled;
134
+ private setupEventListeners(): void {
135
+ this.eventManager.setupEventListeners();
133
136
  }
134
137
 
135
138
  /**
136
- * Get current verbose setting
137
- * @returns {boolean} Current verbose setting
139
+ * Sets up event listeners for graph-based events
140
+ * @private
141
+ * @description Attaches all graph-based event triggers
138
142
  */
139
- public isVerbose(): boolean {
140
- return this.verbose;
143
+ private setupGraphEventListeners(): void {
144
+ this.eventManager.setupGraphEventListeners();
141
145
  }
142
146
 
143
147
  /**
@@ -155,211 +159,58 @@ export class GraphFlow<T extends ZodSchema> {
155
159
  inputs: any,
156
160
  triggeredByEvent: boolean = false
157
161
  ): Promise<void> {
158
- const node = this.nodes.get(nodeName);
159
- if (!node) throw new Error(`Node "${nodeName}" not found.`);
160
-
161
- this.addLog(`🚀 Starting node "${nodeName}"`);
162
- this.eventEmitter.emit("nodeStarted", { name: nodeName });
163
-
164
- try {
165
- const localContext = structuredClone(context);
166
-
167
- if (node.condition && !node.condition(localContext)) {
168
- this.addLog(`⏭️ Skipping node "${nodeName}" - condition not met`);
169
- return;
170
- }
171
-
172
- // Validate inputs
173
- if (node.inputs) {
174
- if (!inputs) {
175
- this.addLog(`❌ Missing required inputs for node "${nodeName}"`);
176
- throw new Error(`Inputs required for node "${nodeName}"`);
177
- }
178
- this.addLog(`📥 Validating inputs for node "${nodeName}"`);
179
- inputs = node.inputs.parse(inputs);
180
- }
181
-
182
- // Handle retry logic
183
- if (node.retry && node.retry.maxAttempts > 0) {
184
- let attempts = 0;
185
- let lastError: Error | null = null;
186
-
187
- while (attempts < node.retry.maxAttempts) {
188
- try {
189
- this.addLog(`🔄 Attempt ${attempts + 1}/${node.retry.maxAttempts}`);
190
- await node.execute(localContext, inputs);
191
- lastError = null;
192
- break;
193
- } catch (error: any) {
194
- lastError = error as Error;
195
- attempts++;
196
- this.addLog(`❌ Attempt ${attempts} failed: ${error.message}`);
197
-
198
- if (attempts === node.retry.maxAttempts) {
199
- // Si toutes les tentatives ont échoué et qu'il y a un gestionnaire d'échec
200
- if (node.retry.onRetryFailed) {
201
- this.addLog(
202
- `🔄 Executing retry failure handler for node "${nodeName}"`
203
- );
204
- try {
205
- await node.retry.onRetryFailed(lastError, localContext);
206
- // Si le gestionnaire d'échec réussit, on continue l'exécution
207
- // SEULEMENT si le gestionnaire a explicitement retourné true
208
- if (node.retry.continueOnFailed) {
209
- this.addLog(
210
- `✅ Retry failure handler succeeded for node "${nodeName}" - continuing execution`
211
- );
212
- break;
213
- } else {
214
- this.addLog(
215
- `⚠️ Retry failure handler executed but node "${nodeName}" will still fail`
216
- );
217
- throw lastError;
218
- }
219
- } catch (handlerError: any) {
220
- this.addLog(
221
- `❌ Retry failure handler failed for node "${nodeName}": ${handlerError.message}`
222
- );
223
- throw handlerError;
224
- }
225
- }
226
- // Si pas de gestionnaire d'échec ou si le gestionnaire a échoué
227
- throw lastError;
228
- }
229
-
230
- if (attempts < node.retry.maxAttempts) {
231
- this.addLog(
232
- `⏳ Waiting ${node.retry.delay}ms before next attempt`
233
- );
234
- await new Promise((resolve) =>
235
- setTimeout(resolve, node.retry?.delay || 0)
236
- );
237
- }
238
- }
239
- }
240
- } else {
241
- await node.execute(localContext, inputs);
242
- }
243
-
244
- // Validate outputs
245
- if (node.outputs) {
246
- this.addLog(`📤 Validating outputs for node "${nodeName}"`);
247
- node.outputs.parse(localContext);
248
- }
249
-
250
- Object.assign(context, localContext);
251
-
252
- this.addLog(
253
- `✅ Node "${nodeName}" executed successfully ${JSON.stringify(context)}`
254
- );
255
- this.eventEmitter.emit("nodeCompleted", { name: nodeName });
256
-
257
- // Handle waitForEvent
258
- if (node.waitForEvent && !triggeredByEvent) {
259
- this.addLog(
260
- `⏳ Node "${nodeName}" waiting for events: ${node.events?.join(", ")}`
261
- );
262
-
263
- await new Promise<void>((resolve) => {
264
- const eventHandler = () => {
265
- this.addLog(`🚀 Event received for node "${nodeName}"`);
266
- resolve();
267
- };
268
-
269
- node.events?.forEach((event) => {
270
- this.eventEmitter.once(event, eventHandler);
271
- });
272
- });
162
+ return this.nodeExecutor.executeNode(
163
+ nodeName,
164
+ context,
165
+ inputs,
166
+ triggeredByEvent
167
+ );
168
+ }
273
169
 
274
- const nextNodes =
275
- typeof node.next === "function"
276
- ? node.next(context)
277
- : node.next || [];
278
-
279
- if (nextNodes.length > 0) {
280
- this.addLog(`➡️ Executing next nodes: ${nextNodes.join(", ")}`);
281
-
282
- // Créer un contexte unique pour toutes les branches
283
- const branchContext = structuredClone(context);
284
-
285
- // Exécuter les branches séquentiellement avec le même contexte
286
- for (const nextNodeName of nextNodes) {
287
- this.addLog(`🔄 Starting branch for node "${nextNodeName}"`);
288
- const nextNode = this.nodes.get(nextNodeName);
289
- if (nextNode) {
290
- // Utiliser le même contexte pour toutes les branches
291
- await this.executeNode(
292
- nextNodeName,
293
- branchContext,
294
- undefined,
295
- nextNode.waitForEvent
296
- );
297
- }
298
- this.addLog(`✅ Branch "${nextNodeName}" completed`);
299
- }
170
+ private addLog(message: string): void {
171
+ this.logger.addLog(message);
172
+ }
300
173
 
301
- // Mettre à jour le contexte global avec le résultat final des branches
302
- Object.assign(context, branchContext);
303
- this.context = structuredClone(context);
174
+ public getLogs(): string[] {
175
+ return this.logger.getLogs();
176
+ }
304
177
 
305
- this.eventEmitter.emit("graphCompleted", {
306
- name: this.name,
307
- context: this.context,
308
- });
178
+ public clearLogs(): void {
179
+ this.logger.clearLogs();
180
+ }
309
181
 
310
- return;
311
- }
312
- }
182
+ /**
183
+ * Get the observer instance for monitoring graph state and events
184
+ */
185
+ public observe(
186
+ options: {
187
+ debounce?: number;
188
+ delay?: number;
189
+ stream?: boolean;
190
+ properties?: (keyof GraphContext<T> extends string
191
+ ? keyof GraphContext<T>
192
+ : never)[];
193
+ onStreamLetter?: (data: { letter: string; property: string }) => void;
194
+ onStreamComplete?: () => void;
195
+ } = {}
196
+ ): GraphObservable<T> {
197
+ return this.observer.state(options) as GraphObservable<T>;
198
+ }
313
199
 
314
- // Execute next nodes
315
- const nextNodes =
316
- typeof node.next === "function"
317
- ? node.next(localContext)
318
- : node.next || [];
319
-
320
- if (nextNodes.length > 0) {
321
- this.addLog(`➡️ Executing next nodes: ${nextNodes.join(", ")}`);
322
-
323
- // Créer un contexte unique pour toutes les branches
324
- const branchContext = structuredClone(context);
325
-
326
- // Exécuter les branches séquentiellement avec le même contexte
327
- for (const nextNodeName of nextNodes) {
328
- this.addLog(`🔄 Starting branch for node "${nextNodeName}"`);
329
- const nextNode = this.nodes.get(nextNodeName);
330
- if (nextNode) {
331
- // Utiliser le même contexte pour toutes les branches
332
- await this.executeNode(
333
- nextNodeName,
334
- branchContext,
335
- undefined,
336
- nextNode.waitForEvent
337
- );
338
- }
339
- this.addLog(`✅ Branch "${nextNodeName}" completed`);
340
- }
341
-
342
- // Mettre à jour le contexte global avec le résultat final des branches
343
- Object.assign(context, branchContext);
344
- this.context = structuredClone(context);
345
- }
346
- } catch (error: any) {
347
- this.addLog(`❌ Error in node "${nodeName}": ${error.message}`);
348
- this.eventEmitter.emit("nodeError", { name: nodeName, error });
349
- throw error;
350
- }
200
+ /**
201
+ * Enable or disable verbose logging
202
+ * @param {boolean} enabled - Whether to enable verbose logging
203
+ */
204
+ public setVerbose(enabled: boolean): void {
205
+ this.logger.setVerbose(enabled);
351
206
  }
352
207
 
353
208
  /**
354
- * Validates the current context against the schema
355
- * @private
356
- * @param {GraphContext<T>} context - Context to validate
357
- * @throws {Error} If validation fails
209
+ * Get current verbose setting
210
+ * @returns {boolean} Current verbose setting
358
211
  */
359
- private validateContext(context: GraphContext<T>): void {
360
- if (this.validator) {
361
- this.validator.parse(context);
362
- }
212
+ public isVerbose(): boolean {
213
+ return this.logger.isVerbose();
363
214
  }
364
215
 
365
216
  /**
@@ -369,13 +220,12 @@ export class GraphFlow<T extends ZodSchema> {
369
220
  * @param {any} inputParams - Optional input parameters for the start node
370
221
  * @returns {Promise<GraphContext<T>>} Final context after execution
371
222
  */
372
- async execute(
223
+ public async execute(
373
224
  startNode: string,
374
225
  inputParams?: any,
375
226
  inputContext?: Partial<GraphContext<T>>
376
227
  ): Promise<GraphContext<T>> {
377
228
  if (inputParams) {
378
- // Merge inputParams into context
379
229
  Object.assign(this.context, inputParams);
380
230
  }
381
231
 
@@ -386,7 +236,12 @@ export class GraphFlow<T extends ZodSchema> {
386
236
  this.eventEmitter.emit("graphStarted", { name: this.name });
387
237
 
388
238
  try {
389
- await this.executeNode(startNode, this.context, inputParams, false);
239
+ await this.nodeExecutor.executeNode(
240
+ startNode,
241
+ this.context,
242
+ inputParams,
243
+ false
244
+ );
390
245
 
391
246
  this.eventEmitter.emit("graphCompleted", {
392
247
  name: this.name,
@@ -405,36 +260,19 @@ export class GraphFlow<T extends ZodSchema> {
405
260
  * Emits an event to trigger event-based nodes
406
261
  * @param {string} eventName - Name of the event to emit
407
262
  * @param {Partial<GraphContext<T>>} data - Optional data to merge with context
408
- * @returns {Promise<GraphContext<T>>} Updated context after event handling
263
+ * @returns {Promise<void>}
409
264
  */
410
265
  public async emit(
411
266
  eventName: string,
412
267
  data?: Partial<GraphContext<T>>
413
- ): Promise<GraphContext<T>> {
414
- const workingContext = structuredClone(this.context);
415
-
416
- if (data) {
417
- Object.assign(workingContext, data);
418
- }
419
-
420
- const eventNodes = Array.from(this.nodes.values()).filter((node) =>
421
- node.events?.includes(eventName)
422
- );
423
-
424
- // Execute event nodes sequentially with shared context
425
- for (const node of eventNodes) {
426
- await this.executeNode(node.name, workingContext, undefined, true);
427
- }
428
-
429
- // Update global context after all event nodes are executed
430
- this.context = structuredClone(workingContext);
431
-
432
- this.eventEmitter.emit("graphCompleted", {
433
- name: this.name,
434
- context: this.context,
435
- });
436
-
437
- return this.getContext();
268
+ ): Promise<void> {
269
+ const event: GraphEvent<T> = {
270
+ type: eventName,
271
+ payload: data,
272
+ timestamp: Date.now(),
273
+ };
274
+ this.eventSubject.next(event);
275
+ await new Promise((resolve) => setTimeout(resolve, 0));
438
276
  }
439
277
 
440
278
  /**
@@ -442,15 +280,15 @@ export class GraphFlow<T extends ZodSchema> {
442
280
  * @param {string} eventName - Name of the event to listen for
443
281
  * @param {Function} handler - Handler function to execute when event is emitted
444
282
  */
445
- on(eventName: string, handler: (...args: any[]) => void): void {
446
- this.eventEmitter.on(eventName, handler);
283
+ public on(eventName: string, handler: (...args: any[]) => void): void {
284
+ this.eventManager.on(eventName, handler);
447
285
  }
448
286
 
449
287
  /**
450
288
  * Updates the graph definition with new configuration
451
289
  * @param {GraphDefinition<T>} definition - New graph definition
452
290
  */
453
- load(definition: GraphDefinition<T>): void {
291
+ public load(definition: GraphDefinition<T>): void {
454
292
  // Clear all existing nodes
455
293
  this.nodes.clear();
456
294
  // Wipe out old node-based event listeners
@@ -512,8 +350,8 @@ export class GraphFlow<T extends ZodSchema> {
512
350
  * @param {string} message - Message to log
513
351
  * @param {any} data - Optional data to log
514
352
  */
515
- log(message: string, data?: any): void {
516
- console.log(`[Graph ${this.name}] ${message}`, data);
353
+ public log(message: string, data?: any): void {
354
+ this.logger.log(message, data);
517
355
  }
518
356
 
519
357
  /**
@@ -521,105 +359,35 @@ export class GraphFlow<T extends ZodSchema> {
521
359
  * @param {Node<T>} node - Node to add
522
360
  * @throws {Error} If node with same name already exists
523
361
  */
524
- addNode(node: Node<T, any>): void {
362
+ public addNode(node: Node<T, any>): void {
525
363
  this.nodes.set(node.name, node);
526
- if (node.events && node.events.length > 0) {
527
- for (const evt of node.events) {
528
- this.eventEmitter.on(evt, async (data?: Partial<GraphContext<T>>) => {
529
- const freshContext = this.createNewContext();
530
- if (data) Object.assign(freshContext, data);
531
- await this.executeNode(node.name, freshContext, undefined, true);
532
- });
533
- }
534
- }
364
+ this.eventManager.setupEventListeners();
535
365
  }
536
366
 
537
367
  /**
538
368
  * Removes a node from the graph
539
369
  * @param {string} nodeName - Name of the node to remove
540
370
  */
541
- removeNode(nodeName: string): void {
542
- const node = this.nodes.get(nodeName);
543
- if (!node) return;
544
-
545
- // remove the node from the map
371
+ public removeNode(nodeName: string): void {
546
372
  this.nodes.delete(nodeName);
547
-
548
- // remove any of its event-based listeners
549
- if (node.events && node.events.length > 0) {
550
- for (const evt of node.events) {
551
- // removeAllListeners(evt) would also remove other node listeners,
552
- // so we need a more fine-grained approach. Ideally, we should keep a reference
553
- // to the exact listener function we attached. For brevity, let's remove all for that event:
554
- this.eventEmitter.removeAllListeners(evt);
555
- }
556
- // Then reattach the others that remain in the graph
557
- for (const n of this.nodes.values()) {
558
- if (n.events && n.events.length > 0) {
559
- n.events.forEach((e) => {
560
- this.eventEmitter.on(e, async (data?: Partial<GraphContext<T>>) => {
561
- const freshContext = this.createNewContext();
562
- if (data) Object.assign(freshContext, data);
563
- await this.executeNode(n.name, freshContext, undefined, true);
564
- });
565
- });
566
- }
567
- }
568
- }
373
+ this.eventManager.setupEventListeners();
569
374
  }
570
375
 
571
376
  /**
572
377
  * Returns all nodes in the graph
573
378
  * @returns {Node<T>[]} Array of all nodes
574
379
  */
575
- getNodes(): Node<T, any>[] {
380
+ public getNodes(): Node<T, any>[] {
576
381
  return Array.from(this.nodes.values());
577
382
  }
578
383
 
579
- private setupGraphEventListeners(): void {
580
- if (this.graphEvents && this.graphEvents.length > 0) {
581
- this.graphEvents.forEach((event) => {
582
- this.eventEmitter.on(event, async (data?: Partial<GraphContext<T>>) => {
583
- const freshContext = this.createNewContext();
584
- if (data) Object.assign(freshContext, data);
585
-
586
- // Emit "graphStarted"
587
- this.eventEmitter.emit("graphStarted", { name: this.name });
588
-
589
- try {
590
- // Execute the graph starting from the entry node
591
- if (!this.entryNode) {
592
- throw new Error("No entry node defined for graph event handling");
593
- }
594
-
595
- await this.executeNode(
596
- this.entryNode,
597
- freshContext,
598
- undefined,
599
- false
600
- );
601
-
602
- // Emit "graphCompleted"
603
- this.eventEmitter.emit("graphCompleted", {
604
- name: this.name,
605
- context: this.context,
606
- });
607
- } catch (error) {
608
- // Emit "graphError"
609
- this.eventEmitter.emit("graphError", { name: this.name, error });
610
- this.globalErrorHandler?.(error as Error, freshContext);
611
- throw error;
612
- }
613
- });
614
- });
615
- }
616
- }
617
-
618
- getLogs(): string[] {
619
- return [...this.logs];
620
- }
621
-
622
- clearLogs(): void {
623
- this.logs = [];
384
+ /**
385
+ * Cleanup resources
386
+ */
387
+ public destroy(): void {
388
+ this.destroySubject.next();
389
+ this.destroySubject.complete();
390
+ this.eventSubject.complete();
391
+ this.stateSubject.complete();
624
392
  }
625
393
  }