@unlaxer/tramli 1.5.3 → 1.6.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.
@@ -26,7 +26,7 @@ class FlowEngine {
26
26
  return flow;
27
27
  }
28
28
  async resumeAndExecute(flowId, definition, externalData) {
29
- const flow = this.store.loadForUpdate(flowId);
29
+ const flow = this.store.loadForUpdate(flowId, definition);
30
30
  if (!flow)
31
31
  throw new flow_error_js_1.FlowError('FLOW_NOT_FOUND', `Flow ${flowId} not found or already completed`);
32
32
  if (externalData) {
@@ -63,9 +63,9 @@ class FlowEngine {
63
63
  flow.transitionTo(transition.to);
64
64
  this.store.recordTransition(flow.id, from, transition.to, guard.name, flow.context);
65
65
  }
66
- catch {
66
+ catch (e) {
67
67
  flow.context.restoreFrom(backup);
68
- this.handleError(flow, currentState);
68
+ this.handleError(flow, currentState, e instanceof Error ? e : new Error(String(e)));
69
69
  this.store.save(flow);
70
70
  return flow;
71
71
  }
@@ -140,9 +140,9 @@ class FlowEngine {
140
140
  this.store.recordTransition(flow.id, from, target, `${branch.name}:${label}`, flow.context);
141
141
  }
142
142
  }
143
- catch {
143
+ catch (e) {
144
144
  flow.context.restoreFrom(backup);
145
- this.handleError(flow, flow.currentState);
145
+ this.handleError(flow, flow.currentState, e instanceof Error ? e : new Error(String(e)));
146
146
  return;
147
147
  }
148
148
  depth++;
@@ -225,7 +225,9 @@ class FlowEngine {
225
225
  this.store.save(parentFlow);
226
226
  return parentFlow;
227
227
  }
228
- handleError(flow, fromState) {
228
+ handleError(flow, fromState, cause) {
229
+ if (cause)
230
+ flow.setLastError(`${cause.constructor.name}: ${cause.message}`);
229
231
  const errorTarget = flow.definition.errorTransitions.get(fromState);
230
232
  if (errorTarget) {
231
233
  const from = flow.currentState;
@@ -12,6 +12,7 @@ export declare class FlowInstance<S extends string> {
12
12
  readonly expiresAt: Date;
13
13
  private _exitState;
14
14
  private _activeSubFlow;
15
+ private _lastError;
15
16
  constructor(id: string, sessionId: string, definition: FlowDefinition<S>, context: FlowContext, currentState: S, expiresAt: Date);
16
17
  /**
17
18
  * Restore a FlowInstance from persisted state.
@@ -24,6 +25,8 @@ export declare class FlowInstance<S extends string> {
24
25
  get exitState(): string | null;
25
26
  get isCompleted(): boolean;
26
27
  get activeSubFlow(): FlowInstance<any> | null;
28
+ /** Last error message (set when a processor throws and error transition fires). */
29
+ get lastError(): string | null;
27
30
  /** State path from root to deepest active sub-flow. */
28
31
  statePath(): string[];
29
32
  /** State path as slash-separated string. */
@@ -41,4 +44,5 @@ export declare class FlowInstance<S extends string> {
41
44
  /** @internal */ complete(exitState: string): void;
42
45
  /** @internal */ setVersion(version: number): void;
43
46
  /** @internal */ setActiveSubFlow(sub: FlowInstance<any> | null): void;
47
+ /** @internal */ setLastError(error: string | null): void;
44
48
  }
@@ -13,6 +13,7 @@ class FlowInstance {
13
13
  expiresAt;
14
14
  _exitState;
15
15
  _activeSubFlow = null;
16
+ _lastError = null;
16
17
  constructor(id, sessionId, definition, context, currentState, expiresAt) {
17
18
  this.id = id;
18
19
  this.sessionId = sessionId;
@@ -50,6 +51,8 @@ class FlowInstance {
50
51
  get exitState() { return this._exitState; }
51
52
  get isCompleted() { return this._exitState !== null; }
52
53
  get activeSubFlow() { return this._activeSubFlow; }
54
+ /** Last error message (set when a processor throws and error transition fires). */
55
+ get lastError() { return this._lastError; }
53
56
  /** State path from root to deepest active sub-flow. */
54
57
  statePath() {
55
58
  const path = [this._currentState];
@@ -100,5 +103,6 @@ class FlowInstance {
100
103
  /** @internal */ complete(exitState) { this._exitState = exitState; }
101
104
  /** @internal */ setVersion(version) { this._version = version; }
102
105
  /** @internal */ setActiveSubFlow(sub) { this._activeSubFlow = sub; }
106
+ /** @internal */ setLastError(error) { this._lastError = error; }
103
107
  }
104
108
  exports.FlowInstance = FlowInstance;
@@ -12,7 +12,7 @@ export declare class InMemoryFlowStore {
12
12
  private flows;
13
13
  private _transitionLog;
14
14
  create(flow: FlowInstance<any>): void;
15
- loadForUpdate<S extends string>(flowId: string): FlowInstance<S> | undefined;
15
+ loadForUpdate<S extends string>(flowId: string, _definition?: any): FlowInstance<S> | undefined;
16
16
  save(flow: FlowInstance<any>): void;
17
17
  recordTransition(flowId: string, from: string | null, to: string, trigger: string, _ctx: FlowContext): void;
18
18
  get transitionLog(): readonly TransitionRecord[];
@@ -7,7 +7,7 @@ class InMemoryFlowStore {
7
7
  create(flow) {
8
8
  this.flows.set(flow.id, flow);
9
9
  }
10
- loadForUpdate(flowId) {
10
+ loadForUpdate(flowId, _definition) {
11
11
  const flow = this.flows.get(flowId);
12
12
  if (!flow || flow.isCompleted)
13
13
  return undefined;
@@ -23,7 +23,7 @@ export class FlowEngine {
23
23
  return flow;
24
24
  }
25
25
  async resumeAndExecute(flowId, definition, externalData) {
26
- const flow = this.store.loadForUpdate(flowId);
26
+ const flow = this.store.loadForUpdate(flowId, definition);
27
27
  if (!flow)
28
28
  throw new FlowError('FLOW_NOT_FOUND', `Flow ${flowId} not found or already completed`);
29
29
  if (externalData) {
@@ -60,9 +60,9 @@ export class FlowEngine {
60
60
  flow.transitionTo(transition.to);
61
61
  this.store.recordTransition(flow.id, from, transition.to, guard.name, flow.context);
62
62
  }
63
- catch {
63
+ catch (e) {
64
64
  flow.context.restoreFrom(backup);
65
- this.handleError(flow, currentState);
65
+ this.handleError(flow, currentState, e instanceof Error ? e : new Error(String(e)));
66
66
  this.store.save(flow);
67
67
  return flow;
68
68
  }
@@ -137,9 +137,9 @@ export class FlowEngine {
137
137
  this.store.recordTransition(flow.id, from, target, `${branch.name}:${label}`, flow.context);
138
138
  }
139
139
  }
140
- catch {
140
+ catch (e) {
141
141
  flow.context.restoreFrom(backup);
142
- this.handleError(flow, flow.currentState);
142
+ this.handleError(flow, flow.currentState, e instanceof Error ? e : new Error(String(e)));
143
143
  return;
144
144
  }
145
145
  depth++;
@@ -222,7 +222,9 @@ export class FlowEngine {
222
222
  this.store.save(parentFlow);
223
223
  return parentFlow;
224
224
  }
225
- handleError(flow, fromState) {
225
+ handleError(flow, fromState, cause) {
226
+ if (cause)
227
+ flow.setLastError(`${cause.constructor.name}: ${cause.message}`);
226
228
  const errorTarget = flow.definition.errorTransitions.get(fromState);
227
229
  if (errorTarget) {
228
230
  const from = flow.currentState;
@@ -12,6 +12,7 @@ export declare class FlowInstance<S extends string> {
12
12
  readonly expiresAt: Date;
13
13
  private _exitState;
14
14
  private _activeSubFlow;
15
+ private _lastError;
15
16
  constructor(id: string, sessionId: string, definition: FlowDefinition<S>, context: FlowContext, currentState: S, expiresAt: Date);
16
17
  /**
17
18
  * Restore a FlowInstance from persisted state.
@@ -24,6 +25,8 @@ export declare class FlowInstance<S extends string> {
24
25
  get exitState(): string | null;
25
26
  get isCompleted(): boolean;
26
27
  get activeSubFlow(): FlowInstance<any> | null;
28
+ /** Last error message (set when a processor throws and error transition fires). */
29
+ get lastError(): string | null;
27
30
  /** State path from root to deepest active sub-flow. */
28
31
  statePath(): string[];
29
32
  /** State path as slash-separated string. */
@@ -41,4 +44,5 @@ export declare class FlowInstance<S extends string> {
41
44
  /** @internal */ complete(exitState: string): void;
42
45
  /** @internal */ setVersion(version: number): void;
43
46
  /** @internal */ setActiveSubFlow(sub: FlowInstance<any> | null): void;
47
+ /** @internal */ setLastError(error: string | null): void;
44
48
  }
@@ -10,6 +10,7 @@ export class FlowInstance {
10
10
  expiresAt;
11
11
  _exitState;
12
12
  _activeSubFlow = null;
13
+ _lastError = null;
13
14
  constructor(id, sessionId, definition, context, currentState, expiresAt) {
14
15
  this.id = id;
15
16
  this.sessionId = sessionId;
@@ -47,6 +48,8 @@ export class FlowInstance {
47
48
  get exitState() { return this._exitState; }
48
49
  get isCompleted() { return this._exitState !== null; }
49
50
  get activeSubFlow() { return this._activeSubFlow; }
51
+ /** Last error message (set when a processor throws and error transition fires). */
52
+ get lastError() { return this._lastError; }
50
53
  /** State path from root to deepest active sub-flow. */
51
54
  statePath() {
52
55
  const path = [this._currentState];
@@ -97,4 +100,5 @@ export class FlowInstance {
97
100
  /** @internal */ complete(exitState) { this._exitState = exitState; }
98
101
  /** @internal */ setVersion(version) { this._version = version; }
99
102
  /** @internal */ setActiveSubFlow(sub) { this._activeSubFlow = sub; }
103
+ /** @internal */ setLastError(error) { this._lastError = error; }
100
104
  }
@@ -12,7 +12,7 @@ export declare class InMemoryFlowStore {
12
12
  private flows;
13
13
  private _transitionLog;
14
14
  create(flow: FlowInstance<any>): void;
15
- loadForUpdate<S extends string>(flowId: string): FlowInstance<S> | undefined;
15
+ loadForUpdate<S extends string>(flowId: string, _definition?: any): FlowInstance<S> | undefined;
16
16
  save(flow: FlowInstance<any>): void;
17
17
  recordTransition(flowId: string, from: string | null, to: string, trigger: string, _ctx: FlowContext): void;
18
18
  get transitionLog(): readonly TransitionRecord[];
@@ -4,7 +4,7 @@ export class InMemoryFlowStore {
4
4
  create(flow) {
5
5
  this.flows.set(flow.id, flow);
6
6
  }
7
- loadForUpdate(flowId) {
7
+ loadForUpdate(flowId, _definition) {
8
8
  const flow = this.flows.get(flowId);
9
9
  if (!flow || flow.isCompleted)
10
10
  return undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unlaxer/tramli",
3
- "version": "1.5.3",
3
+ "version": "1.6.0",
4
4
  "description": "Constrained flow engine — state machines that prevent invalid transitions at build time",
5
5
  "type": "module",
6
6
  "exports": {