@unlaxer/tramli 1.11.0 → 1.13.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.
@@ -12,6 +12,10 @@ export declare class FlowDefinition<S extends string> {
12
12
  readonly terminalStates: Set<S>;
13
13
  readonly dataFlowGraph: DataFlowGraph<S> | null;
14
14
  readonly warnings: string[];
15
+ readonly exceptionRoutes: Map<S, Array<{
16
+ errorClass: new (...args: any[]) => Error;
17
+ target: S;
18
+ }>>;
15
19
  private constructor();
16
20
  transitionsFrom(state: S): Transition<S>[];
17
21
  externalFrom(state: S): Transition<S> | undefined;
@@ -29,6 +33,7 @@ export declare class Builder<S extends string> {
29
33
  private maxGuardRetries;
30
34
  private readonly transitions;
31
35
  private readonly errorTransitions;
36
+ private readonly _exceptionRoutes;
32
37
  private readonly initiallyAvailableKeys;
33
38
  constructor(name: string, stateConfig: Record<S, StateConfig>);
34
39
  initiallyAvailable(...keys: FlowKey<unknown>[]): this;
@@ -36,6 +41,8 @@ export declare class Builder<S extends string> {
36
41
  setMaxGuardRetries(max: number): this;
37
42
  from(state: S): FromBuilder<S>;
38
43
  onError(from: S, to: S): this;
44
+ /** Route specific error types to specific states. Checked before onError. */
45
+ onStepError(from: S, errorClass: new (...args: any[]) => Error, to: S): this;
39
46
  onAnyError(errorState: S): this;
40
47
  /** @internal */
41
48
  addTransition(t: Transition<S>): void;
@@ -14,6 +14,7 @@ class FlowDefinition {
14
14
  terminalStates;
15
15
  dataFlowGraph;
16
16
  warnings;
17
+ exceptionRoutes;
17
18
  constructor(name, stateConfig, ttl, maxGuardRetries, transitions, errorTransitions) {
18
19
  this.name = name;
19
20
  this.stateConfig = stateConfig;
@@ -90,6 +91,7 @@ class Builder {
90
91
  maxGuardRetries = 3;
91
92
  transitions = [];
92
93
  errorTransitions = new Map();
94
+ _exceptionRoutes = new Map();
93
95
  initiallyAvailableKeys = [];
94
96
  constructor(name, stateConfig) {
95
97
  this.name = name;
@@ -109,6 +111,13 @@ class Builder {
109
111
  this.errorTransitions.set(from, to);
110
112
  return this;
111
113
  }
114
+ /** Route specific error types to specific states. Checked before onError. */
115
+ onStepError(from, errorClass, to) {
116
+ if (!this._exceptionRoutes.has(from))
117
+ this._exceptionRoutes.set(from, []);
118
+ this._exceptionRoutes.get(from).push({ errorClass, target: to });
119
+ return this;
120
+ }
112
121
  onAnyError(errorState) {
113
122
  for (const s of Object.keys(this.stateConfig)) {
114
123
  if (!this.stateConfig[s].terminal)
@@ -152,6 +161,7 @@ class Builder {
152
161
  warnings.push(`Perpetual flow '${this.name}' has External transitions — ensure events are always delivered to avoid deadlock (liveness risk)`);
153
162
  }
154
163
  result.warnings = warnings;
164
+ result.exceptionRoutes = new Map(this._exceptionRoutes);
155
165
  return result;
156
166
  }
157
167
  validate(def) {
@@ -266,6 +266,23 @@ class FlowEngine {
266
266
  }
267
267
  }
268
268
  this.errorLogger?.({ flowId: flow.id, from: fromState, to: null, trigger: 'error', cause: cause ?? null });
269
+ // 1. Try exception-typed routes first (onStepError)
270
+ if (cause && flow.definition.exceptionRoutes) {
271
+ const routes = flow.definition.exceptionRoutes.get(fromState);
272
+ if (routes) {
273
+ for (const route of routes) {
274
+ if (cause instanceof route.errorClass) {
275
+ const from = flow.currentState;
276
+ flow.transitionTo(route.target);
277
+ this.store.recordTransition(flow.id, from, route.target, `error:${cause.constructor.name}`, flow.context);
278
+ if (flow.definition.stateConfig[route.target].terminal)
279
+ flow.complete(route.target);
280
+ return;
281
+ }
282
+ }
283
+ }
284
+ }
285
+ // 2. Fall back to state-based error transition (onError)
269
286
  const errorTarget = flow.definition.errorTransitions.get(fromState);
270
287
  if (errorTarget) {
271
288
  const from = flow.currentState;
@@ -12,6 +12,10 @@ export declare class FlowDefinition<S extends string> {
12
12
  readonly terminalStates: Set<S>;
13
13
  readonly dataFlowGraph: DataFlowGraph<S> | null;
14
14
  readonly warnings: string[];
15
+ readonly exceptionRoutes: Map<S, Array<{
16
+ errorClass: new (...args: any[]) => Error;
17
+ target: S;
18
+ }>>;
15
19
  private constructor();
16
20
  transitionsFrom(state: S): Transition<S>[];
17
21
  externalFrom(state: S): Transition<S> | undefined;
@@ -29,6 +33,7 @@ export declare class Builder<S extends string> {
29
33
  private maxGuardRetries;
30
34
  private readonly transitions;
31
35
  private readonly errorTransitions;
36
+ private readonly _exceptionRoutes;
32
37
  private readonly initiallyAvailableKeys;
33
38
  constructor(name: string, stateConfig: Record<S, StateConfig>);
34
39
  initiallyAvailable(...keys: FlowKey<unknown>[]): this;
@@ -36,6 +41,8 @@ export declare class Builder<S extends string> {
36
41
  setMaxGuardRetries(max: number): this;
37
42
  from(state: S): FromBuilder<S>;
38
43
  onError(from: S, to: S): this;
44
+ /** Route specific error types to specific states. Checked before onError. */
45
+ onStepError(from: S, errorClass: new (...args: any[]) => Error, to: S): this;
39
46
  onAnyError(errorState: S): this;
40
47
  /** @internal */
41
48
  addTransition(t: Transition<S>): void;
@@ -11,6 +11,7 @@ export class FlowDefinition {
11
11
  terminalStates;
12
12
  dataFlowGraph;
13
13
  warnings;
14
+ exceptionRoutes;
14
15
  constructor(name, stateConfig, ttl, maxGuardRetries, transitions, errorTransitions) {
15
16
  this.name = name;
16
17
  this.stateConfig = stateConfig;
@@ -86,6 +87,7 @@ export class Builder {
86
87
  maxGuardRetries = 3;
87
88
  transitions = [];
88
89
  errorTransitions = new Map();
90
+ _exceptionRoutes = new Map();
89
91
  initiallyAvailableKeys = [];
90
92
  constructor(name, stateConfig) {
91
93
  this.name = name;
@@ -105,6 +107,13 @@ export class Builder {
105
107
  this.errorTransitions.set(from, to);
106
108
  return this;
107
109
  }
110
+ /** Route specific error types to specific states. Checked before onError. */
111
+ onStepError(from, errorClass, to) {
112
+ if (!this._exceptionRoutes.has(from))
113
+ this._exceptionRoutes.set(from, []);
114
+ this._exceptionRoutes.get(from).push({ errorClass, target: to });
115
+ return this;
116
+ }
108
117
  onAnyError(errorState) {
109
118
  for (const s of Object.keys(this.stateConfig)) {
110
119
  if (!this.stateConfig[s].terminal)
@@ -148,6 +157,7 @@ export class Builder {
148
157
  warnings.push(`Perpetual flow '${this.name}' has External transitions — ensure events are always delivered to avoid deadlock (liveness risk)`);
149
158
  }
150
159
  result.warnings = warnings;
160
+ result.exceptionRoutes = new Map(this._exceptionRoutes);
151
161
  return result;
152
162
  }
153
163
  validate(def) {
@@ -263,6 +263,23 @@ export class FlowEngine {
263
263
  }
264
264
  }
265
265
  this.errorLogger?.({ flowId: flow.id, from: fromState, to: null, trigger: 'error', cause: cause ?? null });
266
+ // 1. Try exception-typed routes first (onStepError)
267
+ if (cause && flow.definition.exceptionRoutes) {
268
+ const routes = flow.definition.exceptionRoutes.get(fromState);
269
+ if (routes) {
270
+ for (const route of routes) {
271
+ if (cause instanceof route.errorClass) {
272
+ const from = flow.currentState;
273
+ flow.transitionTo(route.target);
274
+ this.store.recordTransition(flow.id, from, route.target, `error:${cause.constructor.name}`, flow.context);
275
+ if (flow.definition.stateConfig[route.target].terminal)
276
+ flow.complete(route.target);
277
+ return;
278
+ }
279
+ }
280
+ }
281
+ }
282
+ // 2. Fall back to state-based error transition (onError)
266
283
  const errorTarget = flow.definition.errorTransitions.get(fromState);
267
284
  if (errorTarget) {
268
285
  const from = flow.currentState;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unlaxer/tramli",
3
- "version": "1.11.0",
3
+ "version": "1.13.0",
4
4
  "description": "Constrained flow engine — state machines that prevent invalid transitions at build time",
5
5
  "type": "module",
6
6
  "exports": {