@unlaxer/tramli 1.5.2 → 1.5.3
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/dist/cjs/data-flow-graph.d.ts +92 -0
- package/dist/cjs/data-flow-graph.js +451 -0
- package/dist/cjs/flow-context.d.ts +19 -0
- package/dist/cjs/flow-context.js +44 -0
- package/dist/cjs/flow-definition.d.ts +85 -0
- package/dist/cjs/flow-definition.js +488 -0
- package/dist/cjs/flow-engine.d.ts +13 -0
- package/dist/cjs/flow-engine.js +242 -0
- package/dist/cjs/flow-error.d.ts +14 -0
- package/dist/cjs/flow-error.js +34 -0
- package/dist/cjs/flow-instance.d.ts +44 -0
- package/dist/cjs/flow-instance.js +104 -0
- package/dist/cjs/flow-key.d.ts +10 -0
- package/dist/cjs/flow-key.js +6 -0
- package/dist/cjs/in-memory-flow-store.d.ts +19 -0
- package/dist/cjs/in-memory-flow-store.js +27 -0
- package/dist/cjs/index.d.ts +16 -0
- package/dist/cjs/index.js +29 -0
- package/dist/cjs/mermaid-generator.d.ts +9 -0
- package/dist/cjs/mermaid-generator.js +80 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/skeleton-generator.d.ts +10 -0
- package/dist/cjs/skeleton-generator.js +48 -0
- package/dist/cjs/tramli.d.ts +8 -0
- package/dist/cjs/tramli.js +14 -0
- package/dist/cjs/types.d.ts +75 -0
- package/dist/cjs/types.js +2 -0
- package/dist/esm/data-flow-graph.d.ts +92 -0
- package/dist/esm/data-flow-graph.js +447 -0
- package/dist/esm/flow-context.d.ts +19 -0
- package/dist/esm/flow-context.js +40 -0
- package/dist/esm/flow-definition.d.ts +85 -0
- package/dist/esm/flow-definition.js +480 -0
- package/dist/esm/flow-engine.d.ts +13 -0
- package/dist/esm/flow-engine.js +238 -0
- package/dist/esm/flow-error.d.ts +14 -0
- package/dist/esm/flow-error.js +30 -0
- package/dist/esm/flow-instance.d.ts +44 -0
- package/dist/esm/flow-instance.js +100 -0
- package/dist/esm/flow-key.d.ts +10 -0
- package/dist/esm/flow-key.js +3 -0
- package/dist/esm/in-memory-flow-store.d.ts +19 -0
- package/dist/esm/in-memory-flow-store.js +23 -0
- package/dist/esm/index.d.ts +16 -0
- package/dist/esm/index.js +11 -0
- package/dist/esm/mermaid-generator.d.ts +9 -0
- package/dist/esm/mermaid-generator.js +76 -0
- package/dist/esm/skeleton-generator.d.ts +10 -0
- package/dist/esm/skeleton-generator.js +44 -0
- package/dist/esm/tramli.d.ts +8 -0
- package/dist/esm/tramli.js +10 -0
- package/dist/esm/types.d.ts +75 -0
- package/dist/esm/types.js +1 -0
- package/package.json +8 -4
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
import { FlowError } from './flow-error.js';
|
|
2
|
+
import { DataFlowGraph } from './data-flow-graph.js';
|
|
3
|
+
export class FlowDefinition {
|
|
4
|
+
name;
|
|
5
|
+
stateConfig;
|
|
6
|
+
ttl; // milliseconds
|
|
7
|
+
maxGuardRetries;
|
|
8
|
+
transitions;
|
|
9
|
+
errorTransitions;
|
|
10
|
+
initialState;
|
|
11
|
+
terminalStates;
|
|
12
|
+
dataFlowGraph;
|
|
13
|
+
constructor(name, stateConfig, ttl, maxGuardRetries, transitions, errorTransitions) {
|
|
14
|
+
this.name = name;
|
|
15
|
+
this.stateConfig = stateConfig;
|
|
16
|
+
this.ttl = ttl;
|
|
17
|
+
this.maxGuardRetries = maxGuardRetries;
|
|
18
|
+
this.transitions = [...transitions];
|
|
19
|
+
this.errorTransitions = new Map(errorTransitions);
|
|
20
|
+
let initial = null;
|
|
21
|
+
const terminals = new Set();
|
|
22
|
+
for (const [state, cfg] of Object.entries(stateConfig)) {
|
|
23
|
+
if (cfg.initial)
|
|
24
|
+
initial = state;
|
|
25
|
+
if (cfg.terminal)
|
|
26
|
+
terminals.add(state);
|
|
27
|
+
}
|
|
28
|
+
this.initialState = initial;
|
|
29
|
+
this.terminalStates = terminals;
|
|
30
|
+
}
|
|
31
|
+
transitionsFrom(state) {
|
|
32
|
+
return this.transitions.filter(t => t.from === state);
|
|
33
|
+
}
|
|
34
|
+
externalFrom(state) {
|
|
35
|
+
return this.transitions.find(t => t.from === state && t.type === 'external');
|
|
36
|
+
}
|
|
37
|
+
allStates() {
|
|
38
|
+
return Object.keys(this.stateConfig);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Create a new FlowDefinition with a sub-flow inserted before a specific transition.
|
|
42
|
+
*/
|
|
43
|
+
withPlugin(from, to, pluginFlow) {
|
|
44
|
+
const newTransitions = [];
|
|
45
|
+
let replaced = false;
|
|
46
|
+
for (const t of this.transitions) {
|
|
47
|
+
if (t.from === from && t.to === to && !replaced) {
|
|
48
|
+
const exitMap = new Map();
|
|
49
|
+
for (const terminal of pluginFlow.terminalStates)
|
|
50
|
+
exitMap.set(terminal, to);
|
|
51
|
+
newTransitions.push({
|
|
52
|
+
from, to: from, type: 'sub_flow',
|
|
53
|
+
processor: t.processor, guard: undefined, branch: undefined,
|
|
54
|
+
branchTargets: new Map(),
|
|
55
|
+
subFlowDefinition: pluginFlow, exitMappings: exitMap,
|
|
56
|
+
});
|
|
57
|
+
replaced = true;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
newTransitions.push(t);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const result = Object.create(FlowDefinition.prototype);
|
|
64
|
+
Object.assign(result, {
|
|
65
|
+
name: this.name + '+plugin:' + pluginFlow.name,
|
|
66
|
+
stateConfig: this.stateConfig, ttl: this.ttl,
|
|
67
|
+
maxGuardRetries: this.maxGuardRetries,
|
|
68
|
+
transitions: newTransitions,
|
|
69
|
+
errorTransitions: new Map(this.errorTransitions),
|
|
70
|
+
initialState: this.initialState,
|
|
71
|
+
terminalStates: this.terminalStates,
|
|
72
|
+
dataFlowGraph: this.dataFlowGraph, // reuse parent's graph
|
|
73
|
+
});
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
// ─── Builder ─────────────────────────────────────────────
|
|
77
|
+
static builder(name, stateConfig) {
|
|
78
|
+
return new Builder(name, stateConfig);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export class Builder {
|
|
82
|
+
name;
|
|
83
|
+
stateConfig;
|
|
84
|
+
ttl = 5 * 60 * 1000; // 5 minutes
|
|
85
|
+
maxGuardRetries = 3;
|
|
86
|
+
transitions = [];
|
|
87
|
+
errorTransitions = new Map();
|
|
88
|
+
initiallyAvailableKeys = [];
|
|
89
|
+
constructor(name, stateConfig) {
|
|
90
|
+
this.name = name;
|
|
91
|
+
this.stateConfig = stateConfig;
|
|
92
|
+
}
|
|
93
|
+
initiallyAvailable(...keys) {
|
|
94
|
+
for (const k of keys)
|
|
95
|
+
this.initiallyAvailableKeys.push(k);
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
setTtl(ms) { this.ttl = ms; return this; }
|
|
99
|
+
setMaxGuardRetries(max) { this.maxGuardRetries = max; return this; }
|
|
100
|
+
from(state) {
|
|
101
|
+
return new FromBuilder(this, state);
|
|
102
|
+
}
|
|
103
|
+
onError(from, to) {
|
|
104
|
+
this.errorTransitions.set(from, to);
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
onAnyError(errorState) {
|
|
108
|
+
for (const s of Object.keys(this.stateConfig)) {
|
|
109
|
+
if (!this.stateConfig[s].terminal)
|
|
110
|
+
this.errorTransitions.set(s, errorState);
|
|
111
|
+
}
|
|
112
|
+
return this;
|
|
113
|
+
}
|
|
114
|
+
/** @internal */
|
|
115
|
+
addTransition(t) { this.transitions.push(t); }
|
|
116
|
+
build() {
|
|
117
|
+
const def = FlowDefinition.builder(this.name, this.stateConfig);
|
|
118
|
+
// Build via private constructor
|
|
119
|
+
const result = Object.create(FlowDefinition.prototype);
|
|
120
|
+
Object.assign(result, {
|
|
121
|
+
name: this.name,
|
|
122
|
+
stateConfig: this.stateConfig,
|
|
123
|
+
ttl: this.ttl,
|
|
124
|
+
maxGuardRetries: this.maxGuardRetries,
|
|
125
|
+
transitions: [...this.transitions],
|
|
126
|
+
errorTransitions: new Map(this.errorTransitions),
|
|
127
|
+
});
|
|
128
|
+
// Compute initial/terminal
|
|
129
|
+
let initial = null;
|
|
130
|
+
const terminals = new Set();
|
|
131
|
+
for (const [state, cfg] of Object.entries(this.stateConfig)) {
|
|
132
|
+
if (cfg.initial)
|
|
133
|
+
initial = state;
|
|
134
|
+
if (cfg.terminal)
|
|
135
|
+
terminals.add(state);
|
|
136
|
+
}
|
|
137
|
+
result.initialState = initial;
|
|
138
|
+
result.terminalStates = terminals;
|
|
139
|
+
result.dataFlowGraph = null;
|
|
140
|
+
this.validate(result);
|
|
141
|
+
result.dataFlowGraph = DataFlowGraph.build(result, this.initiallyAvailableKeys);
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
validate(def) {
|
|
145
|
+
const errors = [];
|
|
146
|
+
if (!def.initialState) {
|
|
147
|
+
errors.push('No initial state found (exactly one state must have initial=true)');
|
|
148
|
+
}
|
|
149
|
+
this.checkReachability(def, errors);
|
|
150
|
+
this.checkPathToTerminal(def, errors);
|
|
151
|
+
this.checkDag(def, errors);
|
|
152
|
+
this.checkExternalUniqueness(def, errors);
|
|
153
|
+
this.checkBranchCompleteness(def, errors);
|
|
154
|
+
this.checkRequiresProduces(def, errors);
|
|
155
|
+
this.checkAutoExternalConflict(def, errors);
|
|
156
|
+
this.checkTerminalNoOutgoing(def, errors);
|
|
157
|
+
this.checkSubFlowExitCompleteness(def, errors);
|
|
158
|
+
this.checkSubFlowNestingDepth(def, errors, 0);
|
|
159
|
+
this.checkSubFlowCircularRef(def, errors, new Set());
|
|
160
|
+
if (errors.length > 0) {
|
|
161
|
+
throw new FlowError('INVALID_FLOW_DEFINITION', `Flow '${this.name}' has ${errors.length} validation error(s):\n - ${errors.join('\n - ')}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
checkReachability(def, errors) {
|
|
165
|
+
if (!def.initialState)
|
|
166
|
+
return;
|
|
167
|
+
const visited = new Set();
|
|
168
|
+
const queue = [def.initialState];
|
|
169
|
+
visited.add(def.initialState);
|
|
170
|
+
while (queue.length > 0) {
|
|
171
|
+
const current = queue.shift();
|
|
172
|
+
for (const t of def.transitionsFrom(current)) {
|
|
173
|
+
if (t.type === 'sub_flow' && t.exitMappings) {
|
|
174
|
+
for (const target of t.exitMappings.values()) {
|
|
175
|
+
if (!visited.has(target)) {
|
|
176
|
+
visited.add(target);
|
|
177
|
+
queue.push(target);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (!visited.has(t.to)) {
|
|
183
|
+
visited.add(t.to);
|
|
184
|
+
queue.push(t.to);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const errTarget = def.errorTransitions.get(current);
|
|
188
|
+
if (errTarget && !visited.has(errTarget)) {
|
|
189
|
+
visited.add(errTarget);
|
|
190
|
+
queue.push(errTarget);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
for (const s of def.allStates()) {
|
|
194
|
+
if (!visited.has(s) && !def.stateConfig[s].terminal) {
|
|
195
|
+
errors.push(`State ${s} is not reachable from ${def.initialState}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
checkPathToTerminal(def, errors) {
|
|
200
|
+
if (!def.initialState)
|
|
201
|
+
return;
|
|
202
|
+
const visited = new Set();
|
|
203
|
+
if (!this.canReachTerminal(def, def.initialState, visited)) {
|
|
204
|
+
errors.push(`No path from ${def.initialState} to any terminal state`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
canReachTerminal(def, state, visited) {
|
|
208
|
+
if (def.stateConfig[state].terminal)
|
|
209
|
+
return true;
|
|
210
|
+
if (visited.has(state))
|
|
211
|
+
return false;
|
|
212
|
+
visited.add(state);
|
|
213
|
+
for (const t of def.transitionsFrom(state)) {
|
|
214
|
+
if (t.type === 'sub_flow' && t.exitMappings) {
|
|
215
|
+
for (const target of t.exitMappings.values()) {
|
|
216
|
+
if (this.canReachTerminal(def, target, visited))
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (this.canReachTerminal(def, t.to, visited))
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
const errTarget = def.errorTransitions.get(state);
|
|
225
|
+
return errTarget !== undefined && this.canReachTerminal(def, errTarget, visited);
|
|
226
|
+
}
|
|
227
|
+
checkDag(def, errors) {
|
|
228
|
+
const autoGraph = new Map();
|
|
229
|
+
for (const t of def.transitions) {
|
|
230
|
+
if (t.type === 'auto' || t.type === 'branch') {
|
|
231
|
+
if (!autoGraph.has(t.from))
|
|
232
|
+
autoGraph.set(t.from, new Set());
|
|
233
|
+
autoGraph.get(t.from).add(t.to);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const visited = new Set();
|
|
237
|
+
const inStack = new Set();
|
|
238
|
+
for (const s of def.allStates()) {
|
|
239
|
+
if (!visited.has(s) && this.hasCycle(autoGraph, s, visited, inStack)) {
|
|
240
|
+
errors.push(`Auto/Branch transitions contain a cycle involving ${s}`);
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
hasCycle(graph, node, visited, inStack) {
|
|
246
|
+
visited.add(node);
|
|
247
|
+
inStack.add(node);
|
|
248
|
+
for (const neighbor of graph.get(node) ?? []) {
|
|
249
|
+
if (inStack.has(neighbor))
|
|
250
|
+
return true;
|
|
251
|
+
if (!visited.has(neighbor) && this.hasCycle(graph, neighbor, visited, inStack))
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
inStack.delete(node);
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
checkExternalUniqueness(def, errors) {
|
|
258
|
+
const counts = new Map();
|
|
259
|
+
for (const t of def.transitions) {
|
|
260
|
+
if (t.type === 'external')
|
|
261
|
+
counts.set(t.from, (counts.get(t.from) ?? 0) + 1);
|
|
262
|
+
}
|
|
263
|
+
for (const [state, count] of counts) {
|
|
264
|
+
if (count > 1)
|
|
265
|
+
errors.push(`State ${state} has ${count} external transitions (max 1)`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
checkBranchCompleteness(def, errors) {
|
|
269
|
+
const allStates = new Set(def.allStates());
|
|
270
|
+
for (const t of def.transitions) {
|
|
271
|
+
if (t.type === 'branch' && t.branchTargets.size > 0) {
|
|
272
|
+
for (const [label, target] of t.branchTargets) {
|
|
273
|
+
if (!allStates.has(target)) {
|
|
274
|
+
errors.push(`Branch target '${label}' -> ${target} is not a valid state`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
checkRequiresProduces(def, errors) {
|
|
281
|
+
if (!def.initialState)
|
|
282
|
+
return;
|
|
283
|
+
const stateAvailable = new Map();
|
|
284
|
+
this.checkRequiresProducesFrom(def, def.initialState, new Set(this.initiallyAvailableKeys), stateAvailable, errors);
|
|
285
|
+
}
|
|
286
|
+
checkRequiresProducesFrom(def, state, available, stateAvailable, errors) {
|
|
287
|
+
if (stateAvailable.has(state)) {
|
|
288
|
+
const existing = stateAvailable.get(state);
|
|
289
|
+
let isSubset = true;
|
|
290
|
+
for (const a of available) {
|
|
291
|
+
if (!existing.has(a)) {
|
|
292
|
+
isSubset = false;
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (isSubset)
|
|
297
|
+
return;
|
|
298
|
+
// intersection
|
|
299
|
+
for (const a of [...existing]) {
|
|
300
|
+
if (!available.has(a))
|
|
301
|
+
existing.delete(a);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
stateAvailable.set(state, new Set(available));
|
|
306
|
+
}
|
|
307
|
+
for (const t of def.transitionsFrom(state)) {
|
|
308
|
+
const newAvailable = new Set(stateAvailable.get(state));
|
|
309
|
+
if (t.guard) {
|
|
310
|
+
for (const req of t.guard.requires) {
|
|
311
|
+
if (!newAvailable.has(req))
|
|
312
|
+
errors.push(`Guard '${t.guard.name}' at ${t.from} requires ${req} but it may not be available`);
|
|
313
|
+
}
|
|
314
|
+
for (const p of t.guard.produces)
|
|
315
|
+
newAvailable.add(p);
|
|
316
|
+
}
|
|
317
|
+
if (t.branch) {
|
|
318
|
+
for (const req of t.branch.requires) {
|
|
319
|
+
if (!newAvailable.has(req))
|
|
320
|
+
errors.push(`Branch '${t.branch.name}' at ${t.from} requires ${req} but it may not be available`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (t.processor) {
|
|
324
|
+
for (const req of t.processor.requires) {
|
|
325
|
+
if (!newAvailable.has(req))
|
|
326
|
+
errors.push(`Processor '${t.processor.name}' at ${t.from} -> ${t.to} requires ${req} but it may not be available`);
|
|
327
|
+
}
|
|
328
|
+
for (const p of t.processor.produces)
|
|
329
|
+
newAvailable.add(p);
|
|
330
|
+
}
|
|
331
|
+
this.checkRequiresProducesFrom(def, t.to, newAvailable, stateAvailable, errors);
|
|
332
|
+
// Error path analysis: if processor fails, its produces are NOT available
|
|
333
|
+
if (t.processor) {
|
|
334
|
+
const errorTarget = def.errorTransitions.get(t.from);
|
|
335
|
+
if (errorTarget) {
|
|
336
|
+
const errorAvailable = new Set(stateAvailable.get(state));
|
|
337
|
+
if (t.guard) {
|
|
338
|
+
for (const p of t.guard.produces)
|
|
339
|
+
errorAvailable.add(p);
|
|
340
|
+
}
|
|
341
|
+
this.checkRequiresProducesFrom(def, errorTarget, errorAvailable, stateAvailable, errors);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
checkAutoExternalConflict(def, errors) {
|
|
347
|
+
for (const state of def.allStates()) {
|
|
348
|
+
const trans = def.transitionsFrom(state);
|
|
349
|
+
const hasAuto = trans.some(t => t.type === 'auto' || t.type === 'branch');
|
|
350
|
+
const hasExternal = trans.some(t => t.type === 'external');
|
|
351
|
+
if (hasAuto && hasExternal) {
|
|
352
|
+
errors.push(`State ${state} has both auto/branch and external transitions — auto takes priority, making external unreachable`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
checkTerminalNoOutgoing(def, errors) {
|
|
357
|
+
for (const t of def.transitions) {
|
|
358
|
+
if (def.stateConfig[t.from].terminal && t.type !== 'sub_flow') {
|
|
359
|
+
errors.push(`Terminal state ${t.from} has an outgoing transition to ${t.to}`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
checkSubFlowNestingDepth(def, errors, depth) {
|
|
364
|
+
if (depth > 3) {
|
|
365
|
+
errors.push(`SubFlow nesting depth exceeds maximum of 3 (flow: ${def.name})`);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
for (const t of def.transitions) {
|
|
369
|
+
if (t.type === 'sub_flow' && t.subFlowDefinition) {
|
|
370
|
+
this.checkSubFlowNestingDepth(t.subFlowDefinition, errors, depth + 1);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
checkSubFlowCircularRef(def, errors, visited) {
|
|
375
|
+
if (visited.has(def.name)) {
|
|
376
|
+
errors.push(`Circular sub-flow reference detected: ${[...visited].join(' -> ')} -> ${def.name}`);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
visited.add(def.name);
|
|
380
|
+
for (const t of def.transitions) {
|
|
381
|
+
if (t.type === 'sub_flow' && t.subFlowDefinition) {
|
|
382
|
+
this.checkSubFlowCircularRef(t.subFlowDefinition, errors, new Set(visited));
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
checkSubFlowExitCompleteness(def, errors) {
|
|
387
|
+
for (const t of def.transitions) {
|
|
388
|
+
if (t.type !== 'sub_flow' || !t.subFlowDefinition)
|
|
389
|
+
continue;
|
|
390
|
+
const subDef = t.subFlowDefinition;
|
|
391
|
+
for (const terminal of subDef.terminalStates) {
|
|
392
|
+
if (!t.exitMappings?.has(terminal)) {
|
|
393
|
+
errors.push(`SubFlow '${subDef.name}' at ${t.from} has terminal state ${terminal} with no onExit mapping`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
export class FromBuilder {
|
|
400
|
+
builder;
|
|
401
|
+
fromState;
|
|
402
|
+
constructor(builder, fromState) {
|
|
403
|
+
this.builder = builder;
|
|
404
|
+
this.fromState = fromState;
|
|
405
|
+
}
|
|
406
|
+
auto(to, processor) {
|
|
407
|
+
this.builder.addTransition({
|
|
408
|
+
from: this.fromState, to, type: 'auto', processor,
|
|
409
|
+
guard: undefined, branch: undefined, branchTargets: new Map(),
|
|
410
|
+
});
|
|
411
|
+
return this.builder;
|
|
412
|
+
}
|
|
413
|
+
external(to, guard, processor) {
|
|
414
|
+
this.builder.addTransition({
|
|
415
|
+
from: this.fromState, to, type: 'external', processor,
|
|
416
|
+
guard, branch: undefined, branchTargets: new Map(),
|
|
417
|
+
});
|
|
418
|
+
return this.builder;
|
|
419
|
+
}
|
|
420
|
+
branch(branch) {
|
|
421
|
+
return new BranchBuilder(this.builder, this.fromState, branch);
|
|
422
|
+
}
|
|
423
|
+
subFlow(subFlowDef) {
|
|
424
|
+
return new SubFlowBuilder(this.builder, this.fromState, subFlowDef);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
export class SubFlowBuilder {
|
|
428
|
+
builder;
|
|
429
|
+
fromState;
|
|
430
|
+
subFlowDef;
|
|
431
|
+
exitMap = new Map();
|
|
432
|
+
constructor(builder, fromState, subFlowDef) {
|
|
433
|
+
this.builder = builder;
|
|
434
|
+
this.fromState = fromState;
|
|
435
|
+
this.subFlowDef = subFlowDef;
|
|
436
|
+
}
|
|
437
|
+
onExit(terminalName, parentState) {
|
|
438
|
+
this.exitMap.set(terminalName, parentState);
|
|
439
|
+
return this;
|
|
440
|
+
}
|
|
441
|
+
endSubFlow() {
|
|
442
|
+
this.builder.addTransition({
|
|
443
|
+
from: this.fromState, to: this.fromState, type: 'sub_flow',
|
|
444
|
+
processor: undefined, guard: undefined, branch: undefined,
|
|
445
|
+
branchTargets: new Map(),
|
|
446
|
+
subFlowDefinition: this.subFlowDef,
|
|
447
|
+
exitMappings: new Map(this.exitMap),
|
|
448
|
+
});
|
|
449
|
+
return this.builder;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
export class BranchBuilder {
|
|
453
|
+
builder;
|
|
454
|
+
fromState;
|
|
455
|
+
branch;
|
|
456
|
+
targets = new Map();
|
|
457
|
+
processors = new Map();
|
|
458
|
+
constructor(builder, fromState, branch) {
|
|
459
|
+
this.builder = builder;
|
|
460
|
+
this.fromState = fromState;
|
|
461
|
+
this.branch = branch;
|
|
462
|
+
}
|
|
463
|
+
to(state, label, processor) {
|
|
464
|
+
this.targets.set(label, state);
|
|
465
|
+
if (processor)
|
|
466
|
+
this.processors.set(label, processor);
|
|
467
|
+
return this;
|
|
468
|
+
}
|
|
469
|
+
endBranch() {
|
|
470
|
+
for (const [label, target] of this.targets) {
|
|
471
|
+
this.builder.addTransition({
|
|
472
|
+
from: this.fromState, to: target, type: 'branch',
|
|
473
|
+
processor: this.processors.get(label),
|
|
474
|
+
guard: undefined, branch: this.branch,
|
|
475
|
+
branchTargets: new Map(this.targets),
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
return this.builder;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FlowDefinition } from './flow-definition.js';
|
|
2
|
+
import { FlowInstance } from './flow-instance.js';
|
|
3
|
+
import type { InMemoryFlowStore } from './in-memory-flow-store.js';
|
|
4
|
+
export declare class FlowEngine {
|
|
5
|
+
private readonly store;
|
|
6
|
+
constructor(store: InMemoryFlowStore);
|
|
7
|
+
startFlow<S extends string>(definition: FlowDefinition<S>, sessionId: string, initialData: Map<string, unknown>): Promise<FlowInstance<S>>;
|
|
8
|
+
resumeAndExecute<S extends string>(flowId: string, definition: FlowDefinition<S>, externalData?: Map<string, unknown>): Promise<FlowInstance<S>>;
|
|
9
|
+
private executeAutoChain;
|
|
10
|
+
private executeSubFlow;
|
|
11
|
+
private resumeSubFlow;
|
|
12
|
+
private handleError;
|
|
13
|
+
}
|