@ai.ntellect/core 0.7.4 → 0.7.6
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/dist/graph/controller.d.ts +9 -10
- package/dist/graph/controller.d.ts.map +1 -1
- package/dist/graph/controller.js +27 -28
- package/dist/graph/controller.js.map +1 -1
- package/dist/graph/index.d.ts +5 -3
- package/dist/graph/index.d.ts.map +1 -1
- package/dist/graph/index.js +13 -9
- package/dist/graph/index.js.map +1 -1
- package/dist/graph/node.d.ts +30 -11
- package/dist/graph/node.d.ts.map +1 -1
- package/dist/graph/node.js +64 -66
- package/dist/graph/node.js.map +1 -1
- package/dist/graph/observer.d.ts.map +1 -1
- package/dist/graph/observer.js +0 -1
- package/dist/graph/observer.js.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/graph/controller.ts +41 -40
- package/graph/index.ts +17 -13
- package/graph/node.ts +104 -95
- package/graph/observer.ts +0 -1
- package/package.json +1 -1
- package/test/graph/controller.test.ts +187 -0
- package/test/graph/index.test.ts +33 -22
- package/test/graph/node.test.ts +141 -6
- package/types/index.ts +2 -5
@@ -0,0 +1,187 @@
|
|
1
|
+
import { expect } from "chai";
|
2
|
+
import { z } from "zod";
|
3
|
+
import { GraphController } from "../../graph/controller";
|
4
|
+
import { GraphFlow } from "../../graph/index";
|
5
|
+
import { Node } from "../../types";
|
6
|
+
|
7
|
+
describe("GraphController", () => {
|
8
|
+
const TestSchema = z.object({
|
9
|
+
counter: z.number(),
|
10
|
+
message: z.string(),
|
11
|
+
});
|
12
|
+
|
13
|
+
const createTestGraph = (name: string): GraphFlow<typeof TestSchema> => {
|
14
|
+
const nodes: Node<typeof TestSchema>[] = [
|
15
|
+
{
|
16
|
+
name: "start",
|
17
|
+
execute: async (context, params) => {
|
18
|
+
context.counter = params?.value ?? 0;
|
19
|
+
context.message = params?.prefix ? `${params.prefix}-${name}` : name;
|
20
|
+
},
|
21
|
+
},
|
22
|
+
{
|
23
|
+
name: "increment",
|
24
|
+
execute: async (context) => {
|
25
|
+
context.counter += 1;
|
26
|
+
},
|
27
|
+
},
|
28
|
+
];
|
29
|
+
|
30
|
+
return new GraphFlow(name, {
|
31
|
+
name,
|
32
|
+
nodes,
|
33
|
+
schema: TestSchema,
|
34
|
+
context: { counter: 0, message: "" },
|
35
|
+
});
|
36
|
+
};
|
37
|
+
|
38
|
+
describe("Sequential Execution", () => {
|
39
|
+
it("should execute graphs sequentially with different params and params", async () => {
|
40
|
+
const graph1 = createTestGraph("graph1");
|
41
|
+
const graph2 = createTestGraph("graph2");
|
42
|
+
const graph3 = createTestGraph("graph3");
|
43
|
+
|
44
|
+
const params = [{ value: 10 }, { value: 20 }, { value: 30 }];
|
45
|
+
|
46
|
+
const params2 = [
|
47
|
+
{ prefix: "test1" },
|
48
|
+
{ prefix: "test2" },
|
49
|
+
{ prefix: "test3" },
|
50
|
+
];
|
51
|
+
|
52
|
+
const results = await GraphController.executeSequential(
|
53
|
+
[graph1, graph2, graph3],
|
54
|
+
["start", "start", "start"],
|
55
|
+
params.map((value, i) => ({ ...value, prefix: params2[i].prefix }))
|
56
|
+
);
|
57
|
+
|
58
|
+
expect(results).to.have.length(3);
|
59
|
+
expect(results[0].counter).to.equal(10);
|
60
|
+
expect(results[1].counter).to.equal(20);
|
61
|
+
expect(results[2].counter).to.equal(30);
|
62
|
+
expect(results[0].message).to.equal("test1-graph1");
|
63
|
+
expect(results[1].message).to.equal("test2-graph2");
|
64
|
+
expect(results[2].message).to.equal("test3-graph3");
|
65
|
+
});
|
66
|
+
|
67
|
+
it("should handle missing params and params gracefully", async () => {
|
68
|
+
const graph1 = createTestGraph("graph1");
|
69
|
+
const graph2 = createTestGraph("graph2");
|
70
|
+
|
71
|
+
const results = await GraphController.executeSequential(
|
72
|
+
[graph1, graph2],
|
73
|
+
["start", "start"]
|
74
|
+
);
|
75
|
+
|
76
|
+
expect(results).to.have.length(2);
|
77
|
+
expect(results[0].counter).to.equal(0);
|
78
|
+
expect(results[1].counter).to.equal(0);
|
79
|
+
expect(results[0].message).to.equal("graph1");
|
80
|
+
expect(results[1].message).to.equal("graph2");
|
81
|
+
});
|
82
|
+
});
|
83
|
+
|
84
|
+
describe("Parallel Execution", () => {
|
85
|
+
it("should execute graphs in parallel with concurrency limit", async () => {
|
86
|
+
const graphs = Array.from({ length: 5 }, (_, i) =>
|
87
|
+
createTestGraph(`graph${i + 1}`)
|
88
|
+
);
|
89
|
+
|
90
|
+
const params = Array.from({ length: 5 }, (_, i) => ({
|
91
|
+
value: (i + 1) * 10,
|
92
|
+
prefix: `test${i + 1}`,
|
93
|
+
}));
|
94
|
+
|
95
|
+
// Ajouter un délai dans l'exécution
|
96
|
+
const originalExecute = graphs[0].execute;
|
97
|
+
graphs[0].execute = async (...args) => {
|
98
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
99
|
+
return originalExecute.apply(graphs[0], args);
|
100
|
+
};
|
101
|
+
|
102
|
+
const startTime = Date.now();
|
103
|
+
const results = await GraphController.executeParallel(
|
104
|
+
graphs,
|
105
|
+
Array(5).fill("start"),
|
106
|
+
2,
|
107
|
+
params
|
108
|
+
);
|
109
|
+
const executionTime = Date.now() - startTime;
|
110
|
+
|
111
|
+
expect(executionTime).to.be.greaterThan(0);
|
112
|
+
expect(results).to.have.length(5);
|
113
|
+
results.forEach((result, i) => {
|
114
|
+
expect(result.counter).to.equal((i + 1) * 10);
|
115
|
+
expect(result.message).to.equal(`test${i + 1}-graph${i + 1}`);
|
116
|
+
});
|
117
|
+
});
|
118
|
+
|
119
|
+
it("should handle errors in parallel execution", async () => {
|
120
|
+
const errorGraph = new GraphFlow("errorGraph", {
|
121
|
+
name: "errorGraph",
|
122
|
+
nodes: [
|
123
|
+
{
|
124
|
+
name: "start",
|
125
|
+
execute: async () => {
|
126
|
+
throw new Error("Test error");
|
127
|
+
},
|
128
|
+
},
|
129
|
+
],
|
130
|
+
schema: TestSchema,
|
131
|
+
context: { counter: 0, message: "" },
|
132
|
+
});
|
133
|
+
|
134
|
+
const successGraph = createTestGraph("successGraph");
|
135
|
+
|
136
|
+
try {
|
137
|
+
await GraphController.executeParallel(
|
138
|
+
[errorGraph, successGraph],
|
139
|
+
["start", "start"],
|
140
|
+
2
|
141
|
+
);
|
142
|
+
expect.fail("Should have thrown an error");
|
143
|
+
} catch (error: any) {
|
144
|
+
expect(error.message).to.equal("Test error");
|
145
|
+
}
|
146
|
+
});
|
147
|
+
});
|
148
|
+
|
149
|
+
describe("Complex Workflows", () => {
|
150
|
+
it("should handle mixed sequential and parallel execution", async () => {
|
151
|
+
const graphs = Array.from({ length: 4 }, (_, i) =>
|
152
|
+
createTestGraph(`graph${i + 1}`)
|
153
|
+
);
|
154
|
+
|
155
|
+
// Exécuter les deux premiers graphes en parallèle
|
156
|
+
const parallelResults = await GraphController.executeParallel(
|
157
|
+
graphs.slice(0, 2),
|
158
|
+
["start", "start"],
|
159
|
+
2,
|
160
|
+
[
|
161
|
+
{ value: 10, prefix: "parallel1" },
|
162
|
+
{ value: 20, prefix: "parallel2" },
|
163
|
+
]
|
164
|
+
);
|
165
|
+
|
166
|
+
// Puis exécuter les deux suivants séquentiellement
|
167
|
+
const sequentialResults = await GraphController.executeSequential(
|
168
|
+
graphs.slice(2),
|
169
|
+
["start", "start"],
|
170
|
+
[
|
171
|
+
{ value: 30, prefix: "seq1" },
|
172
|
+
{ value: 40, prefix: "seq2" },
|
173
|
+
]
|
174
|
+
);
|
175
|
+
|
176
|
+
const allResults = [...parallelResults, ...sequentialResults];
|
177
|
+
expect(allResults).to.have.length(4);
|
178
|
+
expect(allResults.map((r) => r.counter)).to.deep.equal([10, 20, 30, 40]);
|
179
|
+
expect(allResults.map((r) => r.message)).to.deep.equal([
|
180
|
+
"parallel1-graph1",
|
181
|
+
"parallel2-graph2",
|
182
|
+
"seq1-graph3",
|
183
|
+
"seq2-graph4",
|
184
|
+
]);
|
185
|
+
});
|
186
|
+
});
|
187
|
+
});
|
package/test/graph/index.test.ts
CHANGED
@@ -2,7 +2,7 @@ import { expect } from "chai";
|
|
2
2
|
import EventEmitter from "events";
|
3
3
|
import sinon from "sinon";
|
4
4
|
import { z } from "zod";
|
5
|
-
import {
|
5
|
+
import { GraphController } from "../../graph/controller";
|
6
6
|
import { GraphFlow } from "../../graph/index";
|
7
7
|
import { GraphContext, GraphDefinition, Node } from "../../types";
|
8
8
|
|
@@ -181,7 +181,8 @@ describe("GraphFlow", function () {
|
|
181
181
|
outputs: z.object({
|
182
182
|
value: z.number().min(5),
|
183
183
|
}),
|
184
|
-
execute: async (context, inputs
|
184
|
+
execute: async (context, inputs?: { increment: number }) => {
|
185
|
+
if (!inputs) throw new Error("Inputs required");
|
185
186
|
context.value = (context.value ?? 0) + inputs.increment;
|
186
187
|
},
|
187
188
|
next: [],
|
@@ -230,30 +231,31 @@ describe("GraphFlow", function () {
|
|
230
231
|
*/
|
231
232
|
it("should retry a node execution when it fails", async () => {
|
232
233
|
let attempts = 0;
|
233
|
-
const
|
234
|
-
|
235
|
-
|
236
|
-
execute: async () => {
|
234
|
+
const retryNode: Node<TestSchema> = {
|
235
|
+
name: "retryNode",
|
236
|
+
execute: async (context) => {
|
237
237
|
attempts++;
|
238
238
|
if (attempts < 3) {
|
239
239
|
throw new Error("Temporary failure");
|
240
240
|
}
|
241
|
+
context.value = 42;
|
241
242
|
},
|
242
243
|
retry: {
|
243
244
|
maxAttempts: 3,
|
244
245
|
delay: 100,
|
245
246
|
},
|
246
|
-
}
|
247
|
+
};
|
247
248
|
|
248
249
|
const graph = new GraphFlow("test", {
|
249
250
|
name: "test",
|
250
251
|
schema: TestSchema,
|
251
252
|
context: { value: 0 },
|
252
|
-
nodes:
|
253
|
+
nodes: [retryNode],
|
253
254
|
});
|
254
255
|
|
255
|
-
await graph.execute("
|
256
|
+
await graph.execute("retryNode");
|
256
257
|
expect(attempts).to.equal(3);
|
258
|
+
expect(graph.getContext().value).to.equal(42);
|
257
259
|
});
|
258
260
|
|
259
261
|
/**
|
@@ -300,21 +302,22 @@ describe("GraphFlow", function () {
|
|
300
302
|
* Tests input validation error handling
|
301
303
|
*/
|
302
304
|
it("should throw error when node input validation fails", async () => {
|
303
|
-
const
|
304
|
-
|
305
|
-
|
305
|
+
const node: Node<TestSchema> = {
|
306
|
+
name: "test",
|
307
|
+
inputs: z.object({
|
308
|
+
value: z.number().min(0),
|
309
|
+
}),
|
310
|
+
execute: async (context, inputs) => {
|
311
|
+
if (!inputs) throw new Error("Inputs required");
|
312
|
+
context.value = inputs.value;
|
313
|
+
},
|
314
|
+
};
|
306
315
|
|
307
316
|
const graph = new GraphFlow("test", {
|
308
317
|
name: "test",
|
309
318
|
schema: TestSchema,
|
310
319
|
context: { value: 0 },
|
311
|
-
nodes: [
|
312
|
-
{
|
313
|
-
name: "test",
|
314
|
-
inputs: InputSchema,
|
315
|
-
execute: async () => {},
|
316
|
-
},
|
317
|
-
],
|
320
|
+
nodes: [node],
|
318
321
|
});
|
319
322
|
|
320
323
|
try {
|
@@ -356,6 +359,13 @@ describe("GraphFlow", function () {
|
|
356
359
|
* Tests successful input/output validation flow
|
357
360
|
*/
|
358
361
|
it("should successfully validate both inputs and outputs", async function () {
|
362
|
+
const graph = new GraphFlow("test", {
|
363
|
+
name: "test",
|
364
|
+
schema: TestSchema,
|
365
|
+
context: { value: 0 },
|
366
|
+
nodes: [],
|
367
|
+
});
|
368
|
+
|
359
369
|
const validatedNode: Node<TestSchema, { increment: number }> = {
|
360
370
|
name: "validatedNode",
|
361
371
|
inputs: z.object({
|
@@ -364,7 +374,8 @@ describe("GraphFlow", function () {
|
|
364
374
|
outputs: z.object({
|
365
375
|
value: z.number().min(0).max(10),
|
366
376
|
}),
|
367
|
-
execute: async (context, inputs
|
377
|
+
execute: async (context, inputs?: { increment: number }) => {
|
378
|
+
if (!inputs) throw new Error("Inputs required");
|
368
379
|
context.value = (context.value ?? 0) + inputs.increment;
|
369
380
|
},
|
370
381
|
next: [],
|
@@ -547,7 +558,7 @@ describe("GraphFlow", function () {
|
|
547
558
|
graph2.addNode(processNode2);
|
548
559
|
graph2.addNode(finalizeNode2);
|
549
560
|
|
550
|
-
const results = await
|
561
|
+
const results = await GraphController.executeParallel(
|
551
562
|
[graph1, graph2],
|
552
563
|
["process1", "process2"],
|
553
564
|
2,
|
@@ -595,7 +606,7 @@ describe("GraphFlow", function () {
|
|
595
606
|
graph1.addNode(startNode1);
|
596
607
|
graph2.addNode(startNode2);
|
597
608
|
|
598
|
-
const results = await
|
609
|
+
const results = await GraphController.executeSequential(
|
599
610
|
[graph1, graph2],
|
600
611
|
["start1", "start2"],
|
601
612
|
[{}, {}]
|
package/test/graph/node.test.ts
CHANGED
@@ -5,7 +5,7 @@ import { BehaviorSubject, Subject } from "rxjs";
|
|
5
5
|
import { z } from "zod";
|
6
6
|
import { GraphEventManager } from "../../graph/event-manager";
|
7
7
|
import { GraphLogger } from "../../graph/logger";
|
8
|
-
import { GraphNode } from "../../graph/node";
|
8
|
+
import { GraphNode, NodeParams } from "../../graph/node";
|
9
9
|
import { GraphContext } from "../../types";
|
10
10
|
|
11
11
|
use(chaiAsPromised);
|
@@ -107,7 +107,7 @@ describe("GraphNode", () => {
|
|
107
107
|
const nodes = new Map();
|
108
108
|
nodes.set("test", {
|
109
109
|
name: "test",
|
110
|
-
execute: async (
|
110
|
+
execute: async () => {
|
111
111
|
throw new Error("Test error");
|
112
112
|
},
|
113
113
|
});
|
@@ -121,13 +121,17 @@ describe("GraphNode", () => {
|
|
121
121
|
);
|
122
122
|
|
123
123
|
try {
|
124
|
-
await node.executeNode(
|
125
|
-
|
124
|
+
await node.executeNode(
|
125
|
+
"test",
|
126
|
+
{ counter: 0, message: "Hello" },
|
127
|
+
null,
|
128
|
+
false
|
129
|
+
);
|
130
|
+
expect.fail("Test error");
|
126
131
|
} catch (error: any) {
|
127
132
|
expect(error.message).to.equal("Test error");
|
128
133
|
const errorEvents = events.filter((e) => e.type === "nodeError");
|
129
134
|
expect(errorEvents).to.have.lengthOf(1);
|
130
|
-
expect(errorEvents[0].payload.error.message).to.equal("Test error");
|
131
135
|
}
|
132
136
|
});
|
133
137
|
|
@@ -175,7 +179,7 @@ describe("GraphNode", () => {
|
|
175
179
|
const nodes = new Map();
|
176
180
|
nodes.set("test", {
|
177
181
|
name: "test",
|
178
|
-
execute: async (context: TestContext) => {
|
182
|
+
execute: async (context: TestContext, inputs?: any) => {
|
179
183
|
context.counter = context.counter; // Même valeur
|
180
184
|
context.message = "New"; // Nouvelle valeur
|
181
185
|
},
|
@@ -194,4 +198,135 @@ describe("GraphNode", () => {
|
|
194
198
|
expect(stateChanges).to.have.lengthOf(1); // Seulement pour message
|
195
199
|
expect(stateChanges[0].payload.property).to.equal("message");
|
196
200
|
});
|
201
|
+
|
202
|
+
it("should execute node with parameters", async () => {
|
203
|
+
const nodes = new Map();
|
204
|
+
nodes.set("test", {
|
205
|
+
name: "test",
|
206
|
+
execute: async (context: TestContext, inputs?: any) => {
|
207
|
+
context.counter = inputs?.value ?? 0;
|
208
|
+
context.message = inputs?.message ?? "Default";
|
209
|
+
},
|
210
|
+
});
|
211
|
+
|
212
|
+
node = new GraphNode(
|
213
|
+
nodes,
|
214
|
+
logger,
|
215
|
+
eventManager,
|
216
|
+
eventSubject,
|
217
|
+
stateSubject
|
218
|
+
);
|
219
|
+
|
220
|
+
await node.executeNode(
|
221
|
+
"test",
|
222
|
+
{ counter: 0, message: "Hello" },
|
223
|
+
{ value: 5, message: "Custom" },
|
224
|
+
false
|
225
|
+
);
|
226
|
+
|
227
|
+
const stateChanges = events.filter((e) => e.type === "nodeStateChanged");
|
228
|
+
expect(stateChanges).to.have.lengthOf(2);
|
229
|
+
expect(stateChanges[0].payload.newValue).to.equal(5);
|
230
|
+
expect(stateChanges[1].payload.newValue).to.equal("Custom");
|
231
|
+
});
|
232
|
+
|
233
|
+
it("should use default values when no parameters provided", async () => {
|
234
|
+
const nodes = new Map();
|
235
|
+
nodes.set("test", {
|
236
|
+
name: "test",
|
237
|
+
execute: async (
|
238
|
+
context: TestContext,
|
239
|
+
_inputs: any,
|
240
|
+
params?: NodeParams
|
241
|
+
) => {
|
242
|
+
context.counter = params?.increment || 1;
|
243
|
+
context.message = params?.message || "Default";
|
244
|
+
},
|
245
|
+
});
|
246
|
+
|
247
|
+
node = new GraphNode(
|
248
|
+
nodes,
|
249
|
+
logger,
|
250
|
+
eventManager,
|
251
|
+
eventSubject,
|
252
|
+
stateSubject
|
253
|
+
);
|
254
|
+
|
255
|
+
await node.executeNode("test", { counter: 0, message: "Hello" }, null);
|
256
|
+
|
257
|
+
const stateChanges = events.filter((e) => e.type === "nodeStateChanged");
|
258
|
+
expect(stateChanges).to.have.lengthOf(2);
|
259
|
+
expect(stateChanges[0].payload.newValue).to.equal(1); // counter (default)
|
260
|
+
expect(stateChanges[1].payload.newValue).to.equal("Default"); // message (default)
|
261
|
+
});
|
262
|
+
|
263
|
+
it("should properly handle node inputs", async () => {
|
264
|
+
const nodes = new Map();
|
265
|
+
nodes.set("test", {
|
266
|
+
name: "test",
|
267
|
+
execute: async (context: TestContext, inputs: any) => {
|
268
|
+
context.counter = inputs.value;
|
269
|
+
context.message = inputs.message;
|
270
|
+
},
|
271
|
+
});
|
272
|
+
|
273
|
+
node = new GraphNode(
|
274
|
+
nodes,
|
275
|
+
logger,
|
276
|
+
eventManager,
|
277
|
+
eventSubject,
|
278
|
+
stateSubject
|
279
|
+
);
|
280
|
+
|
281
|
+
const testInputs = {
|
282
|
+
value: 42,
|
283
|
+
message: "Test Input",
|
284
|
+
};
|
285
|
+
|
286
|
+
await node.executeNode(
|
287
|
+
"test",
|
288
|
+
{ counter: 0, message: "Hello" },
|
289
|
+
testInputs
|
290
|
+
);
|
291
|
+
|
292
|
+
const stateChanges = events.filter((e) => e.type === "nodeStateChanged");
|
293
|
+
expect(stateChanges).to.have.lengthOf(2);
|
294
|
+
expect(stateChanges[0].payload.newValue).to.equal(42); // counter from input
|
295
|
+
expect(stateChanges[1].payload.newValue).to.equal("Test Input"); // message from input
|
296
|
+
});
|
297
|
+
|
298
|
+
it("should not emit duplicate state changes", async () => {
|
299
|
+
const nodes = new Map();
|
300
|
+
nodes.set("test", {
|
301
|
+
name: "test",
|
302
|
+
execute: async (context: TestContext) => {
|
303
|
+
context.counter = 1; // Valeur fixe au lieu d'incrémentations
|
304
|
+
context.counter = 1; // Même valeur
|
305
|
+
context.message = "New";
|
306
|
+
context.message = "New"; // Même valeur
|
307
|
+
},
|
308
|
+
});
|
309
|
+
|
310
|
+
node = new GraphNode(
|
311
|
+
nodes,
|
312
|
+
logger,
|
313
|
+
eventManager,
|
314
|
+
eventSubject,
|
315
|
+
stateSubject
|
316
|
+
);
|
317
|
+
|
318
|
+
await node.executeNode("test", { counter: 0, message: "Hello" }, null);
|
319
|
+
|
320
|
+
// Vérifier qu'il n'y a pas de doublons dans les événements
|
321
|
+
const stateChanges = events.filter((e) => e.type === "nodeStateChanged");
|
322
|
+
const uniqueChanges = new Set(
|
323
|
+
stateChanges.map(
|
324
|
+
(e) =>
|
325
|
+
`${e.payload.property}-${e.payload.oldValue}-${e.payload.newValue}`
|
326
|
+
)
|
327
|
+
);
|
328
|
+
|
329
|
+
expect(stateChanges.length).to.equal(uniqueChanges.size);
|
330
|
+
expect(stateChanges).to.have.lengthOf(2); // Un pour counter, un pour message
|
331
|
+
});
|
197
332
|
});
|
package/types/index.ts
CHANGED
@@ -86,12 +86,9 @@ export interface Node<T extends ZodSchema, I = any> {
|
|
86
86
|
/** Schema for node outputs */
|
87
87
|
outputs?: ZodSchema;
|
88
88
|
/** Execute function for the node */
|
89
|
-
execute: (
|
90
|
-
context: GraphContext<T>,
|
91
|
-
inputs: I extends void ? never : I
|
92
|
-
) => Promise<void>;
|
89
|
+
execute: (context: GraphContext<T>, params?: I) => Promise<void>;
|
93
90
|
/** Optional condition for node execution */
|
94
|
-
condition?: (context: GraphContext<T
|
91
|
+
condition?: (context: GraphContext<T>, params?: I) => boolean;
|
95
92
|
/** Array of next node names */
|
96
93
|
next?: string[] | ((context: GraphContext<T>) => string[]);
|
97
94
|
/** Array of event names that trigger this node */
|