@ai.ntellect/core 0.7.1 → 0.7.4

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 (86) hide show
  1. package/README.md +13 -28
  2. package/dist/graph/controller.d.ts +33 -0
  3. package/dist/graph/controller.d.ts.map +1 -0
  4. package/dist/graph/controller.js +73 -0
  5. package/dist/graph/controller.js.map +1 -0
  6. package/dist/graph/event-manager.d.ts +92 -0
  7. package/dist/graph/event-manager.d.ts.map +1 -0
  8. package/dist/graph/event-manager.js +244 -0
  9. package/dist/graph/event-manager.js.map +1 -0
  10. package/dist/graph/index.d.ts +157 -0
  11. package/dist/graph/index.d.ts.map +1 -0
  12. package/dist/graph/index.js +299 -0
  13. package/dist/graph/index.js.map +1 -0
  14. package/dist/graph/logger.d.ts +46 -0
  15. package/dist/graph/logger.d.ts.map +1 -0
  16. package/dist/graph/logger.js +69 -0
  17. package/dist/graph/logger.js.map +1 -0
  18. package/dist/graph/node.d.ts +103 -0
  19. package/dist/graph/node.d.ts.map +1 -0
  20. package/dist/graph/node.js +284 -0
  21. package/dist/graph/node.js.map +1 -0
  22. package/dist/graph/observer.d.ts +113 -0
  23. package/dist/graph/observer.d.ts.map +1 -0
  24. package/dist/graph/observer.js +199 -0
  25. package/dist/graph/observer.js.map +1 -0
  26. package/dist/index.d.ts +26 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +42 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/interfaces/index.d.ts +447 -0
  31. package/dist/interfaces/index.d.ts.map +1 -0
  32. package/dist/interfaces/index.js +75 -0
  33. package/dist/interfaces/index.js.map +1 -0
  34. package/dist/modules/agenda/adapters/node-cron/index.d.ts +17 -0
  35. package/dist/modules/agenda/adapters/node-cron/index.d.ts.map +1 -0
  36. package/dist/modules/agenda/adapters/node-cron/index.js +30 -0
  37. package/dist/modules/agenda/adapters/node-cron/index.js.map +1 -0
  38. package/dist/modules/agenda/index.d.ts +63 -0
  39. package/dist/modules/agenda/index.d.ts.map +1 -0
  40. package/dist/modules/agenda/index.js +141 -0
  41. package/dist/modules/agenda/index.js.map +1 -0
  42. package/dist/modules/embedding/adapters/ai/index.d.ts +29 -0
  43. package/dist/modules/embedding/adapters/ai/index.d.ts.map +1 -0
  44. package/dist/modules/embedding/adapters/ai/index.js +58 -0
  45. package/dist/modules/embedding/adapters/ai/index.js.map +1 -0
  46. package/dist/modules/embedding/index.d.ts +36 -0
  47. package/dist/modules/embedding/index.d.ts.map +1 -0
  48. package/dist/modules/embedding/index.js +60 -0
  49. package/dist/modules/embedding/index.js.map +1 -0
  50. package/dist/modules/memory/adapters/in-memory/index.d.ts +120 -0
  51. package/dist/modules/memory/adapters/in-memory/index.d.ts.map +1 -0
  52. package/dist/modules/memory/adapters/in-memory/index.js +211 -0
  53. package/dist/modules/memory/adapters/in-memory/index.js.map +1 -0
  54. package/dist/modules/memory/adapters/meilisearch/index.d.ts +110 -0
  55. package/dist/modules/memory/adapters/meilisearch/index.d.ts.map +1 -0
  56. package/dist/modules/memory/adapters/meilisearch/index.js +321 -0
  57. package/dist/modules/memory/adapters/meilisearch/index.js.map +1 -0
  58. package/dist/modules/memory/adapters/redis/index.d.ts +82 -0
  59. package/dist/modules/memory/adapters/redis/index.d.ts.map +1 -0
  60. package/dist/modules/memory/adapters/redis/index.js +159 -0
  61. package/dist/modules/memory/adapters/redis/index.js.map +1 -0
  62. package/dist/modules/memory/index.d.ts +67 -0
  63. package/dist/modules/memory/index.d.ts.map +1 -0
  64. package/dist/modules/memory/index.js +104 -0
  65. package/dist/modules/memory/index.js.map +1 -0
  66. package/dist/types/index.d.ts +166 -0
  67. package/dist/types/index.d.ts.map +1 -0
  68. package/dist/types/index.js +3 -0
  69. package/dist/types/index.js.map +1 -0
  70. package/dist/utils/generate-action-schema.d.ts +5 -0
  71. package/dist/utils/generate-action-schema.d.ts.map +1 -0
  72. package/dist/utils/generate-action-schema.js +44 -0
  73. package/dist/utils/generate-action-schema.js.map +1 -0
  74. package/dist/utils/header-builder.d.ts +12 -0
  75. package/dist/utils/header-builder.d.ts.map +1 -0
  76. package/dist/utils/header-builder.js +35 -0
  77. package/dist/utils/header-builder.js.map +1 -0
  78. package/graph/event-manager.ts +9 -2
  79. package/graph/node.ts +25 -63
  80. package/graph/observer.ts +17 -9
  81. package/package.json +5 -1
  82. package/test/graph/controller.test.ts +0 -0
  83. package/test/graph/event-manager.test.ts +72 -0
  84. package/test/graph/index.test.ts +41 -34
  85. package/test/graph/node.test.ts +197 -0
  86. package/tsconfig.json +13 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-action-schema.js","sourceRoot":"","sources":["../../utils/generate-action-schema.ts"],"names":[],"mappings":";;;AAAA,6BAAwB;AAGjB,MAAM,oBAAoB,GAAG,CAAC,MAAwB,EAAE,EAAE;IAC/D,OAAO,MAAM;SACV,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACb,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACrD,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM;YAC/B,CAAC,CAAC,IAAA,uBAAe,EAAC,QAAQ,CAAC,MAAM,CAAC;YAClC,CAAC,CAAC,eAAe,CAAC;QACpB,OAAO,aAAa,KAAK,CAAC,IAAI,iBAAiB,SAAS,EAAE,CAAC;IAC7D,CAAC,CAAC;SACD,IAAI,CAAC,MAAM,CAAC,CAAC;AAClB,CAAC,CAAC;AAVW,QAAA,oBAAoB,wBAU/B;AAEK,MAAM,eAAe,GAAG,CAAC,MAAiB,EAAU,EAAE;IAC3D,IAAI,MAAM,YAAY,OAAC,CAAC,SAAS,EAAE,CAAC;QAClC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC7C,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;YAC1C,MAAM,WAAW,GAAI,KAAa,CAAC,IAAI,CAAC,WAAW,CAAC;YACpD,MAAM,SAAS,GAAG,IAAA,uBAAe,EAAC,KAAkB,CAAC,CAAC;YACtD,OAAO,WAAW;gBAChB,CAAC,CAAC,GAAG,GAAG,KAAK,SAAS,OAAO,WAAW,EAAE;gBAC1C,CAAC,CAAC,GAAG,GAAG,KAAK,SAAS,EAAE,CAAC;QAC7B,CAAC,CAAC,CAAC;QACH,OAAO,aAAa,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;IAC5C,CAAC;IAED,IAAI,MAAM,YAAY,OAAC,CAAC,QAAQ,EAAE,CAAC;QACjC,OAAO,WAAW,IAAA,uBAAe,EAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC;IACvD,CAAC;IAED,IAAI,MAAM,YAAY,OAAC,CAAC,SAAS,EAAE,CAAC;QAClC,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,IAAI,MAAM,YAAY,OAAC,CAAC,SAAS,EAAE,CAAC;QAClC,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,IAAI,MAAM,YAAY,OAAC,CAAC,UAAU,EAAE,CAAC;QACnC,OAAO,aAAa,CAAC;IACvB,CAAC;IAED,OAAO,aAAa,CAAC;AACvB,CAAC,CAAC;AA9BW,QAAA,eAAe,mBA8B1B"}
@@ -0,0 +1,12 @@
1
+ type HeaderValue = string | string[] | undefined;
2
+ export declare class LLMHeaderBuilder {
3
+ private headers;
4
+ private _result;
5
+ constructor();
6
+ addHeader(key: string, value: HeaderValue): LLMHeaderBuilder;
7
+ valueOf(): string;
8
+ toString(): string;
9
+ static create(): LLMHeaderBuilder;
10
+ }
11
+ export {};
12
+ //# sourceMappingURL=header-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"header-builder.d.ts","sourceRoot":"","sources":["../../utils/header-builder.ts"],"names":[],"mappings":"AAAA,KAAK,WAAW,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC;AAEjD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,OAAO,CAAS;;IAOxB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,gBAAgB;IAiB5D,OAAO,IAAI,MAAM;IAIjB,QAAQ,IAAI,MAAM;IAIlB,MAAM,CAAC,MAAM,IAAI,gBAAgB;CAGlC"}
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LLMHeaderBuilder = void 0;
4
+ class LLMHeaderBuilder {
5
+ constructor() {
6
+ this.headers = new Map();
7
+ this._result = "";
8
+ }
9
+ addHeader(key, value) {
10
+ if (Array.isArray(value)) {
11
+ this.headers.set(key, value.join("\n"));
12
+ }
13
+ else {
14
+ this.headers.set(key, value);
15
+ }
16
+ // Build result immediately
17
+ this._result = Array.from(this.headers.entries())
18
+ .filter(([_, value]) => value !== undefined)
19
+ .map(([key, value]) => `# ${key}: ${value}`)
20
+ .join("\n")
21
+ .trim();
22
+ return this;
23
+ }
24
+ valueOf() {
25
+ return this._result;
26
+ }
27
+ toString() {
28
+ return this._result;
29
+ }
30
+ static create() {
31
+ return new LLMHeaderBuilder();
32
+ }
33
+ }
34
+ exports.LLMHeaderBuilder = LLMHeaderBuilder;
35
+ //# sourceMappingURL=header-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"header-builder.js","sourceRoot":"","sources":["../../utils/header-builder.ts"],"names":[],"mappings":";;;AAEA,MAAa,gBAAgB;IAI3B;QACE,IAAI,CAAC,OAAO,GAAG,IAAI,GAAG,EAAE,CAAC;QACzB,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;IACpB,CAAC;IAED,SAAS,CAAC,GAAW,EAAE,KAAkB;QACvC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1C,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC/B,CAAC;QAED,2BAA2B;QAC3B,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;aAC9C,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC;aAC3C,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,GAAG,KAAK,KAAK,EAAE,CAAC;aAC3C,IAAI,CAAC,IAAI,CAAC;aACV,IAAI,EAAE,CAAC;QAEV,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO;QACL,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,QAAQ;QACN,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,MAAM,CAAC,MAAM;QACX,OAAO,IAAI,gBAAgB,EAAE,CAAC;IAChC,CAAC;CACF;AArCD,4CAqCC"}
@@ -72,9 +72,16 @@ export class GraphEventManager<T extends ZodSchema> {
72
72
  payload?: P,
73
73
  context?: GraphContext<T>
74
74
  ): void {
75
- const event: GraphEvent<T> = { type, payload, timestamp: Date.now() };
75
+ // Éviter la double imbrication des événements
76
+ const event = {
77
+ type,
78
+ payload,
79
+ timestamp: Date.now(),
80
+ };
81
+
82
+ // Émettre l'événement une seule fois
76
83
  this.eventSubject.next(event);
77
- this.eventEmitter.emit(type, event);
84
+ this.eventEmitter.emit(type, payload);
78
85
  }
79
86
 
80
87
  /**
package/graph/node.ts CHANGED
@@ -72,52 +72,41 @@ export class GraphNode<T extends ZodSchema> {
72
72
  const node = this.nodes.get(nodeName);
73
73
  if (!node) throw new Error(`Node "${nodeName}" not found.`);
74
74
 
75
- this.logger.addLog(`🚀 Starting node "${nodeName}"`);
76
- this.emitEvent("nodeStarted", { name: nodeName, context: { ...context } });
75
+ this.logger.addLog(`🚀 Starting node "${nodeName}`);
76
+ this.emitEvent("nodeStarted", { name: nodeName, context });
77
77
 
78
78
  try {
79
+ // Vérifier la condition avant d'exécuter
80
+ if (node.condition && !node.condition(context)) {
81
+ this.logger.addLog(
82
+ `⏭️ Skipping node "${nodeName}" - condition not met`
83
+ );
84
+ return;
85
+ }
86
+
79
87
  const contextProxy = new Proxy(context, {
80
88
  set: (target, prop, value) => {
81
89
  const oldValue = target[prop];
82
- target[prop] = value;
90
+ if (oldValue === value) return true;
83
91
 
92
+ target[prop] = value;
84
93
  this.emitEvent("nodeStateChanged", {
85
94
  nodeName,
86
- name: nodeName,
87
95
  property: prop.toString(),
88
96
  oldValue,
89
97
  newValue: value,
90
- context: { ...target },
98
+ context: target,
91
99
  });
92
100
 
93
101
  return true;
94
102
  },
95
- get: (target, prop) => {
96
- return target[prop];
97
- },
103
+ get: (target, prop) => target[prop],
98
104
  });
99
105
 
100
- if (node.condition && !node.condition(contextProxy)) {
101
- this.logger.addLog(
102
- `⏭️ Skipping node "${nodeName}" - condition not met`
103
- );
104
- return;
105
- }
106
-
107
- if (node.inputs) {
108
- await this.validateInputs(node, inputs, nodeName);
109
- }
110
-
111
- if (node.retry && node.retry.maxAttempts > 0) {
112
- await this.executeWithRetry(node, contextProxy, inputs, nodeName);
113
- } else {
114
- await node.execute(contextProxy, inputs);
115
- }
116
-
117
- if (node.outputs) {
118
- await this.validateOutputs(node, contextProxy, nodeName);
119
- }
106
+ // Exécuter le nœud
107
+ await node.execute(contextProxy, inputs);
120
108
 
109
+ // Gérer la suite uniquement si pas déclenché par un événement
121
110
  if (!triggeredByEvent) {
122
111
  const nextNodes =
123
112
  typeof node.next === "function"
@@ -125,47 +114,20 @@ export class GraphNode<T extends ZodSchema> {
125
114
  : node.next || [];
126
115
 
127
116
  for (const nextNodeName of nextNodes) {
128
- const nextNode = this.nodes.get(nextNodeName);
129
- if (
130
- nextNode &&
131
- (!nextNode.condition || nextNode.condition(contextProxy))
132
- ) {
133
- await this.executeNode(
134
- nextNodeName,
135
- contextProxy,
136
- undefined,
137
- false
138
- );
139
- }
117
+ await this.executeNode(nextNodeName, context, undefined, false);
140
118
  }
141
119
  }
142
120
 
143
- if (!triggeredByEvent) {
144
- await this.handleEvents(node, nodeName, contextProxy);
145
- }
146
-
147
121
  this.logger.addLog(`✅ Node "${nodeName}" executed successfully`);
148
- this.emitEvent("nodeCompleted", {
149
- name: nodeName,
150
- context: { ...contextProxy },
151
- });
152
- } catch (error: any) {
153
- const errorToThrow =
154
- error instanceof Error
155
- ? error
156
- : new Error(error.message || "Unknown error");
157
-
122
+ this.emitEvent("nodeCompleted", { name: nodeName, context });
123
+ } catch (error) {
158
124
  this.logger.addLog(
159
- `❌ Error in node "${nodeName}": ${errorToThrow.message}`
125
+ `❌ Error in node "${nodeName}": ${
126
+ error instanceof Error ? error.message : String(error)
127
+ }`
160
128
  );
161
-
162
- this.emitEvent("nodeError", {
163
- name: nodeName,
164
- error: errorToThrow,
165
- context,
166
- });
167
-
168
- throw errorToThrow;
129
+ this.emitEvent("nodeError", { name: nodeName, error, context });
130
+ throw error;
169
131
  }
170
132
  }
171
133
 
package/graph/observer.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  firstValueFrom,
7
7
  } from "rxjs";
8
8
  import {
9
+ debounceTime,
9
10
  distinctUntilChanged,
10
11
  filter,
11
12
  map,
@@ -55,16 +56,23 @@ export class GraphObserver<T extends ZodSchema> {
55
56
  } = {}
56
57
  ): GraphObservable<T> {
57
58
  const baseObservable = new Observable<any>((subscriber) => {
58
- const subscription = this.eventSubject
59
- .pipe(
60
- filter(
61
- (event) =>
62
- event.type === "nodeStateChanged" ||
63
- event.type === "nodeStarted" ||
64
- event.type === "nodeCompleted"
65
- ),
59
+ // Combine les événements avec l'état actuel
60
+ const subscription = combineLatest([
61
+ this.eventSubject.pipe(
62
+ filter((event) => event.type === "nodeStateChanged"),
66
63
  map((event) => event.payload.context),
67
- startWith(this.stateSubject.getValue()),
64
+ distinctUntilChanged(
65
+ (prev, curr) => JSON.stringify(prev) === JSON.stringify(curr)
66
+ ),
67
+ debounceTime(options.debounce || 100)
68
+ ),
69
+ this.stateSubject,
70
+ ])
71
+ .pipe(
72
+ map(([eventContext, stateContext]) => ({
73
+ ...stateContext,
74
+ ...eventContext,
75
+ })),
68
76
  distinctUntilChanged(
69
77
  (prev, curr) => JSON.stringify(prev) === JSON.stringify(curr)
70
78
  )
package/package.json CHANGED
@@ -1,10 +1,12 @@
1
1
  {
2
2
  "name": "@ai.ntellect/core",
3
- "version": "0.7.1",
3
+ "version": "0.7.4",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
7
7
  "build": "rm -rf dist && tsc",
8
+ "prepare": "npm run build",
9
+ "prepublishOnly": "npm run build",
8
10
  "test": "mocha -r ./node_modules/ts-node/register",
9
11
  "test:all": "mocha -r ./node_modules/ts-node/register 'test/**/*.test.ts'",
10
12
  "test:coverage": "nyc --reporter=text --reporter=html pnpm test",
@@ -24,10 +26,12 @@
24
26
  },
25
27
  "devDependencies": {
26
28
  "@types/chai": "^4.3.20",
29
+ "@types/chai-as-promised": "^8.0.1",
27
30
  "@types/mocha": "^10.0.10",
28
31
  "@types/node": "^20",
29
32
  "@types/sinon": "^17.0.3",
30
33
  "chai": "^4.5.0",
34
+ "chai-as-promised": "^8.0.1",
31
35
  "mocha": "^10.0.0",
32
36
  "nyc": "^17.1.0",
33
37
  "redis": "^4.6.13",
File without changes
@@ -0,0 +1,72 @@
1
+ import { expect } from "chai";
2
+ import { EventEmitter } from "events";
3
+ import { z } from "zod";
4
+ import { GraphEventManager } from "../../graph/event-manager";
5
+
6
+ describe("GraphEventManager", () => {
7
+ const TestSchema = z.object({
8
+ counter: z.number(),
9
+ message: z.string(),
10
+ });
11
+
12
+ let eventManager: GraphEventManager<typeof TestSchema>;
13
+ let eventEmitter: EventEmitter;
14
+ let events: any[] = [];
15
+
16
+ beforeEach(() => {
17
+ events = [];
18
+ eventEmitter = new EventEmitter();
19
+ eventManager = new GraphEventManager(eventEmitter, new Map(), "test", {
20
+ counter: 0,
21
+ message: "Hello",
22
+ });
23
+ });
24
+
25
+ it("should emit events without duplication", () => {
26
+ const emittedEvents: any[] = [];
27
+ eventEmitter.on("test", (event) => emittedEvents.push(event));
28
+
29
+ eventManager.emitEvent("test", { data: "test" });
30
+
31
+ expect(emittedEvents).to.have.lengthOf(1);
32
+ expect(emittedEvents[0]).to.deep.equal({ data: "test" });
33
+ });
34
+
35
+ it("should handle nodeStateChanged events correctly", () => {
36
+ const stateChanges: any[] = [];
37
+ eventEmitter.on("nodeStateChanged", (event) => stateChanges.push(event));
38
+
39
+ eventManager.emitEvent("nodeStateChanged", {
40
+ nodeName: "test",
41
+ property: "counter",
42
+ oldValue: 0,
43
+ newValue: 1,
44
+ context: { counter: 1, message: "Hello" },
45
+ });
46
+
47
+ expect(stateChanges).to.have.lengthOf(1);
48
+ expect(stateChanges[0].nodeName).to.equal("test");
49
+ expect(stateChanges[0].context.counter).to.equal(1);
50
+ });
51
+
52
+ it("should setup and cleanup event listeners", () => {
53
+ const nodes = new Map();
54
+ nodes.set("test", {
55
+ name: "test",
56
+ events: ["customEvent"],
57
+ execute: async () => {},
58
+ });
59
+
60
+ eventManager = new GraphEventManager(eventEmitter, nodes, "test", {
61
+ counter: 0,
62
+ message: "Hello",
63
+ });
64
+
65
+ eventManager.setupEventListeners();
66
+ expect(eventEmitter.listenerCount("customEvent")).to.equal(1);
67
+
68
+ // Réinitialiser les listeners
69
+ eventManager.setupEventListeners();
70
+ expect(eventEmitter.listenerCount("customEvent")).to.equal(1);
71
+ });
72
+ });
@@ -37,7 +37,7 @@ type TestSchema = typeof TestSchema;
37
37
  * The tests use a simple numeric value-based context to demonstrate state changes
38
38
  * and a transaction-based event payload for testing event correlation.
39
39
  */
40
- describe("Graph", function () {
40
+ describe("GraphFlow", function () {
41
41
  let graph: GraphFlow<TestSchema>;
42
42
  let eventEmitter: EventEmitter;
43
43
 
@@ -228,27 +228,32 @@ describe("Graph", function () {
228
228
  * - Context preservation between attempts
229
229
  * Essential for handling transient failures in workflows
230
230
  */
231
- it("should retry a node execution when it fails", async function () {
232
- let attemptCount = 0;
233
- const retryNode: Node<TestSchema> = {
234
- name: "retryNode",
235
- retry: { maxAttempts: 3, delay: 0 },
236
- execute: async (context) => {
237
- attemptCount++;
238
- if (attemptCount < 3) {
231
+ it("should retry a node execution when it fails", async () => {
232
+ let attempts = 0;
233
+ const nodes = new Map();
234
+ nodes.set("test", {
235
+ name: "test",
236
+ execute: async () => {
237
+ attempts++;
238
+ if (attempts < 3) {
239
239
  throw new Error("Temporary failure");
240
240
  }
241
- context.value = (context.value ?? 0) + 1;
242
241
  },
243
- next: [],
244
- };
242
+ retry: {
243
+ maxAttempts: 3,
244
+ delay: 100,
245
+ },
246
+ });
245
247
 
246
- graph.addNode(retryNode);
247
- await graph.execute("retryNode");
248
+ const graph = new GraphFlow("test", {
249
+ name: "test",
250
+ schema: TestSchema,
251
+ context: { value: 0 },
252
+ nodes: Array.from(nodes.values()),
253
+ });
248
254
 
249
- const context = graph.getContext();
250
- expect(context.value).to.equal(1);
251
- expect(attemptCount).to.equal(3);
255
+ await graph.execute("test");
256
+ expect(attempts).to.equal(3);
252
257
  });
253
258
 
254
259
  /**
@@ -294,27 +299,29 @@ describe("Graph", function () {
294
299
  /**
295
300
  * Tests input validation error handling
296
301
  */
297
- it("should throw error when node input validation fails", async function () {
298
- const nodeWithInput: Node<TestSchema, { amount: number }> = {
299
- name: "inputNode",
300
- inputs: z.object({
301
- amount: z.number().min(0),
302
- }),
303
- execute: async (context, inputs: { amount: number }) => {
304
- context.value = (context.value ?? 0) + inputs.amount;
305
- },
306
- next: [],
307
- };
302
+ it("should throw error when node input validation fails", async () => {
303
+ const InputSchema = z.object({
304
+ value: z.number().min(0),
305
+ });
308
306
 
309
- graph.addNode(nodeWithInput);
307
+ const graph = new GraphFlow("test", {
308
+ name: "test",
309
+ schema: TestSchema,
310
+ context: { value: 0 },
311
+ nodes: [
312
+ {
313
+ name: "test",
314
+ inputs: InputSchema,
315
+ execute: async () => {},
316
+ },
317
+ ],
318
+ });
310
319
 
311
320
  try {
312
- await graph.execute("inputNode", { amount: -1 });
321
+ await graph.execute("test", { value: -1 });
313
322
  expect.fail("Should have thrown an error");
314
- } catch (error) {
315
- expect((error as Error).message).to.include(
316
- "Number must be greater than or equal to 0"
317
- );
323
+ } catch (error: any) {
324
+ expect(error.message).to.include("Number must be greater than or equal");
318
325
  }
319
326
  });
320
327
 
@@ -0,0 +1,197 @@
1
+ import { expect, use } from "chai";
2
+ import chaiAsPromised from "chai-as-promised";
3
+ import { EventEmitter } from "events";
4
+ import { BehaviorSubject, Subject } from "rxjs";
5
+ import { z } from "zod";
6
+ import { GraphEventManager } from "../../graph/event-manager";
7
+ import { GraphLogger } from "../../graph/logger";
8
+ import { GraphNode } from "../../graph/node";
9
+ import { GraphContext } from "../../types";
10
+
11
+ use(chaiAsPromised);
12
+
13
+ describe("GraphNode", () => {
14
+ const TestSchema = z.object({
15
+ counter: z.number(),
16
+ message: z.string(),
17
+ });
18
+
19
+ type TestContext = GraphContext<typeof TestSchema>;
20
+
21
+ let node: GraphNode<typeof TestSchema>;
22
+ let eventManager: GraphEventManager<typeof TestSchema>;
23
+ let logger: GraphLogger;
24
+ let eventEmitter: EventEmitter;
25
+ let eventSubject: Subject<any>;
26
+ let stateSubject: BehaviorSubject<any>;
27
+ let events: any[] = [];
28
+
29
+ beforeEach(() => {
30
+ events = [];
31
+ eventEmitter = new EventEmitter();
32
+ eventSubject = new Subject();
33
+ stateSubject = new BehaviorSubject({ counter: 0, message: "Hello" });
34
+ logger = new GraphLogger("test", false);
35
+ eventManager = new GraphEventManager(eventEmitter, new Map(), "test", {
36
+ counter: 0,
37
+ message: "Hello",
38
+ });
39
+
40
+ // Capture des événements
41
+ eventSubject.subscribe((event) => events.push(event));
42
+
43
+ node = new GraphNode(
44
+ new Map(),
45
+ logger,
46
+ eventManager,
47
+ eventSubject,
48
+ stateSubject
49
+ );
50
+ });
51
+
52
+ it("should execute a simple node", async () => {
53
+ const nodes = new Map();
54
+ nodes.set("test", {
55
+ name: "test",
56
+ execute: async (context: TestContext) => {
57
+ context.counter++;
58
+ },
59
+ });
60
+
61
+ node = new GraphNode(
62
+ nodes,
63
+ logger,
64
+ eventManager,
65
+ eventSubject,
66
+ stateSubject
67
+ );
68
+
69
+ await node.executeNode("test", { counter: 0, message: "Hello" }, null);
70
+
71
+ // Vérifier les événements émis
72
+ expect(events).to.have.lengthOf(3); // nodeStarted, nodeStateChanged, nodeCompleted
73
+ expect(events[0].type).to.equal("nodeStarted");
74
+ expect(events[1].type).to.equal("nodeStateChanged");
75
+ expect(events[2].type).to.equal("nodeCompleted");
76
+ });
77
+
78
+ it("should handle node condition", async () => {
79
+ const nodes = new Map();
80
+ nodes.set("test", {
81
+ name: "test",
82
+ condition: (context: TestContext) => context.counter < 5,
83
+ execute: async (context: TestContext) => {
84
+ context.counter++;
85
+ },
86
+ });
87
+
88
+ node = new GraphNode(
89
+ nodes,
90
+ logger,
91
+ eventManager,
92
+ eventSubject,
93
+ stateSubject
94
+ );
95
+
96
+ // Test avec condition vraie
97
+ await node.executeNode("test", { counter: 0, message: "Hello" }, null);
98
+ expect(events.some((e) => e.type === "nodeStateChanged")).to.be.true;
99
+
100
+ // Test avec condition fausse
101
+ events = [];
102
+ await node.executeNode("test", { counter: 5, message: "Hello" }, null);
103
+ expect(events.some((e) => e.type === "nodeStateChanged")).to.be.false;
104
+ });
105
+
106
+ it("should handle errors", async () => {
107
+ const nodes = new Map();
108
+ nodes.set("test", {
109
+ name: "test",
110
+ execute: async (_context: TestContext) => {
111
+ throw new Error("Test error");
112
+ },
113
+ });
114
+
115
+ node = new GraphNode(
116
+ nodes,
117
+ logger,
118
+ eventManager,
119
+ eventSubject,
120
+ stateSubject
121
+ );
122
+
123
+ try {
124
+ await node.executeNode("test", { counter: 0, message: "Hello" }, null);
125
+ expect.fail("Should have thrown an error");
126
+ } catch (error: any) {
127
+ expect(error.message).to.equal("Test error");
128
+ const errorEvents = events.filter((e) => e.type === "nodeError");
129
+ expect(errorEvents).to.have.lengthOf(1);
130
+ expect(errorEvents[0].payload.error.message).to.equal("Test error");
131
+ }
132
+ });
133
+
134
+ it("should emit events exactly once per state change", async () => {
135
+ const nodes = new Map();
136
+ nodes.set("test", {
137
+ name: "test",
138
+ execute: async (context: TestContext) => {
139
+ context.counter++;
140
+ context.message = "Updated";
141
+ },
142
+ });
143
+
144
+ node = new GraphNode(
145
+ nodes,
146
+ logger,
147
+ eventManager,
148
+ eventSubject,
149
+ stateSubject
150
+ );
151
+ await node.executeNode("test", { counter: 0, message: "Hello" }, null);
152
+
153
+ // Compter les occurrences de chaque type d'événement
154
+ const eventCounts = events.reduce((acc, event) => {
155
+ acc[event.type] = (acc[event.type] || 0) + 1;
156
+ return acc;
157
+ }, {} as Record<string, number>);
158
+
159
+ expect(eventCounts).to.deep.equal({
160
+ nodeStarted: 1,
161
+ nodeStateChanged: 2, // Un pour counter, un pour message
162
+ nodeCompleted: 1,
163
+ });
164
+
165
+ // Vérifier l'ordre des événements
166
+ expect(events.map((e) => e.type)).to.deep.equal([
167
+ "nodeStarted",
168
+ "nodeStateChanged", // counter
169
+ "nodeStateChanged", // message
170
+ "nodeCompleted",
171
+ ]);
172
+ });
173
+
174
+ it("should emit nodeStateChanged only for actual changes", async () => {
175
+ const nodes = new Map();
176
+ nodes.set("test", {
177
+ name: "test",
178
+ execute: async (context: TestContext) => {
179
+ context.counter = context.counter; // Même valeur
180
+ context.message = "New"; // Nouvelle valeur
181
+ },
182
+ });
183
+
184
+ node = new GraphNode(
185
+ nodes,
186
+ logger,
187
+ eventManager,
188
+ eventSubject,
189
+ stateSubject
190
+ );
191
+ await node.executeNode("test", { counter: 0, message: "Hello" }, null);
192
+
193
+ const stateChanges = events.filter((e) => e.type === "nodeStateChanged");
194
+ expect(stateChanges).to.have.lengthOf(1); // Seulement pour message
195
+ expect(stateChanges[0].payload.property).to.equal("message");
196
+ });
197
+ });
package/tsconfig.json CHANGED
@@ -2,14 +2,25 @@
2
2
  "compilerOptions": {
3
3
  "target": "es2016",
4
4
  "module": "commonjs",
5
+ "declaration": true,
6
+ "declarationMap": true,
7
+ "sourceMap": true,
5
8
  "esModuleInterop": true,
6
9
  "forceConsistentCasingInFileNames": true,
7
10
  "strict": true,
8
11
  "skipLibCheck": true,
9
12
  "baseUrl": ".",
10
13
  "outDir": "./dist",
11
- "types": ["node", "mocha"]
14
+ "types": ["node", "mocha"],
15
+ "resolveJsonModule": true
12
16
  },
13
- "include": ["src/**/*", "graph/**/*", "types/**/*", "interfaces/**/*"],
17
+ "include": [
18
+ "index.ts",
19
+ "modules/**/*",
20
+ "graph/**/*",
21
+ "types/**/*",
22
+ "interfaces/**/*",
23
+ "utils/**/*"
24
+ ],
14
25
  "exclude": ["node_modules", "dist", "test", "examples"]
15
26
  }