@arronqzy/blueprint-dsl 0.0.1
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 +31 -0
- package/readme.md +32 -0
- package/src/behaviors/default.ts +44 -0
- package/src/blueprint-signal.ts +14 -0
- package/src/clock-config.ts +232 -0
- package/src/core/behavior-registry.ts +32 -0
- package/src/core/behavior.ts +13 -0
- package/src/core/executor.ts +51 -0
- package/src/core/scheduler-loop.ts +13 -0
- package/src/fetch-config.ts +244 -0
- package/src/index.ts +29 -0
- package/src/json-config.ts +37 -0
- package/src/lifecycle.ts +45 -0
- package/src/logic-config.ts +53 -0
- package/src/node-signal.ts +83 -0
- package/src/nodes/definitions.ts +95 -0
- package/src/runtime/blueprint-cycle.ts +136 -0
- package/src/runtime/clock-active-registry.ts +30 -0
- package/src/runtime/clock-scheduler.ts +49 -0
- package/src/runtime/execution-trace.ts +55 -0
- package/src/runtime/graph-runner.ts +1326 -0
- package/src/swagger.ts +192 -0
- package/src/type.ts +34 -0
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@arronqzy/blueprint-dsl",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Abuilder blueprint DSL runtime",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"files": [
|
|
7
|
+
"src",
|
|
8
|
+
"readme.md"
|
|
9
|
+
],
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./src/index.ts"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"rxjs": "^7.8.2"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"eslint": "^8.57.0",
|
|
18
|
+
"typescript": "^5.5.4",
|
|
19
|
+
"@arronqzy/eslint-config": "0.0.0",
|
|
20
|
+
"@arronqzy/typescript-config": "0.0.0"
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"lint": "eslint \"src/**/*.{ts,tsx}\"",
|
|
27
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
28
|
+
"build": "pnpm run typecheck",
|
|
29
|
+
"dev": "pnpm run typecheck --watch"
|
|
30
|
+
}
|
|
31
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
```ts
|
|
2
|
+
const behaviors = new BehaviorRegistry();
|
|
3
|
+
|
|
4
|
+
behaviors.registerJS("print-js", async ({ io }) => {
|
|
5
|
+
console.log(await io.getInput("msg"));
|
|
6
|
+
io.emitFlow("out");
|
|
7
|
+
});
|
|
8
|
+
|
|
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
|
+
const PrintNode: NodeDefinition = {
|
|
17
|
+
type: "Print",
|
|
18
|
+
inputs: [{ name: "msg", kind: "data" }],
|
|
19
|
+
outputs: [{ name: "out", kind: "flow" }],
|
|
20
|
+
behavior: { kind: "js", ref: "print-js" }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
await executor.executeToken({
|
|
25
|
+
tokenId: "t1",
|
|
26
|
+
nodeId: "n1",
|
|
27
|
+
nodeType: "Print",
|
|
28
|
+
inPort: "in",
|
|
29
|
+
scope: { vars: new Map() }
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createFalseSignal,
|
|
3
|
+
createTrueSignal,
|
|
4
|
+
isFalseSignal,
|
|
5
|
+
isTrueSignal,
|
|
6
|
+
} from "../node-signal.js";
|
|
7
|
+
import type { LifecycleSignal } from "../lifecycle.js";
|
|
8
|
+
import { BehaviorRegistry } from "../core/behavior-registry.js";
|
|
9
|
+
|
|
10
|
+
const LIFECYCLE_SIGNAL_KEY = "__lifecycleSignal";
|
|
11
|
+
const BLUEPRINT_ACTIVATION_INPUT_KEY = "__blueprintActivationInput";
|
|
12
|
+
|
|
13
|
+
export function registerDefaultBehaviors(registry: BehaviorRegistry) {
|
|
14
|
+
registry.registerJS("logic-noop", async ({ io }) => {
|
|
15
|
+
const input = await io.getInput("in");
|
|
16
|
+
if (isFalseSignal(input)) {
|
|
17
|
+
io.setOutput("out", input);
|
|
18
|
+
} else if (isTrueSignal(input)) {
|
|
19
|
+
io.setOutput("out", createTrueSignal(input.value));
|
|
20
|
+
} else {
|
|
21
|
+
io.setOutput("out", createTrueSignal(undefined));
|
|
22
|
+
}
|
|
23
|
+
io.emitFlow("out");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
registry.registerJS("lifecycle-emit", async ({ token, io }) => {
|
|
27
|
+
const lifecycle = token.scope.vars.get(LIFECYCLE_SIGNAL_KEY) as
|
|
28
|
+
| LifecycleSignal
|
|
29
|
+
| undefined;
|
|
30
|
+
if (!lifecycle) {
|
|
31
|
+
io.setOutput("out", createFalseSignal("生命周期信号缺失"));
|
|
32
|
+
} else if (lifecycle.phase === "blueprintActivated") {
|
|
33
|
+
io.setOutput(
|
|
34
|
+
"out",
|
|
35
|
+
createTrueSignal(token.scope.vars.get(BLUEPRINT_ACTIVATION_INPUT_KEY))
|
|
36
|
+
);
|
|
37
|
+
} else {
|
|
38
|
+
io.setOutput("out", createTrueSignal(lifecycle));
|
|
39
|
+
}
|
|
40
|
+
io.emitFlow("out");
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { LIFECYCLE_SIGNAL_KEY, BLUEPRINT_ACTIVATION_INPUT_KEY };
|
|
@@ -0,0 +1,14 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,232 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
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>
|
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|
+
}
|