@unlaxer/tramli 3.2.0 → 3.3.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/flow-engine.d.ts +3 -0
- package/dist/cjs/flow-engine.js +37 -20
- package/dist/cjs/pipeline.js +4 -2
- package/dist/esm/flow-engine.d.ts +3 -0
- package/dist/esm/flow-engine.js +37 -20
- package/dist/esm/pipeline.js +4 -2
- package/package.json +1 -1
|
@@ -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;
|
|
@@ -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) {
|
|
@@ -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;
|
|
@@ -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) {
|