@unlaxer/tramli 3.2.0 → 3.4.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/dist/cjs/data-flow-graph.d.ts +1 -1
- package/dist/cjs/data-flow-graph.js +9 -4
- package/dist/cjs/flow-definition.d.ts +3 -0
- package/dist/cjs/flow-definition.js +13 -1
- package/dist/cjs/flow-engine.d.ts +3 -0
- package/dist/cjs/flow-engine.js +38 -21
- package/dist/cjs/pipeline.js +4 -2
- package/dist/cjs/types.d.ts +2 -0
- package/dist/esm/data-flow-graph.d.ts +1 -1
- package/dist/esm/data-flow-graph.js +9 -4
- package/dist/esm/flow-definition.d.ts +3 -0
- package/dist/esm/flow-definition.js +13 -1
- package/dist/esm/flow-engine.d.ts +3 -0
- package/dist/esm/flow-engine.js +38 -21
- package/dist/esm/pipeline.js +4 -2
- package/dist/esm/types.d.ts +2 -0
- package/package.json +1 -1
|
@@ -88,5 +88,5 @@ export declare class DataFlowGraph<S extends string> {
|
|
|
88
88
|
private static collectEdges;
|
|
89
89
|
/** Version compatibility: check if v1 instances can resume on v2 definition. */
|
|
90
90
|
static versionCompatibility<S extends string>(before: DataFlowGraph<S>, after: DataFlowGraph<S>): string[];
|
|
91
|
-
static build<S extends string>(def: FlowDefinition<S>, initiallyAvailable: string[]): DataFlowGraph<S>;
|
|
91
|
+
static build<S extends string>(def: FlowDefinition<S>, initiallyAvailable: string[], externallyProvided?: string[]): DataFlowGraph<S>;
|
|
92
92
|
}
|
|
@@ -370,14 +370,15 @@ class DataFlowGraph {
|
|
|
370
370
|
return issues;
|
|
371
371
|
}
|
|
372
372
|
// ─── Builder ─────────────────────────────────────────────
|
|
373
|
-
static build(def, initiallyAvailable) {
|
|
373
|
+
static build(def, initiallyAvailable, externallyProvided = []) {
|
|
374
374
|
const stateAvail = new Map();
|
|
375
375
|
const producers = new Map();
|
|
376
376
|
const consumers = new Map();
|
|
377
377
|
const allProduced = new Set(initiallyAvailable);
|
|
378
378
|
const allConsumed = new Set();
|
|
379
|
+
const extSet = new Set(externallyProvided);
|
|
379
380
|
if (def.initialState) {
|
|
380
|
-
traverse(def, def.initialState, new Set(initiallyAvailable), stateAvail, producers, consumers, allProduced, allConsumed);
|
|
381
|
+
traverse(def, def.initialState, new Set(initiallyAvailable), extSet, stateAvail, producers, consumers, allProduced, allConsumed);
|
|
381
382
|
// Mark initially available types as produced by "initial"
|
|
382
383
|
for (const key of initiallyAvailable) {
|
|
383
384
|
if (!producers.has(key))
|
|
@@ -391,7 +392,7 @@ class DataFlowGraph {
|
|
|
391
392
|
}
|
|
392
393
|
}
|
|
393
394
|
exports.DataFlowGraph = DataFlowGraph;
|
|
394
|
-
function traverse(def, state, available, stateAvail, producers, consumers, allProduced, allConsumed) {
|
|
395
|
+
function traverse(def, state, available, externallyProvided, stateAvail, producers, consumers, allProduced, allConsumed) {
|
|
395
396
|
if (stateAvail.has(state)) {
|
|
396
397
|
const existing = stateAvail.get(state);
|
|
397
398
|
let isSubset = true;
|
|
@@ -413,6 +414,10 @@ function traverse(def, state, available, stateAvail, producers, consumers, allPr
|
|
|
413
414
|
}
|
|
414
415
|
for (const t of def.transitionsFrom(state)) {
|
|
415
416
|
const newAvail = new Set(stateAvail.get(state));
|
|
417
|
+
if (t.type === 'external') {
|
|
418
|
+
for (const k of externallyProvided)
|
|
419
|
+
newAvail.add(k);
|
|
420
|
+
}
|
|
416
421
|
if (t.guard) {
|
|
417
422
|
for (const req of t.guard.requires) {
|
|
418
423
|
addTo(consumers, req, { name: t.guard.name, fromState: t.from, toState: t.to, kind: 'guard' });
|
|
@@ -441,7 +446,7 @@ function traverse(def, state, available, stateAvail, producers, consumers, allPr
|
|
|
441
446
|
newAvail.add(prod);
|
|
442
447
|
}
|
|
443
448
|
}
|
|
444
|
-
traverse(def, t.to, newAvail, stateAvail, producers, consumers, allProduced, allConsumed);
|
|
449
|
+
traverse(def, t.to, newAvail, externallyProvided, stateAvail, producers, consumers, allProduced, allConsumed);
|
|
445
450
|
}
|
|
446
451
|
}
|
|
447
452
|
function addTo(map, key, info) {
|
|
@@ -45,9 +45,12 @@ export declare class Builder<S extends string> {
|
|
|
45
45
|
private readonly _enterActions;
|
|
46
46
|
private readonly _exitActions;
|
|
47
47
|
private readonly initiallyAvailableKeys;
|
|
48
|
+
private readonly externallyProvidedKeys;
|
|
48
49
|
private _perpetual;
|
|
49
50
|
constructor(name: string, stateConfig: Record<S, StateConfig>);
|
|
50
51
|
initiallyAvailable(...keys: FlowKey<unknown>[]): this;
|
|
52
|
+
/** Declare data keys injected via resumeAndExecute(externalData), not available at start. */
|
|
53
|
+
externallyProvided(...keys: FlowKey<unknown>[]): this;
|
|
51
54
|
setTtl(ms: number): this;
|
|
52
55
|
setMaxGuardRetries(max: number): this;
|
|
53
56
|
from(state: S): FromBuilder<S>;
|
|
@@ -113,6 +113,7 @@ class Builder {
|
|
|
113
113
|
_enterActions = new Map();
|
|
114
114
|
_exitActions = new Map();
|
|
115
115
|
initiallyAvailableKeys = [];
|
|
116
|
+
externallyProvidedKeys = [];
|
|
116
117
|
_perpetual = false;
|
|
117
118
|
constructor(name, stateConfig) {
|
|
118
119
|
this.name = name;
|
|
@@ -123,6 +124,12 @@ class Builder {
|
|
|
123
124
|
this.initiallyAvailableKeys.push(k);
|
|
124
125
|
return this;
|
|
125
126
|
}
|
|
127
|
+
/** Declare data keys injected via resumeAndExecute(externalData), not available at start. */
|
|
128
|
+
externallyProvided(...keys) {
|
|
129
|
+
for (const k of keys)
|
|
130
|
+
this.externallyProvidedKeys.push(k);
|
|
131
|
+
return this;
|
|
132
|
+
}
|
|
126
133
|
setTtl(ms) { this.ttl = ms; return this; }
|
|
127
134
|
setMaxGuardRetries(max) { this.maxGuardRetries = max; return this; }
|
|
128
135
|
from(state) {
|
|
@@ -185,7 +192,7 @@ class Builder {
|
|
|
185
192
|
result.terminalStates = terminals;
|
|
186
193
|
result.dataFlowGraph = null;
|
|
187
194
|
this.validate(result);
|
|
188
|
-
result.dataFlowGraph = data_flow_graph_js_1.DataFlowGraph.build(result, this.initiallyAvailableKeys);
|
|
195
|
+
result.dataFlowGraph = data_flow_graph_js_1.DataFlowGraph.build(result, this.initiallyAvailableKeys, this.externallyProvidedKeys);
|
|
189
196
|
// Build warnings
|
|
190
197
|
const warnings = [];
|
|
191
198
|
const perpetual = terminals.size === 0;
|
|
@@ -354,6 +361,10 @@ class Builder {
|
|
|
354
361
|
}
|
|
355
362
|
for (const t of def.transitionsFrom(state)) {
|
|
356
363
|
const newAvailable = new Set(stateAvailable.get(state));
|
|
364
|
+
if (t.type === 'external') {
|
|
365
|
+
for (const k of this.externallyProvidedKeys)
|
|
366
|
+
newAvailable.add(k);
|
|
367
|
+
}
|
|
357
368
|
if (t.guard) {
|
|
358
369
|
for (const req of t.guard.requires) {
|
|
359
370
|
if (!newAvailable.has(req))
|
|
@@ -534,6 +545,7 @@ class BranchBuilder {
|
|
|
534
545
|
processor: this.processors.get(label),
|
|
535
546
|
guard: undefined, branch: this.branch,
|
|
536
547
|
branchTargets: new Map(this.targets),
|
|
548
|
+
branchLabel: label,
|
|
537
549
|
});
|
|
538
550
|
}
|
|
539
551
|
return this.builder;
|
|
@@ -10,6 +10,7 @@ export interface TransitionLogEntry {
|
|
|
10
10
|
from: string | null;
|
|
11
11
|
to: string;
|
|
12
12
|
trigger: string;
|
|
13
|
+
durationMicros: number;
|
|
13
14
|
}
|
|
14
15
|
export interface StateLogEntry {
|
|
15
16
|
flowId: string;
|
|
@@ -25,6 +26,7 @@ export interface ErrorLogEntry {
|
|
|
25
26
|
to: string | null;
|
|
26
27
|
trigger: string;
|
|
27
28
|
cause: Error | null;
|
|
29
|
+
durationMicros: number;
|
|
28
30
|
}
|
|
29
31
|
export interface GuardLogEntry {
|
|
30
32
|
flowId: string;
|
|
@@ -33,6 +35,7 @@ export interface GuardLogEntry {
|
|
|
33
35
|
guardName: string;
|
|
34
36
|
result: 'accepted' | 'rejected' | 'expired';
|
|
35
37
|
reason?: string;
|
|
38
|
+
durationMicros: number;
|
|
36
39
|
}
|
|
37
40
|
export declare class FlowEngine {
|
|
38
41
|
private readonly store;
|
package/dist/cjs/flow-engine.js
CHANGED
|
@@ -97,10 +97,13 @@ class FlowEngine {
|
|
|
97
97
|
}
|
|
98
98
|
const guard = transition.guard;
|
|
99
99
|
if (guard) {
|
|
100
|
+
const guardStart = this.guardLogger ? performance.now() : 0;
|
|
100
101
|
const output = await guard.validate(flow.context);
|
|
102
|
+
const guardDurationMicros = this.guardLogger ? Math.round((performance.now() - guardStart) * 1000) : 0;
|
|
101
103
|
switch (output.type) {
|
|
102
104
|
case 'accepted': {
|
|
103
|
-
this.logGuard(flow, currentState, guard.name, 'accepted');
|
|
105
|
+
this.logGuard(flow, currentState, guard.name, 'accepted', guardDurationMicros);
|
|
106
|
+
const transStart = this.transitionLogger ? performance.now() : 0;
|
|
104
107
|
const backup = flow.context.snapshot();
|
|
105
108
|
if (output.data) {
|
|
106
109
|
for (const [key, value] of output.data)
|
|
@@ -114,7 +117,7 @@ class FlowEngine {
|
|
|
114
117
|
flow.transitionTo(transition.to);
|
|
115
118
|
this.fireEnter(flow, transition.to);
|
|
116
119
|
this.store.recordTransition(flow.id, from, transition.to, guard.name, flow.context);
|
|
117
|
-
this.logTransition(flow, from, transition.to, guard.name);
|
|
120
|
+
this.logTransition(flow, from, transition.to, guard.name, transStart);
|
|
118
121
|
}
|
|
119
122
|
catch (e) {
|
|
120
123
|
flow.context.restoreFrom(backup);
|
|
@@ -125,7 +128,7 @@ class FlowEngine {
|
|
|
125
128
|
break;
|
|
126
129
|
}
|
|
127
130
|
case 'rejected': {
|
|
128
|
-
this.logGuard(flow, currentState, guard.name, 'rejected', output.reason);
|
|
131
|
+
this.logGuard(flow, currentState, guard.name, 'rejected', guardDurationMicros, output.reason);
|
|
129
132
|
flow.incrementGuardFailure(guard.name);
|
|
130
133
|
if (flow.guardFailureCount >= definition.maxGuardRetries) {
|
|
131
134
|
this.handleError(flow, currentState);
|
|
@@ -134,7 +137,7 @@ class FlowEngine {
|
|
|
134
137
|
return flow;
|
|
135
138
|
}
|
|
136
139
|
case 'expired': {
|
|
137
|
-
this.logGuard(flow, currentState, guard.name, 'expired');
|
|
140
|
+
this.logGuard(flow, currentState, guard.name, 'expired', guardDurationMicros);
|
|
138
141
|
flow.complete('EXPIRED');
|
|
139
142
|
this.store.save(flow);
|
|
140
143
|
return flow;
|
|
@@ -142,12 +145,13 @@ class FlowEngine {
|
|
|
142
145
|
}
|
|
143
146
|
}
|
|
144
147
|
else {
|
|
148
|
+
const transStart = this.transitionLogger ? performance.now() : 0;
|
|
145
149
|
const from = flow.currentState;
|
|
146
150
|
this.fireExit(flow, from);
|
|
147
151
|
flow.transitionTo(transition.to);
|
|
148
152
|
this.fireEnter(flow, transition.to);
|
|
149
153
|
this.store.recordTransition(flow.id, from, transition.to, 'external', flow.context);
|
|
150
|
-
this.logTransition(flow, from, transition.to, 'external');
|
|
154
|
+
this.logTransition(flow, from, transition.to, 'external', transStart);
|
|
151
155
|
}
|
|
152
156
|
await this.executeAutoChain(flow);
|
|
153
157
|
this.store.save(flow);
|
|
@@ -175,6 +179,7 @@ class FlowEngine {
|
|
|
175
179
|
if (!autoOrBranch)
|
|
176
180
|
break;
|
|
177
181
|
const backup = flow.context.snapshot();
|
|
182
|
+
const stepStart = this.transitionLogger ? performance.now() : 0;
|
|
178
183
|
try {
|
|
179
184
|
if (autoOrBranch.type === 'auto') {
|
|
180
185
|
if (autoOrBranch.processor) {
|
|
@@ -187,7 +192,7 @@ class FlowEngine {
|
|
|
187
192
|
this.fireEnter(flow, autoOrBranch.to);
|
|
188
193
|
const trigger = autoOrBranch.processor?.name ?? 'auto';
|
|
189
194
|
this.store.recordTransition(flow.id, from, autoOrBranch.to, trigger, flow.context);
|
|
190
|
-
this.logTransition(flow, from, autoOrBranch.to, trigger);
|
|
195
|
+
this.logTransition(flow, from, autoOrBranch.to, trigger, stepStart);
|
|
191
196
|
}
|
|
192
197
|
else {
|
|
193
198
|
const branch = autoOrBranch.branch;
|
|
@@ -196,7 +201,7 @@ class FlowEngine {
|
|
|
196
201
|
if (!target) {
|
|
197
202
|
throw new flow_error_js_1.FlowError('UNKNOWN_BRANCH', `Branch '${branch.name}' returned unknown label: ${label}`);
|
|
198
203
|
}
|
|
199
|
-
const specific = transitions.find(t => t.type === 'branch' && t.to === target) ?? autoOrBranch;
|
|
204
|
+
const specific = transitions.find(t => t.type === 'branch' && t.branchLabel === label) ?? transitions.find(t => t.type === 'branch' && t.to === target) ?? autoOrBranch;
|
|
200
205
|
if (specific.processor)
|
|
201
206
|
await specific.processor.process(flow.context);
|
|
202
207
|
const from = flow.currentState;
|
|
@@ -205,7 +210,7 @@ class FlowEngine {
|
|
|
205
210
|
this.fireEnter(flow, target);
|
|
206
211
|
const trigger = `${branch.name}:${label}`;
|
|
207
212
|
this.store.recordTransition(flow.id, from, target, trigger, flow.context);
|
|
208
|
-
this.logTransition(flow, from, target, trigger);
|
|
213
|
+
this.logTransition(flow, from, target, trigger, stepStart);
|
|
209
214
|
}
|
|
210
215
|
}
|
|
211
216
|
catch (e) {
|
|
@@ -229,13 +234,14 @@ class FlowEngine {
|
|
|
229
234
|
parentFlow.setActiveSubFlow(null);
|
|
230
235
|
const target = exitMappings.get(subFlow.exitState);
|
|
231
236
|
if (target) {
|
|
237
|
+
const sfStart = this.transitionLogger ? performance.now() : 0;
|
|
232
238
|
const from = parentFlow.currentState;
|
|
233
239
|
this.fireExit(parentFlow, from);
|
|
234
240
|
parentFlow.transitionTo(target);
|
|
235
241
|
this.fireEnter(parentFlow, target);
|
|
236
242
|
const trigger = `subFlow:${subDef.name}/${subFlow.exitState}`;
|
|
237
243
|
this.store.recordTransition(parentFlow.id, from, target, trigger, parentFlow.context);
|
|
238
|
-
this.logTransition(parentFlow, from, target, trigger);
|
|
244
|
+
this.logTransition(parentFlow, from, target, trigger, sfStart);
|
|
239
245
|
return 1;
|
|
240
246
|
}
|
|
241
247
|
// Error bubbling: no exit mapping → fall back to parent's error transitions
|
|
@@ -253,17 +259,20 @@ class FlowEngine {
|
|
|
253
259
|
}
|
|
254
260
|
const guard = transition.guard;
|
|
255
261
|
if (guard) {
|
|
262
|
+
const guardStart = this.guardLogger ? performance.now() : 0;
|
|
256
263
|
const output = await guard.validate(parentFlow.context);
|
|
264
|
+
const guardDur = this.guardLogger ? Math.round((performance.now() - guardStart) * 1000) : 0;
|
|
257
265
|
if (output.type === 'accepted') {
|
|
258
266
|
if (output.data) {
|
|
259
267
|
for (const [key, value] of output.data)
|
|
260
268
|
parentFlow.context.put(key, value);
|
|
261
269
|
}
|
|
270
|
+
const sfStart = this.transitionLogger ? performance.now() : 0;
|
|
262
271
|
const sfFrom = subFlow.currentState;
|
|
263
272
|
subFlow.transitionTo(transition.to);
|
|
264
273
|
this.store.recordTransition(parentFlow.id, sfFrom, transition.to, guard.name, parentFlow.context);
|
|
265
|
-
this.logTransition(parentFlow, sfFrom, transition.to, guard.name);
|
|
266
|
-
this.logGuard(parentFlow, sfFrom, guard.name, 'accepted');
|
|
274
|
+
this.logTransition(parentFlow, sfFrom, transition.to, guard.name, sfStart);
|
|
275
|
+
this.logGuard(parentFlow, sfFrom, guard.name, 'accepted', guardDur);
|
|
267
276
|
}
|
|
268
277
|
else if (output.type === 'rejected') {
|
|
269
278
|
subFlow.incrementGuardFailure();
|
|
@@ -290,13 +299,14 @@ class FlowEngine {
|
|
|
290
299
|
if (subFlowT?.exitMappings) {
|
|
291
300
|
const target = subFlowT.exitMappings.get(subFlow.exitState);
|
|
292
301
|
if (target) {
|
|
302
|
+
const exitStart = this.transitionLogger ? performance.now() : 0;
|
|
293
303
|
const from = parentFlow.currentState;
|
|
294
304
|
this.fireExit(parentFlow, from);
|
|
295
305
|
parentFlow.transitionTo(target);
|
|
296
306
|
this.fireEnter(parentFlow, target);
|
|
297
307
|
const trigger = `subFlow:${subDef.name}/${subFlow.exitState}`;
|
|
298
308
|
this.store.recordTransition(parentFlow.id, from, target, trigger, parentFlow.context);
|
|
299
|
-
this.logTransition(parentFlow, from, target, trigger);
|
|
309
|
+
this.logTransition(parentFlow, from, target, trigger, exitStart);
|
|
300
310
|
await this.executeAutoChain(parentFlow);
|
|
301
311
|
}
|
|
302
312
|
}
|
|
@@ -323,16 +333,23 @@ class FlowEngine {
|
|
|
323
333
|
if (action)
|
|
324
334
|
action(flow.context);
|
|
325
335
|
}
|
|
326
|
-
logTransition(flow, from, to, trigger) {
|
|
327
|
-
this.transitionLogger
|
|
336
|
+
logTransition(flow, from, to, trigger, startMs) {
|
|
337
|
+
if (this.transitionLogger) {
|
|
338
|
+
const durationMicros = Math.round((performance.now() - startMs) * 1000);
|
|
339
|
+
this.transitionLogger({ flowId: flow.id, flowName: flow.definition.name, from, to, trigger, durationMicros });
|
|
340
|
+
}
|
|
328
341
|
}
|
|
329
|
-
logError(flow, from, to, trigger, cause) {
|
|
330
|
-
this.errorLogger
|
|
342
|
+
logError(flow, from, to, trigger, cause, startMs) {
|
|
343
|
+
if (this.errorLogger) {
|
|
344
|
+
const durationMicros = Math.round((performance.now() - startMs) * 1000);
|
|
345
|
+
this.errorLogger({ flowId: flow.id, flowName: flow.definition.name, from, to, trigger, cause, durationMicros });
|
|
346
|
+
}
|
|
331
347
|
}
|
|
332
|
-
logGuard(flow, state, guardName, result, reason) {
|
|
333
|
-
this.guardLogger?.({ flowId: flow.id, flowName: flow.definition.name, state, guardName, result, reason });
|
|
348
|
+
logGuard(flow, state, guardName, result, durationMicros, reason) {
|
|
349
|
+
this.guardLogger?.({ flowId: flow.id, flowName: flow.definition.name, state, guardName, result, reason, durationMicros });
|
|
334
350
|
}
|
|
335
351
|
handleError(flow, fromState, cause) {
|
|
352
|
+
const errorStart = (this.transitionLogger || this.errorLogger) ? performance.now() : 0;
|
|
336
353
|
if (cause) {
|
|
337
354
|
flow.setLastError(`${cause.constructor.name}: ${cause.message}`);
|
|
338
355
|
if (cause instanceof flow_error_js_1.FlowError) {
|
|
@@ -342,7 +359,7 @@ class FlowEngine {
|
|
|
342
359
|
cause.withContextSnapshot(available, new Set());
|
|
343
360
|
}
|
|
344
361
|
}
|
|
345
|
-
this.logError(flow, fromState, null, 'error', cause ?? null);
|
|
362
|
+
this.logError(flow, fromState, null, 'error', cause ?? null, errorStart);
|
|
346
363
|
// 1. Try exception-typed routes first (onStepError)
|
|
347
364
|
if (cause && flow.definition.exceptionRoutes) {
|
|
348
365
|
const routes = flow.definition.exceptionRoutes.get(fromState);
|
|
@@ -353,7 +370,7 @@ class FlowEngine {
|
|
|
353
370
|
flow.transitionTo(route.target);
|
|
354
371
|
const trigger = `error:${cause.constructor.name}`;
|
|
355
372
|
this.store.recordTransition(flow.id, from, route.target, trigger, flow.context);
|
|
356
|
-
this.logTransition(flow, from, route.target, trigger);
|
|
373
|
+
this.logTransition(flow, from, route.target, trigger, errorStart);
|
|
357
374
|
if (flow.definition.stateConfig[route.target]?.terminal)
|
|
358
375
|
flow.complete(route.target);
|
|
359
376
|
return;
|
|
@@ -367,7 +384,7 @@ class FlowEngine {
|
|
|
367
384
|
const from = flow.currentState;
|
|
368
385
|
flow.transitionTo(errorTarget);
|
|
369
386
|
this.store.recordTransition(flow.id, from, errorTarget, 'error', flow.context);
|
|
370
|
-
this.logTransition(flow, from, errorTarget, 'error');
|
|
387
|
+
this.logTransition(flow, from, errorTarget, 'error', errorStart);
|
|
371
388
|
if (flow.definition.stateConfig[errorTarget]?.terminal)
|
|
372
389
|
flow.complete(errorTarget);
|
|
373
390
|
}
|
package/dist/cjs/pipeline.js
CHANGED
|
@@ -92,14 +92,16 @@ class Pipeline {
|
|
|
92
92
|
const completed = [];
|
|
93
93
|
let prev = 'initial';
|
|
94
94
|
for (const step of this.steps) {
|
|
95
|
-
this.transitionLogger
|
|
95
|
+
const stepStart = (this.transitionLogger || this.errorLogger) ? performance.now() : 0;
|
|
96
|
+
this.transitionLogger?.({ flowId, flowName: this.name, from: prev, to: step.name, trigger: step.name, durationMicros: 0 });
|
|
96
97
|
const keysBefore = this.stateLogger ? new Set(ctx.snapshot().keys()) : null;
|
|
97
98
|
try {
|
|
98
99
|
await step.process(ctx);
|
|
99
100
|
}
|
|
100
101
|
catch (e) {
|
|
101
102
|
const err = e instanceof Error ? e : new Error(String(e));
|
|
102
|
-
|
|
103
|
+
const durationMicros = Math.round((performance.now() - stepStart) * 1000);
|
|
104
|
+
this.errorLogger?.({ flowId, flowName: this.name, from: prev, to: step.name, trigger: step.name, cause: err, durationMicros });
|
|
103
105
|
throw new PipelineException(step.name, [...completed], ctx, err);
|
|
104
106
|
}
|
|
105
107
|
if (this.strictMode) {
|
package/dist/cjs/types.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export interface Transition<S extends string> {
|
|
|
26
26
|
guard?: TransitionGuard<S>;
|
|
27
27
|
branch?: BranchProcessor<S>;
|
|
28
28
|
branchTargets: Map<string, S>;
|
|
29
|
+
/** Label assigned by builder .to(target, label, processor). Used for branch label-specific processor matching. */
|
|
30
|
+
branchLabel?: string;
|
|
29
31
|
subFlowDefinition?: import('./flow-definition.js').FlowDefinition<any>;
|
|
30
32
|
exitMappings?: Map<string, S>;
|
|
31
33
|
/** Per-state timeout in milliseconds. If set, resumeAndExecute checks this before guard. */
|
|
@@ -88,5 +88,5 @@ export declare class DataFlowGraph<S extends string> {
|
|
|
88
88
|
private static collectEdges;
|
|
89
89
|
/** Version compatibility: check if v1 instances can resume on v2 definition. */
|
|
90
90
|
static versionCompatibility<S extends string>(before: DataFlowGraph<S>, after: DataFlowGraph<S>): string[];
|
|
91
|
-
static build<S extends string>(def: FlowDefinition<S>, initiallyAvailable: string[]): DataFlowGraph<S>;
|
|
91
|
+
static build<S extends string>(def: FlowDefinition<S>, initiallyAvailable: string[], externallyProvided?: string[]): DataFlowGraph<S>;
|
|
92
92
|
}
|
|
@@ -367,14 +367,15 @@ export class DataFlowGraph {
|
|
|
367
367
|
return issues;
|
|
368
368
|
}
|
|
369
369
|
// ─── Builder ─────────────────────────────────────────────
|
|
370
|
-
static build(def, initiallyAvailable) {
|
|
370
|
+
static build(def, initiallyAvailable, externallyProvided = []) {
|
|
371
371
|
const stateAvail = new Map();
|
|
372
372
|
const producers = new Map();
|
|
373
373
|
const consumers = new Map();
|
|
374
374
|
const allProduced = new Set(initiallyAvailable);
|
|
375
375
|
const allConsumed = new Set();
|
|
376
|
+
const extSet = new Set(externallyProvided);
|
|
376
377
|
if (def.initialState) {
|
|
377
|
-
traverse(def, def.initialState, new Set(initiallyAvailable), stateAvail, producers, consumers, allProduced, allConsumed);
|
|
378
|
+
traverse(def, def.initialState, new Set(initiallyAvailable), extSet, stateAvail, producers, consumers, allProduced, allConsumed);
|
|
378
379
|
// Mark initially available types as produced by "initial"
|
|
379
380
|
for (const key of initiallyAvailable) {
|
|
380
381
|
if (!producers.has(key))
|
|
@@ -387,7 +388,7 @@ export class DataFlowGraph {
|
|
|
387
388
|
return new DataFlowGraph(stateAvail, producers, consumers, allProduced, allConsumed);
|
|
388
389
|
}
|
|
389
390
|
}
|
|
390
|
-
function traverse(def, state, available, stateAvail, producers, consumers, allProduced, allConsumed) {
|
|
391
|
+
function traverse(def, state, available, externallyProvided, stateAvail, producers, consumers, allProduced, allConsumed) {
|
|
391
392
|
if (stateAvail.has(state)) {
|
|
392
393
|
const existing = stateAvail.get(state);
|
|
393
394
|
let isSubset = true;
|
|
@@ -409,6 +410,10 @@ function traverse(def, state, available, stateAvail, producers, consumers, allPr
|
|
|
409
410
|
}
|
|
410
411
|
for (const t of def.transitionsFrom(state)) {
|
|
411
412
|
const newAvail = new Set(stateAvail.get(state));
|
|
413
|
+
if (t.type === 'external') {
|
|
414
|
+
for (const k of externallyProvided)
|
|
415
|
+
newAvail.add(k);
|
|
416
|
+
}
|
|
412
417
|
if (t.guard) {
|
|
413
418
|
for (const req of t.guard.requires) {
|
|
414
419
|
addTo(consumers, req, { name: t.guard.name, fromState: t.from, toState: t.to, kind: 'guard' });
|
|
@@ -437,7 +442,7 @@ function traverse(def, state, available, stateAvail, producers, consumers, allPr
|
|
|
437
442
|
newAvail.add(prod);
|
|
438
443
|
}
|
|
439
444
|
}
|
|
440
|
-
traverse(def, t.to, newAvail, stateAvail, producers, consumers, allProduced, allConsumed);
|
|
445
|
+
traverse(def, t.to, newAvail, externallyProvided, stateAvail, producers, consumers, allProduced, allConsumed);
|
|
441
446
|
}
|
|
442
447
|
}
|
|
443
448
|
function addTo(map, key, info) {
|
|
@@ -45,9 +45,12 @@ export declare class Builder<S extends string> {
|
|
|
45
45
|
private readonly _enterActions;
|
|
46
46
|
private readonly _exitActions;
|
|
47
47
|
private readonly initiallyAvailableKeys;
|
|
48
|
+
private readonly externallyProvidedKeys;
|
|
48
49
|
private _perpetual;
|
|
49
50
|
constructor(name: string, stateConfig: Record<S, StateConfig>);
|
|
50
51
|
initiallyAvailable(...keys: FlowKey<unknown>[]): this;
|
|
52
|
+
/** Declare data keys injected via resumeAndExecute(externalData), not available at start. */
|
|
53
|
+
externallyProvided(...keys: FlowKey<unknown>[]): this;
|
|
51
54
|
setTtl(ms: number): this;
|
|
52
55
|
setMaxGuardRetries(max: number): this;
|
|
53
56
|
from(state: S): FromBuilder<S>;
|
|
@@ -109,6 +109,7 @@ export class Builder {
|
|
|
109
109
|
_enterActions = new Map();
|
|
110
110
|
_exitActions = new Map();
|
|
111
111
|
initiallyAvailableKeys = [];
|
|
112
|
+
externallyProvidedKeys = [];
|
|
112
113
|
_perpetual = false;
|
|
113
114
|
constructor(name, stateConfig) {
|
|
114
115
|
this.name = name;
|
|
@@ -119,6 +120,12 @@ export class Builder {
|
|
|
119
120
|
this.initiallyAvailableKeys.push(k);
|
|
120
121
|
return this;
|
|
121
122
|
}
|
|
123
|
+
/** Declare data keys injected via resumeAndExecute(externalData), not available at start. */
|
|
124
|
+
externallyProvided(...keys) {
|
|
125
|
+
for (const k of keys)
|
|
126
|
+
this.externallyProvidedKeys.push(k);
|
|
127
|
+
return this;
|
|
128
|
+
}
|
|
122
129
|
setTtl(ms) { this.ttl = ms; return this; }
|
|
123
130
|
setMaxGuardRetries(max) { this.maxGuardRetries = max; return this; }
|
|
124
131
|
from(state) {
|
|
@@ -181,7 +188,7 @@ export class Builder {
|
|
|
181
188
|
result.terminalStates = terminals;
|
|
182
189
|
result.dataFlowGraph = null;
|
|
183
190
|
this.validate(result);
|
|
184
|
-
result.dataFlowGraph = DataFlowGraph.build(result, this.initiallyAvailableKeys);
|
|
191
|
+
result.dataFlowGraph = DataFlowGraph.build(result, this.initiallyAvailableKeys, this.externallyProvidedKeys);
|
|
185
192
|
// Build warnings
|
|
186
193
|
const warnings = [];
|
|
187
194
|
const perpetual = terminals.size === 0;
|
|
@@ -350,6 +357,10 @@ export class Builder {
|
|
|
350
357
|
}
|
|
351
358
|
for (const t of def.transitionsFrom(state)) {
|
|
352
359
|
const newAvailable = new Set(stateAvailable.get(state));
|
|
360
|
+
if (t.type === 'external') {
|
|
361
|
+
for (const k of this.externallyProvidedKeys)
|
|
362
|
+
newAvailable.add(k);
|
|
363
|
+
}
|
|
353
364
|
if (t.guard) {
|
|
354
365
|
for (const req of t.guard.requires) {
|
|
355
366
|
if (!newAvailable.has(req))
|
|
@@ -527,6 +538,7 @@ export class BranchBuilder {
|
|
|
527
538
|
processor: this.processors.get(label),
|
|
528
539
|
guard: undefined, branch: this.branch,
|
|
529
540
|
branchTargets: new Map(this.targets),
|
|
541
|
+
branchLabel: label,
|
|
530
542
|
});
|
|
531
543
|
}
|
|
532
544
|
return this.builder;
|
|
@@ -10,6 +10,7 @@ export interface TransitionLogEntry {
|
|
|
10
10
|
from: string | null;
|
|
11
11
|
to: string;
|
|
12
12
|
trigger: string;
|
|
13
|
+
durationMicros: number;
|
|
13
14
|
}
|
|
14
15
|
export interface StateLogEntry {
|
|
15
16
|
flowId: string;
|
|
@@ -25,6 +26,7 @@ export interface ErrorLogEntry {
|
|
|
25
26
|
to: string | null;
|
|
26
27
|
trigger: string;
|
|
27
28
|
cause: Error | null;
|
|
29
|
+
durationMicros: number;
|
|
28
30
|
}
|
|
29
31
|
export interface GuardLogEntry {
|
|
30
32
|
flowId: string;
|
|
@@ -33,6 +35,7 @@ export interface GuardLogEntry {
|
|
|
33
35
|
guardName: string;
|
|
34
36
|
result: 'accepted' | 'rejected' | 'expired';
|
|
35
37
|
reason?: string;
|
|
38
|
+
durationMicros: number;
|
|
36
39
|
}
|
|
37
40
|
export declare class FlowEngine {
|
|
38
41
|
private readonly store;
|
package/dist/esm/flow-engine.js
CHANGED
|
@@ -94,10 +94,13 @@ export class FlowEngine {
|
|
|
94
94
|
}
|
|
95
95
|
const guard = transition.guard;
|
|
96
96
|
if (guard) {
|
|
97
|
+
const guardStart = this.guardLogger ? performance.now() : 0;
|
|
97
98
|
const output = await guard.validate(flow.context);
|
|
99
|
+
const guardDurationMicros = this.guardLogger ? Math.round((performance.now() - guardStart) * 1000) : 0;
|
|
98
100
|
switch (output.type) {
|
|
99
101
|
case 'accepted': {
|
|
100
|
-
this.logGuard(flow, currentState, guard.name, 'accepted');
|
|
102
|
+
this.logGuard(flow, currentState, guard.name, 'accepted', guardDurationMicros);
|
|
103
|
+
const transStart = this.transitionLogger ? performance.now() : 0;
|
|
101
104
|
const backup = flow.context.snapshot();
|
|
102
105
|
if (output.data) {
|
|
103
106
|
for (const [key, value] of output.data)
|
|
@@ -111,7 +114,7 @@ export class FlowEngine {
|
|
|
111
114
|
flow.transitionTo(transition.to);
|
|
112
115
|
this.fireEnter(flow, transition.to);
|
|
113
116
|
this.store.recordTransition(flow.id, from, transition.to, guard.name, flow.context);
|
|
114
|
-
this.logTransition(flow, from, transition.to, guard.name);
|
|
117
|
+
this.logTransition(flow, from, transition.to, guard.name, transStart);
|
|
115
118
|
}
|
|
116
119
|
catch (e) {
|
|
117
120
|
flow.context.restoreFrom(backup);
|
|
@@ -122,7 +125,7 @@ export class FlowEngine {
|
|
|
122
125
|
break;
|
|
123
126
|
}
|
|
124
127
|
case 'rejected': {
|
|
125
|
-
this.logGuard(flow, currentState, guard.name, 'rejected', output.reason);
|
|
128
|
+
this.logGuard(flow, currentState, guard.name, 'rejected', guardDurationMicros, output.reason);
|
|
126
129
|
flow.incrementGuardFailure(guard.name);
|
|
127
130
|
if (flow.guardFailureCount >= definition.maxGuardRetries) {
|
|
128
131
|
this.handleError(flow, currentState);
|
|
@@ -131,7 +134,7 @@ export class FlowEngine {
|
|
|
131
134
|
return flow;
|
|
132
135
|
}
|
|
133
136
|
case 'expired': {
|
|
134
|
-
this.logGuard(flow, currentState, guard.name, 'expired');
|
|
137
|
+
this.logGuard(flow, currentState, guard.name, 'expired', guardDurationMicros);
|
|
135
138
|
flow.complete('EXPIRED');
|
|
136
139
|
this.store.save(flow);
|
|
137
140
|
return flow;
|
|
@@ -139,12 +142,13 @@ export class FlowEngine {
|
|
|
139
142
|
}
|
|
140
143
|
}
|
|
141
144
|
else {
|
|
145
|
+
const transStart = this.transitionLogger ? performance.now() : 0;
|
|
142
146
|
const from = flow.currentState;
|
|
143
147
|
this.fireExit(flow, from);
|
|
144
148
|
flow.transitionTo(transition.to);
|
|
145
149
|
this.fireEnter(flow, transition.to);
|
|
146
150
|
this.store.recordTransition(flow.id, from, transition.to, 'external', flow.context);
|
|
147
|
-
this.logTransition(flow, from, transition.to, 'external');
|
|
151
|
+
this.logTransition(flow, from, transition.to, 'external', transStart);
|
|
148
152
|
}
|
|
149
153
|
await this.executeAutoChain(flow);
|
|
150
154
|
this.store.save(flow);
|
|
@@ -172,6 +176,7 @@ export class FlowEngine {
|
|
|
172
176
|
if (!autoOrBranch)
|
|
173
177
|
break;
|
|
174
178
|
const backup = flow.context.snapshot();
|
|
179
|
+
const stepStart = this.transitionLogger ? performance.now() : 0;
|
|
175
180
|
try {
|
|
176
181
|
if (autoOrBranch.type === 'auto') {
|
|
177
182
|
if (autoOrBranch.processor) {
|
|
@@ -184,7 +189,7 @@ export class FlowEngine {
|
|
|
184
189
|
this.fireEnter(flow, autoOrBranch.to);
|
|
185
190
|
const trigger = autoOrBranch.processor?.name ?? 'auto';
|
|
186
191
|
this.store.recordTransition(flow.id, from, autoOrBranch.to, trigger, flow.context);
|
|
187
|
-
this.logTransition(flow, from, autoOrBranch.to, trigger);
|
|
192
|
+
this.logTransition(flow, from, autoOrBranch.to, trigger, stepStart);
|
|
188
193
|
}
|
|
189
194
|
else {
|
|
190
195
|
const branch = autoOrBranch.branch;
|
|
@@ -193,7 +198,7 @@ export class FlowEngine {
|
|
|
193
198
|
if (!target) {
|
|
194
199
|
throw new FlowError('UNKNOWN_BRANCH', `Branch '${branch.name}' returned unknown label: ${label}`);
|
|
195
200
|
}
|
|
196
|
-
const specific = transitions.find(t => t.type === 'branch' && t.to === target) ?? autoOrBranch;
|
|
201
|
+
const specific = transitions.find(t => t.type === 'branch' && t.branchLabel === label) ?? transitions.find(t => t.type === 'branch' && t.to === target) ?? autoOrBranch;
|
|
197
202
|
if (specific.processor)
|
|
198
203
|
await specific.processor.process(flow.context);
|
|
199
204
|
const from = flow.currentState;
|
|
@@ -202,7 +207,7 @@ export class FlowEngine {
|
|
|
202
207
|
this.fireEnter(flow, target);
|
|
203
208
|
const trigger = `${branch.name}:${label}`;
|
|
204
209
|
this.store.recordTransition(flow.id, from, target, trigger, flow.context);
|
|
205
|
-
this.logTransition(flow, from, target, trigger);
|
|
210
|
+
this.logTransition(flow, from, target, trigger, stepStart);
|
|
206
211
|
}
|
|
207
212
|
}
|
|
208
213
|
catch (e) {
|
|
@@ -226,13 +231,14 @@ export class FlowEngine {
|
|
|
226
231
|
parentFlow.setActiveSubFlow(null);
|
|
227
232
|
const target = exitMappings.get(subFlow.exitState);
|
|
228
233
|
if (target) {
|
|
234
|
+
const sfStart = this.transitionLogger ? performance.now() : 0;
|
|
229
235
|
const from = parentFlow.currentState;
|
|
230
236
|
this.fireExit(parentFlow, from);
|
|
231
237
|
parentFlow.transitionTo(target);
|
|
232
238
|
this.fireEnter(parentFlow, target);
|
|
233
239
|
const trigger = `subFlow:${subDef.name}/${subFlow.exitState}`;
|
|
234
240
|
this.store.recordTransition(parentFlow.id, from, target, trigger, parentFlow.context);
|
|
235
|
-
this.logTransition(parentFlow, from, target, trigger);
|
|
241
|
+
this.logTransition(parentFlow, from, target, trigger, sfStart);
|
|
236
242
|
return 1;
|
|
237
243
|
}
|
|
238
244
|
// Error bubbling: no exit mapping → fall back to parent's error transitions
|
|
@@ -250,17 +256,20 @@ export class FlowEngine {
|
|
|
250
256
|
}
|
|
251
257
|
const guard = transition.guard;
|
|
252
258
|
if (guard) {
|
|
259
|
+
const guardStart = this.guardLogger ? performance.now() : 0;
|
|
253
260
|
const output = await guard.validate(parentFlow.context);
|
|
261
|
+
const guardDur = this.guardLogger ? Math.round((performance.now() - guardStart) * 1000) : 0;
|
|
254
262
|
if (output.type === 'accepted') {
|
|
255
263
|
if (output.data) {
|
|
256
264
|
for (const [key, value] of output.data)
|
|
257
265
|
parentFlow.context.put(key, value);
|
|
258
266
|
}
|
|
267
|
+
const sfStart = this.transitionLogger ? performance.now() : 0;
|
|
259
268
|
const sfFrom = subFlow.currentState;
|
|
260
269
|
subFlow.transitionTo(transition.to);
|
|
261
270
|
this.store.recordTransition(parentFlow.id, sfFrom, transition.to, guard.name, parentFlow.context);
|
|
262
|
-
this.logTransition(parentFlow, sfFrom, transition.to, guard.name);
|
|
263
|
-
this.logGuard(parentFlow, sfFrom, guard.name, 'accepted');
|
|
271
|
+
this.logTransition(parentFlow, sfFrom, transition.to, guard.name, sfStart);
|
|
272
|
+
this.logGuard(parentFlow, sfFrom, guard.name, 'accepted', guardDur);
|
|
264
273
|
}
|
|
265
274
|
else if (output.type === 'rejected') {
|
|
266
275
|
subFlow.incrementGuardFailure();
|
|
@@ -287,13 +296,14 @@ export class FlowEngine {
|
|
|
287
296
|
if (subFlowT?.exitMappings) {
|
|
288
297
|
const target = subFlowT.exitMappings.get(subFlow.exitState);
|
|
289
298
|
if (target) {
|
|
299
|
+
const exitStart = this.transitionLogger ? performance.now() : 0;
|
|
290
300
|
const from = parentFlow.currentState;
|
|
291
301
|
this.fireExit(parentFlow, from);
|
|
292
302
|
parentFlow.transitionTo(target);
|
|
293
303
|
this.fireEnter(parentFlow, target);
|
|
294
304
|
const trigger = `subFlow:${subDef.name}/${subFlow.exitState}`;
|
|
295
305
|
this.store.recordTransition(parentFlow.id, from, target, trigger, parentFlow.context);
|
|
296
|
-
this.logTransition(parentFlow, from, target, trigger);
|
|
306
|
+
this.logTransition(parentFlow, from, target, trigger, exitStart);
|
|
297
307
|
await this.executeAutoChain(parentFlow);
|
|
298
308
|
}
|
|
299
309
|
}
|
|
@@ -320,16 +330,23 @@ export class FlowEngine {
|
|
|
320
330
|
if (action)
|
|
321
331
|
action(flow.context);
|
|
322
332
|
}
|
|
323
|
-
logTransition(flow, from, to, trigger) {
|
|
324
|
-
this.transitionLogger
|
|
333
|
+
logTransition(flow, from, to, trigger, startMs) {
|
|
334
|
+
if (this.transitionLogger) {
|
|
335
|
+
const durationMicros = Math.round((performance.now() - startMs) * 1000);
|
|
336
|
+
this.transitionLogger({ flowId: flow.id, flowName: flow.definition.name, from, to, trigger, durationMicros });
|
|
337
|
+
}
|
|
325
338
|
}
|
|
326
|
-
logError(flow, from, to, trigger, cause) {
|
|
327
|
-
this.errorLogger
|
|
339
|
+
logError(flow, from, to, trigger, cause, startMs) {
|
|
340
|
+
if (this.errorLogger) {
|
|
341
|
+
const durationMicros = Math.round((performance.now() - startMs) * 1000);
|
|
342
|
+
this.errorLogger({ flowId: flow.id, flowName: flow.definition.name, from, to, trigger, cause, durationMicros });
|
|
343
|
+
}
|
|
328
344
|
}
|
|
329
|
-
logGuard(flow, state, guardName, result, reason) {
|
|
330
|
-
this.guardLogger?.({ flowId: flow.id, flowName: flow.definition.name, state, guardName, result, reason });
|
|
345
|
+
logGuard(flow, state, guardName, result, durationMicros, reason) {
|
|
346
|
+
this.guardLogger?.({ flowId: flow.id, flowName: flow.definition.name, state, guardName, result, reason, durationMicros });
|
|
331
347
|
}
|
|
332
348
|
handleError(flow, fromState, cause) {
|
|
349
|
+
const errorStart = (this.transitionLogger || this.errorLogger) ? performance.now() : 0;
|
|
333
350
|
if (cause) {
|
|
334
351
|
flow.setLastError(`${cause.constructor.name}: ${cause.message}`);
|
|
335
352
|
if (cause instanceof FlowError) {
|
|
@@ -339,7 +356,7 @@ export class FlowEngine {
|
|
|
339
356
|
cause.withContextSnapshot(available, new Set());
|
|
340
357
|
}
|
|
341
358
|
}
|
|
342
|
-
this.logError(flow, fromState, null, 'error', cause ?? null);
|
|
359
|
+
this.logError(flow, fromState, null, 'error', cause ?? null, errorStart);
|
|
343
360
|
// 1. Try exception-typed routes first (onStepError)
|
|
344
361
|
if (cause && flow.definition.exceptionRoutes) {
|
|
345
362
|
const routes = flow.definition.exceptionRoutes.get(fromState);
|
|
@@ -350,7 +367,7 @@ export class FlowEngine {
|
|
|
350
367
|
flow.transitionTo(route.target);
|
|
351
368
|
const trigger = `error:${cause.constructor.name}`;
|
|
352
369
|
this.store.recordTransition(flow.id, from, route.target, trigger, flow.context);
|
|
353
|
-
this.logTransition(flow, from, route.target, trigger);
|
|
370
|
+
this.logTransition(flow, from, route.target, trigger, errorStart);
|
|
354
371
|
if (flow.definition.stateConfig[route.target]?.terminal)
|
|
355
372
|
flow.complete(route.target);
|
|
356
373
|
return;
|
|
@@ -364,7 +381,7 @@ export class FlowEngine {
|
|
|
364
381
|
const from = flow.currentState;
|
|
365
382
|
flow.transitionTo(errorTarget);
|
|
366
383
|
this.store.recordTransition(flow.id, from, errorTarget, 'error', flow.context);
|
|
367
|
-
this.logTransition(flow, from, errorTarget, 'error');
|
|
384
|
+
this.logTransition(flow, from, errorTarget, 'error', errorStart);
|
|
368
385
|
if (flow.definition.stateConfig[errorTarget]?.terminal)
|
|
369
386
|
flow.complete(errorTarget);
|
|
370
387
|
}
|
package/dist/esm/pipeline.js
CHANGED
|
@@ -87,14 +87,16 @@ export class Pipeline {
|
|
|
87
87
|
const completed = [];
|
|
88
88
|
let prev = 'initial';
|
|
89
89
|
for (const step of this.steps) {
|
|
90
|
-
this.transitionLogger
|
|
90
|
+
const stepStart = (this.transitionLogger || this.errorLogger) ? performance.now() : 0;
|
|
91
|
+
this.transitionLogger?.({ flowId, flowName: this.name, from: prev, to: step.name, trigger: step.name, durationMicros: 0 });
|
|
91
92
|
const keysBefore = this.stateLogger ? new Set(ctx.snapshot().keys()) : null;
|
|
92
93
|
try {
|
|
93
94
|
await step.process(ctx);
|
|
94
95
|
}
|
|
95
96
|
catch (e) {
|
|
96
97
|
const err = e instanceof Error ? e : new Error(String(e));
|
|
97
|
-
|
|
98
|
+
const durationMicros = Math.round((performance.now() - stepStart) * 1000);
|
|
99
|
+
this.errorLogger?.({ flowId, flowName: this.name, from: prev, to: step.name, trigger: step.name, cause: err, durationMicros });
|
|
98
100
|
throw new PipelineException(step.name, [...completed], ctx, err);
|
|
99
101
|
}
|
|
100
102
|
if (this.strictMode) {
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export interface Transition<S extends string> {
|
|
|
26
26
|
guard?: TransitionGuard<S>;
|
|
27
27
|
branch?: BranchProcessor<S>;
|
|
28
28
|
branchTargets: Map<string, S>;
|
|
29
|
+
/** Label assigned by builder .to(target, label, processor). Used for branch label-specific processor matching. */
|
|
30
|
+
branchLabel?: string;
|
|
29
31
|
subFlowDefinition?: import('./flow-definition.js').FlowDefinition<any>;
|
|
30
32
|
exitMappings?: Map<string, S>;
|
|
31
33
|
/** Per-state timeout in milliseconds. If set, resumeAndExecute checks this before guard. */
|