@arronqzy/blueprint-dsl 1.0.1 → 1.0.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.
- package/package.json +3 -3
- package/readme.md +54 -12
- package/src/behaviors/default.ts +2 -1
- package/src/fetch-config.ts +15 -0
- package/src/runtime/graph-runner.ts +140 -22
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arronqzy/blueprint-dsl",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Abuilder blueprint DSL runtime",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"files": [
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"eslint": "^8.57.0",
|
|
18
18
|
"typescript": "^5.5.4",
|
|
19
|
-
"@arronqzy/
|
|
20
|
-
"@arronqzy/
|
|
19
|
+
"@arronqzy/eslint-config": "0.0.0",
|
|
20
|
+
"@arronqzy/typescript-config": "1.0.0"
|
|
21
21
|
},
|
|
22
22
|
"publishConfig": {
|
|
23
23
|
"access": "public"
|
package/readme.md
CHANGED
|
@@ -1,4 +1,31 @@
|
|
|
1
|
+
# @arronqzy/blueprint-dsl
|
|
2
|
+
|
|
3
|
+
Abuilder 蓝图的 **DSL 与运行时** 包,定义节点类型、行为注册与图执行逻辑,不依赖 React。
|
|
4
|
+
|
|
5
|
+
## 功能概览
|
|
6
|
+
|
|
7
|
+
- **节点定义**:Flow / Data 端口、节点元数据与默认行为
|
|
8
|
+
- **行为注册表**:`BehaviorRegistry` 注册 JS / 内置行为
|
|
9
|
+
- **执行器**:按 Token 驱动节点执行、Scope 传递
|
|
10
|
+
- **图运行器**:整图调度、循环检测、执行轨迹
|
|
11
|
+
- **内置节点**:逻辑判断、HTTP 请求、JSON 处理、时钟定时、Swagger 等
|
|
12
|
+
- **时钟调度**:多 tick 输出、中止与活跃状态订阅
|
|
13
|
+
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm add @arronqzy/blueprint-dsl rxjs
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 快速开始
|
|
21
|
+
|
|
1
22
|
```ts
|
|
23
|
+
import {
|
|
24
|
+
BehaviorRegistry,
|
|
25
|
+
Executor,
|
|
26
|
+
type NodeDefinition,
|
|
27
|
+
} from "@arronqzy/blueprint-dsl";
|
|
28
|
+
|
|
2
29
|
const behaviors = new BehaviorRegistry();
|
|
3
30
|
|
|
4
31
|
behaviors.registerJS("print-js", async ({ io }) => {
|
|
@@ -6,27 +33,42 @@ behaviors.registerJS("print-js", async ({ io }) => {
|
|
|
6
33
|
io.emitFlow("out");
|
|
7
34
|
});
|
|
8
35
|
|
|
9
|
-
behaviors.registerJS("sleep-js", async ({ io }) => {
|
|
10
|
-
const ms = await io.getInput("ms");
|
|
11
|
-
await new Promise((r) => setTimeout(r, ms));
|
|
12
|
-
io.emitFlow("out");
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
|
|
16
36
|
const PrintNode: NodeDefinition = {
|
|
17
37
|
type: "Print",
|
|
18
38
|
inputs: [{ name: "msg", kind: "data" }],
|
|
19
39
|
outputs: [{ name: "out", kind: "flow" }],
|
|
20
|
-
behavior: { kind: "js", ref: "print-js" }
|
|
21
|
-
}
|
|
40
|
+
behavior: { kind: "js", ref: "print-js" },
|
|
41
|
+
};
|
|
22
42
|
|
|
43
|
+
const executor = new Executor(behaviors);
|
|
23
44
|
|
|
24
45
|
await executor.executeToken({
|
|
25
46
|
tokenId: "t1",
|
|
26
47
|
nodeId: "n1",
|
|
27
48
|
nodeType: "Print",
|
|
28
49
|
inPort: "in",
|
|
29
|
-
scope: { vars: new Map() }
|
|
30
|
-
})
|
|
31
|
-
|
|
50
|
+
scope: { vars: new Map() },
|
|
51
|
+
});
|
|
32
52
|
```
|
|
53
|
+
|
|
54
|
+
## 核心模块
|
|
55
|
+
|
|
56
|
+
| 路径 | 说明 |
|
|
57
|
+
|------|------|
|
|
58
|
+
| `core/behavior.js` | 行为接口与 IO |
|
|
59
|
+
| `core/behavior-registry.js` | 行为注册 |
|
|
60
|
+
| `core/executor.js` | 单节点执行 |
|
|
61
|
+
| `runtime/graph-runner.js` | 整图运行 |
|
|
62
|
+
| `runtime/execution-trace.js` | 执行轨迹 |
|
|
63
|
+
| `runtime/clock-scheduler.js` | 时钟节点调度 |
|
|
64
|
+
| `nodes/definitions.js` | 内置节点定义 |
|
|
65
|
+
| `fetch-config.js` / `logic-config.js` 等 | 各类型节点配置结构 |
|
|
66
|
+
|
|
67
|
+
## 与上层包的关系
|
|
68
|
+
|
|
69
|
+
- **`@arronqzy/react-blueprint`**:React Flow 画布、调试 UI、蓝图库
|
|
70
|
+
- **`@arronqzy/react-view`**:蓝图执行结果通过 Scope 驱动视图节点
|
|
71
|
+
|
|
72
|
+
## 许可证
|
|
73
|
+
|
|
74
|
+
MIT
|
package/src/behaviors/default.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { BehaviorRegistry } from "../core/behavior-registry.js";
|
|
|
9
9
|
|
|
10
10
|
const LIFECYCLE_SIGNAL_KEY = "__lifecycleSignal";
|
|
11
11
|
const BLUEPRINT_ACTIVATION_INPUT_KEY = "__blueprintActivationInput";
|
|
12
|
+
const UI_EVENT_PAYLOAD_KEY = "__uiEventPayload";
|
|
12
13
|
|
|
13
14
|
export function registerDefaultBehaviors(registry: BehaviorRegistry) {
|
|
14
15
|
registry.registerJS("logic-noop", async ({ io }) => {
|
|
@@ -41,4 +42,4 @@ export function registerDefaultBehaviors(registry: BehaviorRegistry) {
|
|
|
41
42
|
});
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
export { LIFECYCLE_SIGNAL_KEY, BLUEPRINT_ACTIVATION_INPUT_KEY };
|
|
45
|
+
export { LIFECYCLE_SIGNAL_KEY, BLUEPRINT_ACTIVATION_INPUT_KEY, UI_EVENT_PAYLOAD_KEY };
|
package/src/fetch-config.ts
CHANGED
|
@@ -67,6 +67,21 @@ export const DEFAULT_FETCH_REQUEST_CONFIG: FetchRequestConfig = {
|
|
|
67
67
|
timeoutMs: 30000,
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
+
/** 合并默认项与节点上保存的 fetch 配置,供运行时发起请求 */
|
|
71
|
+
export function normalizeFetchRequestConfig(
|
|
72
|
+
config?: FetchRequestConfig
|
|
73
|
+
): FetchRequestConfig {
|
|
74
|
+
return {
|
|
75
|
+
...DEFAULT_FETCH_REQUEST_CONFIG,
|
|
76
|
+
...config,
|
|
77
|
+
headers: {
|
|
78
|
+
...DEFAULT_FETCH_REQUEST_CONFIG.headers,
|
|
79
|
+
...config?.headers,
|
|
80
|
+
},
|
|
81
|
+
swaggerEndpoints: config?.swaggerEndpoints,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
70
85
|
export type FetchResultValue = {
|
|
71
86
|
status: number;
|
|
72
87
|
statusText: string;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { FetchRequestConfig } from "../fetch-config.js";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
executeFetch,
|
|
4
|
+
normalizeFetchRequestConfig,
|
|
5
|
+
resolveFetchRequestUrl,
|
|
6
|
+
} from "../fetch-config.js";
|
|
3
7
|
import type { JsonNodeConfig } from "../json-config.js";
|
|
4
8
|
import {
|
|
5
9
|
DEFAULT_JSON_NODE_CONFIG,
|
|
@@ -34,6 +38,7 @@ import {
|
|
|
34
38
|
registerDefaultBehaviors,
|
|
35
39
|
BLUEPRINT_ACTIVATION_INPUT_KEY,
|
|
36
40
|
LIFECYCLE_SIGNAL_KEY,
|
|
41
|
+
UI_EVENT_PAYLOAD_KEY,
|
|
37
42
|
} from "../behaviors/default.js";
|
|
38
43
|
import { BehaviorRegistry } from "../core/behavior-registry.js";
|
|
39
44
|
import { Executor } from "../core/executor.js";
|
|
@@ -203,6 +208,69 @@ export class BlueprintGraphRunner {
|
|
|
203
208
|
this.cycleValidated = true;
|
|
204
209
|
}
|
|
205
210
|
|
|
211
|
+
private injectedInputs = new Map<string, Value>();
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* 从视图交互触发指定蓝图节点:向该节点 in 口注入真信号并执行下游。
|
|
215
|
+
* payload 写入 scope(UI_EVENT_PAYLOAD_KEY)供 Logic 等节点读取。
|
|
216
|
+
*/
|
|
217
|
+
async triggerNode(nodeId: string, inputValue?: unknown): Promise<void> {
|
|
218
|
+
await this.ensureNoBlueprintCycle();
|
|
219
|
+
this.requireNode(nodeId);
|
|
220
|
+
const key = `${nodeId}:in`;
|
|
221
|
+
this.injectedInputs.set(key, createTrueSignal(inputValue ?? true));
|
|
222
|
+
try {
|
|
223
|
+
await this.runFromNodeWithUiPayload(nodeId, inputValue);
|
|
224
|
+
} finally {
|
|
225
|
+
this.injectedInputs.delete(key);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private async runFromNodeWithUiPayload(
|
|
230
|
+
nodeId: string,
|
|
231
|
+
inputValue?: unknown
|
|
232
|
+
) {
|
|
233
|
+
const queue: ExecutionToken[] = [
|
|
234
|
+
{
|
|
235
|
+
tokenId: createTokenId(),
|
|
236
|
+
nodeId,
|
|
237
|
+
nodeType: this.requireNode(nodeId).nodeType,
|
|
238
|
+
inPort: "in",
|
|
239
|
+
scope: cloneScope(),
|
|
240
|
+
},
|
|
241
|
+
];
|
|
242
|
+
queue[0]!.scope.vars.set(UI_EVENT_PAYLOAD_KEY, inputValue);
|
|
243
|
+
|
|
244
|
+
const executor = this.createExecutor(queue);
|
|
245
|
+
|
|
246
|
+
while (queue.length > 0) {
|
|
247
|
+
const token = queue.shift()!;
|
|
248
|
+
if (!this.syncTokenFromGraph(token)) continue;
|
|
249
|
+
try {
|
|
250
|
+
await executor.executeToken(token);
|
|
251
|
+
} catch (error) {
|
|
252
|
+
this.setNodeOutput(
|
|
253
|
+
token.nodeId,
|
|
254
|
+
"out",
|
|
255
|
+
createFalseSignal(
|
|
256
|
+
error instanceof Error ? error.message : String(error)
|
|
257
|
+
)
|
|
258
|
+
);
|
|
259
|
+
const nextNodes = this.findSignalTargets(token.nodeId, "out");
|
|
260
|
+
for (const target of nextNodes) {
|
|
261
|
+
queue.push({
|
|
262
|
+
tokenId: createTokenId(),
|
|
263
|
+
nodeId: target.nodeId,
|
|
264
|
+
nodeType: target.nodeType,
|
|
265
|
+
inPort: target.inPort,
|
|
266
|
+
scope: token.scope,
|
|
267
|
+
correlationId: token.correlationId ?? token.tokenId,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
206
274
|
async emitLifecycle(phase: PageLifecyclePhase): Promise<void> {
|
|
207
275
|
await this.ensureNoBlueprintCycle();
|
|
208
276
|
const signal = createLifecycleSignal(phase);
|
|
@@ -275,6 +343,7 @@ export class BlueprintGraphRunner {
|
|
|
275
343
|
return;
|
|
276
344
|
}
|
|
277
345
|
const token = queue.shift()!;
|
|
346
|
+
if (!this.syncTokenFromGraph(token)) continue;
|
|
278
347
|
try {
|
|
279
348
|
await executor.executeToken(token);
|
|
280
349
|
} catch (error) {
|
|
@@ -357,6 +426,7 @@ export class BlueprintGraphRunner {
|
|
|
357
426
|
/** 调试过程中同步最新蓝图拓扑与节点配置 */
|
|
358
427
|
updateGraph(graph: RunnableGraph): void {
|
|
359
428
|
this.graph = graph;
|
|
429
|
+
this.syncDebugQueueWithLatestGraph();
|
|
360
430
|
}
|
|
361
431
|
|
|
362
432
|
/**
|
|
@@ -367,14 +437,7 @@ export class BlueprintGraphRunner {
|
|
|
367
437
|
if (!this.debugQueue) return;
|
|
368
438
|
|
|
369
439
|
if (this.traceEntries.length === 0) {
|
|
370
|
-
|
|
371
|
-
if (!head) return;
|
|
372
|
-
const node = this.graph.nodes.find((n) => n.id === head.nodeId);
|
|
373
|
-
if (!node) {
|
|
374
|
-
this.debugQueue = [];
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
head.nodeType = node.nodeType;
|
|
440
|
+
this.syncDebugQueueWithLatestGraph();
|
|
378
441
|
return;
|
|
379
442
|
}
|
|
380
443
|
|
|
@@ -423,7 +486,9 @@ export class BlueprintGraphRunner {
|
|
|
423
486
|
});
|
|
424
487
|
}
|
|
425
488
|
}
|
|
426
|
-
this.debugQueue
|
|
489
|
+
if (newQueue.length > 0 || this.debugQueue.length === 0) {
|
|
490
|
+
this.debugQueue = newQueue;
|
|
491
|
+
}
|
|
427
492
|
}
|
|
428
493
|
|
|
429
494
|
canDebugStepBack(): boolean {
|
|
@@ -479,7 +544,9 @@ export class BlueprintGraphRunner {
|
|
|
479
544
|
|
|
480
545
|
this.applyLifecycleScopeForNode(lifecycleNodeId);
|
|
481
546
|
|
|
482
|
-
this.debugExecutor = this.createExecutor(this.debugQueue
|
|
547
|
+
this.debugExecutor = this.createExecutor(this.debugQueue, {
|
|
548
|
+
emitToActiveDebugQueue: true,
|
|
549
|
+
});
|
|
483
550
|
this.debugHistory = [this.captureDebugSnapshot()];
|
|
484
551
|
}
|
|
485
552
|
|
|
@@ -542,6 +609,13 @@ export class BlueprintGraphRunner {
|
|
|
542
609
|
try {
|
|
543
610
|
this.pushDebugSnapshot();
|
|
544
611
|
const token = this.debugQueue.shift()!;
|
|
612
|
+
if (!this.syncTokenFromGraph(token)) {
|
|
613
|
+
this.pushDebugSnapshot();
|
|
614
|
+
return {
|
|
615
|
+
done: this.debugQueue.length === 0,
|
|
616
|
+
haltedByFalseSignal: false,
|
|
617
|
+
};
|
|
618
|
+
}
|
|
545
619
|
|
|
546
620
|
if (token.nodeType === CLOCK_NODE_TYPE) {
|
|
547
621
|
const node = this.requireNode(token.nodeId);
|
|
@@ -721,6 +795,12 @@ export class BlueprintGraphRunner {
|
|
|
721
795
|
this.debugExecutor ??
|
|
722
796
|
this.createExecutor(this.debugQueue ?? []);
|
|
723
797
|
|
|
798
|
+
if (!this.syncTokenFromGraph(token)) {
|
|
799
|
+
return this.appendDebugTraceEntry(token, nodeLabelById, startedAt, {
|
|
800
|
+
error: "节点已从蓝图中移除",
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
|
|
724
804
|
try {
|
|
725
805
|
await runExecutor.executeToken(token);
|
|
726
806
|
} catch (err) {
|
|
@@ -941,6 +1021,7 @@ export class BlueprintGraphRunner {
|
|
|
941
1021
|
resolveLibraryBlueprint: runner.resolveLibraryBlueprint,
|
|
942
1022
|
rootLibraryBlueprintId: libraryId,
|
|
943
1023
|
resolveBlueprintName: runner.resolveBlueprintName,
|
|
1024
|
+
onViewScopeUpdate: runner.onViewScopeUpdate,
|
|
944
1025
|
});
|
|
945
1026
|
await subRunner.emitBlueprintActivated(input.value, nestedScope);
|
|
946
1027
|
const nestedOutputs = await subRunner.runFromFlowEntries(nestedScope, {
|
|
@@ -975,22 +1056,32 @@ export class BlueprintGraphRunner {
|
|
|
975
1056
|
const node = runner.requireNode(token.nodeId);
|
|
976
1057
|
|
|
977
1058
|
try {
|
|
978
|
-
const
|
|
1059
|
+
const inputPort = token.inPort || "in";
|
|
1060
|
+
const input = await io.getInput(inputPort);
|
|
1061
|
+
if (isFalseSignal(input)) {
|
|
1062
|
+
io.setOutput("out", input);
|
|
1063
|
+
io.emitFlow("out");
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
979
1066
|
if (!isTrueSignal(input)) {
|
|
980
1067
|
io.setOutput(
|
|
981
1068
|
"out",
|
|
982
|
-
createFalseSignal(
|
|
1069
|
+
createFalseSignal(
|
|
1070
|
+
input === undefined
|
|
1071
|
+
? "数据源节点未收到上游输出,请确认连线与上游节点已执行"
|
|
1072
|
+
: "数据源节点需要收到真信号后才会发起请求"
|
|
1073
|
+
)
|
|
983
1074
|
);
|
|
984
1075
|
io.emitFlow("out");
|
|
985
1076
|
return;
|
|
986
1077
|
}
|
|
987
1078
|
|
|
988
|
-
const config
|
|
989
|
-
|
|
990
|
-
...node.fetchConfig,
|
|
991
|
-
};
|
|
1079
|
+
const config = normalizeFetchRequestConfig(node.fetchConfig);
|
|
1080
|
+
resolveFetchRequestUrl(config);
|
|
992
1081
|
|
|
993
|
-
const result = await executeFetch(config
|
|
1082
|
+
const result = await executeFetch(config, {
|
|
1083
|
+
allowHttpError: runner.isDebugSessionActive(),
|
|
1084
|
+
});
|
|
994
1085
|
io.setOutput("out", createTrueSignal(result));
|
|
995
1086
|
} catch (error) {
|
|
996
1087
|
io.setOutput(
|
|
@@ -1190,6 +1281,7 @@ export class BlueprintGraphRunner {
|
|
|
1190
1281
|
|
|
1191
1282
|
while (queue.length > 0) {
|
|
1192
1283
|
const token = queue.shift()!;
|
|
1284
|
+
if (!this.syncTokenFromGraph(token)) continue;
|
|
1193
1285
|
try {
|
|
1194
1286
|
await executor.executeToken(token);
|
|
1195
1287
|
} catch (error) {
|
|
@@ -1215,7 +1307,10 @@ export class BlueprintGraphRunner {
|
|
|
1215
1307
|
}
|
|
1216
1308
|
}
|
|
1217
1309
|
|
|
1218
|
-
private createExecutor(
|
|
1310
|
+
private createExecutor(
|
|
1311
|
+
queue: ExecutionToken[],
|
|
1312
|
+
options?: { emitToActiveDebugQueue?: boolean }
|
|
1313
|
+
) {
|
|
1219
1314
|
return new Executor({
|
|
1220
1315
|
getNodeDefinition,
|
|
1221
1316
|
behaviors: this.behaviors,
|
|
@@ -1223,15 +1318,20 @@ export class BlueprintGraphRunner {
|
|
|
1223
1318
|
// 由 blueprint-ref-run 行为处理
|
|
1224
1319
|
},
|
|
1225
1320
|
getInputValue: async (token, port) => {
|
|
1226
|
-
|
|
1321
|
+
const resolvedPort = port || token.inPort || "in";
|
|
1322
|
+
return this.resolvePortInputValue(token.nodeId, resolvedPort);
|
|
1227
1323
|
},
|
|
1228
1324
|
setNodeOutput: (token, port, value) => {
|
|
1229
1325
|
this.setNodeOutput(token.nodeId, port, value);
|
|
1230
1326
|
},
|
|
1231
1327
|
emitFlow: (token, outPort) => {
|
|
1328
|
+
const targetQueue =
|
|
1329
|
+
options?.emitToActiveDebugQueue && this.debugQueue
|
|
1330
|
+
? this.debugQueue
|
|
1331
|
+
: queue;
|
|
1232
1332
|
const nextNodes = this.findSignalTargets(token.nodeId, outPort);
|
|
1233
1333
|
for (const target of nextNodes) {
|
|
1234
|
-
|
|
1334
|
+
targetQueue.push({
|
|
1235
1335
|
tokenId: createTokenId(),
|
|
1236
1336
|
nodeId: target.nodeId,
|
|
1237
1337
|
nodeType: target.nodeType,
|
|
@@ -1271,6 +1371,21 @@ export class BlueprintGraphRunner {
|
|
|
1271
1371
|
return node;
|
|
1272
1372
|
}
|
|
1273
1373
|
|
|
1374
|
+
/** 队列中的 token 可能是在节点改类型(如视图绑定)之前入队的,执行前与最新图对齐 */
|
|
1375
|
+
private syncTokenFromGraph(token: ExecutionToken): boolean {
|
|
1376
|
+
const node = this.graph.nodes.find((n) => n.id === token.nodeId);
|
|
1377
|
+
if (!node) return false;
|
|
1378
|
+
token.nodeType = node.nodeType;
|
|
1379
|
+
return true;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
private syncDebugQueueWithLatestGraph(): void {
|
|
1383
|
+
if (!this.debugQueue) return;
|
|
1384
|
+
this.debugQueue = this.debugQueue.filter((token) =>
|
|
1385
|
+
this.syncTokenFromGraph(token)
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1274
1389
|
private findSignalTargets(nodeId: string, outPort: string) {
|
|
1275
1390
|
return this.graph.edges
|
|
1276
1391
|
.filter(
|
|
@@ -1294,8 +1409,11 @@ export class BlueprintGraphRunner {
|
|
|
1294
1409
|
);
|
|
1295
1410
|
}
|
|
1296
1411
|
|
|
1297
|
-
/**
|
|
1412
|
+
/** 单端口多连线:任一为真则视为真(或语义);支持 triggerNode 注入的入口信号 */
|
|
1298
1413
|
private resolvePortInputValue(nodeId: string, port: string): Value | undefined {
|
|
1414
|
+
const injected = this.injectedInputs.get(`${nodeId}:${port}`);
|
|
1415
|
+
if (injected !== undefined) return injected;
|
|
1416
|
+
|
|
1299
1417
|
const inboundEdges = this.findInboundSignalEdges(nodeId, port);
|
|
1300
1418
|
if (inboundEdges.length === 0) return undefined;
|
|
1301
1419
|
|