@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) {
|
package/dist/cjs/flow-engine.js
CHANGED
|
@@ -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) {
|
package/dist/esm/flow-engine.js
CHANGED
|
@@ -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;
|