@unlaxer/tramli 1.5.3 → 1.7.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 +5 -1
- package/dist/cjs/flow-engine.js +23 -8
- package/dist/cjs/flow-instance.d.ts +4 -0
- package/dist/cjs/flow-instance.js +4 -0
- package/dist/cjs/in-memory-flow-store.d.ts +1 -1
- package/dist/cjs/in-memory-flow-store.js +1 -1
- package/dist/cjs/tramli.d.ts +3 -1
- package/dist/cjs/tramli.js +2 -2
- package/dist/esm/flow-engine.d.ts +5 -1
- package/dist/esm/flow-engine.js +23 -8
- package/dist/esm/flow-instance.d.ts +4 -0
- package/dist/esm/flow-instance.js +4 -0
- package/dist/esm/in-memory-flow-store.d.ts +1 -1
- package/dist/esm/in-memory-flow-store.js +1 -1
- package/dist/esm/tramli.d.ts +3 -1
- package/dist/esm/tramli.js +2 -2
- package/package.json +1 -1
|
@@ -3,11 +3,15 @@ import { FlowInstance } from './flow-instance.js';
|
|
|
3
3
|
import type { InMemoryFlowStore } from './in-memory-flow-store.js';
|
|
4
4
|
export declare class FlowEngine {
|
|
5
5
|
private readonly store;
|
|
6
|
-
|
|
6
|
+
private readonly strictMode;
|
|
7
|
+
constructor(store: InMemoryFlowStore, options?: {
|
|
8
|
+
strictMode?: boolean;
|
|
9
|
+
});
|
|
7
10
|
startFlow<S extends string>(definition: FlowDefinition<S>, sessionId: string, initialData: Map<string, unknown>): Promise<FlowInstance<S>>;
|
|
8
11
|
resumeAndExecute<S extends string>(flowId: string, definition: FlowDefinition<S>, externalData?: Map<string, unknown>): Promise<FlowInstance<S>>;
|
|
9
12
|
private executeAutoChain;
|
|
10
13
|
private executeSubFlow;
|
|
11
14
|
private resumeSubFlow;
|
|
15
|
+
private verifyProduces;
|
|
12
16
|
private handleError;
|
|
13
17
|
}
|
package/dist/cjs/flow-engine.js
CHANGED
|
@@ -7,8 +7,10 @@ const flow_error_js_1 = require("./flow-error");
|
|
|
7
7
|
const MAX_CHAIN_DEPTH = 10;
|
|
8
8
|
class FlowEngine {
|
|
9
9
|
store;
|
|
10
|
-
|
|
10
|
+
strictMode;
|
|
11
|
+
constructor(store, options) {
|
|
11
12
|
this.store = store;
|
|
13
|
+
this.strictMode = options?.strictMode ?? false;
|
|
12
14
|
}
|
|
13
15
|
async startFlow(definition, sessionId, initialData) {
|
|
14
16
|
const flowId = crypto.randomUUID();
|
|
@@ -26,7 +28,7 @@ class FlowEngine {
|
|
|
26
28
|
return flow;
|
|
27
29
|
}
|
|
28
30
|
async resumeAndExecute(flowId, definition, externalData) {
|
|
29
|
-
const flow = this.store.loadForUpdate(flowId);
|
|
31
|
+
const flow = this.store.loadForUpdate(flowId, definition);
|
|
30
32
|
if (!flow)
|
|
31
33
|
throw new flow_error_js_1.FlowError('FLOW_NOT_FOUND', `Flow ${flowId} not found or already completed`);
|
|
32
34
|
if (externalData) {
|
|
@@ -63,9 +65,9 @@ class FlowEngine {
|
|
|
63
65
|
flow.transitionTo(transition.to);
|
|
64
66
|
this.store.recordTransition(flow.id, from, transition.to, guard.name, flow.context);
|
|
65
67
|
}
|
|
66
|
-
catch {
|
|
68
|
+
catch (e) {
|
|
67
69
|
flow.context.restoreFrom(backup);
|
|
68
|
-
this.handleError(flow, currentState);
|
|
70
|
+
this.handleError(flow, currentState, e instanceof Error ? e : new Error(String(e)));
|
|
69
71
|
this.store.save(flow);
|
|
70
72
|
return flow;
|
|
71
73
|
}
|
|
@@ -119,8 +121,10 @@ class FlowEngine {
|
|
|
119
121
|
const backup = flow.context.snapshot();
|
|
120
122
|
try {
|
|
121
123
|
if (autoOrBranch.type === 'auto') {
|
|
122
|
-
if (autoOrBranch.processor)
|
|
124
|
+
if (autoOrBranch.processor) {
|
|
123
125
|
await autoOrBranch.processor.process(flow.context);
|
|
126
|
+
this.verifyProduces(autoOrBranch.processor, flow.context);
|
|
127
|
+
}
|
|
124
128
|
const from = flow.currentState;
|
|
125
129
|
flow.transitionTo(autoOrBranch.to);
|
|
126
130
|
this.store.recordTransition(flow.id, from, autoOrBranch.to, autoOrBranch.processor?.name ?? 'auto', flow.context);
|
|
@@ -140,9 +144,9 @@ class FlowEngine {
|
|
|
140
144
|
this.store.recordTransition(flow.id, from, target, `${branch.name}:${label}`, flow.context);
|
|
141
145
|
}
|
|
142
146
|
}
|
|
143
|
-
catch {
|
|
147
|
+
catch (e) {
|
|
144
148
|
flow.context.restoreFrom(backup);
|
|
145
|
-
this.handleError(flow, flow.currentState);
|
|
149
|
+
this.handleError(flow, flow.currentState, e instanceof Error ? e : new Error(String(e)));
|
|
146
150
|
return;
|
|
147
151
|
}
|
|
148
152
|
depth++;
|
|
@@ -225,7 +229,18 @@ class FlowEngine {
|
|
|
225
229
|
this.store.save(parentFlow);
|
|
226
230
|
return parentFlow;
|
|
227
231
|
}
|
|
228
|
-
|
|
232
|
+
verifyProduces(processor, ctx) {
|
|
233
|
+
if (!this.strictMode)
|
|
234
|
+
return;
|
|
235
|
+
for (const prod of processor.produces) {
|
|
236
|
+
if (!ctx.has(prod)) {
|
|
237
|
+
throw new flow_error_js_1.FlowError('PRODUCES_VIOLATION', `Processor '${processor.name}' declares produces ${prod} but did not put it in context (strictMode)`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
handleError(flow, fromState, cause) {
|
|
242
|
+
if (cause)
|
|
243
|
+
flow.setLastError(`${cause.constructor.name}: ${cause.message}`);
|
|
229
244
|
const errorTarget = flow.definition.errorTransitions.get(fromState);
|
|
230
245
|
if (errorTarget) {
|
|
231
246
|
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[];
|
package/dist/cjs/tramli.d.ts
CHANGED
|
@@ -4,5 +4,7 @@ import { FlowEngine } from './flow-engine.js';
|
|
|
4
4
|
import type { InMemoryFlowStore } from './in-memory-flow-store.js';
|
|
5
5
|
export declare class Tramli {
|
|
6
6
|
static define<S extends string>(name: string, stateConfig: Record<S, StateConfig>): Builder<S>;
|
|
7
|
-
static engine(store: InMemoryFlowStore
|
|
7
|
+
static engine(store: InMemoryFlowStore, options?: {
|
|
8
|
+
strictMode?: boolean;
|
|
9
|
+
}): FlowEngine;
|
|
8
10
|
}
|
package/dist/cjs/tramli.js
CHANGED
|
@@ -7,8 +7,8 @@ class Tramli {
|
|
|
7
7
|
static define(name, stateConfig) {
|
|
8
8
|
return new flow_definition_js_1.Builder(name, stateConfig);
|
|
9
9
|
}
|
|
10
|
-
static engine(store) {
|
|
11
|
-
return new flow_engine_js_1.FlowEngine(store);
|
|
10
|
+
static engine(store, options) {
|
|
11
|
+
return new flow_engine_js_1.FlowEngine(store, options);
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
exports.Tramli = Tramli;
|
|
@@ -3,11 +3,15 @@ import { FlowInstance } from './flow-instance.js';
|
|
|
3
3
|
import type { InMemoryFlowStore } from './in-memory-flow-store.js';
|
|
4
4
|
export declare class FlowEngine {
|
|
5
5
|
private readonly store;
|
|
6
|
-
|
|
6
|
+
private readonly strictMode;
|
|
7
|
+
constructor(store: InMemoryFlowStore, options?: {
|
|
8
|
+
strictMode?: boolean;
|
|
9
|
+
});
|
|
7
10
|
startFlow<S extends string>(definition: FlowDefinition<S>, sessionId: string, initialData: Map<string, unknown>): Promise<FlowInstance<S>>;
|
|
8
11
|
resumeAndExecute<S extends string>(flowId: string, definition: FlowDefinition<S>, externalData?: Map<string, unknown>): Promise<FlowInstance<S>>;
|
|
9
12
|
private executeAutoChain;
|
|
10
13
|
private executeSubFlow;
|
|
11
14
|
private resumeSubFlow;
|
|
15
|
+
private verifyProduces;
|
|
12
16
|
private handleError;
|
|
13
17
|
}
|
package/dist/esm/flow-engine.js
CHANGED
|
@@ -4,8 +4,10 @@ import { FlowError } from './flow-error.js';
|
|
|
4
4
|
const MAX_CHAIN_DEPTH = 10;
|
|
5
5
|
export class FlowEngine {
|
|
6
6
|
store;
|
|
7
|
-
|
|
7
|
+
strictMode;
|
|
8
|
+
constructor(store, options) {
|
|
8
9
|
this.store = store;
|
|
10
|
+
this.strictMode = options?.strictMode ?? false;
|
|
9
11
|
}
|
|
10
12
|
async startFlow(definition, sessionId, initialData) {
|
|
11
13
|
const flowId = crypto.randomUUID();
|
|
@@ -23,7 +25,7 @@ export class FlowEngine {
|
|
|
23
25
|
return flow;
|
|
24
26
|
}
|
|
25
27
|
async resumeAndExecute(flowId, definition, externalData) {
|
|
26
|
-
const flow = this.store.loadForUpdate(flowId);
|
|
28
|
+
const flow = this.store.loadForUpdate(flowId, definition);
|
|
27
29
|
if (!flow)
|
|
28
30
|
throw new FlowError('FLOW_NOT_FOUND', `Flow ${flowId} not found or already completed`);
|
|
29
31
|
if (externalData) {
|
|
@@ -60,9 +62,9 @@ export class FlowEngine {
|
|
|
60
62
|
flow.transitionTo(transition.to);
|
|
61
63
|
this.store.recordTransition(flow.id, from, transition.to, guard.name, flow.context);
|
|
62
64
|
}
|
|
63
|
-
catch {
|
|
65
|
+
catch (e) {
|
|
64
66
|
flow.context.restoreFrom(backup);
|
|
65
|
-
this.handleError(flow, currentState);
|
|
67
|
+
this.handleError(flow, currentState, e instanceof Error ? e : new Error(String(e)));
|
|
66
68
|
this.store.save(flow);
|
|
67
69
|
return flow;
|
|
68
70
|
}
|
|
@@ -116,8 +118,10 @@ export class FlowEngine {
|
|
|
116
118
|
const backup = flow.context.snapshot();
|
|
117
119
|
try {
|
|
118
120
|
if (autoOrBranch.type === 'auto') {
|
|
119
|
-
if (autoOrBranch.processor)
|
|
121
|
+
if (autoOrBranch.processor) {
|
|
120
122
|
await autoOrBranch.processor.process(flow.context);
|
|
123
|
+
this.verifyProduces(autoOrBranch.processor, flow.context);
|
|
124
|
+
}
|
|
121
125
|
const from = flow.currentState;
|
|
122
126
|
flow.transitionTo(autoOrBranch.to);
|
|
123
127
|
this.store.recordTransition(flow.id, from, autoOrBranch.to, autoOrBranch.processor?.name ?? 'auto', flow.context);
|
|
@@ -137,9 +141,9 @@ export class FlowEngine {
|
|
|
137
141
|
this.store.recordTransition(flow.id, from, target, `${branch.name}:${label}`, flow.context);
|
|
138
142
|
}
|
|
139
143
|
}
|
|
140
|
-
catch {
|
|
144
|
+
catch (e) {
|
|
141
145
|
flow.context.restoreFrom(backup);
|
|
142
|
-
this.handleError(flow, flow.currentState);
|
|
146
|
+
this.handleError(flow, flow.currentState, e instanceof Error ? e : new Error(String(e)));
|
|
143
147
|
return;
|
|
144
148
|
}
|
|
145
149
|
depth++;
|
|
@@ -222,7 +226,18 @@ export class FlowEngine {
|
|
|
222
226
|
this.store.save(parentFlow);
|
|
223
227
|
return parentFlow;
|
|
224
228
|
}
|
|
225
|
-
|
|
229
|
+
verifyProduces(processor, ctx) {
|
|
230
|
+
if (!this.strictMode)
|
|
231
|
+
return;
|
|
232
|
+
for (const prod of processor.produces) {
|
|
233
|
+
if (!ctx.has(prod)) {
|
|
234
|
+
throw new FlowError('PRODUCES_VIOLATION', `Processor '${processor.name}' declares produces ${prod} but did not put it in context (strictMode)`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
handleError(flow, fromState, cause) {
|
|
239
|
+
if (cause)
|
|
240
|
+
flow.setLastError(`${cause.constructor.name}: ${cause.message}`);
|
|
226
241
|
const errorTarget = flow.definition.errorTransitions.get(fromState);
|
|
227
242
|
if (errorTarget) {
|
|
228
243
|
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[];
|
package/dist/esm/tramli.d.ts
CHANGED
|
@@ -4,5 +4,7 @@ import { FlowEngine } from './flow-engine.js';
|
|
|
4
4
|
import type { InMemoryFlowStore } from './in-memory-flow-store.js';
|
|
5
5
|
export declare class Tramli {
|
|
6
6
|
static define<S extends string>(name: string, stateConfig: Record<S, StateConfig>): Builder<S>;
|
|
7
|
-
static engine(store: InMemoryFlowStore
|
|
7
|
+
static engine(store: InMemoryFlowStore, options?: {
|
|
8
|
+
strictMode?: boolean;
|
|
9
|
+
}): FlowEngine;
|
|
8
10
|
}
|
package/dist/esm/tramli.js
CHANGED