weifuwu 0.90.0 → 0.91.0
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/README.md +4 -2
- package/dist/client/components/WordCloud/WordCloud.d.ts +79 -0
- package/dist/client/components/index.d.ts +2 -0
- package/dist/client/components/index.js +33 -33
- package/dist/client/components/style.css +95 -1
- package/dist/client/layout/weifuwu-layout.css +19 -0
- package/dist/client/vdom/core/v2/diff.d.ts +2 -2
- package/dist/client/vdom/hooks/env.d.ts +20 -0
- package/dist/client/vdom/index.js +3 -3
- package/dist/client/vdom/testing.js +2 -2
- package/dist/server/ai/approvals.d.ts +24 -0
- package/dist/server/ai/assemble.d.ts +19 -0
- package/dist/server/ai/client.d.ts +22 -0
- package/dist/server/ai/contracts.d.ts +67 -2
- package/dist/server/ai/index.d.ts +14 -43
- package/dist/server/ai/memory-server.d.ts +50 -0
- package/dist/server/ai/memory.d.ts +46 -0
- package/dist/server/ai/multimodal.d.ts +52 -0
- package/dist/server/ai/openai.d.ts +16 -0
- package/dist/server/db/body.d.ts +31 -0
- package/dist/server/db/consistency.d.ts +28 -0
- package/dist/server/db/contracts.d.ts +3 -27
- package/dist/server/db/filter.d.ts +24 -0
- package/dist/server/db/gql-from-shape.d.ts +42 -0
- package/dist/server/db/http.d.ts +38 -0
- package/dist/server/db/memory-sql.d.ts +66 -8
- package/dist/server/db/ops.d.ts +70 -0
- package/dist/server/db/orm.d.ts +110 -0
- package/dist/server/db/query-builder.d.ts +4 -2
- package/dist/server/db/query.d.ts +90 -32
- package/dist/server/db/rest-from-shape.d.ts +56 -0
- package/dist/server/db/schema.d.ts +78 -0
- package/dist/server/db/server.d.ts +0 -2
- package/dist/server/db/shape.d.ts +131 -0
- package/dist/server/db/typed-query.d.ts +113 -0
- package/dist/server/email/api.d.ts +15 -0
- package/dist/server/email/contracts.d.ts +8 -3
- package/dist/server/email/index.d.ts +23 -30
- package/dist/server/email/memory-email-server.d.ts +43 -0
- package/dist/server/email/memory-email.d.ts +30 -0
- package/dist/server/index.d.ts +33 -10
- package/dist/server/index.js +9843 -4133
- package/dist/server/messager/index.d.ts +133 -12
- package/dist/server/postgres/client.d.ts +4 -4
- package/dist/server/postgres/types.d.ts +33 -13
- package/dist/server/redis/types.d.ts +2 -0
- package/dist/server/response.d.ts +3 -3
- package/dist/server/types.d.ts +2 -5
- package/dist/server/user/app-auth.d.ts +74 -0
- package/dist/server/user/index.d.ts +277 -5
- package/dist/server/workflow/contracts.d.ts +202 -0
- package/dist/server/workflow/expression.d.ts +89 -0
- package/dist/server/workflow/index.d.ts +70 -0
- package/dist/server/workflow/index.js +2476 -0
- package/dist/server/workflow/runner.d.ts +19 -0
- package/dist/server/workflow/std.d.ts +27 -0
- package/dist/server/workflow/steps.d.ts +12 -0
- package/dist/server/workflow/store.d.ts +19 -0
- package/dist/server/workflow/tojs.d.ts +28 -0
- package/dist/server/workflow/validate.d.ts +23 -0
- package/dist/server/workflow/views.d.ts +66 -0
- package/dist/server/workflow/wfjs.d.ts +27 -0
- package/dist/server/workflows/cron.d.ts +16 -0
- package/dist/server/workflows/index.d.ts +267 -0
- package/dist/shared/zod.d.ts +351 -0
- package/docs/client.md +6 -0
- package/docs/server.md +455 -11
- package/package.json +9 -3
- package/dist/server/db/postgres-server.d.ts +0 -39
- package/dist/server/db/sql-parser.d.ts +0 -24
- package/dist/server/db/test-servers.d.ts +0 -21
- package/dist/server/email/smtp.d.ts +0 -34
|
@@ -0,0 +1,2476 @@
|
|
|
1
|
+
// src/server/workflow/std.ts
|
|
2
|
+
function requireArgs(name, args, n) {
|
|
3
|
+
if (args.length !== n) throw new Error(`std.${name}: \u671F\u671B ${n} \u4E2A\u53C2\u6570\uFF0C\u6536\u5230 ${args.length}`);
|
|
4
|
+
}
|
|
5
|
+
function toNum(name, v, pos) {
|
|
6
|
+
const n = Number(v);
|
|
7
|
+
if (!Number.isFinite(n)) throw new Error(`std.${name}: \u53C2\u6570 ${pos + 1} \u4E0D\u662F\u6709\u9650\u6570\u5B57\uFF08${String(v)}\uFF09`);
|
|
8
|
+
return n;
|
|
9
|
+
}
|
|
10
|
+
var math = {
|
|
11
|
+
sum: (a) => {
|
|
12
|
+
requireArgs("sum", a, 1);
|
|
13
|
+
const xs = a[0];
|
|
14
|
+
if (!Array.isArray(xs)) throw new Error("std.sum: \u671F\u671B\u6570\u7EC4");
|
|
15
|
+
return xs.reduce((acc, v) => acc + toNum("sum", v, 0), 0);
|
|
16
|
+
},
|
|
17
|
+
avg: (a) => {
|
|
18
|
+
requireArgs("avg", a, 1);
|
|
19
|
+
const xs = a[0];
|
|
20
|
+
if (!Array.isArray(xs)) throw new Error("std.avg: \u671F\u671B\u6570\u7EC4");
|
|
21
|
+
if (xs.length === 0) throw new Error("std.avg: \u7A7A\u6570\u7EC4");
|
|
22
|
+
return xs.reduce((acc, v) => acc + toNum("avg", v, 0), 0) / xs.length;
|
|
23
|
+
},
|
|
24
|
+
clamp: (a) => {
|
|
25
|
+
requireArgs("clamp", a, 3);
|
|
26
|
+
const v = toNum("clamp", a[0], 0);
|
|
27
|
+
const lo = toNum("clamp", a[1], 1);
|
|
28
|
+
const hi = toNum("clamp", a[2], 2);
|
|
29
|
+
if (lo > hi) throw new Error("std.clamp: lo > hi");
|
|
30
|
+
return Math.min(hi, Math.max(lo, v));
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
var strings = {
|
|
34
|
+
upper: (a) => {
|
|
35
|
+
requireArgs("upper", a, 1);
|
|
36
|
+
if (typeof a[0] !== "string") throw new Error(`std.upper: \u671F\u671B\u5B57\u7B26\u4E32\uFF08${String(a[0])}\uFF09`);
|
|
37
|
+
return a[0].toUpperCase();
|
|
38
|
+
},
|
|
39
|
+
lower: (a) => {
|
|
40
|
+
requireArgs("lower", a, 1);
|
|
41
|
+
if (typeof a[0] !== "string") throw new Error(`std.lower: \u671F\u671B\u5B57\u7B26\u4E32\uFF08${String(a[0])}\uFF09`);
|
|
42
|
+
return a[0].toLowerCase();
|
|
43
|
+
},
|
|
44
|
+
join: (a) => {
|
|
45
|
+
requireArgs("join", a, 2);
|
|
46
|
+
if (!Array.isArray(a[0]) || typeof a[1] !== "string") throw new Error("std.join: \u671F\u671B(\u6570\u7EC4, \u5206\u9694\u7B26)");
|
|
47
|
+
return a[0].map((v) => String(v)).join(a[1]);
|
|
48
|
+
},
|
|
49
|
+
split: (a) => {
|
|
50
|
+
requireArgs("split", a, 2);
|
|
51
|
+
if (typeof a[0] !== "string" || typeof a[1] !== "string") throw new Error("std.split: \u671F\u671B(\u5B57\u7B26\u4E32, \u5206\u9694\u7B26)");
|
|
52
|
+
return a[0].split(a[1]);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var collections = {
|
|
56
|
+
count: (a) => {
|
|
57
|
+
requireArgs("count", a, 1);
|
|
58
|
+
if (Array.isArray(a[0])) return a[0].length;
|
|
59
|
+
if (typeof a[0] === "string") return a[0].length;
|
|
60
|
+
throw new Error(`std.count: \u671F\u671B\u6570\u7EC4\u6216\u5B57\u7B26\u4E32\uFF08${String(a[0])}\uFF09`);
|
|
61
|
+
},
|
|
62
|
+
pick: (a) => {
|
|
63
|
+
requireArgs("pick", a, 2);
|
|
64
|
+
if (!Array.isArray(a[0])) throw new Error("std.pick: \u671F\u671B\u6570\u7EC4");
|
|
65
|
+
const i = toNum("pick", a[1], 1);
|
|
66
|
+
if (!Number.isInteger(i) || i < 0 || i >= a[0].length) throw new Error(`std.pick: \u7D22\u5F15 ${i} \u8D8A\u754C\uFF08\u957F\u5EA6 ${a[0].length}\uFF09`);
|
|
67
|
+
return a[0][i];
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var STD_MODULES = {
|
|
71
|
+
"wf://std/math": math,
|
|
72
|
+
"wf://std/strings": strings,
|
|
73
|
+
"wf://std/collections": collections
|
|
74
|
+
};
|
|
75
|
+
var STD_FNS = { ...math, ...strings, ...collections };
|
|
76
|
+
var STD_NAMES = Object.keys(STD_FNS);
|
|
77
|
+
|
|
78
|
+
// src/server/workflow/expression.ts
|
|
79
|
+
var IDENT = /[A-Za-z_$][A-Za-z0-9_$]*/;
|
|
80
|
+
var NUM = /[0-9]+(?:\.[0-9]+)?/;
|
|
81
|
+
var THREE_CHAR_OPS = ["===", "!=="];
|
|
82
|
+
var TWO_CHAR_OPS = ["==", "!=", "&&", "||", "<=", ">="];
|
|
83
|
+
var ONE_CHAR_OPS = ["!", "+", "-", "*", "/", "%", "<", ">", "?"];
|
|
84
|
+
function tokenize(src) {
|
|
85
|
+
const tokens = [];
|
|
86
|
+
let i = 0;
|
|
87
|
+
const n = src.length;
|
|
88
|
+
while (i < n) {
|
|
89
|
+
const ch = src[i];
|
|
90
|
+
if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
|
|
91
|
+
i++;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (ch === '"' || ch === "'") {
|
|
95
|
+
const quote = ch;
|
|
96
|
+
let j = i + 1;
|
|
97
|
+
let out = "";
|
|
98
|
+
while (j < n && src[j] !== quote) {
|
|
99
|
+
if (src[j] === "\\") {
|
|
100
|
+
const esc = src[j + 1];
|
|
101
|
+
if (esc === "\\" || esc === quote) {
|
|
102
|
+
out += esc;
|
|
103
|
+
j += 2;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (esc === "n") {
|
|
107
|
+
out += "\n";
|
|
108
|
+
j += 2;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (esc === "t") {
|
|
112
|
+
out += " ";
|
|
113
|
+
j += 2;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
throw new Error(`expression: invalid escape '\\${esc ?? ""}' at ${j} (in ${JSON.stringify(src)})`);
|
|
117
|
+
}
|
|
118
|
+
out += src[j];
|
|
119
|
+
j++;
|
|
120
|
+
}
|
|
121
|
+
if (j >= n) throw new Error(`expression: unterminated string (in ${JSON.stringify(src)})`);
|
|
122
|
+
tokens.push({ t: "str", v: out, pos: i });
|
|
123
|
+
i = j + 1;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const three = src.slice(i, i + 3);
|
|
127
|
+
if (THREE_CHAR_OPS.includes(three)) {
|
|
128
|
+
tokens.push({ t: "op", v: three, pos: i });
|
|
129
|
+
i += 3;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const two = src.slice(i, i + 2);
|
|
133
|
+
if (TWO_CHAR_OPS.includes(two)) {
|
|
134
|
+
tokens.push({ t: "op", v: two, pos: i });
|
|
135
|
+
i += 2;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (ONE_CHAR_OPS.includes(ch)) {
|
|
139
|
+
tokens.push({ t: "op", v: ch, pos: i });
|
|
140
|
+
i++;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (ch === "." || ch === "[" || ch === "]" || ch === "(" || ch === ")" || ch === "," || ch === ":") {
|
|
144
|
+
tokens.push({ t: "punc", v: ch, pos: i });
|
|
145
|
+
i++;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (ch >= "0" && ch <= "9") {
|
|
149
|
+
const m2 = NUM.exec(src.slice(i));
|
|
150
|
+
if (!m2) throw new Error(`expression: invalid number at ${i} (in ${JSON.stringify(src)})`);
|
|
151
|
+
tokens.push({ t: "num", v: Number(m2[0]), pos: i });
|
|
152
|
+
i += m2[0].length;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const m = IDENT.exec(src.slice(i));
|
|
156
|
+
if (m) {
|
|
157
|
+
tokens.push({ t: "ident", v: m[0], pos: i });
|
|
158
|
+
i += m[0].length;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
throw new Error(`expression: unexpected character '${ch}' at ${i} (in ${JSON.stringify(src)})`);
|
|
162
|
+
}
|
|
163
|
+
tokens.push({ t: "eof", pos: n });
|
|
164
|
+
return tokens;
|
|
165
|
+
}
|
|
166
|
+
var Parser = class {
|
|
167
|
+
i = 0;
|
|
168
|
+
tokens;
|
|
169
|
+
src;
|
|
170
|
+
depth = 0;
|
|
171
|
+
constructor(tokens, src) {
|
|
172
|
+
this.tokens = tokens;
|
|
173
|
+
this.src = src;
|
|
174
|
+
}
|
|
175
|
+
peek() {
|
|
176
|
+
return this.tokens[this.i];
|
|
177
|
+
}
|
|
178
|
+
next() {
|
|
179
|
+
return this.tokens[this.i++];
|
|
180
|
+
}
|
|
181
|
+
fail(msg, pos) {
|
|
182
|
+
throw new Error(`${msg} at ${pos} (in ${JSON.stringify(this.src)})`);
|
|
183
|
+
}
|
|
184
|
+
expectPunc(v) {
|
|
185
|
+
const tk = this.next();
|
|
186
|
+
if (tk.t !== "punc" || tk.v !== v) this.fail(`expression: expected '${v}'`, tk.pos);
|
|
187
|
+
}
|
|
188
|
+
parse() {
|
|
189
|
+
const node = this.parseTernary();
|
|
190
|
+
const tk = this.peek();
|
|
191
|
+
if (tk.t !== "eof") this.fail(`expression: unexpected '${String(tk.v)}'`, tk.pos);
|
|
192
|
+
return node;
|
|
193
|
+
}
|
|
194
|
+
/** 三元(最低优先级):cond ? then : else */
|
|
195
|
+
parseTernary() {
|
|
196
|
+
if (++this.depth > 65) this.fail("expression: nested expression too deep (max 64 \u5C42)", this.peek().pos);
|
|
197
|
+
try {
|
|
198
|
+
return this.parseTernaryInner();
|
|
199
|
+
} finally {
|
|
200
|
+
this.depth--;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
parseTernaryInner() {
|
|
204
|
+
const cond = this.parseOr();
|
|
205
|
+
const tk = this.peek();
|
|
206
|
+
if (tk.t === "op" && tk.v === "?") {
|
|
207
|
+
this.next();
|
|
208
|
+
const then = this.parseTernary();
|
|
209
|
+
const col = this.next();
|
|
210
|
+
if (col.t !== "punc" || col.v !== ":") this.fail(`expression: expected ':' in ternary`, col.pos);
|
|
211
|
+
const else_ = this.parseTernary();
|
|
212
|
+
return { kind: "ternary", cond, then, else: else_ };
|
|
213
|
+
}
|
|
214
|
+
return cond;
|
|
215
|
+
}
|
|
216
|
+
parseOr() {
|
|
217
|
+
let left = this.parseAnd();
|
|
218
|
+
while (this.isOp("||")) {
|
|
219
|
+
this.next();
|
|
220
|
+
left = { kind: "or", left, right: this.parseAnd() };
|
|
221
|
+
}
|
|
222
|
+
return left;
|
|
223
|
+
}
|
|
224
|
+
parseAnd() {
|
|
225
|
+
let left = this.parseCmp();
|
|
226
|
+
while (this.isOp("&&")) {
|
|
227
|
+
this.next();
|
|
228
|
+
left = { kind: "and", left, right: this.parseCmp() };
|
|
229
|
+
}
|
|
230
|
+
return left;
|
|
231
|
+
}
|
|
232
|
+
parseCmp() {
|
|
233
|
+
const left = this.parseArith();
|
|
234
|
+
const tk = this.peek();
|
|
235
|
+
if (tk.t === "op" && ["==", "!=", "===", "!==", "<", "<=", ">", ">="].includes(tk.v)) {
|
|
236
|
+
this.next();
|
|
237
|
+
return { kind: "compare", op: tk.v, left, right: this.parseArith() };
|
|
238
|
+
}
|
|
239
|
+
return left;
|
|
240
|
+
}
|
|
241
|
+
parseArith() {
|
|
242
|
+
let left = this.parseTerm();
|
|
243
|
+
while (this.isOp("+") || this.isOp("-")) {
|
|
244
|
+
const op = this.next().v;
|
|
245
|
+
left = { kind: "arith", op, left, right: this.parseTerm() };
|
|
246
|
+
}
|
|
247
|
+
return left;
|
|
248
|
+
}
|
|
249
|
+
parseTerm() {
|
|
250
|
+
let left = this.parseUnary();
|
|
251
|
+
while (this.isOp("*") || this.isOp("/") || this.isOp("%")) {
|
|
252
|
+
const op = this.next().v;
|
|
253
|
+
left = { kind: "arith", op, left, right: this.parseUnary() };
|
|
254
|
+
}
|
|
255
|
+
return left;
|
|
256
|
+
}
|
|
257
|
+
isOp(v) {
|
|
258
|
+
const tk = this.peek();
|
|
259
|
+
return tk.t === "op" && tk.v === v;
|
|
260
|
+
}
|
|
261
|
+
parseUnary() {
|
|
262
|
+
const tk = this.peek();
|
|
263
|
+
if (tk.t === "op" && tk.v === "!") {
|
|
264
|
+
this.next();
|
|
265
|
+
return { kind: "not", operand: this.parseUnary() };
|
|
266
|
+
}
|
|
267
|
+
if (tk.t === "op" && tk.v === "-") {
|
|
268
|
+
this.next();
|
|
269
|
+
return { kind: "neg", operand: this.parseUnary() };
|
|
270
|
+
}
|
|
271
|
+
return this.parseAtom();
|
|
272
|
+
}
|
|
273
|
+
parseAtom() {
|
|
274
|
+
const tk = this.peek();
|
|
275
|
+
if (tk.t === "punc" && tk.v === "(") {
|
|
276
|
+
this.next();
|
|
277
|
+
const inner = this.parseTernary();
|
|
278
|
+
this.expectPunc(")");
|
|
279
|
+
return inner;
|
|
280
|
+
}
|
|
281
|
+
if (tk.t === "num") {
|
|
282
|
+
this.next();
|
|
283
|
+
return { kind: "literal", value: tk.v };
|
|
284
|
+
}
|
|
285
|
+
if (tk.t === "str") {
|
|
286
|
+
this.next();
|
|
287
|
+
return { kind: "literal", value: tk.v };
|
|
288
|
+
}
|
|
289
|
+
if (tk.t === "ident") {
|
|
290
|
+
const kw = tk.v;
|
|
291
|
+
if (kw === "null" || kw === "true" || kw === "false") {
|
|
292
|
+
this.next();
|
|
293
|
+
return { kind: "literal", value: kw === "null" ? null : kw === "true" };
|
|
294
|
+
}
|
|
295
|
+
const path = this.parsePath();
|
|
296
|
+
const after = this.peek();
|
|
297
|
+
if (path.kind === "path" && after.t === "punc" && after.v === "(") {
|
|
298
|
+
this.next();
|
|
299
|
+
const args = [];
|
|
300
|
+
if (!this.isPunc2(")")) {
|
|
301
|
+
for (; ; ) {
|
|
302
|
+
args.push(this.parseTernary());
|
|
303
|
+
if (this.isPunc2(",")) {
|
|
304
|
+
this.next();
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
this.expectPunc(")");
|
|
311
|
+
return { kind: "call", name: path.segments[0], args };
|
|
312
|
+
}
|
|
313
|
+
return path;
|
|
314
|
+
}
|
|
315
|
+
this.fail(`expression: unexpected '${tk.t === "eof" ? "(end)" : String(tk.v)}'`, tk.pos);
|
|
316
|
+
}
|
|
317
|
+
isPunc2(v) {
|
|
318
|
+
const tk = this.peek();
|
|
319
|
+
return tk.t === "punc" && tk.v === v;
|
|
320
|
+
}
|
|
321
|
+
parsePath() {
|
|
322
|
+
const segments = [];
|
|
323
|
+
const first = this.next();
|
|
324
|
+
if (first.t !== "ident") this.fail(`expression: expected path`, first.pos);
|
|
325
|
+
segments.push(first.v);
|
|
326
|
+
for (; ; ) {
|
|
327
|
+
const tk = this.peek();
|
|
328
|
+
if (tk.t === "punc" && tk.v === ".") {
|
|
329
|
+
this.next();
|
|
330
|
+
const seg = this.next();
|
|
331
|
+
if (seg.t !== "ident") this.fail(`expression: expected property after '.'`, seg.pos);
|
|
332
|
+
segments.push(seg.v);
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (tk.t === "punc" && tk.v === "[") {
|
|
336
|
+
this.next();
|
|
337
|
+
const inner = this.next();
|
|
338
|
+
if (inner.t === "op" && inner.v === "*") {
|
|
339
|
+
this.expectPunc("]");
|
|
340
|
+
segments.push("*");
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
if (inner.t !== "num" || !Number.isInteger(inner.v)) this.fail(`expression: expected integer index or '*' in '[]'`, inner.pos);
|
|
344
|
+
this.expectPunc("]");
|
|
345
|
+
segments.push(inner.v);
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
return { kind: "path", segments };
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
function parse(src) {
|
|
354
|
+
return new Parser(tokenize(src), src).parse();
|
|
355
|
+
}
|
|
356
|
+
function getPath(root, segments) {
|
|
357
|
+
function rest(cur, i) {
|
|
358
|
+
const seg = segments[i];
|
|
359
|
+
if (seg === void 0) return cur;
|
|
360
|
+
if (cur === null || cur === void 0) return void 0;
|
|
361
|
+
if (seg === "*") {
|
|
362
|
+
if (!Array.isArray(cur)) return [];
|
|
363
|
+
const out = [];
|
|
364
|
+
for (const el of cur) {
|
|
365
|
+
const v = rest(el, i + 1);
|
|
366
|
+
if (Array.isArray(v)) out.push(...v);
|
|
367
|
+
else if (v !== void 0) out.push(v);
|
|
368
|
+
}
|
|
369
|
+
return out;
|
|
370
|
+
}
|
|
371
|
+
let next;
|
|
372
|
+
if (typeof seg === "number") {
|
|
373
|
+
next = Array.isArray(cur) ? cur[seg] : void 0;
|
|
374
|
+
} else if (seg === "length") {
|
|
375
|
+
if (Array.isArray(cur)) next = cur.length;
|
|
376
|
+
else if (typeof cur === "string") next = cur.length;
|
|
377
|
+
else if (typeof cur === "object") next = cur[seg];
|
|
378
|
+
else next = void 0;
|
|
379
|
+
} else if (typeof cur === "object") {
|
|
380
|
+
next = cur[seg];
|
|
381
|
+
} else {
|
|
382
|
+
next = void 0;
|
|
383
|
+
}
|
|
384
|
+
return rest(next, i + 1);
|
|
385
|
+
}
|
|
386
|
+
return rest(root, 0);
|
|
387
|
+
}
|
|
388
|
+
function arith(op, l, r) {
|
|
389
|
+
if (typeof l !== "number" || typeof r !== "number") {
|
|
390
|
+
throw new Error(`expression: arithmetic '${op}' requires numbers (got ${typeName(l)} ${op} ${typeName(r)})`);
|
|
391
|
+
}
|
|
392
|
+
let out;
|
|
393
|
+
switch (op) {
|
|
394
|
+
case "+":
|
|
395
|
+
out = l + r;
|
|
396
|
+
break;
|
|
397
|
+
case "-":
|
|
398
|
+
out = l - r;
|
|
399
|
+
break;
|
|
400
|
+
case "*":
|
|
401
|
+
out = l * r;
|
|
402
|
+
break;
|
|
403
|
+
case "/":
|
|
404
|
+
out = l / r;
|
|
405
|
+
break;
|
|
406
|
+
case "%":
|
|
407
|
+
out = l % r;
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
if (!Number.isFinite(out)) throw new Error(`expression: arithmetic '${op}' produced non-finite result`);
|
|
411
|
+
return out;
|
|
412
|
+
}
|
|
413
|
+
function typeName(v) {
|
|
414
|
+
if (v === null) return "null";
|
|
415
|
+
if (Array.isArray(v)) return "array";
|
|
416
|
+
return typeof v;
|
|
417
|
+
}
|
|
418
|
+
function evaluate(node, ctx, fns) {
|
|
419
|
+
switch (node.kind) {
|
|
420
|
+
case "path":
|
|
421
|
+
return getPath(ctx, node.segments);
|
|
422
|
+
case "literal":
|
|
423
|
+
return node.value;
|
|
424
|
+
case "neg": {
|
|
425
|
+
const v = evaluate(node.operand, ctx, fns);
|
|
426
|
+
if (typeof v !== "number") throw new Error(`expression: unary '-' requires number (got ${typeName(v)})`);
|
|
427
|
+
return -v;
|
|
428
|
+
}
|
|
429
|
+
case "arith":
|
|
430
|
+
return arith(node.op, evaluate(node.left, ctx, fns), evaluate(node.right, ctx, fns));
|
|
431
|
+
case "ternary":
|
|
432
|
+
return evaluateBoolean(node.cond, ctx, fns) ? evaluate(node.then, ctx, fns) : evaluate(node.else, ctx, fns);
|
|
433
|
+
case "call": {
|
|
434
|
+
if (!fns?.[node.name]) throw new Error(`expression: \u672A\u6CE8\u518C\u51FD\u6570 '${node.name}'\uFF08std \u7EAF\u51FD\u6570\uFF1A${STD_NAMES.join("/")}\uFF09`);
|
|
435
|
+
return fns[node.name](node.args.map((a) => evaluate(a, ctx, fns)));
|
|
436
|
+
}
|
|
437
|
+
case "compare": {
|
|
438
|
+
const l = evaluate(node.left, ctx, fns);
|
|
439
|
+
const r = evaluate(node.right, ctx, fns);
|
|
440
|
+
switch (node.op) {
|
|
441
|
+
// 宽松相等(JS 语义)
|
|
442
|
+
// eslint-disable-next-line eqeqeq
|
|
443
|
+
case "==":
|
|
444
|
+
return l == r;
|
|
445
|
+
// eslint-disable-next-line eqeqeq
|
|
446
|
+
case "!=":
|
|
447
|
+
return !(l == r);
|
|
448
|
+
// 严格相等(JS === 语义——不类型转换)
|
|
449
|
+
case "===":
|
|
450
|
+
return l === r;
|
|
451
|
+
case "!==":
|
|
452
|
+
return l !== r;
|
|
453
|
+
case "<":
|
|
454
|
+
return l < r;
|
|
455
|
+
case "<=":
|
|
456
|
+
return l <= r;
|
|
457
|
+
case ">":
|
|
458
|
+
return l > r;
|
|
459
|
+
case ">=":
|
|
460
|
+
return l >= r;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
case "not":
|
|
464
|
+
return !toBoolean(evaluate(node.operand, ctx, fns));
|
|
465
|
+
// JS 语义:返回操作数(非 boolean 包装)
|
|
466
|
+
case "and": {
|
|
467
|
+
const l = evaluate(node.left, ctx, fns);
|
|
468
|
+
return toBoolean(l) ? evaluate(node.right, ctx, fns) : l;
|
|
469
|
+
}
|
|
470
|
+
case "or": {
|
|
471
|
+
const l = evaluate(node.left, ctx, fns);
|
|
472
|
+
return toBoolean(l) ? l : evaluate(node.right, ctx, fns);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function toBoolean(v) {
|
|
477
|
+
return Boolean(v);
|
|
478
|
+
}
|
|
479
|
+
function evaluateBoolean(node, ctx, fns) {
|
|
480
|
+
return toBoolean(evaluate(node, ctx, fns));
|
|
481
|
+
}
|
|
482
|
+
function compile(src, fns) {
|
|
483
|
+
const node = parse(src);
|
|
484
|
+
return (ctx) => evaluate(node, ctx, fns);
|
|
485
|
+
}
|
|
486
|
+
function stringifyValue(v) {
|
|
487
|
+
if (v === null || v === void 0) return "";
|
|
488
|
+
if (typeof v === "string") return v;
|
|
489
|
+
if (typeof v === "object") return JSON.stringify(v);
|
|
490
|
+
return String(v);
|
|
491
|
+
}
|
|
492
|
+
function interpolate(template, ctx, fns) {
|
|
493
|
+
if (!template.includes("{{")) return template;
|
|
494
|
+
let out = "";
|
|
495
|
+
let i = 0;
|
|
496
|
+
for (; ; ) {
|
|
497
|
+
const open = template.indexOf("{{", i);
|
|
498
|
+
if (open === -1) {
|
|
499
|
+
out += template.slice(i);
|
|
500
|
+
break;
|
|
501
|
+
}
|
|
502
|
+
const close = template.indexOf("}}", open + 2);
|
|
503
|
+
if (close === -1) {
|
|
504
|
+
throw new Error(`workflow/expression: interpolate: unclosed '{{' at ${open} (in ${JSON.stringify(template)})`);
|
|
505
|
+
}
|
|
506
|
+
const src = template.slice(open + 2, close).trim();
|
|
507
|
+
if (!src) throw new Error(`workflow/expression: interpolate: empty expression (in ${JSON.stringify(template)})`);
|
|
508
|
+
out += template.slice(i, open);
|
|
509
|
+
out += stringifyValue(evaluate(parse(src), ctx, fns));
|
|
510
|
+
i = close + 2;
|
|
511
|
+
}
|
|
512
|
+
return out;
|
|
513
|
+
}
|
|
514
|
+
function toSrc(node) {
|
|
515
|
+
switch (node.kind) {
|
|
516
|
+
case "path":
|
|
517
|
+
return node.segments.reduce((acc, s) => acc + (typeof s === "number" ? `[${s}]` : s === "*" ? "[*]" : acc === "" ? s : `.${s}`), "");
|
|
518
|
+
case "literal":
|
|
519
|
+
return node.value === null ? "null" : typeof node.value === "string" ? `'${node.value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'` : String(node.value);
|
|
520
|
+
case "ternary":
|
|
521
|
+
return `(${toSrc(node.cond)} ? ${toSrc(node.then)} : ${toSrc(node.else)})`;
|
|
522
|
+
case "call":
|
|
523
|
+
return `${node.name}(${node.args.map((a) => toSrc(a)).join(", ")})`;
|
|
524
|
+
case "neg":
|
|
525
|
+
return `-${toSrc(node.operand)}`;
|
|
526
|
+
case "arith":
|
|
527
|
+
return `(${toSrc(node.left)} ${node.op} ${toSrc(node.right)})`;
|
|
528
|
+
case "compare":
|
|
529
|
+
return `(${toSrc(node.left)} ${node.op} ${toSrc(node.right)})`;
|
|
530
|
+
case "not":
|
|
531
|
+
return `!${toSrc(node.operand)}`;
|
|
532
|
+
case "and":
|
|
533
|
+
return `(${toSrc(node.left)} && ${toSrc(node.right)})`;
|
|
534
|
+
case "or":
|
|
535
|
+
return `(${toSrc(node.left)} || ${toSrc(node.right)})`;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// src/server/workflow/steps.ts
|
|
540
|
+
var httpStep = {
|
|
541
|
+
type: "http",
|
|
542
|
+
label: "HTTP \u8BF7\u6C42",
|
|
543
|
+
fields: [
|
|
544
|
+
{ name: "url", label: "URL", type: "string" },
|
|
545
|
+
{ name: "method", label: "\u65B9\u6CD5", type: "string" }
|
|
546
|
+
],
|
|
547
|
+
required: ["url"],
|
|
548
|
+
async run(config, ctx, env) {
|
|
549
|
+
const url = String(config.url);
|
|
550
|
+
const method = method_(config.method);
|
|
551
|
+
const timeoutMs = typeof config.timeoutMs === "number" && config.timeoutMs > 0 ? config.timeoutMs : 1e4;
|
|
552
|
+
const fetchFn = env.fetch ?? globalThis.fetch;
|
|
553
|
+
if (!fetchFn) throw new Error("http step: no fetch available");
|
|
554
|
+
const headers = config.headers ?? {};
|
|
555
|
+
const body = typeof config.body === "string" ? config.body : void 0;
|
|
556
|
+
const res = await fetchFn(url, {
|
|
557
|
+
method,
|
|
558
|
+
headers,
|
|
559
|
+
body,
|
|
560
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
561
|
+
});
|
|
562
|
+
const maxBytes = typeof config.maxBytes === "number" && config.maxBytes > 0 ? config.maxBytes : 10 * 1024 * 1024;
|
|
563
|
+
const len = Number(res.headers.get("content-length") ?? 0);
|
|
564
|
+
if (len > maxBytes) throw new Error(`http step: \u54CD\u5E94 ${len} \u5B57\u8282\u8D85\u8FC7\u4E0A\u9650 ${maxBytes}`);
|
|
565
|
+
let text;
|
|
566
|
+
if (res.body) {
|
|
567
|
+
const reader = res.body.getReader();
|
|
568
|
+
const chunks = [];
|
|
569
|
+
let total = 0;
|
|
570
|
+
for (; ; ) {
|
|
571
|
+
const { done, value } = await reader.read();
|
|
572
|
+
if (done) break;
|
|
573
|
+
total += value.length;
|
|
574
|
+
if (total > maxBytes) {
|
|
575
|
+
await reader.cancel();
|
|
576
|
+
throw new Error(`http step: \u54CD\u5E94\u8D85\u8FC7\u4E0A\u9650 ${maxBytes} \u5B57\u8282\uFF08\u53EF\u7ECF config.maxBytes \u8C03\u6574\uFF09`);
|
|
577
|
+
}
|
|
578
|
+
chunks.push(value);
|
|
579
|
+
}
|
|
580
|
+
text = new TextDecoder().decode(concatChunks(chunks));
|
|
581
|
+
} else {
|
|
582
|
+
text = await res.text();
|
|
583
|
+
}
|
|
584
|
+
const data = { status: res.status, text };
|
|
585
|
+
try {
|
|
586
|
+
data.json = JSON.parse(text);
|
|
587
|
+
} catch {
|
|
588
|
+
}
|
|
589
|
+
return data;
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
function concatChunks(chunks) {
|
|
593
|
+
const total = chunks.reduce((n, c) => n + c.length, 0);
|
|
594
|
+
const out = new Uint8Array(total);
|
|
595
|
+
let off = 0;
|
|
596
|
+
for (const c of chunks) {
|
|
597
|
+
out.set(c, off);
|
|
598
|
+
off += c.length;
|
|
599
|
+
}
|
|
600
|
+
return out;
|
|
601
|
+
}
|
|
602
|
+
function method_(m) {
|
|
603
|
+
const v = String(m ?? "GET").toUpperCase();
|
|
604
|
+
return ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"].includes(v) ? v : "GET";
|
|
605
|
+
}
|
|
606
|
+
var templateStep = {
|
|
607
|
+
type: "template",
|
|
608
|
+
label: "\u6570\u636E\u6A21\u677F",
|
|
609
|
+
fields: [{ name: "template", label: "\u6A21\u677F", type: "string" }],
|
|
610
|
+
required: ["template"],
|
|
611
|
+
run(config, ctx) {
|
|
612
|
+
return { text: interpolate(String(config.template), ctx) };
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
var logStep = {
|
|
616
|
+
type: "log",
|
|
617
|
+
label: "\u65E5\u5FD7",
|
|
618
|
+
fields: [{ name: "message", label: "\u6D88\u606F", type: "string" }],
|
|
619
|
+
required: [],
|
|
620
|
+
run(config, ctx, env) {
|
|
621
|
+
const text = interpolate(String(config.message ?? ""), ctx);
|
|
622
|
+
env.log?.(text);
|
|
623
|
+
return { text };
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
var aiStep = {
|
|
627
|
+
type: "ai",
|
|
628
|
+
label: "AI \u751F\u6210",
|
|
629
|
+
fields: [
|
|
630
|
+
{ name: "prompt", label: "\u63D0\u793A\u8BCD\uFF08\u652F\u6301 {{}} \u63D2\u503C\uFF09", type: "string" },
|
|
631
|
+
{ name: "system", label: "\u7CFB\u7EDF\u6307\u4EE4", type: "string" }
|
|
632
|
+
],
|
|
633
|
+
required: ["prompt"],
|
|
634
|
+
/** 外部 LLM 调用有成本——dry 打桩 */
|
|
635
|
+
effects: true,
|
|
636
|
+
async run(config, ctx, env) {
|
|
637
|
+
if (!env.ai?.chat) throw new Error("ai step: \u672A\u6CE8\u5165 ai \u9002\u914D\u5668\uFF08workflow({ ai })\uFF09");
|
|
638
|
+
const system = config.system ? interpolate(String(config.system), ctx) : void 0;
|
|
639
|
+
const user = interpolate(String(config.prompt), ctx);
|
|
640
|
+
const res = await env.ai.chat({
|
|
641
|
+
messages: [
|
|
642
|
+
...system ? [{ role: "system", content: system }] : [],
|
|
643
|
+
{ role: "user", content: user }
|
|
644
|
+
]
|
|
645
|
+
});
|
|
646
|
+
return { text: res.content };
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
var emailStep = {
|
|
650
|
+
type: "email",
|
|
651
|
+
label: "\u53D1\u9001\u90AE\u4EF6",
|
|
652
|
+
fields: [
|
|
653
|
+
{ name: "to", label: "\u6536\u4EF6\u4EBA", type: "string" },
|
|
654
|
+
{ name: "subject", label: "\u4E3B\u9898\uFF08\u652F\u6301 {{}} \u63D2\u503C\uFF09", type: "string" },
|
|
655
|
+
{ name: "body", label: "\u6B63\u6587\uFF08\u652F\u6301 {{}} \u63D2\u503C\uFF09", type: "string" }
|
|
656
|
+
],
|
|
657
|
+
required: ["to"],
|
|
658
|
+
/** 真发送——dry 打桩 */
|
|
659
|
+
effects: true,
|
|
660
|
+
async run(config, ctx, env) {
|
|
661
|
+
if (!env.email?.send) throw new Error("email step: \u672A\u6CE8\u5165 email \u9002\u914D\u5668\uFF08workflow({ email })\uFF09");
|
|
662
|
+
const to = interpolate(String(config.to), ctx);
|
|
663
|
+
const subject = config.subject ? interpolate(String(config.subject), ctx) : "workflow \u901A\u77E5";
|
|
664
|
+
const body = config.body ? interpolate(String(config.body), ctx) : "";
|
|
665
|
+
const res = await env.email.send({ to: to.split(",").map((s) => s.trim()).filter(Boolean), subject, body });
|
|
666
|
+
if (!res.ok) throw new Error(`email step: \u53D1\u9001\u5931\u8D25${res.id ? ` (${res.id})` : ""}`);
|
|
667
|
+
return { id: res.id };
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
var storeStep = {
|
|
671
|
+
type: "store",
|
|
672
|
+
label: "KV \u5B58\u50A8",
|
|
673
|
+
fields: [
|
|
674
|
+
{ name: "op", label: "\u64CD\u4F5C\uFF08get/set\uFF09", type: "string" },
|
|
675
|
+
{ name: "key", label: "\u952E", type: "string" },
|
|
676
|
+
{ name: "value", label: "\u503C\uFF08set \u65F6\uFF09", type: "string" }
|
|
677
|
+
],
|
|
678
|
+
required: ["op", "key"],
|
|
679
|
+
run: async (config, ctx, env) => {
|
|
680
|
+
const store = env.store;
|
|
681
|
+
if (!store) throw new Error("store \u6B65\u9AA4\u9700\u8981\u5B58\u50A8\u6CE8\u5165\uFF1Aworkflow({ store }) \u6216 workflow({ redis })");
|
|
682
|
+
const op = String(config.op);
|
|
683
|
+
const key = interpolate(String(config.key), ctx);
|
|
684
|
+
if (op === "get") {
|
|
685
|
+
return await store.get(key);
|
|
686
|
+
}
|
|
687
|
+
if (op === "set") {
|
|
688
|
+
const value = interpolate(String(config.value ?? ""), ctx);
|
|
689
|
+
await store.set(key, value);
|
|
690
|
+
return value;
|
|
691
|
+
}
|
|
692
|
+
throw new Error(`store \u672A\u77E5\u64CD\u4F5C\uFF1A'${op}'\uFF08get/set\uFF09`);
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
var steps = [httpStep, templateStep, logStep, aiStep, emailStep, storeStep];
|
|
696
|
+
function builtinSteps() {
|
|
697
|
+
return steps;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// src/server/workflow/runner.ts
|
|
701
|
+
async function runWorkflow(def, opts, env, registry) {
|
|
702
|
+
const mode = opts.mode ?? "live";
|
|
703
|
+
const dry = mode === "dry";
|
|
704
|
+
const startedAt = Date.now();
|
|
705
|
+
const steps2 = {};
|
|
706
|
+
const result = { status: "success", executed: [], skippedSteps: [], stepResults: steps2, dry, startedAt, finishedAt: 0 };
|
|
707
|
+
const budgetMs = opts.budgetMs ?? 6e5;
|
|
708
|
+
const budgetSignal = AbortSignal.timeout(budgetMs);
|
|
709
|
+
const signal = opts.signal ? AbortSignal.any([opts.signal, budgetSignal]) : budgetSignal;
|
|
710
|
+
const rt = { result, steps: steps2, dry, signal, budgetMs, def, fnDepth: 0 };
|
|
711
|
+
const flow = await execSteps(def.steps, { steps: steps2, vars: {}, input: opts.input }, env, registry, rt);
|
|
712
|
+
void flow;
|
|
713
|
+
result.finishedAt = Date.now();
|
|
714
|
+
return result;
|
|
715
|
+
}
|
|
716
|
+
async function execSteps(steps2, ctx, env, registry, rt) {
|
|
717
|
+
const { result, steps: out } = rt;
|
|
718
|
+
for (const step of steps2) {
|
|
719
|
+
if (rt.signal?.aborted) {
|
|
720
|
+
result.status = "error";
|
|
721
|
+
result.error = rt.signal.reason?.name === "TimeoutError" ? `\u6267\u884C\u8D85\u65F6\uFF08\u9884\u7B97 ${rt.budgetMs ?? 6e5}ms\u2014\u2014\u53EF\u7ECF budgetMs \u8C03\u6574\uFF09` : `aborted before step '${step.id}'`;
|
|
722
|
+
return { k: "return" };
|
|
723
|
+
}
|
|
724
|
+
if (step.when !== void 0) {
|
|
725
|
+
let pass;
|
|
726
|
+
try {
|
|
727
|
+
pass = toBoolean(evaluateExpr(step.when, ctx, `when of '${step.id}'`));
|
|
728
|
+
} catch (e) {
|
|
729
|
+
out[step.id] = { ok: false, error: `when \u8868\u8FBE\u5F0F\u9519\u8BEF\uFF1A${e.message}` };
|
|
730
|
+
result.status = "error";
|
|
731
|
+
result.error = `step '${step.id}': ${e.message}`;
|
|
732
|
+
return { k: "return" };
|
|
733
|
+
}
|
|
734
|
+
if (!pass) {
|
|
735
|
+
result.skippedSteps.push(step.id);
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
switch (step.type) {
|
|
740
|
+
case "assign": {
|
|
741
|
+
const cfg = step.config;
|
|
742
|
+
try {
|
|
743
|
+
ctx.vars[cfg.target] = evaluateExpr(cfg.value, ctx, `\u8D4B\u503C ${cfg.target}`);
|
|
744
|
+
out[step.id] = { ok: true, data: ctx.vars[cfg.target] };
|
|
745
|
+
result.executed.push(step.id);
|
|
746
|
+
} catch (e) {
|
|
747
|
+
return fail(rt, step, e);
|
|
748
|
+
}
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
case "if": {
|
|
752
|
+
const cfg = step.config;
|
|
753
|
+
const flow = await execIf(step, cfg, ctx, env, registry, rt);
|
|
754
|
+
if (flow.k !== "continue") return flow;
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
case "try": {
|
|
758
|
+
const cfg = step.config;
|
|
759
|
+
const flow = await execTry(step, cfg, ctx, env, registry, rt);
|
|
760
|
+
if (flow.k !== "continue") return flow;
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
case "while": {
|
|
764
|
+
const cfg = step.config;
|
|
765
|
+
const flow = await execWhile(step, cfg, ctx, env, registry, rt);
|
|
766
|
+
if (flow.k !== "continue") return flow;
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
case "for": {
|
|
770
|
+
const cfg = step.config;
|
|
771
|
+
const flow = await execFor(step, cfg, ctx, env, registry, rt);
|
|
772
|
+
if (flow.k !== "continue") return flow;
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
case "return": {
|
|
776
|
+
const cfg = step.config;
|
|
777
|
+
const value = cfg.value !== void 0 ? evaluateExpr(cfg.value, ctx, `return`) : void 0;
|
|
778
|
+
out[step.id] = { ok: true, data: value };
|
|
779
|
+
result.executed.push(step.id);
|
|
780
|
+
return { k: "return", value };
|
|
781
|
+
}
|
|
782
|
+
case "call": {
|
|
783
|
+
const cfg = step.config;
|
|
784
|
+
const fn = rt.def.functions?.find((f) => f.name === cfg.name);
|
|
785
|
+
if (!fn) {
|
|
786
|
+
out[step.id] = { ok: false, error: `\u672A\u77E5\u51FD\u6570\uFF1A'${cfg.name}'` };
|
|
787
|
+
result.status = "error";
|
|
788
|
+
result.error = `step '${step.id}': \u672A\u77E5\u51FD\u6570 '${cfg.name}'`;
|
|
789
|
+
return { k: "return" };
|
|
790
|
+
}
|
|
791
|
+
if (rt.fnDepth >= 64) {
|
|
792
|
+
out[step.id] = { ok: false, error: `\u51FD\u6570\u9012\u5F52\u6DF1\u5EA6\u8D85\u8FC7 64\uFF08${cfg.name}\uFF09` };
|
|
793
|
+
result.status = "error";
|
|
794
|
+
result.error = `step '${step.id}': \u51FD\u6570\u9012\u5F52\u6DF1\u5EA6\u8D85\u8FC7 64`;
|
|
795
|
+
return { k: "return" };
|
|
796
|
+
}
|
|
797
|
+
const localVars = {};
|
|
798
|
+
try {
|
|
799
|
+
for (let i = 0; i < fn.params.length; i++) {
|
|
800
|
+
const argSrc = cfg.args?.[i] ?? "undefined";
|
|
801
|
+
localVars[fn.params[i]] = evaluateExpr(argSrc, ctx, `\u51FD\u6570 '${cfg.name}' \u53C2\u6570 ${fn.params[i]}`);
|
|
802
|
+
}
|
|
803
|
+
} catch (e) {
|
|
804
|
+
out[step.id] = { ok: false, error: e.message };
|
|
805
|
+
result.status = "error";
|
|
806
|
+
result.error = `step '${step.id}': ${e.message}`;
|
|
807
|
+
return { k: "return" };
|
|
808
|
+
}
|
|
809
|
+
const fnCtx = { ...ctx, vars: localVars };
|
|
810
|
+
rt.fnDepth++;
|
|
811
|
+
const fnFlow = await execSteps(fn.step.steps, fnCtx, env, registry, rt);
|
|
812
|
+
rt.fnDepth--;
|
|
813
|
+
if (fnFlow.k === "continue") {
|
|
814
|
+
out[step.id] = { ok: true, data: void 0 };
|
|
815
|
+
} else {
|
|
816
|
+
out[step.id] = { ok: true, data: fnFlow.value };
|
|
817
|
+
}
|
|
818
|
+
result.executed.push(step.id);
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
const handler = registry.get(step.type);
|
|
823
|
+
if (!handler) {
|
|
824
|
+
out[step.id] = { ok: false, error: `\u672A\u6CE8\u518C\u6B65\u9AA4\u7C7B\u578B\uFF1A'${step.type}'` };
|
|
825
|
+
result.status = "error";
|
|
826
|
+
result.error = `step '${step.id}': \u672A\u6CE8\u518C\u7C7B\u578B '${step.type}'`;
|
|
827
|
+
return { k: "return" };
|
|
828
|
+
}
|
|
829
|
+
const config = step.config ?? {};
|
|
830
|
+
try {
|
|
831
|
+
let data;
|
|
832
|
+
if (rt.dry && handler.effects) {
|
|
833
|
+
data = void 0;
|
|
834
|
+
out[step.id] = { ok: true, dry: true };
|
|
835
|
+
} else {
|
|
836
|
+
data = await handler.run(config, ctx, env);
|
|
837
|
+
out[step.id] = { ok: true, data };
|
|
838
|
+
}
|
|
839
|
+
result.executed.push(step.id);
|
|
840
|
+
} catch (e) {
|
|
841
|
+
return fail(rt, step, e);
|
|
842
|
+
}
|
|
843
|
+
if (rt.signal?.aborted) {
|
|
844
|
+
result.status = "error";
|
|
845
|
+
result.error = rt.signal.reason?.name === "TimeoutError" ? `\u6267\u884C\u8D85\u65F6\uFF08\u9884\u7B97 ${rt.budgetMs ?? 6e5}ms\u2014\u2014\u53EF\u7ECF budgetMs \u8C03\u6574\uFF09` : `aborted after step '${step.id}'`;
|
|
846
|
+
return { k: "return" };
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
return { k: "continue" };
|
|
850
|
+
}
|
|
851
|
+
async function execIf(step, cfg, ctx, env, registry, rt) {
|
|
852
|
+
const { result, steps: out } = rt;
|
|
853
|
+
let satisfied;
|
|
854
|
+
try {
|
|
855
|
+
satisfied = toBoolean(evaluateExpr(cfg.when, ctx, `if '${step.id}' \u6761\u4EF6`));
|
|
856
|
+
} catch (e) {
|
|
857
|
+
out[step.id] = { ok: false, error: `if \u8868\u8FBE\u5F0F\u9519\u8BEF\uFF1A${e.message}` };
|
|
858
|
+
result.status = "error";
|
|
859
|
+
result.error = `step '${step.id}': ${e.message}`;
|
|
860
|
+
return { k: "return" };
|
|
861
|
+
}
|
|
862
|
+
out[step.id] = { ok: true, data: { satisfied } };
|
|
863
|
+
result.executed.push(step.id);
|
|
864
|
+
const chain = satisfied ? cfg.then : cfg.else ?? void 0;
|
|
865
|
+
return chain ? execSteps(chain.steps, ctx, env, registry, rt) : { k: "continue" };
|
|
866
|
+
}
|
|
867
|
+
async function execTry(step, cfg, ctx, env, registry, rt) {
|
|
868
|
+
const { result, steps: out } = rt;
|
|
869
|
+
const preError = result.error;
|
|
870
|
+
const f = await execSteps(cfg.step.steps, ctx, env, registry, rt);
|
|
871
|
+
if (f.k === "return" && result.status === "error" && result.error !== preError) {
|
|
872
|
+
const err = result.error ?? "\u672A\u77E5\u9519\u8BEF";
|
|
873
|
+
out[step.id] = { ok: false, error: err };
|
|
874
|
+
result.status = "success";
|
|
875
|
+
result.error = void 0;
|
|
876
|
+
result.executed.push(step.id);
|
|
877
|
+
if (cfg.catch?.steps.length) return execSteps(cfg.catch.steps, ctx, env, registry, rt);
|
|
878
|
+
return { k: "continue" };
|
|
879
|
+
}
|
|
880
|
+
out[step.id] = { ok: true, data: {} };
|
|
881
|
+
result.executed.push(step.id);
|
|
882
|
+
return f;
|
|
883
|
+
}
|
|
884
|
+
async function execWhile(step, cfg, ctx, env, registry, rt) {
|
|
885
|
+
const { result, steps: out } = rt;
|
|
886
|
+
const maxIters = cfg.maxIters ?? 1e3;
|
|
887
|
+
for (let i = 0; ; i++) {
|
|
888
|
+
if (rt.signal?.aborted) {
|
|
889
|
+
result.status = "error";
|
|
890
|
+
result.error = `aborted in while '${step.id}'`;
|
|
891
|
+
return { k: "return" };
|
|
892
|
+
}
|
|
893
|
+
let cond;
|
|
894
|
+
try {
|
|
895
|
+
cond = toBoolean(evaluateExpr(cfg.when, ctx, `while '${step.id}' \u6761\u4EF6`));
|
|
896
|
+
} catch (e) {
|
|
897
|
+
out[step.id] = { ok: false, error: `while \u8868\u8FBE\u5F0F\u9519\u8BEF\uFF1A${e.message}` };
|
|
898
|
+
result.status = "error";
|
|
899
|
+
result.error = `step '${step.id}': ${e.message}`;
|
|
900
|
+
return { k: "return" };
|
|
901
|
+
}
|
|
902
|
+
if (!cond) {
|
|
903
|
+
out[step.id] = { ok: true, data: { iterations: i } };
|
|
904
|
+
result.executed.push(step.id);
|
|
905
|
+
return { k: "continue" };
|
|
906
|
+
}
|
|
907
|
+
if (i >= maxIters) {
|
|
908
|
+
out[step.id] = { ok: false, error: `while \u8D85\u8FC7 maxIters=${maxIters}\uFF08\u9632\u6B7B\u5FAA\u73AF\uFF09` };
|
|
909
|
+
result.status = "error";
|
|
910
|
+
result.error = `step '${step.id}': while \u8D85\u8FC7 maxIters=${maxIters}`;
|
|
911
|
+
return { k: "return" };
|
|
912
|
+
}
|
|
913
|
+
const flow = await execSteps(cfg.step.steps, ctx, env, registry, rt);
|
|
914
|
+
if (flow.k !== "continue") return flow;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
async function execFor(step, cfg, ctx, env, registry, rt) {
|
|
918
|
+
const { result, steps: out } = rt;
|
|
919
|
+
let items;
|
|
920
|
+
try {
|
|
921
|
+
items = evaluateExpr(cfg.items, ctx, `for '${step.id}' items`);
|
|
922
|
+
} catch (e) {
|
|
923
|
+
out[step.id] = { ok: false, error: `for \u8868\u8FBE\u5F0F\u9519\u8BEF\uFF1A${e.message}` };
|
|
924
|
+
result.status = "error";
|
|
925
|
+
result.error = `step '${step.id}': ${e.message}`;
|
|
926
|
+
return { k: "return" };
|
|
927
|
+
}
|
|
928
|
+
if (!Array.isArray(items)) {
|
|
929
|
+
out[step.id] = { ok: false, error: `for items \u5FC5\u987B\u662F\u6570\u7EC4\uFF08\u5F97\u5230 ${String(items)}\uFF09` };
|
|
930
|
+
result.status = "error";
|
|
931
|
+
result.error = `step '${step.id}': for items \u975E\u6570\u7EC4`;
|
|
932
|
+
return { k: "return" };
|
|
933
|
+
}
|
|
934
|
+
const maxIters = cfg.maxIters ?? 1e3;
|
|
935
|
+
if (items.length > maxIters) {
|
|
936
|
+
out[step.id] = { ok: false, error: `for \u8D85\u8FC7 maxIters=${maxIters}\uFF08items ${items.length} \u9879\uFF09` };
|
|
937
|
+
result.status = "error";
|
|
938
|
+
result.error = `step '${step.id}': for \u8D85\u8FC7 maxIters=${maxIters}`;
|
|
939
|
+
return { k: "return" };
|
|
940
|
+
}
|
|
941
|
+
const prevLoop = ctx.loop;
|
|
942
|
+
for (let i = 0; i < items.length; i++) {
|
|
943
|
+
if (rt.signal?.aborted) {
|
|
944
|
+
result.status = "error";
|
|
945
|
+
result.error = `aborted in for '${step.id}'`;
|
|
946
|
+
return { k: "return" };
|
|
947
|
+
}
|
|
948
|
+
ctx.loop = { item: items[i], index: i };
|
|
949
|
+
const flow = await execSteps(cfg.step.steps, ctx, env, registry, rt);
|
|
950
|
+
if (flow.k !== "continue") {
|
|
951
|
+
ctx.loop = prevLoop;
|
|
952
|
+
return flow;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
ctx.loop = prevLoop;
|
|
956
|
+
out[step.id] = { ok: true, data: { iterations: items.length } };
|
|
957
|
+
result.executed.push(step.id);
|
|
958
|
+
return { k: "continue" };
|
|
959
|
+
}
|
|
960
|
+
function fail(rt, step, e) {
|
|
961
|
+
rt.steps[step.id] = { ok: false, error: e.message };
|
|
962
|
+
rt.result.status = "error";
|
|
963
|
+
rt.result.error = `step '${step.id}' (${step.type}): ${e.message}`;
|
|
964
|
+
return { k: "return" };
|
|
965
|
+
}
|
|
966
|
+
function evaluateExpr(src, ctx, where) {
|
|
967
|
+
try {
|
|
968
|
+
return compile(src, STD_FNS)(ctx);
|
|
969
|
+
} catch (e) {
|
|
970
|
+
throw new Error(`\u8868\u8FBE\u5F0F\u9519\u8BEF\uFF08${where}\uFF09\uFF1A${e.message}`);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// src/server/workflow/store.ts
|
|
975
|
+
function redisStore(redis) {
|
|
976
|
+
return {
|
|
977
|
+
get: async (key) => {
|
|
978
|
+
const v = await redis.command("GET", key);
|
|
979
|
+
return v === null || v === void 0 ? null : String(v);
|
|
980
|
+
},
|
|
981
|
+
set: async (key, value) => {
|
|
982
|
+
await redis.command("SET", key, value);
|
|
983
|
+
},
|
|
984
|
+
del: async (key) => {
|
|
985
|
+
await redis.command("DEL", key);
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// src/server/workflow/contracts.ts
|
|
991
|
+
var BUILTIN_TYPES = ["assign", "if", "try", "while", "for", "return", "call"];
|
|
992
|
+
|
|
993
|
+
// src/server/workflow/validate.ts
|
|
994
|
+
var MAX_DEPTH = 16;
|
|
995
|
+
function validate(def, registry) {
|
|
996
|
+
const errors = [];
|
|
997
|
+
const d = def;
|
|
998
|
+
if (!Array.isArray(d?.steps) || d.steps.length === 0) {
|
|
999
|
+
return { ok: false, errors: [{ path: "steps", message: "steps \u5FC5\u987B\u4E3A\u975E\u7A7A\u6570\u7EC4" }] };
|
|
1000
|
+
}
|
|
1001
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1002
|
+
const declaredVars = /* @__PURE__ */ new Set();
|
|
1003
|
+
const stepIds = /* @__PURE__ */ new Set();
|
|
1004
|
+
collectDecls(d, declaredVars, stepIds);
|
|
1005
|
+
validateChain(d.steps, "steps", registry, errors, seen, 0);
|
|
1006
|
+
const refChecks = [];
|
|
1007
|
+
collectRefs(d, refChecks);
|
|
1008
|
+
for (const rc of refChecks) {
|
|
1009
|
+
let ast;
|
|
1010
|
+
try {
|
|
1011
|
+
ast = parse(rc.expr);
|
|
1012
|
+
} catch {
|
|
1013
|
+
continue;
|
|
1014
|
+
}
|
|
1015
|
+
checkRefs(ast, declaredVars, stepIds, rc.path, errors);
|
|
1016
|
+
}
|
|
1017
|
+
return { ok: errors.length === 0, errors };
|
|
1018
|
+
}
|
|
1019
|
+
function collectDecls(def, declaredVars, stepIds) {
|
|
1020
|
+
const walk = (steps2) => {
|
|
1021
|
+
for (const s of steps2) {
|
|
1022
|
+
stepIds.add(s.id);
|
|
1023
|
+
const cfg = s.config ?? {};
|
|
1024
|
+
if (s.type === "assign" && typeof cfg.target === "string") declaredVars.add(cfg.target);
|
|
1025
|
+
const sub = cfg.then;
|
|
1026
|
+
if (sub && Array.isArray(sub.steps)) walk(sub.steps);
|
|
1027
|
+
const sub2 = cfg.else;
|
|
1028
|
+
if (sub2 && Array.isArray(sub2.steps)) walk(sub2.steps);
|
|
1029
|
+
const chain = cfg.step;
|
|
1030
|
+
if (chain && Array.isArray(chain.steps)) walk(chain.steps);
|
|
1031
|
+
const chainC = cfg.catch;
|
|
1032
|
+
if (chainC && Array.isArray(chainC.steps)) walk(chainC.steps);
|
|
1033
|
+
}
|
|
1034
|
+
};
|
|
1035
|
+
walk(def.steps);
|
|
1036
|
+
for (const f of def.functions ?? []) {
|
|
1037
|
+
for (const p of f.params ?? []) declaredVars.add(p);
|
|
1038
|
+
walk(f.step.steps);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
function collectRefs(def, out) {
|
|
1042
|
+
const walk = (steps2, prefix) => {
|
|
1043
|
+
for (const s of steps2) {
|
|
1044
|
+
const cfg = s.config ?? {};
|
|
1045
|
+
const p = `${prefix}/${s.id}`;
|
|
1046
|
+
if (s.type === "assign") {
|
|
1047
|
+
pushE(cfg.value, `${p}.value`);
|
|
1048
|
+
} else if (s.type === "if") {
|
|
1049
|
+
pushE(cfg.when, `${p}.when`);
|
|
1050
|
+
for (const b of ["then", "else"]) {
|
|
1051
|
+
const chain = cfg[b];
|
|
1052
|
+
if (chain && Array.isArray(chain.steps)) walk(chain.steps, `${p}.${b}`);
|
|
1053
|
+
}
|
|
1054
|
+
} else if (s.type === "while") {
|
|
1055
|
+
pushE(cfg.when, `${p}.when`);
|
|
1056
|
+
} else if (s.type === "for") {
|
|
1057
|
+
pushE(cfg.items, `${p}.items`);
|
|
1058
|
+
} else if (s.type === "return") {
|
|
1059
|
+
pushE(cfg.value, `${p}.value`);
|
|
1060
|
+
} else if (s.type === "call") {
|
|
1061
|
+
for (const [i, a] of (cfg.args ?? []).entries()) pushE(a, `${p}.args[${i}]`);
|
|
1062
|
+
} else {
|
|
1063
|
+
for (const [k, v] of Object.entries(cfg)) {
|
|
1064
|
+
if (typeof v === "string") pushTemplate(v, `${p}.${k}`);
|
|
1065
|
+
else if (v && typeof v === "object" && !Array.isArray(v)) walkObj(v, `${p}.${k}`);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
if (typeof s.when === "string") pushE(s.when, `${p}.when`);
|
|
1069
|
+
}
|
|
1070
|
+
};
|
|
1071
|
+
const walkObj = (obj, prefix) => {
|
|
1072
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
1073
|
+
if (typeof v === "string") pushTemplate(v, `${prefix}.${k}`);
|
|
1074
|
+
else if (v && typeof v === "object" && !Array.isArray(v)) walkObj(v, `${prefix}.${k}`);
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
const pushE = (v, path) => {
|
|
1078
|
+
if (typeof v === "string") out.push({ path, expr: v });
|
|
1079
|
+
};
|
|
1080
|
+
const pushTemplate = (v, path) => {
|
|
1081
|
+
for (const m of v.matchAll(/\{\{([\s\S]+?)\}\}/g)) out.push({ path, expr: m[1].trim() });
|
|
1082
|
+
};
|
|
1083
|
+
walk(def.steps, "steps");
|
|
1084
|
+
for (const f of def.functions ?? []) walk(f.step.steps, `functions.${f.name}`);
|
|
1085
|
+
}
|
|
1086
|
+
function checkRefs(node, declaredVars, stepIds, path, errors) {
|
|
1087
|
+
if (!node || typeof node !== "object") return;
|
|
1088
|
+
if ("segments" in node && Array.isArray(node.segments)) {
|
|
1089
|
+
const first = node.segments[0];
|
|
1090
|
+
if (first === "vars" && node.segments[1] === "loop") return;
|
|
1091
|
+
if (first === "vars" && typeof node.segments[1] === "string" && !declaredVars.has(node.segments[1])) {
|
|
1092
|
+
errors.push({ path, message: `\u672A\u58F0\u660E\u53D8\u91CF\u5F15\u7528\uFF1Avars.${node.segments[1]}\uFF08\u987B\u6709 assign target \u58F0\u660E\uFF09` });
|
|
1093
|
+
} else if (first === "steps" && typeof node.segments[1] === "string" && !stepIds.has(node.segments[1])) {
|
|
1094
|
+
errors.push({ path, message: `\u672A\u77E5\u6B65\u9AA4\u5F15\u7528\uFF1Asteps.${node.segments[1]}\uFF08\u987B\u662F\u5DF2\u5B9A\u4E49\u6B65\u9AA4 id\uFF09` });
|
|
1095
|
+
}
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
for (const v of Object.values(node)) {
|
|
1099
|
+
if (v && typeof v === "object") checkRefs(v, declaredVars, stepIds, path, errors);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
function validateChain(steps2, prefix, registry, errors, seen, depth) {
|
|
1103
|
+
if (depth > MAX_DEPTH) {
|
|
1104
|
+
errors.push({ path: prefix, message: `\u5B50\u94FE\u5D4C\u5957\u8D85\u8FC7 ${MAX_DEPTH} \u5C42` });
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
for (const [i, step] of steps2.entries()) {
|
|
1108
|
+
const p = `${prefix}[${i}]`;
|
|
1109
|
+
if (!step || typeof step !== "object") {
|
|
1110
|
+
errors.push({ path: p, message: "step \u5FC5\u987B\u662F\u5BF9\u8C61" });
|
|
1111
|
+
continue;
|
|
1112
|
+
}
|
|
1113
|
+
if (!step.id || typeof step.id !== "string") errors.push({ path: `${p}.id`, message: "\u5FC5\u586B\uFF1A\u552F\u4E00 id" });
|
|
1114
|
+
else if (seen.has(step.id)) errors.push({ path: `${p}.id`, message: `id \u91CD\u590D\uFF1A'${step.id}'` });
|
|
1115
|
+
else seen.add(step.id);
|
|
1116
|
+
if (!step.type || typeof step.type !== "string") {
|
|
1117
|
+
errors.push({ path: `${p}.type`, message: "\u5FC5\u586B\uFF1A\u6B65\u9AA4\u7C7B\u578B" });
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
if (!BUILTIN_TYPES.includes(step.type) && !registry.has(step.type)) {
|
|
1121
|
+
errors.push({ path: `${p}.type`, message: `\u672A\u6CE8\u518C\u7684\u6B65\u9AA4\u7C7B\u578B\uFF1A'${step.type}'` });
|
|
1122
|
+
continue;
|
|
1123
|
+
}
|
|
1124
|
+
const config = step.config ?? {};
|
|
1125
|
+
switch (step.type) {
|
|
1126
|
+
case "assign":
|
|
1127
|
+
checkExprField(config, "target", "\u76EE\u6807\u53D8\u91CF\u540D", errors, `${p}.config.target`, false);
|
|
1128
|
+
checkExprField(config, "value", "\u8D4B\u503C\u8868\u8FBE\u5F0F", errors, `${p}.config.value`, false);
|
|
1129
|
+
break;
|
|
1130
|
+
case "if": {
|
|
1131
|
+
checkExprField(config, "when", "\u6761\u4EF6\u8868\u8FBE\u5F0F", errors, `${p}.config.when`, false);
|
|
1132
|
+
for (const branch of ["then", "else"]) {
|
|
1133
|
+
const chain = config[branch];
|
|
1134
|
+
if (chain !== void 0) {
|
|
1135
|
+
if (!Array.isArray(chain.steps)) errors.push({ path: `${p}.config.${branch}.steps`, message: "\u5FC5\u987B\u662F\u6B65\u9AA4\u6570\u7EC4" });
|
|
1136
|
+
else validateChain(chain.steps, `${p}.config.${branch}.steps`, registry, errors, seen, depth + 1);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
break;
|
|
1140
|
+
}
|
|
1141
|
+
case "try": {
|
|
1142
|
+
validateChainField(config, "step", registry, errors, seen, depth, p, "steps");
|
|
1143
|
+
const cchain = config.catch;
|
|
1144
|
+
if (!cchain || !Array.isArray(cchain.steps)) {
|
|
1145
|
+
errors.push({ path: `${p}.config.catch.steps`, message: "catch \u94FE\u5FC5\u987B\u662F\u6B65\u9AA4\u6570\u7EC4\uFF08v0 try \u5FC5\u914D catch\uFF09" });
|
|
1146
|
+
} else {
|
|
1147
|
+
validateChain(cchain.steps, `${p}.config.catch.steps`, registry, errors, seen, depth + 1);
|
|
1148
|
+
}
|
|
1149
|
+
break;
|
|
1150
|
+
}
|
|
1151
|
+
case "while": {
|
|
1152
|
+
checkExprField(config, "when", "\u5FAA\u73AF\u6761\u4EF6", errors, `${p}.config.when`, false);
|
|
1153
|
+
validateChainField(config, "step", registry, errors, seen, depth, p, "steps");
|
|
1154
|
+
break;
|
|
1155
|
+
}
|
|
1156
|
+
case "for": {
|
|
1157
|
+
checkExprField(config, "items", "\u96C6\u5408\u8868\u8FBE\u5F0F", errors, `${p}.config.items`, false);
|
|
1158
|
+
validateChainField(config, "step", registry, errors, seen, depth, p, "steps");
|
|
1159
|
+
break;
|
|
1160
|
+
}
|
|
1161
|
+
case "return": {
|
|
1162
|
+
if (config.value !== void 0) checkExprField(config, "value", "\u8FD4\u56DE\u503C\u8868\u8FBE\u5F0F", errors, `${p}.config.value`, true);
|
|
1163
|
+
break;
|
|
1164
|
+
}
|
|
1165
|
+
default: {
|
|
1166
|
+
const schema = registry.schema(step.type);
|
|
1167
|
+
for (const field of schema?.required ?? []) {
|
|
1168
|
+
if (typeof config[field] !== "string" || config[field] === "") {
|
|
1169
|
+
errors.push({ path: `${p}.config.${field}`, message: `\u5FC5\u586B\uFF1A${field}` });
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
if (step.when !== void 0 && typeof step.when !== "string") {
|
|
1175
|
+
errors.push({ path: `${p}.when`, message: "\u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u8868\u8FBE\u5F0F" });
|
|
1176
|
+
} else if (typeof step.when === "string") {
|
|
1177
|
+
try {
|
|
1178
|
+
compile(step.when);
|
|
1179
|
+
} catch (e) {
|
|
1180
|
+
errors.push({ path: `${p}.when`, message: `\u8868\u8FBE\u5F0F\u9519\u8BEF\uFF1A${e.message}` });
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
function validateChainField(config, field, registry, errors, seen, depth, p, ctxPath) {
|
|
1186
|
+
const chain = config[field];
|
|
1187
|
+
if (!chain || !Array.isArray(chain.steps)) {
|
|
1188
|
+
errors.push({ path: `${p}.config.${field}.${ctxPath}`, message: "\u5FC5\u987B\u662F\u6B65\u9AA4\u6570\u7EC4" });
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
validateChain(chain.steps, `${p}.config.${field}.${ctxPath}`, registry, errors, seen, depth + 1);
|
|
1192
|
+
}
|
|
1193
|
+
function checkExprField(config, field, label, errors, path, optional) {
|
|
1194
|
+
const v = config[field];
|
|
1195
|
+
if (typeof v !== "string" || v === "") {
|
|
1196
|
+
if (!optional) errors.push({ path, message: `\u5FC5\u586B\uFF1A${label}` });
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
try {
|
|
1200
|
+
compile(v);
|
|
1201
|
+
} catch (e) {
|
|
1202
|
+
errors.push({ path, message: `\u8868\u8FBE\u5F0F\u9519\u8BEF\uFF1A${e.message}` });
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// src/server/workflow/views.ts
|
|
1207
|
+
function toJsonSchema(schemas) {
|
|
1208
|
+
const properties = {};
|
|
1209
|
+
for (const s of schemas) {
|
|
1210
|
+
const fieldProps = {};
|
|
1211
|
+
for (const f of s.fields ?? []) {
|
|
1212
|
+
fieldProps[f.name] = {
|
|
1213
|
+
type: f.type === "number" ? "number" : f.type === "boolean" ? "boolean" : "string",
|
|
1214
|
+
title: f.label,
|
|
1215
|
+
placeholder: f.placeholder
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
properties[s.type] = {
|
|
1219
|
+
type: "object",
|
|
1220
|
+
title: s.label,
|
|
1221
|
+
properties: fieldProps,
|
|
1222
|
+
...s.required && s.required.length ? { required: s.required } : {}
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
return { type: "object", title: "Workflow", properties };
|
|
1226
|
+
}
|
|
1227
|
+
var STEP_LABELS = {
|
|
1228
|
+
http: "HTTP",
|
|
1229
|
+
assign: "\u8D4B\u503C",
|
|
1230
|
+
call: "\u8C03\u7528",
|
|
1231
|
+
if: "\u6761\u4EF6",
|
|
1232
|
+
while: "\u5FAA\u73AF",
|
|
1233
|
+
for: "\u904D\u5386",
|
|
1234
|
+
return: "\u8FD4\u56DE",
|
|
1235
|
+
store: "\u5B58\u50A8",
|
|
1236
|
+
email: "\u90AE\u4EF6",
|
|
1237
|
+
ai: "AI",
|
|
1238
|
+
template: "\u6A21\u677F",
|
|
1239
|
+
log: "\u65E5\u5FD7"
|
|
1240
|
+
};
|
|
1241
|
+
function chainSummary(type, steps2, elseSteps) {
|
|
1242
|
+
if (type === "if") {
|
|
1243
|
+
const thenN = steps2?.length ?? 0;
|
|
1244
|
+
const elseN = elseSteps?.length ?? 0;
|
|
1245
|
+
return `then\xD7${thenN}${elseN ? `/else\xD7${elseN}` : ""}`;
|
|
1246
|
+
}
|
|
1247
|
+
if (type === "while" || type === "for") return `\xD7${steps2?.length ?? 0} \u5B50\u6B65\u9AA4`;
|
|
1248
|
+
return "";
|
|
1249
|
+
}
|
|
1250
|
+
function workflowToDag(def, opts) {
|
|
1251
|
+
const labels = opts?.labels ?? {};
|
|
1252
|
+
const nodes = [];
|
|
1253
|
+
const edges = [];
|
|
1254
|
+
const steps2 = def.steps ?? [];
|
|
1255
|
+
for (const [i, s] of steps2.entries()) {
|
|
1256
|
+
const cfg = s.config ?? {};
|
|
1257
|
+
let label = `${labels[s.type] ?? STEP_LABELS[s.type] ?? s.type} ${s.id}`;
|
|
1258
|
+
if (s.type === "if") {
|
|
1259
|
+
const then = cfg.then?.steps;
|
|
1260
|
+
const els = cfg.else?.steps;
|
|
1261
|
+
const sum = chainSummary("if", then, els);
|
|
1262
|
+
if (sum) label += `\uFF08${sum}\uFF09`;
|
|
1263
|
+
} else if (s.type === "while" || s.type === "for") {
|
|
1264
|
+
const sub = cfg.step?.steps;
|
|
1265
|
+
const sum = chainSummary(s.type, sub);
|
|
1266
|
+
if (sum) label += `\uFF08${sum}\uFF09`;
|
|
1267
|
+
}
|
|
1268
|
+
nodes.push({ id: s.id, label });
|
|
1269
|
+
if (i > 0) edges.push({ from: steps2[i - 1].id, to: s.id });
|
|
1270
|
+
}
|
|
1271
|
+
return { nodes, edges };
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
// src/server/workflow/wfjs.ts
|
|
1275
|
+
var KEYWORDS = /* @__PURE__ */ new Set(["const", "let", "var", "if", "else", "while", "for", "of", "return", "function", "import", "export", "from", "as", "default", "await", "async", "once", "true", "false", "null", "try", "catch", "finally"]);
|
|
1276
|
+
var PUNCS = /* @__PURE__ */ new Set(["{", "}", "(", ")", "[", "]", ";", ",", ":", "."]);
|
|
1277
|
+
var BINARY_OPS = /* @__PURE__ */ new Set(["+", "-", "*", "/", "%", "<", ">", "<=", ">=", "==", "!=", "===", "!==", "&&", "||"]);
|
|
1278
|
+
var isBinaryOp = (v) => BINARY_OPS.has(v);
|
|
1279
|
+
var TWO_OPS = /* @__PURE__ */ new Set(["==", "!=", "<=", ">=", "&&", "||", "+=", "-=", "*=", "/=", "%=", "++", "--"]);
|
|
1280
|
+
var ONE_OPS = /* @__PURE__ */ new Set(["=", "+", "-", "*", "/", "%", "<", ">", "!", "?"]);
|
|
1281
|
+
function tokenizeWfjs(src) {
|
|
1282
|
+
const tokens = [];
|
|
1283
|
+
let i = 0;
|
|
1284
|
+
const n = src.length;
|
|
1285
|
+
while (i < n) {
|
|
1286
|
+
const ch = src[i];
|
|
1287
|
+
if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
|
|
1288
|
+
i++;
|
|
1289
|
+
continue;
|
|
1290
|
+
}
|
|
1291
|
+
if (ch === "#" || ch === "/" && src[i + 1] === "/") {
|
|
1292
|
+
while (i < n && src[i] !== "\n") i++;
|
|
1293
|
+
continue;
|
|
1294
|
+
}
|
|
1295
|
+
if (ch === "`") {
|
|
1296
|
+
let j = i + 1;
|
|
1297
|
+
let raw = "";
|
|
1298
|
+
while (j < n && src[j] !== "`") {
|
|
1299
|
+
if (src[j] === "\\") {
|
|
1300
|
+
raw += src[j] + (src[j + 1] ?? "");
|
|
1301
|
+
j += 2;
|
|
1302
|
+
continue;
|
|
1303
|
+
}
|
|
1304
|
+
raw += src[j];
|
|
1305
|
+
j++;
|
|
1306
|
+
}
|
|
1307
|
+
if (j >= n) throw new Error(`wfjs: unterminated template string at ${i}`);
|
|
1308
|
+
tokens.push({ t: "template", raw, v: "", pos: i, end: j + 1 });
|
|
1309
|
+
i = j + 1;
|
|
1310
|
+
continue;
|
|
1311
|
+
}
|
|
1312
|
+
if (ch === '"' || ch === "'") {
|
|
1313
|
+
const quote = ch;
|
|
1314
|
+
let j = i + 1;
|
|
1315
|
+
let out = "";
|
|
1316
|
+
while (j < n && src[j] !== quote) {
|
|
1317
|
+
if (src[j] === "\\") {
|
|
1318
|
+
const e = src[j + 1];
|
|
1319
|
+
if (e === "n") {
|
|
1320
|
+
out += "\n";
|
|
1321
|
+
j += 2;
|
|
1322
|
+
continue;
|
|
1323
|
+
}
|
|
1324
|
+
if (e === "t") {
|
|
1325
|
+
out += " ";
|
|
1326
|
+
j += 2;
|
|
1327
|
+
continue;
|
|
1328
|
+
}
|
|
1329
|
+
out += e ?? "";
|
|
1330
|
+
j += 2;
|
|
1331
|
+
continue;
|
|
1332
|
+
}
|
|
1333
|
+
out += src[j];
|
|
1334
|
+
j++;
|
|
1335
|
+
}
|
|
1336
|
+
if (j >= n) throw new Error(`wfjs: unterminated string at ${i}`);
|
|
1337
|
+
tokens.push({ t: "str", v: out, pos: i, end: j + 1 });
|
|
1338
|
+
i = j + 1;
|
|
1339
|
+
continue;
|
|
1340
|
+
}
|
|
1341
|
+
const three = src.slice(i, i + 3);
|
|
1342
|
+
if (three === "===" || three === "!==") {
|
|
1343
|
+
tokens.push({ t: "op", v: three, pos: i, end: i + 3 });
|
|
1344
|
+
i += 3;
|
|
1345
|
+
continue;
|
|
1346
|
+
}
|
|
1347
|
+
const two = src.slice(i, i + 2);
|
|
1348
|
+
if (TWO_OPS.has(two)) {
|
|
1349
|
+
tokens.push({ t: "op", v: two, pos: i, end: i + 2 });
|
|
1350
|
+
i += 2;
|
|
1351
|
+
continue;
|
|
1352
|
+
}
|
|
1353
|
+
if (ONE_OPS.has(ch)) {
|
|
1354
|
+
tokens.push({ t: "op", v: ch, pos: i, end: i + 1 });
|
|
1355
|
+
i++;
|
|
1356
|
+
continue;
|
|
1357
|
+
}
|
|
1358
|
+
if (PUNCS.has(ch)) {
|
|
1359
|
+
tokens.push({ t: "punc", v: ch, pos: i, end: i + 1 });
|
|
1360
|
+
i++;
|
|
1361
|
+
continue;
|
|
1362
|
+
}
|
|
1363
|
+
if (ch >= "0" && ch <= "9") {
|
|
1364
|
+
const m2 = /[0-9]+(?:\.[0-9]+)?/.exec(src.slice(i));
|
|
1365
|
+
tokens.push({ t: "num", v: Number(m2[0]), pos: i, end: i + m2[0].length });
|
|
1366
|
+
i += m2[0].length;
|
|
1367
|
+
continue;
|
|
1368
|
+
}
|
|
1369
|
+
const m = /[A-Za-z_$][A-Za-z0-9_$]*/.exec(src.slice(i));
|
|
1370
|
+
if (m) {
|
|
1371
|
+
tokens.push(KEYWORDS.has(m[0]) ? { t: "kw", v: m[0], pos: i, end: i + m[0].length } : { t: "ident", v: m[0], pos: i, end: i + m[0].length });
|
|
1372
|
+
i += m[0].length;
|
|
1373
|
+
continue;
|
|
1374
|
+
}
|
|
1375
|
+
throw new Error(`wfjs: unexpected character '${ch}' at ${i} (in ${JSON.stringify(src)})`);
|
|
1376
|
+
}
|
|
1377
|
+
tokens.push({ t: "eof", v: "", pos: n, end: n });
|
|
1378
|
+
return tokens;
|
|
1379
|
+
}
|
|
1380
|
+
var WfjsParser = class {
|
|
1381
|
+
i = 0;
|
|
1382
|
+
tokens;
|
|
1383
|
+
src;
|
|
1384
|
+
constructor(tokens, src) {
|
|
1385
|
+
this.tokens = tokens;
|
|
1386
|
+
this.src = src;
|
|
1387
|
+
}
|
|
1388
|
+
peek() {
|
|
1389
|
+
return this.tokens[this.i];
|
|
1390
|
+
}
|
|
1391
|
+
next() {
|
|
1392
|
+
return this.tokens[this.i++];
|
|
1393
|
+
}
|
|
1394
|
+
fail(msg, pos) {
|
|
1395
|
+
throw new Error(`${msg} at ${pos} (in ${JSON.stringify(this.src)})`);
|
|
1396
|
+
}
|
|
1397
|
+
expect(v) {
|
|
1398
|
+
const tk = this.next();
|
|
1399
|
+
if (tk.v !== v) this.fail(`wfjs: expected '${v}'`, tk.pos);
|
|
1400
|
+
return tk;
|
|
1401
|
+
}
|
|
1402
|
+
isPunc(v) {
|
|
1403
|
+
const tk = this.peek();
|
|
1404
|
+
return tk.t === "punc" && tk.v === v;
|
|
1405
|
+
}
|
|
1406
|
+
isKw(v) {
|
|
1407
|
+
const tk = this.peek();
|
|
1408
|
+
return tk.t === "kw" && tk.v === v;
|
|
1409
|
+
}
|
|
1410
|
+
parseProgram() {
|
|
1411
|
+
const stmts = [];
|
|
1412
|
+
while (this.peek().t !== "eof") {
|
|
1413
|
+
if (this.isPunc(";")) {
|
|
1414
|
+
this.next();
|
|
1415
|
+
continue;
|
|
1416
|
+
}
|
|
1417
|
+
stmts.push(this.parseStmt());
|
|
1418
|
+
if (this.isPunc(";")) this.next();
|
|
1419
|
+
}
|
|
1420
|
+
return stmts;
|
|
1421
|
+
}
|
|
1422
|
+
parseStmt() {
|
|
1423
|
+
if (this.peek().t === "kw") {
|
|
1424
|
+
const v = this.peek().v;
|
|
1425
|
+
if (v === "const" || v === "let" || v === "var") return this.parseVar();
|
|
1426
|
+
if (v === "if") return this.parseIf();
|
|
1427
|
+
if (v === "try") return this.parseTry();
|
|
1428
|
+
if (v === "while") return this.parseWhile();
|
|
1429
|
+
if (v === "for") return this.parseFor();
|
|
1430
|
+
if (v === "return") return this.parseReturn();
|
|
1431
|
+
if (v === "async") {
|
|
1432
|
+
this.next();
|
|
1433
|
+
return this.parseStmt();
|
|
1434
|
+
}
|
|
1435
|
+
if (v === "await") {
|
|
1436
|
+
this.next();
|
|
1437
|
+
return this.parseStmt();
|
|
1438
|
+
}
|
|
1439
|
+
if (v === "import") return this.parseImport();
|
|
1440
|
+
if (v === "function") return this.parseFunction();
|
|
1441
|
+
if (v === "export") return this.parseExport();
|
|
1442
|
+
}
|
|
1443
|
+
if (this.peek().t === "ident") return this.parseAssignOrCall();
|
|
1444
|
+
this.fail(`wfjs: unexpected '${this.peek().t === "eof" ? "(end)" : String(this.peek().v)}'`, this.peek().pos);
|
|
1445
|
+
}
|
|
1446
|
+
parseTry() {
|
|
1447
|
+
this.expect("try");
|
|
1448
|
+
this.expect("{");
|
|
1449
|
+
const body = this.parseBlockBody();
|
|
1450
|
+
if (!this.isKw("catch")) this.fail(`wfjs: try \u5FC5\u987B\u914D catch\uFF08v0 \u65E0 finally\uFF09\u2014\u2014\u671F\u671B catch`, this.peek().pos);
|
|
1451
|
+
this.next();
|
|
1452
|
+
if (this.peek().t === "punc" && this.peek().v === "(") {
|
|
1453
|
+
this.fail("wfjs: catch \u7ED1\u5B9A\u53D8\u91CF\u6682\u4E0D\u652F\u6301\uFF08ES2019 \u53EF\u9009\u7ED1\u5B9A\u8BED\u4E49\uFF09\u2014\u2014\u9519\u8BEF\u4ECE steps.<tryId>.error \u8BFB\u53D6", this.peek().pos);
|
|
1454
|
+
}
|
|
1455
|
+
this.expect("{");
|
|
1456
|
+
const catchBody = this.parseBlockBody();
|
|
1457
|
+
return { k: "try", body, catchBody };
|
|
1458
|
+
}
|
|
1459
|
+
parseBlockBody() {
|
|
1460
|
+
const stmts = [];
|
|
1461
|
+
while (!this.isPunc("}")) {
|
|
1462
|
+
if (this.peek().t === "eof") this.fail(`wfjs: unclosed block`, this.peek().pos);
|
|
1463
|
+
if (this.isPunc(";")) {
|
|
1464
|
+
this.next();
|
|
1465
|
+
continue;
|
|
1466
|
+
}
|
|
1467
|
+
stmts.push(this.parseStmt());
|
|
1468
|
+
if (this.isPunc(";")) this.next();
|
|
1469
|
+
}
|
|
1470
|
+
this.next();
|
|
1471
|
+
return stmts;
|
|
1472
|
+
}
|
|
1473
|
+
/** export { a, b as c } / export default f——v1 仅函数导出 */
|
|
1474
|
+
parseExport() {
|
|
1475
|
+
this.next();
|
|
1476
|
+
if (this.isKw("default")) {
|
|
1477
|
+
this.next();
|
|
1478
|
+
const name = this.next();
|
|
1479
|
+
if (name.t !== "ident") this.fail("wfjs: export default \u540E\u5FC5\u987B\u662F\u51FD\u6570\u540D", name.pos);
|
|
1480
|
+
return { k: "export", named: [], default: String(name.v) };
|
|
1481
|
+
}
|
|
1482
|
+
this.expect("{");
|
|
1483
|
+
const named = [];
|
|
1484
|
+
while (!this.isPunc("}")) {
|
|
1485
|
+
const name = this.next();
|
|
1486
|
+
if (name.t !== "ident") this.fail("wfjs: export \u6210\u5458\u540D\u5FC5\u987B\u662F\u6807\u8BC6\u7B26", name.pos);
|
|
1487
|
+
let asName = name.v;
|
|
1488
|
+
if (this.isKw("as")) {
|
|
1489
|
+
this.next();
|
|
1490
|
+
const a = this.next();
|
|
1491
|
+
if (a.t !== "ident") this.fail("wfjs: as \u540E\u5FC5\u987B\u662F\u6807\u8BC6\u7B26", a.pos);
|
|
1492
|
+
asName = a.v;
|
|
1493
|
+
}
|
|
1494
|
+
if (asName !== name.v) named.push({ name: name.v, as: asName });
|
|
1495
|
+
else named.push({ name: name.v });
|
|
1496
|
+
if (this.isPunc(",")) {
|
|
1497
|
+
this.next();
|
|
1498
|
+
continue;
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
this.next();
|
|
1502
|
+
return { k: "export", named, default: void 0 };
|
|
1503
|
+
}
|
|
1504
|
+
/** function name(a, b) { body }——先声明后调用(函数提升 v2 裁剪) */
|
|
1505
|
+
parseFunction() {
|
|
1506
|
+
this.next();
|
|
1507
|
+
const name = this.next();
|
|
1508
|
+
if (name.t !== "ident") this.fail("wfjs: function \u540E\u5FC5\u987B\u662F\u51FD\u6570\u540D", name.pos);
|
|
1509
|
+
this.expect("(");
|
|
1510
|
+
const params = [];
|
|
1511
|
+
if (!this.isPunc(")")) {
|
|
1512
|
+
for (; ; ) {
|
|
1513
|
+
const p = this.next();
|
|
1514
|
+
if (p.t !== "ident") this.fail("wfjs: \u53C2\u6570\u5FC5\u987B\u662F\u6807\u8BC6\u7B26", p.pos);
|
|
1515
|
+
params.push(String(p.v));
|
|
1516
|
+
if (this.isPunc(",")) {
|
|
1517
|
+
this.next();
|
|
1518
|
+
continue;
|
|
1519
|
+
}
|
|
1520
|
+
break;
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
this.expect(")");
|
|
1524
|
+
this.expect("{");
|
|
1525
|
+
const body = this.parseBlockBody();
|
|
1526
|
+
return { k: "function", name: String(name.v), params, body };
|
|
1527
|
+
}
|
|
1528
|
+
/** parseBlockBody 在类中——检查存在 */
|
|
1529
|
+
parseImport() {
|
|
1530
|
+
this.next();
|
|
1531
|
+
if (!this.isPunc("{")) this.fail(`wfjs: import \u4EC5\u652F\u6301\u547D\u540D\u5BFC\u5165\uFF08{ ... }\uFF09\u2014\u2014\u9ED8\u8BA4/\u547D\u540D\u7A7A\u95F4\u5BFC\u5165 W8`, this.peek().pos);
|
|
1532
|
+
this.next();
|
|
1533
|
+
const names = [];
|
|
1534
|
+
while (!this.isPunc("}")) {
|
|
1535
|
+
const name = this.next();
|
|
1536
|
+
if (name.t !== "ident") this.fail(`wfjs: import \u6210\u5458\u540D\u5FC5\u987B\u662F\u6807\u8BC6\u7B26`, name.pos);
|
|
1537
|
+
let asName = name.v;
|
|
1538
|
+
if (this.isKw("as")) {
|
|
1539
|
+
this.next();
|
|
1540
|
+
const a = this.next();
|
|
1541
|
+
if (a.t !== "ident") this.fail("wfjs: as \u540E\u5FC5\u987B\u662F\u6807\u8BC6\u7B26", a.pos);
|
|
1542
|
+
asName = a.v;
|
|
1543
|
+
}
|
|
1544
|
+
if (asName !== name.v) names.push({ name: name.v, as: asName });
|
|
1545
|
+
else names.push({ name: name.v });
|
|
1546
|
+
if (this.isPunc(",")) {
|
|
1547
|
+
this.next();
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
this.next();
|
|
1552
|
+
this.expect("from");
|
|
1553
|
+
const from = this.next();
|
|
1554
|
+
if (from.t !== "str") this.fail("wfjs: import \u6765\u6E90\u5FC5\u987B\u662F\u5B57\u7B26\u4E32", from.pos);
|
|
1555
|
+
return { k: "import", names, from: String(from.v) };
|
|
1556
|
+
}
|
|
1557
|
+
/** 方法式调用解析(store.get('k') / store.set('k', v))——返回 null 表示非方法调用 */
|
|
1558
|
+
tryParseMethodCall() {
|
|
1559
|
+
const tk = this.peek();
|
|
1560
|
+
const dot = this.tokens[this.i + 1];
|
|
1561
|
+
const method = this.tokens[this.i + 2];
|
|
1562
|
+
const paren = this.tokens[this.i + 3];
|
|
1563
|
+
if (tk.t !== "ident" || dot?.t !== "punc" || dot.v !== "." || method?.t !== "ident" || paren?.t !== "punc" || paren.v !== "(") return null;
|
|
1564
|
+
const obj = String(tk.v);
|
|
1565
|
+
const name = String(method.v);
|
|
1566
|
+
this.next();
|
|
1567
|
+
this.next();
|
|
1568
|
+
this.next();
|
|
1569
|
+
this.next();
|
|
1570
|
+
const args = [];
|
|
1571
|
+
if (!this.isPunc(")")) {
|
|
1572
|
+
for (; ; ) {
|
|
1573
|
+
args.push({ key: null, value: this.parseValue() });
|
|
1574
|
+
if (this.isPunc(",")) {
|
|
1575
|
+
this.next();
|
|
1576
|
+
continue;
|
|
1577
|
+
}
|
|
1578
|
+
break;
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
this.expect(")");
|
|
1582
|
+
return { name: `@${obj}.${name}`, args };
|
|
1583
|
+
}
|
|
1584
|
+
parseVar() {
|
|
1585
|
+
const kw = this.next();
|
|
1586
|
+
if (kw.t === "kw" && kw.v === "var") this.fail(`wfjs: 'var' \u4E0D\u652F\u6301\uFF08\u51FD\u6570\u4F5C\u7528\u57DF\u8BED\u4E49\u6613\u6DF7\u6DC6\uFF09\u2014\u2014\u8BF7\u7528 let/const`, kw.pos);
|
|
1587
|
+
const name = this.next();
|
|
1588
|
+
if (name.t !== "ident") this.fail(`wfjs: expected variable name after '${kw.v}'`, name.pos);
|
|
1589
|
+
let init = null;
|
|
1590
|
+
if (this.peek().t === "op" && this.peek().v === "=") {
|
|
1591
|
+
this.next();
|
|
1592
|
+
init = this.parseValueOrCall();
|
|
1593
|
+
}
|
|
1594
|
+
return { k: "var", name: name.v, init, isConst: kw.v === "const" };
|
|
1595
|
+
}
|
|
1596
|
+
parseValueOrCall() {
|
|
1597
|
+
let hadAwait = false;
|
|
1598
|
+
if (this.peek().t === "kw" && this.peek().v === "await") {
|
|
1599
|
+
this.next();
|
|
1600
|
+
hadAwait = true;
|
|
1601
|
+
}
|
|
1602
|
+
const method = this.tryParseMethodCall();
|
|
1603
|
+
if (method) return method;
|
|
1604
|
+
const tk = this.peek();
|
|
1605
|
+
const next = this.tokens[this.i + 1];
|
|
1606
|
+
if (hadAwait && tk.t === "ident" && next?.t === "punc" && next.v === "(") {
|
|
1607
|
+
const name = String(this.next().v);
|
|
1608
|
+
this.next();
|
|
1609
|
+
if (BUILTIN_NAMES.has(name)) return { name, args: this.parseCallArgs() };
|
|
1610
|
+
const args = [];
|
|
1611
|
+
if (!this.isPunc(")")) {
|
|
1612
|
+
for (; ; ) {
|
|
1613
|
+
args.push({ key: null, value: this.parseValue() });
|
|
1614
|
+
if (this.isPunc(",")) {
|
|
1615
|
+
this.next();
|
|
1616
|
+
continue;
|
|
1617
|
+
}
|
|
1618
|
+
break;
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
this.expect(")");
|
|
1622
|
+
return { name, args };
|
|
1623
|
+
}
|
|
1624
|
+
if (tk.t === "ident" && next?.t === "punc" && next.v === "(") {
|
|
1625
|
+
const after = this.tokens[this.i + 2];
|
|
1626
|
+
if (after?.t === "punc" && after.v === "{") {
|
|
1627
|
+
this.fail(`wfjs: \u672A\u8BC6\u522B\u8C03\u7528 '${String(tk.v)}'\uFF08\u4EC5\u5185\u7F6E\u6216 std \u7EAF\u51FD\u6570\u2014\u2014\u51FD\u6570\u5B9A\u4E49 W8\uFF09`, tk.pos);
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
return this.parseValue();
|
|
1631
|
+
}
|
|
1632
|
+
parseCallArgs() {
|
|
1633
|
+
if (this.isPunc(")")) {
|
|
1634
|
+
this.next();
|
|
1635
|
+
return [];
|
|
1636
|
+
}
|
|
1637
|
+
if (this.isPunc("{")) {
|
|
1638
|
+
const args = this.parseObjectArgs();
|
|
1639
|
+
this.expect(")");
|
|
1640
|
+
return args;
|
|
1641
|
+
}
|
|
1642
|
+
this.fail(`wfjs: \u5185\u7F6E\u8C03\u7528\u9700\u5BF9\u8C61\u53C2\u6570\uFF08{ key: value }\uFF09\u2014\u2014\u4F4D\u7F6E\u53C2\u6570\u4EC5\u9650 W8 \u51FD\u6570\u8C03\u7528`, this.peek().pos);
|
|
1643
|
+
return [];
|
|
1644
|
+
}
|
|
1645
|
+
parseObjectArgs() {
|
|
1646
|
+
this.expect("{");
|
|
1647
|
+
const args = [];
|
|
1648
|
+
while (!this.isPunc("}")) {
|
|
1649
|
+
const key = this.next();
|
|
1650
|
+
if (key.t !== "ident" && key.t !== "str") this.fail(`wfjs: expected argument key in object literal`, key.pos);
|
|
1651
|
+
this.expect(":");
|
|
1652
|
+
args.push({ key: String(key.v), value: this.parseValue() });
|
|
1653
|
+
if (this.isPunc(",")) this.next();
|
|
1654
|
+
else break;
|
|
1655
|
+
}
|
|
1656
|
+
this.expect("}");
|
|
1657
|
+
return args;
|
|
1658
|
+
}
|
|
1659
|
+
parseValue() {
|
|
1660
|
+
const tk = this.peek();
|
|
1661
|
+
if (tk.t === "template" || tk.t === "str" || tk.t === "num") {
|
|
1662
|
+
const after = this.tokens[this.i + 1];
|
|
1663
|
+
if (after && after.t === "op" && isBinaryOp(after.v)) {
|
|
1664
|
+
return { t: "expr", src: this.scanExpr() };
|
|
1665
|
+
}
|
|
1666
|
+
if (tk.t === "template") {
|
|
1667
|
+
this.next();
|
|
1668
|
+
return { t: "tpl", parts: parseTemplateParts(tk.raw) };
|
|
1669
|
+
}
|
|
1670
|
+
if (tk.t === "str") {
|
|
1671
|
+
this.next();
|
|
1672
|
+
return { t: "str", v: tk.v };
|
|
1673
|
+
}
|
|
1674
|
+
this.next();
|
|
1675
|
+
return { t: "num", v: tk.v };
|
|
1676
|
+
}
|
|
1677
|
+
if (tk.t === "kw" && (tk.v === "true" || tk.v === "false" || tk.v === "null")) {
|
|
1678
|
+
const after = this.tokens[this.i + 1];
|
|
1679
|
+
if (after && after.t === "op" && isBinaryOp(after.v)) return { t: "expr", src: this.scanExpr() };
|
|
1680
|
+
this.next();
|
|
1681
|
+
return { t: "expr", src: tk.v };
|
|
1682
|
+
}
|
|
1683
|
+
if (tk.t === "punc" && tk.v === "{") {
|
|
1684
|
+
this.next();
|
|
1685
|
+
const kvs = [];
|
|
1686
|
+
while (!this.isPunc("}")) {
|
|
1687
|
+
const key = this.next();
|
|
1688
|
+
if (key.t !== "ident" && key.t !== "str") this.fail(`wfjs: expected object key`, key.pos);
|
|
1689
|
+
this.expect(":");
|
|
1690
|
+
kvs.push({ key: String(key.v), value: this.parseValue() });
|
|
1691
|
+
if (this.isPunc(",")) this.next();
|
|
1692
|
+
else break;
|
|
1693
|
+
}
|
|
1694
|
+
this.expect("}");
|
|
1695
|
+
return { t: "obj", kvs };
|
|
1696
|
+
}
|
|
1697
|
+
return { t: "expr", src: this.scanExpr() };
|
|
1698
|
+
}
|
|
1699
|
+
/** 扫描表达式源码(单行裁剪:depth0 换行即边界——跨行表达式明确报错) */
|
|
1700
|
+
scanExpr() {
|
|
1701
|
+
const start = this.peek().pos;
|
|
1702
|
+
let out = "";
|
|
1703
|
+
let depth = 0;
|
|
1704
|
+
let prevEnd = -1;
|
|
1705
|
+
for (; ; ) {
|
|
1706
|
+
const tk = this.peek();
|
|
1707
|
+
if (tk.t === "eof") break;
|
|
1708
|
+
if (depth === 0) {
|
|
1709
|
+
if (tk.t === "punc" && (tk.v === "," || tk.v === ";" || tk.v === ")" || tk.v === "}")) break;
|
|
1710
|
+
if (tk.t === "op" && (tk.v === "=" || tk.v === "+=" || tk.v === "-=" || tk.v === "*=" || tk.v === "/=" || tk.v === "%=")) break;
|
|
1711
|
+
if (prevEnd >= 0 && this.src.slice(prevEnd, tk.pos).includes("\n")) break;
|
|
1712
|
+
}
|
|
1713
|
+
if (tk.t === "punc" && (tk.v === "(" || tk.v === "[")) depth++;
|
|
1714
|
+
if (tk.t === "punc" && (tk.v === ")" || tk.v === "]")) {
|
|
1715
|
+
if (depth === 0) break;
|
|
1716
|
+
depth--;
|
|
1717
|
+
}
|
|
1718
|
+
out += this.src.slice(tk.pos, tk.end);
|
|
1719
|
+
prevEnd = tk.end;
|
|
1720
|
+
this.next();
|
|
1721
|
+
}
|
|
1722
|
+
const trimmed = out.trim();
|
|
1723
|
+
if (!trimmed) this.fail(`wfjs: expected expression`, start);
|
|
1724
|
+
return trimmed;
|
|
1725
|
+
}
|
|
1726
|
+
parseIf() {
|
|
1727
|
+
this.expect("if");
|
|
1728
|
+
let once = false;
|
|
1729
|
+
if (this.isKw("once")) {
|
|
1730
|
+
this.next();
|
|
1731
|
+
once = true;
|
|
1732
|
+
}
|
|
1733
|
+
this.expect("(");
|
|
1734
|
+
const cond = this.scanExpr();
|
|
1735
|
+
this.expect(")");
|
|
1736
|
+
this.expect("{");
|
|
1737
|
+
const then = this.parseBlockBody();
|
|
1738
|
+
let elseStmts = null;
|
|
1739
|
+
if (this.isKw("else")) {
|
|
1740
|
+
this.next();
|
|
1741
|
+
if (this.isPunc("{")) {
|
|
1742
|
+
this.next();
|
|
1743
|
+
elseStmts = this.parseBlockBody();
|
|
1744
|
+
} else elseStmts = [this.parseStmt()];
|
|
1745
|
+
}
|
|
1746
|
+
return { k: "if", cond, then, else: elseStmts, once };
|
|
1747
|
+
}
|
|
1748
|
+
parseWhile() {
|
|
1749
|
+
this.expect("while");
|
|
1750
|
+
this.expect("(");
|
|
1751
|
+
const cond = this.scanExpr();
|
|
1752
|
+
this.expect(")");
|
|
1753
|
+
this.expect("{");
|
|
1754
|
+
const body = this.parseBlockBody();
|
|
1755
|
+
return { k: "while", cond, body };
|
|
1756
|
+
}
|
|
1757
|
+
parseFor() {
|
|
1758
|
+
this.expect("for");
|
|
1759
|
+
this.expect("(");
|
|
1760
|
+
const decl = this.next();
|
|
1761
|
+
if (decl.t !== "kw" || !["const", "let", "var"].includes(decl.v)) this.fail(`wfjs: for-of \u9700\u8981 const/let`, decl.pos);
|
|
1762
|
+
const name = this.next();
|
|
1763
|
+
if (name.t !== "ident") this.fail(`wfjs: expected loop variable`, name.pos);
|
|
1764
|
+
const of = this.next();
|
|
1765
|
+
if (of.t !== "kw" || of.v !== "of") this.fail(`wfjs: only for...of supported`, of.pos);
|
|
1766
|
+
const items = this.scanExpr();
|
|
1767
|
+
this.expect(")");
|
|
1768
|
+
this.expect("{");
|
|
1769
|
+
const body = this.parseBlockBody();
|
|
1770
|
+
return { k: "forof", varName: name.v, items, body };
|
|
1771
|
+
}
|
|
1772
|
+
parseReturn() {
|
|
1773
|
+
const kw = this.next();
|
|
1774
|
+
const next = this.peek();
|
|
1775
|
+
if (this.isPunc(";") || this.isPunc("}")) return { k: "return", value: null };
|
|
1776
|
+
if (next.t === "eof") return { k: "return", value: null };
|
|
1777
|
+
const gap = this.src.slice(kw.end, next.pos);
|
|
1778
|
+
if (gap.includes("\n")) return { k: "return", value: null };
|
|
1779
|
+
return { k: "return", value: this.parseValue() };
|
|
1780
|
+
}
|
|
1781
|
+
parseAssignOrCall() {
|
|
1782
|
+
const method = this.tryParseMethodCall();
|
|
1783
|
+
if (method) return { k: "call", call: method };
|
|
1784
|
+
const name = this.next();
|
|
1785
|
+
if (name.t !== "ident") this.fail(`wfjs: expected identifier`, name.pos);
|
|
1786
|
+
const tk = this.peek();
|
|
1787
|
+
if (tk.t === "punc" && tk.v === "(") {
|
|
1788
|
+
this.next();
|
|
1789
|
+
return { k: "call", call: { name: name.v, args: this.parseCallArgs() } };
|
|
1790
|
+
}
|
|
1791
|
+
if (tk.t === "op" && ["=", "+=", "-=", "*=", "/=", "%="].includes(tk.v)) {
|
|
1792
|
+
const op = tk.v;
|
|
1793
|
+
this.next();
|
|
1794
|
+
return { k: "assign", target: name.v, op, value: this.scanExpr() };
|
|
1795
|
+
}
|
|
1796
|
+
if (tk.t === "op" && (tk.v === "++" || tk.v === "--")) {
|
|
1797
|
+
this.next();
|
|
1798
|
+
return { k: "incdec", target: name.v, inc: tk.v === "++" };
|
|
1799
|
+
}
|
|
1800
|
+
this.fail(`wfjs: expected assignment or call after '${name.v}'`, tk.pos);
|
|
1801
|
+
}
|
|
1802
|
+
};
|
|
1803
|
+
function parseTemplateParts(raw) {
|
|
1804
|
+
const parts = [];
|
|
1805
|
+
let i = 0;
|
|
1806
|
+
let lit = "";
|
|
1807
|
+
while (i < raw.length) {
|
|
1808
|
+
if (raw[i] === "$" && raw[i + 1] === "{") {
|
|
1809
|
+
let depth = 1;
|
|
1810
|
+
let j = i + 2;
|
|
1811
|
+
let expr = "";
|
|
1812
|
+
while (j < raw.length && depth > 0) {
|
|
1813
|
+
if (raw[j] === "\\") {
|
|
1814
|
+
expr += raw[j] + (raw[j + 1] ?? "");
|
|
1815
|
+
j += 2;
|
|
1816
|
+
continue;
|
|
1817
|
+
}
|
|
1818
|
+
if (raw[j] === "{") depth++;
|
|
1819
|
+
if (raw[j] === "}") depth--;
|
|
1820
|
+
if (depth > 0) expr += raw[j];
|
|
1821
|
+
j++;
|
|
1822
|
+
}
|
|
1823
|
+
if (depth !== 0) throw new Error(`wfjs: unclosed '\${' in template`);
|
|
1824
|
+
if (lit) {
|
|
1825
|
+
parts.push(lit);
|
|
1826
|
+
lit = "";
|
|
1827
|
+
}
|
|
1828
|
+
parts.push({ expr: expr.trim() });
|
|
1829
|
+
i = j;
|
|
1830
|
+
continue;
|
|
1831
|
+
}
|
|
1832
|
+
lit += raw[i];
|
|
1833
|
+
i++;
|
|
1834
|
+
}
|
|
1835
|
+
if (lit) parts.push(lit);
|
|
1836
|
+
return parts;
|
|
1837
|
+
}
|
|
1838
|
+
var BUILTIN_NAMES = /* @__PURE__ */ new Set(["http", "email", "ai", "template", "log", "stop"]);
|
|
1839
|
+
function checkExprCall(ast, env, where) {
|
|
1840
|
+
const n = ast;
|
|
1841
|
+
if (!n || typeof n !== "object") return;
|
|
1842
|
+
if (n.kind === "call") {
|
|
1843
|
+
const nm = n.name ?? "";
|
|
1844
|
+
if (SIDE_EFFECT_CALLS.has(nm)) throw new Error(`wfjs: \u526F\u4F5C\u7528\u51FD\u6570 '${nm}()' \u4E0D\u80FD\u51FA\u73B0\u5728\u8868\u8FBE\u5F0F\u5185\uFF08in ${where}\uFF09\u2014\u2014\u9700\u8BED\u53E5\u7EA7\u8C03\u7528`);
|
|
1845
|
+
if (!env.stdFns.has(nm)) throw new Error(`wfjs: \u8868\u8FBE\u5F0F\u5185\u672A\u5BFC\u5165\u51FD\u6570 '${nm}()'\uFF08in ${where}\uFF09\u2014\u2014std \u51FD\u6570\u9700 import \u540E\u53EF\u7528`);
|
|
1846
|
+
}
|
|
1847
|
+
for (const k of ["left", "right", "operand", "cond", "then", "else"]) {
|
|
1848
|
+
if (n[k] && typeof n[k] === "object") checkExprCall(n[k], env, where);
|
|
1849
|
+
}
|
|
1850
|
+
if (n.args) for (const a of n.args) checkExprCall(a, env, where);
|
|
1851
|
+
}
|
|
1852
|
+
var SIDE_EFFECT_CALLS = /* @__PURE__ */ new Set(["http", "email", "ai", "template", "log"]);
|
|
1853
|
+
function rewriteExpr(src, bindings, env, where) {
|
|
1854
|
+
const ast = parse(src);
|
|
1855
|
+
checkExprCall(ast, env, where);
|
|
1856
|
+
walkExpr(ast, (path) => {
|
|
1857
|
+
const first = path.segments[0];
|
|
1858
|
+
if (typeof first !== "string") return;
|
|
1859
|
+
const b = bindings.get(first);
|
|
1860
|
+
if (!b && (first === "input" || first === "steps" || first === "vars" || first === "loop")) return;
|
|
1861
|
+
if (!b) throw new Error(`wfjs: \u672A\u58F0\u660E\u53D8\u91CF '${first}'\uFF08in ${where}: ${src}\uFF09`);
|
|
1862
|
+
const prefix = b.kind === "step" ? ["steps", b.id, "data"] : b.kind === "var" ? ["vars", b.name] : ["loop", "item"];
|
|
1863
|
+
path.segments = [...prefix, ...path.segments.slice(1)];
|
|
1864
|
+
});
|
|
1865
|
+
return toSrc(ast);
|
|
1866
|
+
}
|
|
1867
|
+
function walkExpr(node, visit) {
|
|
1868
|
+
const n = node;
|
|
1869
|
+
if (!n || typeof n !== "object") return;
|
|
1870
|
+
if (n.kind === "path") visit(n);
|
|
1871
|
+
if (n.kind === "ternary") {
|
|
1872
|
+
walkExpr(n.cond, visit);
|
|
1873
|
+
walkExpr(n.then, visit);
|
|
1874
|
+
walkExpr(n.else, visit);
|
|
1875
|
+
return;
|
|
1876
|
+
}
|
|
1877
|
+
if (n.kind === "call") {
|
|
1878
|
+
for (const a of n.args) walkExpr(a, visit);
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
for (const key of [["left", "right"], ["operand"]]) {
|
|
1882
|
+
for (const k of key) {
|
|
1883
|
+
if (n[k] && typeof n[k] === "object") walkExpr(n[k], visit);
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
function unusedName(env, base) {
|
|
1888
|
+
for (let i = 1; ; i++) {
|
|
1889
|
+
const name = `${base}${i}`;
|
|
1890
|
+
if (!env.bindings.has(name) && !env.steps.some((s) => s.id === name)) return name;
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
var FIELD_KIND = {
|
|
1894
|
+
http: { url: "template", method: "template", body: "template", headers: "template", timeoutMs: "template" },
|
|
1895
|
+
email: { to: "template", subject: "template", body: "template" },
|
|
1896
|
+
ai: { prompt: "template", system: "template" },
|
|
1897
|
+
template: { template: "template" },
|
|
1898
|
+
log: { message: "template" },
|
|
1899
|
+
stop: {},
|
|
1900
|
+
store: { key: "expr", value: "expr" }
|
|
1901
|
+
};
|
|
1902
|
+
function compileValue(v, env, where, kind) {
|
|
1903
|
+
switch (v.t) {
|
|
1904
|
+
case "str":
|
|
1905
|
+
return kind === "expr" ? `'${v.v.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'` : v.v;
|
|
1906
|
+
case "num":
|
|
1907
|
+
return kind === "expr" ? String(v.v) : String(v.v);
|
|
1908
|
+
case "expr":
|
|
1909
|
+
return kind === "template" ? `{{${rewriteExpr(v.src, env.bindings, env, where)}}}` : rewriteExpr(v.src, env.bindings, env, where);
|
|
1910
|
+
case "tpl": {
|
|
1911
|
+
if (kind === "expr") throw new Error(`wfjs: \u8868\u8FBE\u5F0F\u5B57\u6BB5\u4E0D\u652F\u6301\u6A21\u677F\u4E32\uFF08in ${where}\uFF09`);
|
|
1912
|
+
let out = "";
|
|
1913
|
+
for (const p of v.parts) {
|
|
1914
|
+
if (typeof p === "string") out += p;
|
|
1915
|
+
else out += `{{${rewriteExpr(p.expr, env.bindings, env, where)}}}`;
|
|
1916
|
+
}
|
|
1917
|
+
return out;
|
|
1918
|
+
}
|
|
1919
|
+
case "obj": {
|
|
1920
|
+
const out = {};
|
|
1921
|
+
for (const kv of v.kvs) out[kv.key] = compileValue(kv.value, env, where, kind);
|
|
1922
|
+
return out;
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
function compileArg(kv, type, env) {
|
|
1927
|
+
if (kv.key === null) {
|
|
1928
|
+
const v = kv.value;
|
|
1929
|
+
if (v.t === "expr") return `{{${rewriteExpr(v.src, env.bindings, env, `store \u53C2\u6570`)}}}`;
|
|
1930
|
+
if (v.t === "str") return v.v;
|
|
1931
|
+
if (v.t === "num") return String(v.v);
|
|
1932
|
+
throw new Error(`wfjs: store \u53C2\u6570\u5FC5\u987B\u662F\u8868\u8FBE\u5F0F\u6216\u5B57\u9762\u91CF`);
|
|
1933
|
+
}
|
|
1934
|
+
const kind = FIELD_KIND[type]?.[kv.key] ?? "template";
|
|
1935
|
+
return compileValue(kv.value, env, `${type}(${kv.key})`, kind);
|
|
1936
|
+
}
|
|
1937
|
+
function childEnv(parent, steps2) {
|
|
1938
|
+
return { bindings: new Map(parent.bindings), consts: new Set(parent.consts), steps: steps2, imports: parent.imports, stdBindings: parent.stdBindings, stdFns: parent.stdFns, scopes: [...parent.scopes, { names: /* @__PURE__ */ new Set() }], hideCount: parent.hideCount, functions: parent.functions, inFunction: parent.inFunction, autoPrefix: parent.autoPrefix, exports: parent.exports, remoteImports: parent.remoteImports };
|
|
1939
|
+
}
|
|
1940
|
+
function declareName(env, name, where) {
|
|
1941
|
+
const top = env.scopes[env.scopes.length - 1];
|
|
1942
|
+
if (top.names.has(name)) throw new Error(`wfjs: \u91CD\u590D\u58F0\u660E '${name}'\uFF08${where}\uFF09`);
|
|
1943
|
+
const shadowed = env.scopes.slice(0, -1).some((s) => s.names.has(name));
|
|
1944
|
+
let internal = name;
|
|
1945
|
+
if (shadowed) {
|
|
1946
|
+
const used = /* @__PURE__ */ new Set();
|
|
1947
|
+
for (const b of env.bindings.values()) {
|
|
1948
|
+
if (b.kind === "var") used.add(b.name);
|
|
1949
|
+
else if (b.kind === "step") used.add(b.id);
|
|
1950
|
+
}
|
|
1951
|
+
do {
|
|
1952
|
+
env.hideCount++;
|
|
1953
|
+
internal = `${name}$${env.hideCount}`;
|
|
1954
|
+
} while (used.has(internal));
|
|
1955
|
+
}
|
|
1956
|
+
top.names.add(name);
|
|
1957
|
+
return internal;
|
|
1958
|
+
}
|
|
1959
|
+
function compileStmt(stmt, env, inLoop) {
|
|
1960
|
+
const auto = () => unusedName(env, `${env.autoPrefix}_${stmt.k}`);
|
|
1961
|
+
switch (stmt.k) {
|
|
1962
|
+
case "export": {
|
|
1963
|
+
const all = env.functions;
|
|
1964
|
+
const check = (n) => {
|
|
1965
|
+
if (!all.some((f) => f.name === n)) throw new Error(`wfjs: \u5BFC\u51FA '${n}' \u5FC5\u987B\u662F\u5DF2\u5B9A\u4E49\u51FD\u6570\uFF08v1 \u51FD\u6570\u5BFC\u51FA\u2014\u2014\u53D8\u91CF\u5BFC\u51FA v2\uFF09`);
|
|
1966
|
+
};
|
|
1967
|
+
stmt.named.forEach((n) => check(n.name));
|
|
1968
|
+
if (stmt.default) check(stmt.default);
|
|
1969
|
+
if (stmt.named.length === 0 && !stmt.default) throw new Error("wfjs: export \u5FC5\u987B\u81F3\u5C11\u5BFC\u51FA\u4E00\u4E2A\u6210\u5458");
|
|
1970
|
+
env.exports.named.push(...stmt.named);
|
|
1971
|
+
if (stmt.default) env.exports.default = stmt.default;
|
|
1972
|
+
return;
|
|
1973
|
+
}
|
|
1974
|
+
case "function": {
|
|
1975
|
+
if (env.inFunction) throw new Error(`wfjs: \u4E0D\u652F\u6301\u5D4C\u5957\u51FD\u6570\uFF08\u51FD\u6570 '${stmt.name}'\uFF09`);
|
|
1976
|
+
const rec = env.functions.find((f) => f.name === stmt.name);
|
|
1977
|
+
if (!rec) throw new Error(`wfjs: \u5185\u90E8\u9519\u8BEF\uFF1A\u51FD\u6570 '${stmt.name}' \u672A\u9884\u6CE8\u518C`);
|
|
1978
|
+
const fnBindings = /* @__PURE__ */ new Map();
|
|
1979
|
+
const fnScopeNames = /* @__PURE__ */ new Set();
|
|
1980
|
+
const fnScopes = [...env.scopes, { names: fnScopeNames }];
|
|
1981
|
+
for (const p of stmt.params) {
|
|
1982
|
+
if (fnScopeNames.has(p)) throw new Error(`wfjs: \u91CD\u590D\u53C2\u6570 '${p}'`);
|
|
1983
|
+
fnScopeNames.add(p);
|
|
1984
|
+
fnBindings.set(p, { kind: "var", name: p });
|
|
1985
|
+
}
|
|
1986
|
+
for (const [n, b] of env.bindings) if (b.kind === "var" && !fnScopeNames.has(n)) fnBindings.set(n, b);
|
|
1987
|
+
const fnEnv = {
|
|
1988
|
+
bindings: fnBindings,
|
|
1989
|
+
consts: /* @__PURE__ */ new Set(),
|
|
1990
|
+
steps: [],
|
|
1991
|
+
imports: env.imports,
|
|
1992
|
+
stdBindings: env.stdBindings,
|
|
1993
|
+
stdFns: env.stdFns,
|
|
1994
|
+
scopes: fnScopes,
|
|
1995
|
+
hideCount: env.hideCount,
|
|
1996
|
+
functions: env.functions,
|
|
1997
|
+
inFunction: true,
|
|
1998
|
+
autoPrefix: `_fn:${stmt.name}:`,
|
|
1999
|
+
exports: env.exports,
|
|
2000
|
+
remoteImports: env.remoteImports
|
|
2001
|
+
};
|
|
2002
|
+
compileInto(stmt.body, fnEnv, false);
|
|
2003
|
+
rec.step = { steps: fnEnv.steps };
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
2006
|
+
case "import": {
|
|
2007
|
+
const isStd = stmt.from.startsWith("wf://std/");
|
|
2008
|
+
if (isStd) {
|
|
2009
|
+
const mod = STD_MODULES[stmt.from] ?? (stmt.from === "wf://std/store" ? { store: null } : void 0);
|
|
2010
|
+
if (!mod) throw new Error(`wfjs: std \u6A21\u5757 '${stmt.from}' \u672A\u6CE8\u518C\uFF08${Object.keys(STD_MODULES).join(" / ")} \u4E0E wf://std/store\uFF09`);
|
|
2011
|
+
for (const { name, as } of stmt.names) {
|
|
2012
|
+
const bound = as ?? name;
|
|
2013
|
+
if (name === "store" && stmt.from === "wf://std/store") {
|
|
2014
|
+
env.stdBindings.set(bound, "store");
|
|
2015
|
+
} else if (mod[name] !== void 0) {
|
|
2016
|
+
env.stdBindings.set(bound, "fn");
|
|
2017
|
+
env.stdFns.add(bound);
|
|
2018
|
+
} else {
|
|
2019
|
+
throw new Error(`wfjs: \u6A21\u5757 '${stmt.from}' \u65E0\u5BFC\u51FA '${name}'`);
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
env.imports.push({ from: stmt.from, names: stmt.names });
|
|
2023
|
+
return;
|
|
2024
|
+
}
|
|
2025
|
+
if (!stmt.from.startsWith("https://")) {
|
|
2026
|
+
throw new Error(`wfjs: \u6A21\u5757 '${stmt.from}' \u4E0D\u5408\u6CD5\uFF08wf://std/* \u6216 https://\u2026\uFF09`);
|
|
2027
|
+
}
|
|
2028
|
+
env.imports.push({ from: stmt.from, names: stmt.names });
|
|
2029
|
+
return;
|
|
2030
|
+
}
|
|
2031
|
+
case "var": {
|
|
2032
|
+
const { name } = stmt;
|
|
2033
|
+
if (BUILTIN_NAMES.has(name)) throw new Error(`wfjs: \u53D8\u91CF\u540D '${name}' \u4E0E\u5185\u7F6E\u51FD\u6570\u51B2\u7A81`);
|
|
2034
|
+
const internal = declareName(env, name, `\u58F0\u660E ${name}`);
|
|
2035
|
+
if (stmt.init && isCall(stmt.init)) {
|
|
2036
|
+
const call = stmt.init;
|
|
2037
|
+
if (!BUILTIN_NAMES.has(call.name) && !call.name.startsWith("@")) {
|
|
2038
|
+
if (env.functions.some((f) => f.name === call.name)) {
|
|
2039
|
+
env.steps.push({ id: internal, type: "call", config: { name: call.name, args: callArgsToExprs(call.args, env) } });
|
|
2040
|
+
env.bindings.set(name, { kind: "step", id: internal });
|
|
2041
|
+
if (stmt.isConst) env.consts.add(internal);
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
2044
|
+
throw new Error(`wfjs: \u672A\u8BC6\u522B\u8C03\u7528 '${call.name}'\uFF08\u5185\u7F6E\uFF1A${[...BUILTIN_NAMES].join("/")}\uFF1B\u51FD\u6570\u9700\u5148\u58F0\u660E\uFF09`);
|
|
2045
|
+
}
|
|
2046
|
+
if (env.inFunction) throw new Error(`wfjs: \u51FD\u6570\u4F53\u5185\u4E0D\u652F\u6301\u526F\u4F5C\u7528\u5185\u7F6E\uFF08${call.name}\uFF09\u2014\u2014\u51FD\u6570\u662F\u7EAF\u903B\u8F91\uFF08v1 \u88C1\u526A\uFF09`);
|
|
2047
|
+
addBuiltinStep(call, internal, env);
|
|
2048
|
+
env.bindings.set(name, { kind: "step", id: internal });
|
|
2049
|
+
} else {
|
|
2050
|
+
const value = stmt.init ? compileValue(stmt.init, env, `\u58F0\u660E ${name}`, "expr") : "null";
|
|
2051
|
+
env.steps.push({ id: internal, type: "assign", config: { target: internal, value } });
|
|
2052
|
+
env.bindings.set(name, { kind: "var", name: internal });
|
|
2053
|
+
}
|
|
2054
|
+
if (stmt.isConst) env.consts.add(internal);
|
|
2055
|
+
return;
|
|
2056
|
+
}
|
|
2057
|
+
case "assign": {
|
|
2058
|
+
const b = env.bindings.get(stmt.target);
|
|
2059
|
+
if (!b) throw new Error(`wfjs: \u672A\u58F0\u660E\u53D8\u91CF '${stmt.target}'`);
|
|
2060
|
+
const internal = b.kind === "var" ? b.name : b.kind === "step" ? b.id : stmt.target;
|
|
2061
|
+
if (env.consts.has(internal)) throw new Error(`wfjs: \u4E0D\u80FD\u7ED9 const '${stmt.target}' \u8D4B\u503C`);
|
|
2062
|
+
const rhs = rewriteExpr(stmt.value, env.bindings, env, `\u8D4B\u503C ${stmt.target}`);
|
|
2063
|
+
const value = stmt.op === "=" ? rhs : `(vars.${internal} ${stmt.op[0]} ${rhs})`;
|
|
2064
|
+
env.steps.push({ id: auto(), type: "assign", config: { target: internal, value } });
|
|
2065
|
+
return;
|
|
2066
|
+
}
|
|
2067
|
+
case "incdec": {
|
|
2068
|
+
const b = env.bindings.get(stmt.target);
|
|
2069
|
+
if (!b) throw new Error(`wfjs: \u672A\u58F0\u660E\u53D8\u91CF '${stmt.target}'`);
|
|
2070
|
+
const internal = b.kind === "var" ? b.name : b.kind === "step" ? b.id : stmt.target;
|
|
2071
|
+
if (env.consts.has(internal)) throw new Error(`wfjs: \u4E0D\u80FD\u7ED9 const '${stmt.target}' \u8D4B\u503C`);
|
|
2072
|
+
env.steps.push({
|
|
2073
|
+
id: auto(),
|
|
2074
|
+
type: "assign",
|
|
2075
|
+
config: { target: internal, value: `(vars.${internal} ${stmt.inc ? "+" : "-"} 1)` }
|
|
2076
|
+
});
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2079
|
+
case "if": {
|
|
2080
|
+
const when = rewriteExpr(stmt.cond, env.bindings, env, "if \u6761\u4EF6");
|
|
2081
|
+
const then = [];
|
|
2082
|
+
compileInto(stmt.then, childEnv(env, then), inLoop);
|
|
2083
|
+
const config = { when };
|
|
2084
|
+
if (stmt.once) config.edge = true;
|
|
2085
|
+
if (then.length) config.then = { steps: then };
|
|
2086
|
+
if (stmt.else) {
|
|
2087
|
+
const elseSteps = [];
|
|
2088
|
+
compileInto(stmt.else, childEnv(env, elseSteps), inLoop);
|
|
2089
|
+
if (elseSteps.length) config.else = { steps: elseSteps };
|
|
2090
|
+
}
|
|
2091
|
+
env.steps.push({ id: auto(), type: "if", config });
|
|
2092
|
+
return;
|
|
2093
|
+
}
|
|
2094
|
+
case "try": {
|
|
2095
|
+
const body = [];
|
|
2096
|
+
compileInto(stmt.body, childEnv(env, body), inLoop);
|
|
2097
|
+
const catchSteps = [];
|
|
2098
|
+
compileInto(stmt.catchBody, childEnv(env, catchSteps), inLoop);
|
|
2099
|
+
const config = { step: { steps: body }, catch: { steps: catchSteps } };
|
|
2100
|
+
env.steps.push({ id: auto(), type: "try", config });
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
2103
|
+
case "while": {
|
|
2104
|
+
const when = rewriteExpr(stmt.cond, env.bindings, env, "while \u6761\u4EF6");
|
|
2105
|
+
const body = [];
|
|
2106
|
+
compileInto(stmt.body, childEnv(env, body), true);
|
|
2107
|
+
env.steps.push({ id: auto(), type: "while", config: { when, step: { steps: body } } });
|
|
2108
|
+
return;
|
|
2109
|
+
}
|
|
2110
|
+
case "forof": {
|
|
2111
|
+
const items = rewriteExpr(stmt.items, env.bindings, env, "for-of");
|
|
2112
|
+
const child = childEnv(env, []);
|
|
2113
|
+
child.bindings.set(stmt.varName, { kind: "loop" });
|
|
2114
|
+
child.scopes[child.scopes.length - 1].names.add(stmt.varName);
|
|
2115
|
+
const body = [];
|
|
2116
|
+
compileInto(stmt.body, childEnv(child, body), true);
|
|
2117
|
+
env.steps.push({ id: auto(), type: "for", config: { items, step: { steps: body } } });
|
|
2118
|
+
return;
|
|
2119
|
+
}
|
|
2120
|
+
case "return": {
|
|
2121
|
+
const value = stmt.value ? compileValue(stmt.value, env, "return", "expr") : void 0;
|
|
2122
|
+
env.steps.push({ id: auto(), type: "return", config: value !== void 0 ? { value } : {} });
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
2125
|
+
case "call": {
|
|
2126
|
+
if (!BUILTIN_NAMES.has(stmt.call.name) && !stmt.call.name.startsWith("@")) {
|
|
2127
|
+
if (!env.functions.some((f) => f.name === stmt.call.name)) {
|
|
2128
|
+
throw new Error(`wfjs: \u672A\u8BC6\u522B\u8C03\u7528 '${stmt.call.name}'\uFF08\u5185\u7F6E\uFF1A${[...BUILTIN_NAMES].join("/")}\uFF1B\u51FD\u6570\u9700\u5148\u58F0\u660E\uFF09`);
|
|
2129
|
+
}
|
|
2130
|
+
env.steps.push({ id: auto(), type: "call", config: { name: stmt.call.name, args: callArgsToExprs(stmt.call.args, env) } });
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
if (env.inFunction) throw new Error(`wfjs: \u51FD\u6570\u4F53\u5185\u4E0D\u652F\u6301\u526F\u4F5C\u7528\u5185\u7F6E\uFF08${stmt.call.name}\uFF09\u2014\u2014\u51FD\u6570\u662F\u7EAF\u903B\u8F91\uFF08v1 \u88C1\u526A\uFF09`);
|
|
2134
|
+
addBuiltinStep(stmt.call, null, env);
|
|
2135
|
+
return;
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
function callArgsToExprs(args, env) {
|
|
2140
|
+
return args.map((a) => {
|
|
2141
|
+
if (a.key !== null) throw new Error(`wfjs: \u51FD\u6570\u8C03\u7528\u4E0D\u652F\u6301\u547D\u540D\u53C2\u6570`);
|
|
2142
|
+
const v = a.value;
|
|
2143
|
+
if (v.t === "expr") return rewriteExpr(v.src, env.bindings, env, `\u51FD\u6570\u53C2\u6570`);
|
|
2144
|
+
if (v.t === "str") return `'${v.v.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
2145
|
+
if (v.t === "num") return String(v.v);
|
|
2146
|
+
if (v.t === "tpl") {
|
|
2147
|
+
let out = "`";
|
|
2148
|
+
for (const p of v.parts) {
|
|
2149
|
+
if (typeof p === "string") out += p;
|
|
2150
|
+
else out += "${" + rewriteExpr(p.expr, env.bindings, env, "\u51FD\u6570\u53C2\u6570\u6A21\u677F") + "}";
|
|
2151
|
+
}
|
|
2152
|
+
return out + "`";
|
|
2153
|
+
}
|
|
2154
|
+
throw new Error(`wfjs: \u51FD\u6570\u53C2\u6570\u503C\u4E0D\u5408\u6CD5`);
|
|
2155
|
+
});
|
|
2156
|
+
}
|
|
2157
|
+
function isCall(v) {
|
|
2158
|
+
return typeof v.name === "string" && Array.isArray(v.args);
|
|
2159
|
+
}
|
|
2160
|
+
function compileInto(stmts, env, inLoop) {
|
|
2161
|
+
for (const s of stmts) compileStmt(s, env, inLoop);
|
|
2162
|
+
}
|
|
2163
|
+
function addBuiltinStep(call, bindId, env) {
|
|
2164
|
+
if (call.name === "@store.get" || call.name === "@store.set") {
|
|
2165
|
+
const method = call.name.slice(1);
|
|
2166
|
+
const op = method.endsWith(".get") ? "get" : "set";
|
|
2167
|
+
if (!env.stdBindings.has("store") || env.stdBindings.get("store") !== "store") {
|
|
2168
|
+
throw new Error(`wfjs: \u4F7F\u7528 store \u524D\u9700\u5BFC\u5165\uFF1Aimport { store } from 'wf://std/store'`);
|
|
2169
|
+
}
|
|
2170
|
+
if (call.args.length !== (op === "get" ? 1 : 2)) {
|
|
2171
|
+
throw new Error(`wfjs: ${method}() \u53C2\u6570\u6570\u91CF\u9519\u8BEF\uFF08${op === "get" ? "key" : "key, value"}\uFF09\uFF0C\u6536\u5230 ${call.args.length}`);
|
|
2172
|
+
}
|
|
2173
|
+
const config2 = { op, key: compileArg(call.args[0], "store", env) };
|
|
2174
|
+
if (op === "set") config2.value = compileArg(call.args[1], "store", env);
|
|
2175
|
+
env.steps.push({ id: bindId ?? unusedName(env, `_store${op}`), type: "store", config: config2 });
|
|
2176
|
+
return;
|
|
2177
|
+
}
|
|
2178
|
+
if (!BUILTIN_NAMES.has(call.name)) {
|
|
2179
|
+
throw new Error(`wfjs: \u672A\u8BC6\u522B\u8C03\u7528 '${call.name}'\uFF08\u5185\u7F6E\uFF1A${[...BUILTIN_NAMES].join("/")}\uFF1B\u51FD\u6570\u8C03\u7528\u5728 W8 wave\uFF09`);
|
|
2180
|
+
}
|
|
2181
|
+
const id = bindId ?? unusedName(env, `_${call.name}`);
|
|
2182
|
+
const config = {};
|
|
2183
|
+
for (const arg of call.args) {
|
|
2184
|
+
config[arg.key] = compileArg(arg, call.name, env);
|
|
2185
|
+
}
|
|
2186
|
+
env.steps.push({ id, type: call.name === "stop" ? "return" : call.name, config });
|
|
2187
|
+
}
|
|
2188
|
+
function validateRemoteModule(url, raw) {
|
|
2189
|
+
const m = raw;
|
|
2190
|
+
if (!m || typeof m !== "object" || !Array.isArray(m.functions)) {
|
|
2191
|
+
throw new Error(`wfjs: \u8FDC\u7A0B\u6A21\u5757 '${url}' \u683C\u5F0F\u4E0D\u5408\u6CD5\uFF08\u671F\u671B { functions: [...] }\u2014\u2014\u767D\u540D\u5355\uFF09`);
|
|
2192
|
+
}
|
|
2193
|
+
for (const [i, f] of m.functions.entries()) {
|
|
2194
|
+
const fn = f;
|
|
2195
|
+
if (typeof fn.name !== "string" || !Array.isArray(fn.params) || typeof fn.step !== "object") {
|
|
2196
|
+
throw new Error(`wfjs: \u8FDC\u7A0B\u6A21\u5757 '${url}' functions[${i}] \u7F3A\u5B57\u6BB5\uFF08name/params/step\uFF09`);
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
return { functions: m.functions };
|
|
2200
|
+
}
|
|
2201
|
+
async function compileWfjs(src, opts) {
|
|
2202
|
+
const parser = new WfjsParser(tokenizeWfjs(src), src);
|
|
2203
|
+
const stmts = parser.parseProgram();
|
|
2204
|
+
const env = { bindings: /* @__PURE__ */ new Map(), consts: /* @__PURE__ */ new Set(), steps: [], imports: [], stdBindings: /* @__PURE__ */ new Map(), stdFns: /* @__PURE__ */ new Set(), scopes: [{ names: /* @__PURE__ */ new Set() }], hideCount: 0, functions: [], inFunction: false, autoPrefix: "", exports: { named: [] }, remoteImports: [] };
|
|
2205
|
+
for (const stmt of stmts) {
|
|
2206
|
+
if (stmt.k === "function") {
|
|
2207
|
+
if (BUILTIN_NAMES.has(stmt.name)) throw new Error(`wfjs: \u51FD\u6570\u540D '${stmt.name}' \u4E0E\u5185\u7F6E\u51B2\u7A81`);
|
|
2208
|
+
if (env.functions.some((f) => f.name === stmt.name)) throw new Error(`wfjs: \u91CD\u590D\u51FD\u6570 '${stmt.name}'`);
|
|
2209
|
+
env.functions.push({ name: stmt.name, params: stmt.params, step: { steps: [] } });
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
for (const stmt of stmts) {
|
|
2213
|
+
if (stmt.k === "import" && !stmt.from.startsWith("wf://std/")) env.remoteImports.push({ from: stmt.from, names: stmt.names });
|
|
2214
|
+
}
|
|
2215
|
+
for (const imp of env.remoteImports) {
|
|
2216
|
+
if (!opts?.remoteFetch) {
|
|
2217
|
+
throw new Error(`wfjs: \u8FDC\u7A0B\u5BFC\u5165 '${imp.from}' \u9700\u8981 compileWfjs({ remoteFetch })`);
|
|
2218
|
+
}
|
|
2219
|
+
const raw = await opts.remoteFetch(imp.from);
|
|
2220
|
+
const mod = validateRemoteModule(imp.from, raw);
|
|
2221
|
+
for (const { name, as } of imp.names) {
|
|
2222
|
+
const bound = as ?? name;
|
|
2223
|
+
const fn = mod.functions.find((f) => f.name === name);
|
|
2224
|
+
if (!fn) throw new Error(`wfjs: \u8FDC\u7A0B\u6A21\u5757 '${imp.from}' \u65E0\u5BFC\u51FA\u51FD\u6570 '${name}'`);
|
|
2225
|
+
if (env.functions.some((f) => f.name === bound)) throw new Error(`wfjs: \u51FD\u6570\u540D '${bound}' \u51B2\u7A81\uFF08\u8FDC\u7A0B\u5BFC\u5165\uFF09`);
|
|
2226
|
+
env.functions.push({ ...fn, name: bound });
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
compileInto(stmts, env, false);
|
|
2230
|
+
const def = { steps: env.steps };
|
|
2231
|
+
if (env.imports.length) def.imports = env.imports;
|
|
2232
|
+
if (env.functions.length) def.functions = env.functions;
|
|
2233
|
+
if (env.exports.named.length || env.exports.default) def.exports = env.exports;
|
|
2234
|
+
return def;
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
// src/server/workflow/tojs.ts
|
|
2238
|
+
function toJs(def) {
|
|
2239
|
+
const head = renderImports(def.imports ?? []);
|
|
2240
|
+
const fns = renderFunctions(def.functions ?? []);
|
|
2241
|
+
const body = renderChain(def.steps, {
|
|
2242
|
+
seenVars: /* @__PURE__ */ new Set(),
|
|
2243
|
+
loopNames: [],
|
|
2244
|
+
inReturn: false
|
|
2245
|
+
}).trimEnd();
|
|
2246
|
+
const tail = renderExports(def.exports);
|
|
2247
|
+
const parts = [head, fns, body, tail].filter(Boolean);
|
|
2248
|
+
return parts.join("\n\n");
|
|
2249
|
+
}
|
|
2250
|
+
function renderExports(exports) {
|
|
2251
|
+
if (!exports) return "";
|
|
2252
|
+
const lines = [];
|
|
2253
|
+
if ((exports.named ?? []).length) {
|
|
2254
|
+
lines.push(`export { ${exports.named.map((n) => n.as && n.as !== n.name ? `${n.name} as ${n.as}` : n.name).join(", ")} }`);
|
|
2255
|
+
}
|
|
2256
|
+
if (exports.default) lines.push(`export default ${exports.default}`);
|
|
2257
|
+
return lines.join("\n");
|
|
2258
|
+
}
|
|
2259
|
+
function renderFunctions(functions) {
|
|
2260
|
+
if (!functions?.length) return "";
|
|
2261
|
+
return functions.map((f) => {
|
|
2262
|
+
const ctx = { seenVars: /* @__PURE__ */ new Set(), loopNames: [], inReturn: false };
|
|
2263
|
+
const body = renderChain(f.step.steps, ctx);
|
|
2264
|
+
return `function ${f.name}(${f.params.join(", ")}) {
|
|
2265
|
+
${indent(body)}
|
|
2266
|
+
}`;
|
|
2267
|
+
}).join("\n");
|
|
2268
|
+
}
|
|
2269
|
+
function renderImports(imports) {
|
|
2270
|
+
if (!imports?.length) return "";
|
|
2271
|
+
return imports.map((imp) => {
|
|
2272
|
+
const names = imp.names.map((n) => n.as && n.as !== n.name ? `${n.name} as ${n.as}` : n.name).join(", ");
|
|
2273
|
+
return `import { ${names} } from '${imp.from}'`;
|
|
2274
|
+
}).join("\n");
|
|
2275
|
+
}
|
|
2276
|
+
function renderChain(steps2, ctx) {
|
|
2277
|
+
return steps2.map((s) => renderStep(s, ctx)).join("\n");
|
|
2278
|
+
}
|
|
2279
|
+
function rewritePathExpr(src, ctx) {
|
|
2280
|
+
const map = (segments) => {
|
|
2281
|
+
const [s0, s1, s2, ...rest] = segments;
|
|
2282
|
+
if (s0 === "steps" && typeof s1 === "string" && (s2 === "data" || s2 === "result") && !s1.startsWith("_")) {
|
|
2283
|
+
return [s1, ...segments.slice(3)];
|
|
2284
|
+
}
|
|
2285
|
+
if (s0 === "vars" && typeof s1 === "string") return [s1, ...segments.slice(2)];
|
|
2286
|
+
if (s0 === "loop" && s1 === "item") return [ctx.loopNames[ctx.loopNames.length - 1] ?? "it", ...segments.slice(2)];
|
|
2287
|
+
return null;
|
|
2288
|
+
};
|
|
2289
|
+
return rewritePath(src, map);
|
|
2290
|
+
}
|
|
2291
|
+
function rewritePath(src, map) {
|
|
2292
|
+
const ast = parse(src);
|
|
2293
|
+
const walk = (n) => {
|
|
2294
|
+
const node = n;
|
|
2295
|
+
if (!node || typeof node !== "object") return;
|
|
2296
|
+
if (node.kind === "path" && node.segments) {
|
|
2297
|
+
const mapped = map(node.segments);
|
|
2298
|
+
if (mapped) node.segments = mapped;
|
|
2299
|
+
return;
|
|
2300
|
+
}
|
|
2301
|
+
if (node.kind === "ternary") {
|
|
2302
|
+
walk(node.cond);
|
|
2303
|
+
walk(node.then);
|
|
2304
|
+
walk(node.else);
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2307
|
+
if (node.kind === "call") {
|
|
2308
|
+
node.args?.forEach(walk);
|
|
2309
|
+
return;
|
|
2310
|
+
}
|
|
2311
|
+
for (const k of ["left", "right", "operand"]) walk(node[k]);
|
|
2312
|
+
};
|
|
2313
|
+
walk(ast);
|
|
2314
|
+
return toSrc(ast);
|
|
2315
|
+
}
|
|
2316
|
+
function renderValue(v, kind, ctx) {
|
|
2317
|
+
if (typeof v !== "string") return JSON.stringify(v);
|
|
2318
|
+
if (kind === "expr") return rewritePathExpr(v, ctx);
|
|
2319
|
+
if (!v.includes("{{")) return `'${v.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
2320
|
+
let out = "`";
|
|
2321
|
+
let i = 0;
|
|
2322
|
+
for (; ; ) {
|
|
2323
|
+
const start = v.indexOf("{{", i);
|
|
2324
|
+
if (start === -1) {
|
|
2325
|
+
out += v.slice(i).replace(/`/g, "\\`").replace(/\\/g, "\\\\");
|
|
2326
|
+
break;
|
|
2327
|
+
}
|
|
2328
|
+
out += v.slice(i, start).replace(/`/g, "\\`").replace(/\\/g, "\\\\");
|
|
2329
|
+
const end = v.indexOf("}}", start + 2);
|
|
2330
|
+
if (end === -1) break;
|
|
2331
|
+
out += "${" + rewritePathExpr(v.slice(start + 2, end), ctx) + "}";
|
|
2332
|
+
i = end + 2;
|
|
2333
|
+
}
|
|
2334
|
+
return out + "`";
|
|
2335
|
+
}
|
|
2336
|
+
function renderObjectParams(type, config, ctx) {
|
|
2337
|
+
const parts = [];
|
|
2338
|
+
for (const [key, v] of Object.entries(config)) {
|
|
2339
|
+
if (v === void 0) continue;
|
|
2340
|
+
const kind = FIELD_KIND[type]?.[key] ?? "template";
|
|
2341
|
+
if (v && typeof v === "object" && !Array.isArray(v)) {
|
|
2342
|
+
parts.push(`${key}: { ${renderObjectParams(type, v, ctx)} }`);
|
|
2343
|
+
continue;
|
|
2344
|
+
}
|
|
2345
|
+
parts.push(`${key}: ${renderValue(v, kind, ctx)}`);
|
|
2346
|
+
}
|
|
2347
|
+
return parts.join(", ");
|
|
2348
|
+
}
|
|
2349
|
+
function renderBuiltin(step, ctx) {
|
|
2350
|
+
const config = step.config ?? {};
|
|
2351
|
+
if (step.type === "call") {
|
|
2352
|
+
const args = (config.args ?? []).map((a) => rewritePathExpr(a, ctx)).join(", ");
|
|
2353
|
+
const call2 = `await ${config.name}(${args})`;
|
|
2354
|
+
return step.id.startsWith("_") || step.id.includes(":") ? call2 : `const ${step.id} = ${call2}`;
|
|
2355
|
+
}
|
|
2356
|
+
if (step.type === "store") {
|
|
2357
|
+
const key = renderValue(String(config.key), "template", ctx);
|
|
2358
|
+
const method = config.op === "get" ? "get" : "set";
|
|
2359
|
+
const args = config.op === "set" ? `${key}, ${renderValue(String(config.value ?? ""), "template", ctx)}` : key;
|
|
2360
|
+
const call2 = `await store.${method}(${args})`;
|
|
2361
|
+
return step.id.startsWith("_") ? call2 : `const ${step.id} = ${call2}`;
|
|
2362
|
+
}
|
|
2363
|
+
const params = renderObjectParams(step.type, config, ctx);
|
|
2364
|
+
const call = `await ${step.type}({ ${params} })`;
|
|
2365
|
+
return step.id.startsWith("_") ? call : `const ${step.id} = ${call}`;
|
|
2366
|
+
}
|
|
2367
|
+
function renderStep(step, ctx) {
|
|
2368
|
+
switch (step.type) {
|
|
2369
|
+
case "assign": {
|
|
2370
|
+
const cfg = step.config;
|
|
2371
|
+
const value = rewritePathExpr(cfg.value, ctx);
|
|
2372
|
+
if (!ctx.seenVars.has(cfg.target)) {
|
|
2373
|
+
ctx.seenVars.add(cfg.target);
|
|
2374
|
+
return `let ${cfg.target} = ${value}`;
|
|
2375
|
+
}
|
|
2376
|
+
return `${cfg.target} = ${value}`;
|
|
2377
|
+
}
|
|
2378
|
+
case "if": {
|
|
2379
|
+
const cfg = step.config;
|
|
2380
|
+
const when = rewritePathExpr(cfg.when, ctx);
|
|
2381
|
+
const head = `if (${when}) {`;
|
|
2382
|
+
const lines = [head];
|
|
2383
|
+
if (cfg.then?.steps.length) lines.push(indent(renderChain(cfg.then.steps, childLoopCtx(ctx))));
|
|
2384
|
+
if (cfg.else?.steps.length) {
|
|
2385
|
+
lines.push(cfg.then?.steps.length ? "} else {" : "} else {");
|
|
2386
|
+
lines.push(indent(renderChain(cfg.else.steps, childLoopCtx(ctx))));
|
|
2387
|
+
}
|
|
2388
|
+
lines.push("}");
|
|
2389
|
+
return lines.join("\n");
|
|
2390
|
+
}
|
|
2391
|
+
case "try": {
|
|
2392
|
+
const cfg = step.config;
|
|
2393
|
+
const lines = ["try {"];
|
|
2394
|
+
if (cfg.step?.steps.length) lines.push(indent(renderChain(cfg.step.steps, childLoopCtx(ctx))));
|
|
2395
|
+
lines.push("} catch {");
|
|
2396
|
+
if (cfg.catch?.steps.length) lines.push(indent(renderChain(cfg.catch.steps, childLoopCtx(ctx))));
|
|
2397
|
+
lines.push("}");
|
|
2398
|
+
return lines.join("\n");
|
|
2399
|
+
}
|
|
2400
|
+
case "while": {
|
|
2401
|
+
const cfg = step.config;
|
|
2402
|
+
const when = rewritePathExpr(cfg.when, ctx);
|
|
2403
|
+
return `while (${when}) {
|
|
2404
|
+
${indent(renderChain(cfg.step.steps, childLoopCtx(ctx)))}
|
|
2405
|
+
}`;
|
|
2406
|
+
}
|
|
2407
|
+
case "for": {
|
|
2408
|
+
const cfg = step.config;
|
|
2409
|
+
const items = rewritePathExpr(cfg.items, ctx);
|
|
2410
|
+
const loopName = `it${ctx.loopNames.length}`;
|
|
2411
|
+
return `for (const ${loopName} of ${items}) {
|
|
2412
|
+
${indent(renderChain(cfg.step.steps, { ...ctx, loopNames: [...ctx.loopNames, loopName] }))}
|
|
2413
|
+
}`;
|
|
2414
|
+
}
|
|
2415
|
+
case "return": {
|
|
2416
|
+
const cfg = step.config;
|
|
2417
|
+
return cfg.value !== void 0 ? `return ${rewritePathExpr(cfg.value, ctx)}` : "return";
|
|
2418
|
+
}
|
|
2419
|
+
default:
|
|
2420
|
+
return renderBuiltin(step, ctx);
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
function childLoopCtx(ctx) {
|
|
2424
|
+
return { ...ctx, inReturn: false };
|
|
2425
|
+
}
|
|
2426
|
+
function indent(s) {
|
|
2427
|
+
return s.split("\n").map((l) => " " + l).join("\n");
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
// src/server/workflow/index.ts
|
|
2431
|
+
function workflow(options) {
|
|
2432
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
2433
|
+
for (const s of builtinSteps()) handlers.set(s.type, s);
|
|
2434
|
+
const env = {
|
|
2435
|
+
fetch: options?.fetch,
|
|
2436
|
+
ai: options?.ai,
|
|
2437
|
+
email: options?.email,
|
|
2438
|
+
log: options?.log,
|
|
2439
|
+
store: options?.store ?? (options?.redis ? redisStore(options.redis) : void 0)
|
|
2440
|
+
};
|
|
2441
|
+
const registry = {
|
|
2442
|
+
get: (type) => handlers.get(type),
|
|
2443
|
+
has: (type) => handlers.has(type),
|
|
2444
|
+
schema: (type) => {
|
|
2445
|
+
const h = handlers.get(type);
|
|
2446
|
+
if (!h) return void 0;
|
|
2447
|
+
return { required: h.required ?? [], fields: h.fields };
|
|
2448
|
+
}
|
|
2449
|
+
};
|
|
2450
|
+
return {
|
|
2451
|
+
validate: (def) => validate(def, registry),
|
|
2452
|
+
execute: (def, opts) => runWorkflow(def, opts ?? {}, env, registry),
|
|
2453
|
+
registerStep: (handler) => {
|
|
2454
|
+
handlers.set(handler.type, handler);
|
|
2455
|
+
},
|
|
2456
|
+
stepSchemas: () => [...handlers.values()].map((h) => ({
|
|
2457
|
+
type: h.type,
|
|
2458
|
+
label: h.label,
|
|
2459
|
+
fields: h.fields,
|
|
2460
|
+
required: h.required ?? []
|
|
2461
|
+
}))
|
|
2462
|
+
};
|
|
2463
|
+
}
|
|
2464
|
+
export {
|
|
2465
|
+
compile,
|
|
2466
|
+
compileWfjs,
|
|
2467
|
+
evaluate,
|
|
2468
|
+
evaluateBoolean,
|
|
2469
|
+
interpolate,
|
|
2470
|
+
parse,
|
|
2471
|
+
redisStore,
|
|
2472
|
+
toJs,
|
|
2473
|
+
toJsonSchema,
|
|
2474
|
+
workflow,
|
|
2475
|
+
workflowToDag
|
|
2476
|
+
};
|