@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arronqzy/blueprint-dsl",
3
- "version": "1.0.1",
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/typescript-config": "1.0.0",
20
- "@arronqzy/eslint-config": "0.0.0"
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
@@ -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 };
@@ -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 { executeFetch, DEFAULT_FETCH_REQUEST_CONFIG } from "../fetch-config.js";
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
- const head = this.debugQueue[0];
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 = newQueue;
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 input = await io.getInput("in");
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: FetchRequestConfig = {
989
- ...DEFAULT_FETCH_REQUEST_CONFIG,
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(queue: ExecutionToken[]) {
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
- return this.resolvePortInputValue(token.nodeId, port);
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
- queue.push({
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