@unlaxer/tramli 3.3.0 → 3.5.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.
@@ -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;
@@ -53,6 +53,10 @@ export declare class FlowEngine {
53
53
  setStateLogger(logger: ((entry: StateLogEntry) => void) | null): void;
54
54
  setErrorLogger(logger: ((entry: ErrorLogEntry) => void) | null): void;
55
55
  setGuardLogger(logger: ((entry: GuardLogEntry) => void) | null): void;
56
+ getTransitionLogger(): ((entry: TransitionLogEntry) => void) | undefined;
57
+ getStateLogger(): ((entry: StateLogEntry) => void) | undefined;
58
+ getErrorLogger(): ((entry: ErrorLogEntry) => void) | undefined;
59
+ getGuardLogger(): ((entry: GuardLogEntry) => void) | undefined;
56
60
  removeAllLoggers(): void;
57
61
  startFlow<S extends string>(definition: FlowDefinition<S>, sessionId: string, initialData: Map<string, unknown>): Promise<FlowInstance<S>>;
58
62
  resumeAndExecute<S extends string>(flowId: string, definition: FlowDefinition<S>, externalData?: Map<string, unknown>): Promise<FlowInstance<S>>;
@@ -31,6 +31,10 @@ class FlowEngine {
31
31
  setGuardLogger(logger) {
32
32
  this.guardLogger = logger ?? undefined;
33
33
  }
34
+ getTransitionLogger() { return this.transitionLogger; }
35
+ getStateLogger() { return this.stateLogger; }
36
+ getErrorLogger() { return this.errorLogger; }
37
+ getGuardLogger() { return this.guardLogger; }
34
38
  removeAllLoggers() {
35
39
  this.transitionLogger = undefined;
36
40
  this.stateLogger = undefined;
@@ -97,13 +101,13 @@ class FlowEngine {
97
101
  }
98
102
  const guard = transition.guard;
99
103
  if (guard) {
100
- const guardStart = this.guardLogger ? performance.now() : 0;
104
+ const guardStart = performance.now();
101
105
  const output = await guard.validate(flow.context);
102
- const guardDurationMicros = this.guardLogger ? Math.round((performance.now() - guardStart) * 1000) : 0;
106
+ const guardDurationMicros = Math.round((performance.now() - guardStart) * 1000);
103
107
  switch (output.type) {
104
108
  case 'accepted': {
105
109
  this.logGuard(flow, currentState, guard.name, 'accepted', guardDurationMicros);
106
- const transStart = this.transitionLogger ? performance.now() : 0;
110
+ const transStart = performance.now();
107
111
  const backup = flow.context.snapshot();
108
112
  if (output.data) {
109
113
  for (const [key, value] of output.data)
@@ -145,7 +149,7 @@ class FlowEngine {
145
149
  }
146
150
  }
147
151
  else {
148
- const transStart = this.transitionLogger ? performance.now() : 0;
152
+ const transStart = performance.now();
149
153
  const from = flow.currentState;
150
154
  this.fireExit(flow, from);
151
155
  flow.transitionTo(transition.to);
@@ -179,7 +183,7 @@ class FlowEngine {
179
183
  if (!autoOrBranch)
180
184
  break;
181
185
  const backup = flow.context.snapshot();
182
- const stepStart = this.transitionLogger ? performance.now() : 0;
186
+ const stepStart = performance.now();
183
187
  try {
184
188
  if (autoOrBranch.type === 'auto') {
185
189
  if (autoOrBranch.processor) {
@@ -201,7 +205,7 @@ class FlowEngine {
201
205
  if (!target) {
202
206
  throw new flow_error_js_1.FlowError('UNKNOWN_BRANCH', `Branch '${branch.name}' returned unknown label: ${label}`);
203
207
  }
204
- const specific = transitions.find(t => t.type === 'branch' && t.to === target) ?? autoOrBranch;
208
+ const specific = transitions.find(t => t.type === 'branch' && t.branchLabel === label) ?? transitions.find(t => t.type === 'branch' && t.to === target) ?? autoOrBranch;
205
209
  if (specific.processor)
206
210
  await specific.processor.process(flow.context);
207
211
  const from = flow.currentState;
@@ -234,7 +238,7 @@ class FlowEngine {
234
238
  parentFlow.setActiveSubFlow(null);
235
239
  const target = exitMappings.get(subFlow.exitState);
236
240
  if (target) {
237
- const sfStart = this.transitionLogger ? performance.now() : 0;
241
+ const sfStart = performance.now();
238
242
  const from = parentFlow.currentState;
239
243
  this.fireExit(parentFlow, from);
240
244
  parentFlow.transitionTo(target);
@@ -259,15 +263,15 @@ class FlowEngine {
259
263
  }
260
264
  const guard = transition.guard;
261
265
  if (guard) {
262
- const guardStart = this.guardLogger ? performance.now() : 0;
266
+ const guardStart = performance.now();
263
267
  const output = await guard.validate(parentFlow.context);
264
- const guardDur = this.guardLogger ? Math.round((performance.now() - guardStart) * 1000) : 0;
268
+ const guardDur = Math.round((performance.now() - guardStart) * 1000);
265
269
  if (output.type === 'accepted') {
266
270
  if (output.data) {
267
271
  for (const [key, value] of output.data)
268
272
  parentFlow.context.put(key, value);
269
273
  }
270
- const sfStart = this.transitionLogger ? performance.now() : 0;
274
+ const sfStart = performance.now();
271
275
  const sfFrom = subFlow.currentState;
272
276
  subFlow.transitionTo(transition.to);
273
277
  this.store.recordTransition(parentFlow.id, sfFrom, transition.to, guard.name, parentFlow.context);
@@ -299,7 +303,7 @@ class FlowEngine {
299
303
  if (subFlowT?.exitMappings) {
300
304
  const target = subFlowT.exitMappings.get(subFlow.exitState);
301
305
  if (target) {
302
- const exitStart = this.transitionLogger ? performance.now() : 0;
306
+ const exitStart = performance.now();
303
307
  const from = parentFlow.currentState;
304
308
  this.fireExit(parentFlow, from);
305
309
  parentFlow.transitionTo(target);
@@ -349,7 +353,7 @@ class FlowEngine {
349
353
  this.guardLogger?.({ flowId: flow.id, flowName: flow.definition.name, state, guardName, result, reason, durationMicros });
350
354
  }
351
355
  handleError(flow, fromState, cause) {
352
- const errorStart = (this.transitionLogger || this.errorLogger) ? performance.now() : 0;
356
+ const errorStart = performance.now();
353
357
  if (cause) {
354
358
  flow.setLastError(`${cause.constructor.name}: ${cause.message}`);
355
359
  if (cause instanceof flow_error_js_1.FlowError) {
@@ -92,7 +92,7 @@ class Pipeline {
92
92
  const completed = [];
93
93
  let prev = 'initial';
94
94
  for (const step of this.steps) {
95
- const stepStart = (this.transitionLogger || this.errorLogger) ? performance.now() : 0;
95
+ const stepStart = performance.now();
96
96
  this.transitionLogger?.({ flowId, flowName: this.name, from: prev, to: step.name, trigger: step.name, durationMicros: 0 });
97
97
  const keysBefore = this.stateLogger ? new Set(ctx.snapshot().keys()) : null;
98
98
  try {
@@ -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;
@@ -53,6 +53,10 @@ export declare class FlowEngine {
53
53
  setStateLogger(logger: ((entry: StateLogEntry) => void) | null): void;
54
54
  setErrorLogger(logger: ((entry: ErrorLogEntry) => void) | null): void;
55
55
  setGuardLogger(logger: ((entry: GuardLogEntry) => void) | null): void;
56
+ getTransitionLogger(): ((entry: TransitionLogEntry) => void) | undefined;
57
+ getStateLogger(): ((entry: StateLogEntry) => void) | undefined;
58
+ getErrorLogger(): ((entry: ErrorLogEntry) => void) | undefined;
59
+ getGuardLogger(): ((entry: GuardLogEntry) => void) | undefined;
56
60
  removeAllLoggers(): void;
57
61
  startFlow<S extends string>(definition: FlowDefinition<S>, sessionId: string, initialData: Map<string, unknown>): Promise<FlowInstance<S>>;
58
62
  resumeAndExecute<S extends string>(flowId: string, definition: FlowDefinition<S>, externalData?: Map<string, unknown>): Promise<FlowInstance<S>>;
@@ -28,6 +28,10 @@ export class FlowEngine {
28
28
  setGuardLogger(logger) {
29
29
  this.guardLogger = logger ?? undefined;
30
30
  }
31
+ getTransitionLogger() { return this.transitionLogger; }
32
+ getStateLogger() { return this.stateLogger; }
33
+ getErrorLogger() { return this.errorLogger; }
34
+ getGuardLogger() { return this.guardLogger; }
31
35
  removeAllLoggers() {
32
36
  this.transitionLogger = undefined;
33
37
  this.stateLogger = undefined;
@@ -94,13 +98,13 @@ export class FlowEngine {
94
98
  }
95
99
  const guard = transition.guard;
96
100
  if (guard) {
97
- const guardStart = this.guardLogger ? performance.now() : 0;
101
+ const guardStart = performance.now();
98
102
  const output = await guard.validate(flow.context);
99
- const guardDurationMicros = this.guardLogger ? Math.round((performance.now() - guardStart) * 1000) : 0;
103
+ const guardDurationMicros = Math.round((performance.now() - guardStart) * 1000);
100
104
  switch (output.type) {
101
105
  case 'accepted': {
102
106
  this.logGuard(flow, currentState, guard.name, 'accepted', guardDurationMicros);
103
- const transStart = this.transitionLogger ? performance.now() : 0;
107
+ const transStart = performance.now();
104
108
  const backup = flow.context.snapshot();
105
109
  if (output.data) {
106
110
  for (const [key, value] of output.data)
@@ -142,7 +146,7 @@ export class FlowEngine {
142
146
  }
143
147
  }
144
148
  else {
145
- const transStart = this.transitionLogger ? performance.now() : 0;
149
+ const transStart = performance.now();
146
150
  const from = flow.currentState;
147
151
  this.fireExit(flow, from);
148
152
  flow.transitionTo(transition.to);
@@ -176,7 +180,7 @@ export class FlowEngine {
176
180
  if (!autoOrBranch)
177
181
  break;
178
182
  const backup = flow.context.snapshot();
179
- const stepStart = this.transitionLogger ? performance.now() : 0;
183
+ const stepStart = performance.now();
180
184
  try {
181
185
  if (autoOrBranch.type === 'auto') {
182
186
  if (autoOrBranch.processor) {
@@ -198,7 +202,7 @@ export class FlowEngine {
198
202
  if (!target) {
199
203
  throw new FlowError('UNKNOWN_BRANCH', `Branch '${branch.name}' returned unknown label: ${label}`);
200
204
  }
201
- const specific = transitions.find(t => t.type === 'branch' && t.to === target) ?? autoOrBranch;
205
+ const specific = transitions.find(t => t.type === 'branch' && t.branchLabel === label) ?? transitions.find(t => t.type === 'branch' && t.to === target) ?? autoOrBranch;
202
206
  if (specific.processor)
203
207
  await specific.processor.process(flow.context);
204
208
  const from = flow.currentState;
@@ -231,7 +235,7 @@ export class FlowEngine {
231
235
  parentFlow.setActiveSubFlow(null);
232
236
  const target = exitMappings.get(subFlow.exitState);
233
237
  if (target) {
234
- const sfStart = this.transitionLogger ? performance.now() : 0;
238
+ const sfStart = performance.now();
235
239
  const from = parentFlow.currentState;
236
240
  this.fireExit(parentFlow, from);
237
241
  parentFlow.transitionTo(target);
@@ -256,15 +260,15 @@ export class FlowEngine {
256
260
  }
257
261
  const guard = transition.guard;
258
262
  if (guard) {
259
- const guardStart = this.guardLogger ? performance.now() : 0;
263
+ const guardStart = performance.now();
260
264
  const output = await guard.validate(parentFlow.context);
261
- const guardDur = this.guardLogger ? Math.round((performance.now() - guardStart) * 1000) : 0;
265
+ const guardDur = Math.round((performance.now() - guardStart) * 1000);
262
266
  if (output.type === 'accepted') {
263
267
  if (output.data) {
264
268
  for (const [key, value] of output.data)
265
269
  parentFlow.context.put(key, value);
266
270
  }
267
- const sfStart = this.transitionLogger ? performance.now() : 0;
271
+ const sfStart = performance.now();
268
272
  const sfFrom = subFlow.currentState;
269
273
  subFlow.transitionTo(transition.to);
270
274
  this.store.recordTransition(parentFlow.id, sfFrom, transition.to, guard.name, parentFlow.context);
@@ -296,7 +300,7 @@ export class FlowEngine {
296
300
  if (subFlowT?.exitMappings) {
297
301
  const target = subFlowT.exitMappings.get(subFlow.exitState);
298
302
  if (target) {
299
- const exitStart = this.transitionLogger ? performance.now() : 0;
303
+ const exitStart = performance.now();
300
304
  const from = parentFlow.currentState;
301
305
  this.fireExit(parentFlow, from);
302
306
  parentFlow.transitionTo(target);
@@ -346,7 +350,7 @@ export class FlowEngine {
346
350
  this.guardLogger?.({ flowId: flow.id, flowName: flow.definition.name, state, guardName, result, reason, durationMicros });
347
351
  }
348
352
  handleError(flow, fromState, cause) {
349
- const errorStart = (this.transitionLogger || this.errorLogger) ? performance.now() : 0;
353
+ const errorStart = performance.now();
350
354
  if (cause) {
351
355
  flow.setLastError(`${cause.constructor.name}: ${cause.message}`);
352
356
  if (cause instanceof FlowError) {
@@ -87,7 +87,7 @@ export class Pipeline {
87
87
  const completed = [];
88
88
  let prev = 'initial';
89
89
  for (const step of this.steps) {
90
- const stepStart = (this.transitionLogger || this.errorLogger) ? performance.now() : 0;
90
+ const stepStart = performance.now();
91
91
  this.transitionLogger?.({ flowId, flowName: this.name, from: prev, to: step.name, trigger: step.name, durationMicros: 0 });
92
92
  const keysBefore = this.stateLogger ? new Set(ctx.snapshot().keys()) : null;
93
93
  try {
@@ -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. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unlaxer/tramli",
3
- "version": "3.3.0",
3
+ "version": "3.5.0",
4
4
  "description": "Constrained flow engine — state machines that prevent invalid transitions at build time",
5
5
  "type": "module",
6
6
  "exports": {