@pylonts/dsl 1.1.6 → 1.1.12

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.
Files changed (88) hide show
  1. package/README.md +4 -0
  2. package/dist/action.d.ts +32 -0
  3. package/dist/action.js +14 -0
  4. package/dist/aggregate.d.ts +38 -0
  5. package/dist/aggregate.js +46 -0
  6. package/dist/business-flow.d.ts +9 -0
  7. package/dist/business-flow.js +72 -0
  8. package/dist/controller.d.ts +17 -9
  9. package/dist/controller.js +8 -2
  10. package/dist/convert.d.ts +28 -10
  11. package/dist/convert.js +16 -5
  12. package/dist/curd.d.ts +7 -10
  13. package/dist/curd.js +3 -1
  14. package/dist/dao.d.ts +81 -53
  15. package/dist/dao.js +291 -12
  16. package/dist/db.d.ts +6 -0
  17. package/dist/db.js +10 -0
  18. package/dist/domain-event.d.ts +48 -0
  19. package/dist/domain-event.js +24 -0
  20. package/dist/dsl.d.ts +17 -2
  21. package/dist/dsl.js +7 -0
  22. package/dist/dto.d.ts +6 -4
  23. package/dist/dto.js +5 -4
  24. package/dist/entity.d.ts +29 -0
  25. package/dist/entity.js +13 -0
  26. package/dist/exception.d.ts +9 -3
  27. package/dist/exception.js +25 -1
  28. package/dist/expr.d.ts +45 -0
  29. package/dist/expr.js +32 -0
  30. package/dist/filter.d.ts +45 -0
  31. package/dist/filter.js +21 -0
  32. package/dist/flow-script.d.ts +108 -0
  33. package/dist/flow-script.js +505 -0
  34. package/dist/flow.d.ts +294 -17
  35. package/dist/flow.js +803 -18
  36. package/dist/index.d.ts +6 -2
  37. package/dist/index.js +6 -2
  38. package/dist/mermaid-driver.js +264 -24
  39. package/dist/mysql-driver.js +3 -0
  40. package/dist/project.d.ts +10 -6
  41. package/dist/project.js +35 -4
  42. package/dist/repository.d.ts +26 -0
  43. package/dist/repository.js +8 -0
  44. package/dist/service.d.ts +14 -2
  45. package/dist/service.js +49 -0
  46. package/dist/third-service.d.ts +5 -0
  47. package/dist/third-service.js +1 -0
  48. package/dist/typebox-driver.js +4 -0
  49. package/dist/utils.d.ts +9 -2
  50. package/dist/utils.js +4 -0
  51. package/docs/aggregate.md +110 -0
  52. package/docs/curd.md +146 -111
  53. package/docs/dao-generation.md +478 -0
  54. package/docs/ddd-principles.md +75 -0
  55. package/docs/domain-event.md +137 -0
  56. package/docs/keyword-matcher.md +182 -0
  57. package/docs/project.md +17 -9
  58. package/docs/token.md +327 -0
  59. package/docs/trans-reentrant.md +85 -0
  60. package/package.json +25 -6
  61. package/src/action.ts +51 -10
  62. package/src/aggregate.ts +104 -0
  63. package/src/business-flow.ts +80 -0
  64. package/src/controller.ts +25 -11
  65. package/src/convert.ts +51 -15
  66. package/src/curd.ts +12 -6
  67. package/src/dao.ts +377 -63
  68. package/src/db.ts +13 -0
  69. package/src/domain-event.ts +74 -0
  70. package/src/dsl.ts +23 -2
  71. package/src/dto.ts +9 -6
  72. package/src/entity.ts +43 -0
  73. package/src/exception.ts +30 -5
  74. package/src/expr.ts +65 -0
  75. package/src/filter.ts +70 -0
  76. package/src/flow-script.ts +696 -0
  77. package/src/flow.ts +1129 -46
  78. package/src/index.ts +6 -2
  79. package/src/mermaid-driver.ts +256 -29
  80. package/src/mysql-driver.ts +3 -0
  81. package/src/project.ts +138 -97
  82. package/src/repository.ts +35 -0
  83. package/src/service.ts +68 -3
  84. package/src/third-service.ts +6 -0
  85. package/src/typebox-driver.ts +4 -0
  86. package/src/utils.ts +13 -2
  87. package/src/endpoint.ts +0 -18
  88. package/src/provider.ts +0 -68
@@ -0,0 +1,505 @@
1
+ // Flow-script: a sequential source layer for flows. Statements read top to
2
+ // bottom like Java — IF/TRY are control steps, invoke/write/THROW/RETURN are
3
+ // actions — and the script compiles down to the graph IR (FlowSchema). The
4
+ // graph's ceremony (node names, edges, exception ends, sub-flow registries)
5
+ // is machine-generated: method throws are routed to auto-synthesized ends,
6
+ // decisions become guard/ifNode diamonds, and TRY/sub bodies become
7
+ // sub-flows carrying only the slots they actually use.
8
+ //
9
+ // The IR stays the single executable model (mermaid, must-analysis, throws
10
+ // coverage, service contracts all consume it); this layer is a lowering, not
11
+ // a parallel model.
12
+ import { defineFlow, defineSlots, edge, guard, ifNode, isCall, isEnd, isFlowNode, isGuard, isIfNode, methodOf, node, tryNode, } from './flow.js';
13
+ /** Call a method as a statement or (in IF position) as a utils predicate. */
14
+ export function invoke(method, args, result) {
15
+ return { kind: 'invoke', method, args, result };
16
+ }
17
+ /** Assign a slot without a method call (a construction). */
18
+ export function write(slot) {
19
+ return { kind: 'write', slot };
20
+ }
21
+ /** Throw an exception — inside an IF branch the condition and the exit merge
22
+ * into one guard check; bare in a block it is an unconditional exit. */
23
+ export function THROW(exception, message) {
24
+ return { kind: 'throw', exception, message };
25
+ }
26
+ /** Return early to the flow's return end. */
27
+ export function RETURN() {
28
+ return { kind: 'return' };
29
+ }
30
+ /** Publish a domain event — the outbox write joins the surrounding
31
+ * transaction. The payload slot must carry exactly the event's fields
32
+ * (compile-time checked against the slot's declared message). */
33
+ export function publish(event, payload) {
34
+ if (payload !== undefined) {
35
+ const type = payload.type;
36
+ if (type?.fields !== undefined) {
37
+ const expected = Object.keys(event.fields);
38
+ const got = Object.keys(type.fields);
39
+ const missing = expected.filter((k) => !got.includes(k));
40
+ const extra = got.filter((k) => !expected.includes(k));
41
+ if (missing.length > 0 || extra.length > 0) {
42
+ throw new Error(`flow-script publish('${event.name}', ${payload.name}): payload fields mismatch — ` +
43
+ `${missing.length > 0 ? `missing ${missing.join(', ')}` : ''}` +
44
+ `${missing.length > 0 && extra.length > 0 ? '; ' : ''}` +
45
+ `${extra.length > 0 ? `unexpected ${extra.join(', ')}` : ''}`);
46
+ }
47
+ }
48
+ }
49
+ return { kind: 'publish', event, payload };
50
+ }
51
+ /** Conditional step: IF(cond).THEN(...) with optional .ELSE(...). The
52
+ * condition is a comparison (lt/gt/eq/...) or a predicate call — an
53
+ * invoke(...) in this position is a utils predicate. */
54
+ export function IF(cond) {
55
+ const c = toCondition(cond);
56
+ return {
57
+ THEN(...steps) {
58
+ if (steps.length === 0) {
59
+ throw new Error('flow-script IF: THEN requires at least one step');
60
+ }
61
+ return {
62
+ kind: 'if',
63
+ cond: c,
64
+ then: steps,
65
+ ELSE(...elseSteps) {
66
+ if (elseSteps.length === 0) {
67
+ throw new Error('flow-script IF: ELSE requires at least one step');
68
+ }
69
+ return { kind: 'if', cond: c, then: steps, else: elseSteps };
70
+ },
71
+ };
72
+ },
73
+ };
74
+ }
75
+ /** Protected region: TRY([...]).CATCH([Exception, [...]], ...).FINALLY([...]). */
76
+ export function TRY(body, name) {
77
+ return {
78
+ kind: 'try',
79
+ name,
80
+ body,
81
+ catches: [],
82
+ CATCH(...routes) {
83
+ return { ...this, catches: [...this.catches, ...routes] };
84
+ },
85
+ FINALLY(steps) {
86
+ return { ...this, finally: steps };
87
+ },
88
+ };
89
+ }
90
+ /** A named sub-flow region. */
91
+ export function sub(name, steps, description) {
92
+ return { kind: 'sub', name, steps, description };
93
+ }
94
+ /** Compile a sequential script into a FlowSchema. options.slots declares the
95
+ * named slots (message bindings); every declared slot must be used somewhere
96
+ * in the compiled flow. */
97
+ export function flowScript(name, options, build) {
98
+ if (options.slots !== undefined && 'args' in options.slots) {
99
+ throw new Error(`flow-script ${name}: "args" is the built-in input slot — declare the input message via the args option`);
100
+ }
101
+ const slots = defineSlots({ args: options.args, ...(options.slots ?? {}) });
102
+ const steps = [];
103
+ build({ next: (...s) => void steps.push(...s), slots });
104
+ const root = {
105
+ name,
106
+ argsMessage: options.args,
107
+ slotMessages: options.slots ?? {},
108
+ usedNames: new Set(),
109
+ ends: new Map(),
110
+ seen: new Set(),
111
+ usedSlots: new Set(),
112
+ edges: [],
113
+ tryTotal: countTrys(steps),
114
+ tryCount: 0,
115
+ entrySlots: [],
116
+ };
117
+ const flow = compileFlowBody(name, options.description, steps, root, root.entrySlots);
118
+ for (const key of Object.keys(options.slots ?? {})) {
119
+ if (!root.usedNames.has(key)) {
120
+ throw new Error(`flow-script ${name}: slot "${key}" is declared but never used`);
121
+ }
122
+ }
123
+ return flow;
124
+ }
125
+ /** The hole the flow's own return end fills: the last statement of every flow
126
+ * links here, and the edges callback swaps it for the real return end. */
127
+ const DANGLE = { type: 'return', name: 'return' };
128
+ function addUsed(ctx, slot) {
129
+ if (slot === undefined)
130
+ return;
131
+ ctx.usedSlots.add(slot);
132
+ ctx.usedNames.add(slot.name);
133
+ }
134
+ function addConditionUsed(ctx, c) {
135
+ if (!isCall(c)) {
136
+ addUsed(ctx, c.field.slot);
137
+ return;
138
+ }
139
+ for (const t of c.args ?? [])
140
+ addUsed(ctx, t);
141
+ addUsed(ctx, c.result);
142
+ }
143
+ /** An invoke in condition position becomes a utils predicate call. */
144
+ function isInvokeStep(c) {
145
+ return c.kind === 'invoke';
146
+ }
147
+ function toCondition(c) {
148
+ if (isInvokeStep(c)) {
149
+ if (c.result !== undefined) {
150
+ throw new Error('flow-script IF: a predicate call cannot assign a result slot');
151
+ }
152
+ return { method: c.method, args: c.args === undefined ? [] : Array.isArray(c.args) ? c.args : [c.args] };
153
+ }
154
+ return c;
155
+ }
156
+ /** Display form mirrors the mermaid driver: owner.name / schema.name. */
157
+ function displayMethodName(m) {
158
+ if ('owner' in m)
159
+ return `${m.owner}.${m.name}`;
160
+ return `${m.schema.name}.${m.name}`;
161
+ }
162
+ /** The flow's exception end for `ex` — one per exception name per flow. */
163
+ function exceptionEnd(ctx, ex) {
164
+ let end = ctx.ends.get(ex.name);
165
+ if (end === undefined) {
166
+ end = { type: 'exception', name: `throw ${ex.name}`, exception: ex, description: 'method throws' };
167
+ ctx.ends.set(ex.name, end);
168
+ }
169
+ return end;
170
+ }
171
+ function methodThrows(m) {
172
+ return 'throws' in m && m.throws !== undefined ? m.throws : [];
173
+ }
174
+ /** Readable condition text used as node/branch labels (and as the throw
175
+ * label when THROW carries no message). */
176
+ function renderCondition(c) {
177
+ if (!isCall(c)) {
178
+ const field = c.field.field;
179
+ const ref = `${c.field.slot.name}.${field.name}`;
180
+ switch (c.op) {
181
+ case 'lt':
182
+ return `${ref} < ${c.value}`;
183
+ case 'le':
184
+ return `${ref} <= ${c.value}`;
185
+ case 'gt':
186
+ return `${ref} > ${c.value}`;
187
+ case 'ge':
188
+ return `${ref} >= ${c.value}`;
189
+ case 'eq':
190
+ return `${ref} = ${renderValue(c.value)}`;
191
+ case 'ne':
192
+ return `${ref} ≠ ${renderValue(c.value)}`;
193
+ case 'isNull':
194
+ return `${ref} is null`;
195
+ case 'isNotNull':
196
+ return `${ref} is not null`;
197
+ }
198
+ }
199
+ const args = (c.args ?? []).map((a) => a.name).join(', ');
200
+ return `${methodOf(c).name}(${args})`;
201
+ }
202
+ function renderValue(v) {
203
+ if (typeof v === 'object')
204
+ return v.symbol;
205
+ return JSON.stringify(v);
206
+ }
207
+ /** Compile statements back to front so every step knows its continuation
208
+ * (the entry of what follows it). Edges are pushed into the flow's own edge
209
+ * list — a continuation shared by several branches is not re-emitted. */
210
+ function compileStatements(steps, ctx, cont, inherited = []) {
211
+ for (let i = 0; i < steps.length - 1; i++) {
212
+ const s = steps[i];
213
+ if (s.kind === 'throw' || s.kind === 'return') {
214
+ throw new Error(`flow-script ${ctx.name}: ${s.kind === 'throw' ? 'THROW' : 'RETURN'} ends its block — steps after it are unreachable`);
215
+ }
216
+ }
217
+ let entry = cont;
218
+ for (let i = steps.length - 1; i >= 0; i--) {
219
+ entry = compileStep(steps[i], ctx, entry, unionSlots(inherited, producedBefore(steps, i)));
220
+ }
221
+ return entry;
222
+ }
223
+ /** Slots produced by top-level invoke/write steps before `index` (script
224
+ * order). Branch-internal productions are excluded — a slot produced only
225
+ * inside one branch is not guaranteed on every path past it. */
226
+ function producedBefore(steps, index) {
227
+ const out = new Set();
228
+ for (let i = 0; i < index; i++) {
229
+ const s = steps[i];
230
+ if (s.kind === 'invoke' && s.result !== undefined)
231
+ out.add(s.result);
232
+ else if (s.kind === 'write')
233
+ out.add(s.slot);
234
+ }
235
+ return [...out];
236
+ }
237
+ function unionSlots(a, b) {
238
+ return [...new Set([...a, ...b])];
239
+ }
240
+ function compileStep(step, ctx, cont, inherit) {
241
+ switch (step.kind) {
242
+ case 'invoke':
243
+ return compileInvoke(step, ctx, cont);
244
+ case 'write':
245
+ return compileWrite(step, ctx, cont);
246
+ case 'throw':
247
+ return compileThrow(step, ctx);
248
+ case 'return':
249
+ return compileReturn(ctx);
250
+ case 'publish':
251
+ return compilePublish(step, ctx, cont);
252
+ case 'if':
253
+ return compileIf(step, ctx, cont, inherit);
254
+ case 'try':
255
+ return compileTry(step, ctx, cont, inherit);
256
+ case 'sub':
257
+ return compileSub(step, ctx, cont, inherit);
258
+ }
259
+ }
260
+ function compileInvoke(step, ctx, cont) {
261
+ if (Array.isArray(step.args))
262
+ step.args.forEach((s) => addUsed(ctx, s));
263
+ else
264
+ addUsed(ctx, step.args);
265
+ addUsed(ctx, step.result);
266
+ const call = {
267
+ method: step.method,
268
+ args: step.args === undefined ? undefined : Array.isArray(step.args) ? step.args : [step.args],
269
+ result: step.result,
270
+ };
271
+ const n = node(displayMethodName(step.method), { methods: [call] });
272
+ ctx.seen.add(n);
273
+ for (const ex of methodThrows(step.method)) {
274
+ const end = exceptionEnd(ctx, ex);
275
+ ctx.seen.add(end);
276
+ ctx.edges.push(edge(n, end, { throws: ex }));
277
+ }
278
+ ctx.edges.push(edge(n, cont));
279
+ return n;
280
+ }
281
+ function compileWrite(step, ctx, cont) {
282
+ addUsed(ctx, step.slot);
283
+ const n = node(`写 ${step.slot.name}`, { writes: [step.slot] });
284
+ ctx.seen.add(n);
285
+ ctx.edges.push(edge(n, cont));
286
+ return n;
287
+ }
288
+ function compileThrow(step, ctx) {
289
+ // Unconditional exit: a check-less guard always takes its route; no
290
+ // outgoing edge — nothing in the block runs after it.
291
+ const label = step.message ?? `throw ${step.exception.name}`;
292
+ const g = guard(label, { checks: [{ when: label, exception: step.exception }] });
293
+ ctx.seen.add(g);
294
+ return g;
295
+ }
296
+ function compileReturn(ctx) {
297
+ const g = guard('返回', { checks: [{ when: '返回', return: true }] });
298
+ ctx.seen.add(g);
299
+ return g;
300
+ }
301
+ function compilePublish(step, ctx, cont) {
302
+ addUsed(ctx, step.payload);
303
+ const n = node(`发布 ${step.event.name}`, {
304
+ publish: { event: step.event, payload: step.payload },
305
+ reads: step.payload ? [step.payload] : undefined,
306
+ });
307
+ ctx.seen.add(n);
308
+ ctx.edges.push(edge(n, cont));
309
+ return n;
310
+ }
311
+ function compileIf(step, ctx, cont, inherit) {
312
+ addConditionUsed(ctx, step.cond);
313
+ const label = renderCondition(step.cond);
314
+ const single = step.then.length === 1 ? step.then[0] : undefined;
315
+ // Single-exit branches merge the condition and the exit into one guard
316
+ // check (the IR's guard shape); the else path is the guard's fall-through.
317
+ if (single !== undefined && (single.kind === 'throw' || single.kind === 'return')) {
318
+ const elseEntry = compileStatements(step.else ?? [], ctx, cont, inherit);
319
+ const g = single.kind === 'throw'
320
+ ? guard(label, {
321
+ checks: [{ when: single.message ?? label, exception: single.exception, check: step.cond }],
322
+ })
323
+ : guard(label, { checks: [{ when: '返回', return: true, check: step.cond }] });
324
+ ctx.seen.add(g);
325
+ ctx.edges.push(edge(g, elseEntry));
326
+ return g;
327
+ }
328
+ const thenEntry = compileStatements(step.then, ctx, cont, inherit);
329
+ let elseEntry = compileStatements(step.else ?? [], ctx, cont, inherit);
330
+ // A trailing IF (its else is the flow exit) cannot target the return end
331
+ // directly — ifNode targets are steps only — so the else runs through a
332
+ // return guard whose implicit exit reaches the return end.
333
+ if (elseEntry === DANGLE) {
334
+ const r = guard('返回', { checks: [{ when: '返回', return: true }] });
335
+ ctx.seen.add(r);
336
+ elseEntry = r;
337
+ }
338
+ const d = ifNode(label, {
339
+ cases: [{ when: label, check: step.cond, to: thenEntry }],
340
+ else: elseEntry,
341
+ });
342
+ ctx.seen.add(d);
343
+ return d;
344
+ }
345
+ /** TRY steps in this flow's own statement tree (branch chains share the
346
+ * flow's compile context; try bodies and sub-flows number their own). */
347
+ function countTrys(steps) {
348
+ let n = 0;
349
+ for (const s of steps) {
350
+ if (s.kind === 'try')
351
+ n += 1;
352
+ else if (s.kind === 'if')
353
+ n += countTrys(s.then) + countTrys(s.else ?? []);
354
+ }
355
+ return n;
356
+ }
357
+ function compileTry(step, ctx, cont, inherit) {
358
+ // The back-to-front walk meets the last try first; ordinal restores the
359
+ // script order for stable flow names.
360
+ const ordinal = ctx.tryTotal - ++ctx.tryCount + 1;
361
+ const suffix = ordinal === 1 ? '' : `${ordinal}`;
362
+ const body = compileFlowBody(`${ctx.name}.tryBody${suffix}`, undefined, step.body, ctx, inherit);
363
+ const catches = step.catches.map(([ex, steps]) => ({
364
+ exception: ex,
365
+ handler: compileFlowBody(`${ctx.name}.catch${ex.name}${suffix}`, undefined, steps, ctx, inherit),
366
+ }));
367
+ const t = tryNode(step.name ?? 'try', {
368
+ body,
369
+ catches,
370
+ finally: step.finally
371
+ ? compileFlowBody(`${ctx.name}.finally${suffix}`, undefined, step.finally, ctx, inherit)
372
+ : undefined,
373
+ });
374
+ ctx.seen.add(t);
375
+ // Handler exception ends rethrow out of the region: route them as typed
376
+ // throws edges to the enclosing flow's ends (Java semantics — a catch
377
+ // handler may throw out of the try).
378
+ for (const c of catches) {
379
+ for (const end of c.handler.nodes) {
380
+ if (!isEnd(end) || end.type !== 'exception')
381
+ continue;
382
+ const ex = end.exception;
383
+ if (ex === undefined) {
384
+ throw new Error(`flow-script ${ctx.name}: exception end "${end.name}" has no exception type`);
385
+ }
386
+ const target = exceptionEnd(ctx, ex);
387
+ ctx.seen.add(target);
388
+ ctx.edges.push(edge(t, target, { throws: ex }));
389
+ }
390
+ }
391
+ ctx.edges.push(edge(t, cont));
392
+ return t;
393
+ }
394
+ function compileSub(step, ctx, cont, inherit) {
395
+ const subFlow = compileFlowBody(`${ctx.name}.${step.name}`, step.description, step.steps, ctx, inherit);
396
+ const n = node(step.name, { flow: subFlow });
397
+ ctx.seen.add(n);
398
+ ctx.edges.push(edge(n, cont));
399
+ return n;
400
+ }
401
+ /** Compile one flow (top-level or sub-flow): its own slots registry (args
402
+ * plus the named slots actually used inside), its own exception ends, and
403
+ * its own return end (linked through DANGLE). */
404
+ function compileFlowBody(name, description, steps, parent, entrySlots) {
405
+ const ctx = {
406
+ name,
407
+ argsMessage: parent.argsMessage,
408
+ slotMessages: parent.slotMessages,
409
+ usedNames: parent.usedNames,
410
+ ends: new Map(),
411
+ seen: new Set(),
412
+ usedSlots: new Set(),
413
+ edges: [],
414
+ tryTotal: countTrys(steps),
415
+ tryCount: 0,
416
+ entrySlots,
417
+ };
418
+ const entry = compileStatements(steps, ctx, DANGLE, entrySlots);
419
+ if (steps.length === 0) {
420
+ // An empty flow (e.g. a swallow catch) needs a real start node.
421
+ const pass = node('pass', {});
422
+ ctx.seen.add(pass);
423
+ ctx.edges.push(edge(pass, DANGLE));
424
+ return buildFlow(name, description, ctx, pass);
425
+ }
426
+ return buildFlow(name, description, ctx, entry);
427
+ }
428
+ function buildFlow(name, description, ctx, start) {
429
+ const nodes = [...ctx.seen];
430
+ const registry = defineSlots(buildSlots(ctx));
431
+ // Entry inheritance only for slots the flow actually consumes; unused
432
+ // productions of the enclosing flow are not this flow's concern.
433
+ const entrySlots = rewriteSlots(nodes, ctx.edges, registry, ctx.entrySlots.filter((s) => ctx.usedSlots.has(s)));
434
+ return defineFlow(name, {
435
+ start,
436
+ description,
437
+ args: ctx.argsMessage,
438
+ slots: registry,
439
+ entrySlots,
440
+ edges: (flow) => ctx.edges.map((e) => (e.end === DANGLE ? { ...e, end: flow.returnEnd } : e)),
441
+ });
442
+ }
443
+ /** The flow's slot registry: args plus the named slots used inside the flow. */
444
+ function buildSlots(ctx) {
445
+ const out = { args: ctx.argsMessage };
446
+ for (const s of ctx.usedSlots) {
447
+ if (s.name === 'args')
448
+ continue;
449
+ const msg = ctx.slotMessages[s.name];
450
+ if (msg === undefined) {
451
+ throw new Error(`flow-script ${ctx.name}: slot "${s.name}" is used but has no declared message`);
452
+ }
453
+ out[s.name] = msg;
454
+ }
455
+ return out;
456
+ }
457
+ /** Every flow compiles with its own registry objects; slot references inside
458
+ * its nodes still point at the script-level registry, so they are re-bound
459
+ * by name to this flow's registry. Returns the entry slots re-bound the same
460
+ * way. */
461
+ function rewriteSlots(nodes, edges, slots, entrySlots) {
462
+ const map = (s) => {
463
+ const t = slots[s.name];
464
+ if (t === undefined) {
465
+ throw new Error(`flow-script: slot "${s.name}" is missing from the compiled registry`);
466
+ }
467
+ return t;
468
+ };
469
+ const ref = (m) => {
470
+ if (!isCall(m))
471
+ return m;
472
+ return { method: m.method, args: m.args?.map(map), result: m.result ? map(m.result) : undefined };
473
+ };
474
+ const cond = (c) => {
475
+ if (!isCall(c)) {
476
+ return { kind: 'comparison', op: c.op, field: { slot: map(c.field.slot), field: c.field.field }, value: c.value };
477
+ }
478
+ return { method: c.method, args: c.args?.map(map), result: c.result ? map(c.result) : undefined };
479
+ };
480
+ for (const n of nodes) {
481
+ if (isFlowNode(n)) {
482
+ n.methods = n.methods?.map(ref);
483
+ if (n.publish)
484
+ n.publish = { event: n.publish.event, payload: n.publish.payload ? map(n.publish.payload) : undefined };
485
+ n.reads = n.reads?.map(map);
486
+ n.writes = n.writes?.map(map);
487
+ }
488
+ if (isGuard(n)) {
489
+ for (const c of n.checks) {
490
+ c.reads = c.reads?.map(map);
491
+ if (c.check)
492
+ c.check = cond(c.check);
493
+ }
494
+ }
495
+ if (isIfNode(n)) {
496
+ for (const c of n.cases)
497
+ c.check = cond(c.check);
498
+ }
499
+ }
500
+ for (const e of edges) {
501
+ if (e.check)
502
+ e.check = cond(e.check);
503
+ }
504
+ return entrySlots.map(map);
505
+ }