@arronqzy/blueprint-dsl 1.0.8 → 1.0.10

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.
@@ -1,64 +0,0 @@
1
- import {
2
- createFalseSignal,
3
- createTrueSignal,
4
- isFalseSignal,
5
- isTrueSignal,
6
- } from "../node-signal.js";
7
- import type { LifecycleSignal } from "../lifecycle.js";
8
- import type { ViewEventSignal } from "../event-config.js";
9
- import { BehaviorRegistry } from "../core/behavior-registry.js";
10
-
11
- const LIFECYCLE_SIGNAL_KEY = "__lifecycleSignal";
12
- const BLUEPRINT_ACTIVATION_INPUT_KEY = "__blueprintActivationInput";
13
- const UI_EVENT_PAYLOAD_KEY = "__uiEventPayload";
14
- const VIEW_EVENT_SIGNAL_KEY = "__viewEventSignal";
15
-
16
- export function registerDefaultBehaviors(registry: BehaviorRegistry) {
17
- registry.registerJS("logic-noop", async ({ io }) => {
18
- const input = await io.getInput("in");
19
- if (isFalseSignal(input)) {
20
- io.setOutput("out", input);
21
- } else if (isTrueSignal(input)) {
22
- io.setOutput("out", createTrueSignal(input.value));
23
- } else {
24
- io.setOutput("out", createTrueSignal(undefined));
25
- }
26
- io.emitFlow("out");
27
- });
28
-
29
- registry.registerJS("lifecycle-emit", async ({ token, io }) => {
30
- const lifecycle = token.scope.vars.get(LIFECYCLE_SIGNAL_KEY) as
31
- | LifecycleSignal
32
- | undefined;
33
- if (!lifecycle) {
34
- io.setOutput("out", createFalseSignal("生命周期信号缺失"));
35
- } else if (lifecycle.phase === "blueprintActivated") {
36
- io.setOutput(
37
- "out",
38
- createTrueSignal(token.scope.vars.get(BLUEPRINT_ACTIVATION_INPUT_KEY))
39
- );
40
- } else {
41
- io.setOutput("out", createTrueSignal(lifecycle));
42
- }
43
- io.emitFlow("out");
44
- });
45
-
46
- registry.registerJS("event-emit", async ({ token, io }) => {
47
- const payload = token.scope.vars.get(VIEW_EVENT_SIGNAL_KEY) as
48
- | ViewEventSignal
49
- | undefined;
50
- if (!payload) {
51
- // 生命周期输入只负责注册,不向下游发出信号
52
- return;
53
- }
54
- io.setOutput("out", createTrueSignal(payload));
55
- io.emitFlow("out");
56
- });
57
- }
58
-
59
- export {
60
- LIFECYCLE_SIGNAL_KEY,
61
- BLUEPRINT_ACTIVATION_INPUT_KEY,
62
- UI_EVENT_PAYLOAD_KEY,
63
- VIEW_EVENT_SIGNAL_KEY,
64
- };
@@ -1,14 +0,0 @@
1
- import type { Value } from "./type.js";
2
-
3
- /** 嵌套蓝图执行完成后各节点的端口输出 */
4
- export type BlueprintNodeOutputs = Record<string, Record<string, Value>>;
5
-
6
- /** 蓝图引用节点真信号携带的输出值 */
7
- export type BlueprintRefOutput = {
8
- nodeId: string;
9
- nodeType: string;
10
- libraryBlueprintId: string;
11
- blueprintName?: string;
12
- /** 嵌套蓝图各节点 out 端口的信号输出 */
13
- nestedOutputs: BlueprintNodeOutputs;
14
- };
@@ -1,232 +0,0 @@
1
- export type ClockNodeConfig = {
2
- /** 两次输出之间的间隔秒数 */
3
- intervalSeconds: number;
4
- /** 输出值中的时间格式化模板,支持 YYYY、MM、DD、HH、mm、ss */
5
- timeFormat: string;
6
- /** 收到真信号后的输出次数,默认 1 */
7
- outputCount: number;
8
- /** 收到真信号后立即输出第一次,再按间隔继续;关闭则首次也等待 n 秒 */
9
- emitImmediately: boolean;
10
- };
11
-
12
- export type ClockSignalOutput = {
13
- /** 按 timeFormat 格式化后的当前时间 */
14
- formatted: string;
15
- /** Unix 毫秒时间戳 */
16
- timestamp: number;
17
- /** ISO 8601 时间字符串 */
18
- isoTime: string;
19
- };
20
-
21
- export const DEFAULT_CLOCK_NODE_CONFIG: ClockNodeConfig = {
22
- intervalSeconds: 0,
23
- timeFormat: "YYYY-MM-DD HH:mm:ss",
24
- outputCount: 1,
25
- emitImmediately: true,
26
- };
27
-
28
- export function normalizeClockConfig(
29
- config?: Partial<ClockNodeConfig> | null
30
- ): ClockNodeConfig {
31
- const intervalRaw = Number(config?.intervalSeconds);
32
- const intervalSeconds =
33
- Number.isFinite(intervalRaw) && intervalRaw > 0
34
- ? Math.floor(intervalRaw)
35
- : 0;
36
-
37
- const outputCountRaw = Number(config?.outputCount);
38
- const outputCount =
39
- Number.isFinite(outputCountRaw) && outputCountRaw > 0
40
- ? Math.floor(outputCountRaw)
41
- : DEFAULT_CLOCK_NODE_CONFIG.outputCount;
42
-
43
- const timeFormat =
44
- config?.timeFormat?.trim() || DEFAULT_CLOCK_NODE_CONFIG.timeFormat;
45
-
46
- return {
47
- intervalSeconds,
48
- timeFormat,
49
- outputCount,
50
- emitImmediately: config?.emitImmediately !== false,
51
- };
52
- }
53
-
54
- export function validateClockScheduleConfig(
55
- config: ClockNodeConfig
56
- ): { ok: true } | { ok: false; error: string } {
57
- const normalized = normalizeClockConfig(config);
58
-
59
- if (normalized.outputCount <= 0) {
60
- return { ok: false, error: "输出次数须大于 0" };
61
- }
62
-
63
- const needsInterval =
64
- normalized.outputCount > 1 ||
65
- (normalized.outputCount === 1 && !normalized.emitImmediately);
66
-
67
- if (needsInterval && normalized.intervalSeconds <= 0) {
68
- return { ok: false, error: "当前配置下时钟信号间隔须大于 0 秒" };
69
- }
70
-
71
- return { ok: true };
72
- }
73
-
74
- const pad2 = (value: number) => String(value).padStart(2, "0");
75
-
76
- /** 将 Date 格式化为指定模板(YYYY/MM/DD/HH/mm/ss) */
77
- export function formatClockTime(date: Date, pattern: string): string {
78
- const tokens: Record<string, string> = {
79
- YYYY: String(date.getFullYear()).padStart(4, "0"),
80
- MM: pad2(date.getMonth() + 1),
81
- DD: pad2(date.getDate()),
82
- HH: pad2(date.getHours()),
83
- mm: pad2(date.getMinutes()),
84
- ss: pad2(date.getSeconds()),
85
- };
86
-
87
- return pattern.replace(/YYYY|MM|DD|HH|mm|ss/g, (token) => tokens[token] ?? token);
88
- }
89
-
90
- export function buildClockSignalValue(
91
- config: ClockNodeConfig,
92
- now = new Date()
93
- ): ClockSignalOutput {
94
- const normalized = normalizeClockConfig(config);
95
- return {
96
- formatted: formatClockTime(now, normalized.timeFormat),
97
- timestamp: now.getTime(),
98
- isoTime: now.toISOString(),
99
- };
100
- }
101
-
102
- function sleep(ms: number, abort: { aborted: boolean }, sessionKey?: string): Promise<void> {
103
- if (ms <= 0 || abort.aborted) return Promise.resolve();
104
- return new Promise((resolve) => {
105
- const timerId = setTimeout(() => {
106
- clearInterval(pollId);
107
- resolve();
108
- }, ms);
109
- const pollId = setInterval(() => {
110
- if (
111
- abort.aborted ||
112
- (sessionKey !== undefined && isClockSessionAborted(sessionKey))
113
- ) {
114
- clearTimeout(timerId);
115
- clearInterval(pollId);
116
- resolve();
117
- }
118
- }, 50);
119
- });
120
- }
121
-
122
- /**
123
- * 第 tickIndex 次输出(从 0 起)相对调度起点的目标延迟(毫秒)。
124
- * - 立即发送且次数 > 1:第 1 次为 0,其余依次为 m、2m…
125
- * - 未立即发送:依次为 m、2m、3m…
126
- */
127
- export function getClockTickDelayOffsetMs(
128
- config: ClockNodeConfig,
129
- tickIndex: number
130
- ): number {
131
- const normalized = normalizeClockConfig(config);
132
- const intervalMs = normalized.intervalSeconds * 1000;
133
-
134
- if (tickIndex <= 0) {
135
- return normalized.emitImmediately ? 0 : intervalMs;
136
- }
137
-
138
- if (normalized.emitImmediately) {
139
- return tickIndex * intervalMs;
140
- }
141
-
142
- return (tickIndex + 1) * intervalMs;
143
- }
144
-
145
- /** 距离第 tickIndex 次输出还应等待的毫秒数(基于调度起点) */
146
- export function getClockTickWaitMs(
147
- config: ClockNodeConfig,
148
- tickIndex: number,
149
- scheduleStartMs: number,
150
- nowMs = Date.now()
151
- ): number {
152
- const targetMs = scheduleStartMs + getClockTickDelayOffsetMs(config, tickIndex);
153
- return Math.max(0, targetMs - nowMs);
154
- }
155
-
156
- const activeSessions = new Map<string, { aborted: boolean }>();
157
- const abortedClockSessions = new Set<string>();
158
-
159
- /** 停止指定会话的时钟调度 */
160
- export function stopClockSchedule(sessionKey: string): void {
161
- const session = activeSessions.get(sessionKey);
162
- if (!session) return;
163
- session.aborted = true;
164
- activeSessions.delete(sessionKey);
165
- }
166
-
167
- export function isClockScheduleActive(sessionKey: string): boolean {
168
- return activeSessions.has(sessionKey);
169
- }
170
-
171
- /** 中止时钟:取消剩余 tick,并标记会话供下游传播循环检测 */
172
- export function abortClockSession(sessionKey: string): void {
173
- stopClockSchedule(sessionKey);
174
- abortedClockSessions.add(sessionKey);
175
- }
176
-
177
- export function isClockSessionAborted(sessionKey: string): boolean {
178
- return abortedClockSessions.has(sessionKey);
179
- }
180
-
181
- export function clearClockSessionAbort(sessionKey: string): void {
182
- abortedClockSessions.delete(sessionKey);
183
- }
184
-
185
- /**
186
- * 按配置依次触发 onTick;新的调度会取消同 sessionKey 的上一次调度。
187
- * 返回取消函数。
188
- */
189
- export function startClockSchedule(options: {
190
- sessionKey: string;
191
- config: ClockNodeConfig;
192
- onTick: () => void | Promise<void>;
193
- onSettled?: () => void;
194
- }): () => void {
195
- stopClockSchedule(options.sessionKey);
196
- clearClockSessionAbort(options.sessionKey);
197
-
198
- const abort = { aborted: false };
199
- activeSessions.set(options.sessionKey, abort);
200
-
201
- void (async () => {
202
- try {
203
- const config = normalizeClockConfig(options.config);
204
- const total = config.outputCount;
205
- const scheduleStartMs = Date.now();
206
-
207
- for (let tickIndex = 0; tickIndex < total; tickIndex++) {
208
- if (abort.aborted || isClockSessionAborted(options.sessionKey)) return;
209
-
210
- const waitMs = getClockTickWaitMs(config, tickIndex, scheduleStartMs);
211
- if (waitMs > 0) {
212
- await sleep(waitMs, abort, options.sessionKey);
213
- }
214
-
215
- if (abort.aborted || isClockSessionAborted(options.sessionKey)) return;
216
- await options.onTick();
217
- }
218
- } finally {
219
- activeSessions.delete(options.sessionKey);
220
- options.onSettled?.();
221
- }
222
- })();
223
-
224
- return () => stopClockSchedule(options.sessionKey);
225
- }
226
-
227
- /** 停止全部时钟调度(如蓝图面板卸载时) */
228
- export function stopAllClockSchedules(): void {
229
- for (const key of [...activeSessions.keys()]) {
230
- abortClockSession(key);
231
- }
232
- }
@@ -1,32 +0,0 @@
1
- // behavior-registry.ts
2
- import { BehaviorFn } from "./behavior"
3
-
4
- type JSBehavior = {
5
- kind: "js"
6
- run: BehaviorFn
7
- }
8
-
9
- type BlueprintBehavior = {
10
- kind: "blueprint"
11
- blueprintId: string
12
- }
13
-
14
- export type Behavior = JSBehavior | BlueprintBehavior
15
-
16
- export class BehaviorRegistry {
17
- private map = new Map<string, Behavior>()
18
-
19
- registerJS(id: string, fn: BehaviorFn) {
20
- this.map.set(id, { kind: "js", run: fn })
21
- }
22
-
23
- registerBlueprint(id: string, blueprintId: string) {
24
- this.map.set(id, { kind: "blueprint", blueprintId })
25
- }
26
-
27
- get(id: string): Behavior {
28
- const b = this.map.get(id)
29
- if (!b) throw new Error(`Behavior not found: ${id}`)
30
- return b
31
- }
32
- }
@@ -1,13 +0,0 @@
1
- // behavior.ts
2
- import { ExecutionToken, Value } from "../type.js"
3
- export type BehaviorContext = {
4
- token: ExecutionToken
5
-
6
- io: {
7
- getInput(name: string): Promise<Value>
8
- setOutput(name: string, value: Value): void
9
- emitFlow(port?: string): void
10
- }
11
- }
12
-
13
- export type BehaviorFn = (ctx: BehaviorContext) => Promise<void>
@@ -1,51 +0,0 @@
1
- // executor.ts
2
- import { BehaviorRegistry } from "./behavior-registry";
3
- import { NodeDefinition, ExecutionToken, Value } from "../type";
4
-
5
- export type ExecutorDeps = {
6
- getNodeDefinition(type: string): NodeDefinition;
7
- behaviors: BehaviorRegistry;
8
-
9
- // 蓝图 runner(给 blueprint behavior 用)
10
- runBlueprint: (args: {
11
- blueprintId: string;
12
- token: ExecutionToken;
13
- }) => Promise<void>;
14
-
15
- // 图能力(flow / data)
16
- getInputValue: (token: ExecutionToken, port: string) => Promise<Value>;
17
- setNodeOutput: (token: ExecutionToken, port: string, value: Value) => void;
18
- emitFlow: (token: ExecutionToken, outPort: string) => void;
19
- };
20
- export class Executor {
21
- constructor(private deps: ExecutorDeps) {}
22
-
23
- async executeToken(token: ExecutionToken): Promise<void> {
24
- const def = this.deps.getNodeDefinition(token.nodeType)
25
- const behavior = this.deps.behaviors.get(def.behavior.ref)
26
-
27
- const io = {
28
- getInput: (name: string) =>
29
- this.deps.getInputValue(token, name),
30
-
31
- setOutput: (name: string, value: Value) =>
32
- this.deps.setNodeOutput(token, name, value),
33
-
34
- emitFlow: (port = "out") =>
35
- this.deps.emitFlow(token, port),
36
- }
37
-
38
- if (behavior.kind === "js") {
39
- await behavior.run({ token, io })
40
- return
41
- }
42
-
43
- if (behavior.kind === "blueprint") {
44
- await this.deps.runBlueprint({
45
- blueprintId: behavior.blueprintId,
46
- token,
47
- })
48
- return
49
- }
50
- }
51
- }
@@ -1,13 +0,0 @@
1
- import { ExecutionToken } from "../type"
2
- import { Executor } from "./executor"
3
-
4
- // scheduler-loop.ts
5
- async function schedulerLoop(
6
- queue: ExecutionToken[],
7
- executor: Executor
8
- ) {
9
- while (queue.length > 0) {
10
- const token = queue.shift()!
11
- await executor.executeToken(token)
12
- }
13
- }
@@ -1,211 +0,0 @@
1
- /** 视图事件节点可监听的常用 DOM 事件 */
2
- export const VIEW_EVENT_TYPES = [
3
- "click",
4
- "dblclick",
5
- "contextmenu",
6
- "mouseenter",
7
- "mouseleave",
8
- ] as const;
9
-
10
- export type ViewEventType = (typeof VIEW_EVENT_TYPES)[number];
11
-
12
- export type ViewEventNodeConfig = {
13
- /** 监听的事件类型,至少一项;默认 click */
14
- eventTypes: ViewEventType[];
15
- };
16
-
17
- export const DEFAULT_VIEW_EVENT_NODE_CONFIG: ViewEventNodeConfig = {
18
- eventTypes: ["click"],
19
- };
20
-
21
- const VIEW_EVENT_TYPE_SET = new Set<string>(VIEW_EVENT_TYPES);
22
-
23
- export function isViewEventType(value: unknown): value is ViewEventType {
24
- return typeof value === "string" && VIEW_EVENT_TYPE_SET.has(value);
25
- }
26
-
27
- export function normalizeViewEventConfig(
28
- config?: Partial<ViewEventNodeConfig> | null
29
- ): ViewEventNodeConfig {
30
- const raw = Array.isArray(config?.eventTypes) ? config.eventTypes : [];
31
- const eventTypes = [
32
- ...new Set(raw.filter(isViewEventType)),
33
- ];
34
- return {
35
- eventTypes:
36
- eventTypes.length > 0
37
- ? eventTypes
38
- : [...DEFAULT_VIEW_EVENT_NODE_CONFIG.eventTypes],
39
- };
40
- }
41
-
42
- /** 可序列化的 DOM 事件快照(原生 Event 无法作为蓝图信号传递) */
43
- export type ViewEventSnapshot = {
44
- type: string;
45
- timeStamp: number;
46
- clientX?: number;
47
- clientY?: number;
48
- pageX?: number;
49
- pageY?: number;
50
- button?: number;
51
- altKey?: boolean;
52
- ctrlKey?: boolean;
53
- metaKey?: boolean;
54
- shiftKey?: boolean;
55
- };
56
-
57
- /** 触发该事件的视图节点摘要 */
58
- export type ViewEventNodeSnapshot = {
59
- id: string;
60
- name?: string;
61
- materialType?: string;
62
- x?: number;
63
- y?: number;
64
- width?: number;
65
- height?: number;
66
- layerId?: string;
67
- };
68
-
69
- /** 事件节点向下游发出的真信号 value */
70
- export type ViewEventSignal = {
71
- /** 触发事件(可序列化快照) */
72
- event: ViewEventSnapshot;
73
- /** 触发节点 */
74
- node: ViewEventNodeSnapshot;
75
- /** 触发的事件类型 */
76
- eventType: ViewEventType;
77
- };
78
-
79
- export function snapshotDomEvent(event: {
80
- type: string;
81
- timeStamp?: number;
82
- clientX?: number;
83
- clientY?: number;
84
- pageX?: number;
85
- pageY?: number;
86
- button?: number;
87
- altKey?: boolean;
88
- ctrlKey?: boolean;
89
- metaKey?: boolean;
90
- shiftKey?: boolean;
91
- }): ViewEventSnapshot {
92
- return {
93
- type: event.type,
94
- timeStamp: Number(event.timeStamp) || Date.now(),
95
- clientX: event.clientX,
96
- clientY: event.clientY,
97
- pageX: event.pageX,
98
- pageY: event.pageY,
99
- button: event.button,
100
- altKey: event.altKey,
101
- ctrlKey: event.ctrlKey,
102
- metaKey: event.metaKey,
103
- shiftKey: event.shiftKey,
104
- };
105
- }
106
-
107
- export function snapshotViewEventNode(
108
- node: Partial<ViewEventNodeSnapshot> & { id: string }
109
- ): ViewEventNodeSnapshot {
110
- return {
111
- id: node.id,
112
- name: node.name,
113
- materialType: node.materialType,
114
- x: node.x,
115
- y: node.y,
116
- width: node.width,
117
- height: node.height,
118
- layerId: node.layerId,
119
- };
120
- }
121
-
122
- export function createViewEventSignal(input: {
123
- event: ViewEventSnapshot;
124
- node: ViewEventNodeSnapshot;
125
- eventType: ViewEventType;
126
- }): ViewEventSignal {
127
- return {
128
- event: input.event,
129
- node: input.node,
130
- eventType: input.eventType,
131
- };
132
- }
133
-
134
- export function collectArmedViewEventNodeIds(
135
- nodes: Array<{
136
- id: string;
137
- nodeType?: string;
138
- lifecyclePhase?: string;
139
- }>,
140
- edges: Array<{ source: string; target: string }>,
141
- eventNodeType: string,
142
- lifecycleNodeType: string,
143
- firedPhases: ReadonlySet<string>
144
- ): Set<string> {
145
- const byId = new Map(nodes.map((node) => [node.id, node]));
146
- const armed = new Set<string>();
147
- for (const node of nodes) {
148
- if (node.nodeType !== eventNodeType) continue;
149
- const registered = edges.some((edge) => {
150
- if (edge.target !== node.id) return false;
151
- const source = byId.get(edge.source);
152
- if (!source || source.nodeType !== lifecycleNodeType) return false;
153
- const phase = source.lifecyclePhase || "mounted";
154
- return firedPhases.has(phase);
155
- });
156
- if (registered) armed.add(node.id);
157
- }
158
- return armed;
159
- }
160
-
161
- /** 仅收集已由生命周期注册的事件节点绑定 */
162
- export function collectArmedViewEventBindings(
163
- nodes: Array<{
164
- id: string;
165
- nodeType?: string;
166
- lifecyclePhase?: string;
167
- viewElementIds?: string[];
168
- eventConfig?: Partial<ViewEventNodeConfig> | null;
169
- }>,
170
- edges: Array<{ source: string; target: string }>,
171
- eventNodeType: string,
172
- lifecycleNodeType: string,
173
- firedPhases: ReadonlySet<string>
174
- ): Map<string, Set<ViewEventType>> {
175
- const armed = collectArmedViewEventNodeIds(
176
- nodes,
177
- edges,
178
- eventNodeType,
179
- lifecycleNodeType,
180
- firedPhases
181
- );
182
- return collectViewEventBindings(nodes, eventNodeType, armed);
183
- }
184
-
185
- export function collectViewEventBindings(
186
- nodes: Array<{
187
- id?: string;
188
- nodeType?: string;
189
- viewElementIds?: string[];
190
- eventConfig?: Partial<ViewEventNodeConfig> | null;
191
- }>,
192
- eventNodeType: string,
193
- armedNodeIds?: ReadonlySet<string>
194
- ): Map<string, Set<ViewEventType>> {
195
- const map = new Map<string, Set<ViewEventType>>();
196
- for (const node of nodes) {
197
- if (node.nodeType !== eventNodeType) continue;
198
- if (armedNodeIds && (!node.id || !armedNodeIds.has(node.id))) continue;
199
- const types = normalizeViewEventConfig(node.eventConfig).eventTypes;
200
- for (const id of node.viewElementIds ?? []) {
201
- if (!id) continue;
202
- let set = map.get(id);
203
- if (!set) {
204
- set = new Set();
205
- map.set(id, set);
206
- }
207
- for (const type of types) set.add(type);
208
- }
209
- }
210
- return map;
211
- }