@unlaxer/tramli 1.9.0 → 1.10.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/index.d.ts +2 -0
- package/dist/cjs/index.js +6 -1
- package/dist/cjs/pipeline.d.ts +55 -0
- package/dist/cjs/pipeline.js +198 -0
- package/dist/cjs/tramli.d.ts +2 -0
- package/dist/cjs/tramli.js +4 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/pipeline.d.ts +55 -0
- package/dist/esm/pipeline.js +191 -0
- package/dist/esm/tramli.d.ts +2 -0
- package/dist/esm/tramli.js +4 -0
- package/package.json +1 -1
package/dist/cjs/index.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export { MermaidGenerator } from './mermaid-generator.js';
|
|
|
11
11
|
export { SkeletonGenerator } from './skeleton-generator.js';
|
|
12
12
|
export type { TargetLanguage } from './skeleton-generator.js';
|
|
13
13
|
export { DataFlowGraph } from './data-flow-graph.js';
|
|
14
|
+
export { Pipeline, PipelineBuilder, PipelineDataFlow, PipelineException } from './pipeline.js';
|
|
15
|
+
export type { PipelineStep } from './pipeline.js';
|
|
14
16
|
export type { NodeInfo } from './data-flow-graph.js';
|
|
15
17
|
export { flowKey } from './flow-key.js';
|
|
16
18
|
export type { FlowKey } from './flow-key.js';
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.flowKey = exports.DataFlowGraph = exports.SkeletonGenerator = exports.MermaidGenerator = exports.InMemoryFlowStore = exports.FlowError = exports.SubFlowBuilder = exports.BranchBuilder = exports.FromBuilder = exports.Builder = exports.FlowDefinition = exports.FlowInstance = exports.FlowContext = exports.FlowEngine = exports.Tramli = void 0;
|
|
3
|
+
exports.flowKey = exports.PipelineException = exports.PipelineDataFlow = exports.PipelineBuilder = exports.Pipeline = exports.DataFlowGraph = exports.SkeletonGenerator = exports.MermaidGenerator = exports.InMemoryFlowStore = exports.FlowError = exports.SubFlowBuilder = exports.BranchBuilder = exports.FromBuilder = exports.Builder = exports.FlowDefinition = exports.FlowInstance = exports.FlowContext = exports.FlowEngine = exports.Tramli = void 0;
|
|
4
4
|
var tramli_js_1 = require("./tramli");
|
|
5
5
|
Object.defineProperty(exports, "Tramli", { enumerable: true, get: function () { return tramli_js_1.Tramli; } });
|
|
6
6
|
var flow_engine_js_1 = require("./flow-engine");
|
|
@@ -25,5 +25,10 @@ var skeleton_generator_js_1 = require("./skeleton-generator");
|
|
|
25
25
|
Object.defineProperty(exports, "SkeletonGenerator", { enumerable: true, get: function () { return skeleton_generator_js_1.SkeletonGenerator; } });
|
|
26
26
|
var data_flow_graph_js_1 = require("./data-flow-graph");
|
|
27
27
|
Object.defineProperty(exports, "DataFlowGraph", { enumerable: true, get: function () { return data_flow_graph_js_1.DataFlowGraph; } });
|
|
28
|
+
var pipeline_js_1 = require("./pipeline");
|
|
29
|
+
Object.defineProperty(exports, "Pipeline", { enumerable: true, get: function () { return pipeline_js_1.Pipeline; } });
|
|
30
|
+
Object.defineProperty(exports, "PipelineBuilder", { enumerable: true, get: function () { return pipeline_js_1.PipelineBuilder; } });
|
|
31
|
+
Object.defineProperty(exports, "PipelineDataFlow", { enumerable: true, get: function () { return pipeline_js_1.PipelineDataFlow; } });
|
|
32
|
+
Object.defineProperty(exports, "PipelineException", { enumerable: true, get: function () { return pipeline_js_1.PipelineException; } });
|
|
28
33
|
var flow_key_js_1 = require("./flow-key");
|
|
29
34
|
Object.defineProperty(exports, "flowKey", { enumerable: true, get: function () { return flow_key_js_1.flowKey; } });
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { FlowContext } from './flow-context.js';
|
|
2
|
+
import { FlowError } from './flow-error.js';
|
|
3
|
+
import type { FlowKey } from './flow-key.js';
|
|
4
|
+
import type { TransitionLogEntry, StateLogEntry, ErrorLogEntry } from './flow-engine.js';
|
|
5
|
+
export interface PipelineStep {
|
|
6
|
+
name: string;
|
|
7
|
+
requires: FlowKey<unknown>[];
|
|
8
|
+
produces: FlowKey<unknown>[];
|
|
9
|
+
process(ctx: FlowContext): Promise<void> | void;
|
|
10
|
+
}
|
|
11
|
+
export declare class PipelineException extends FlowError {
|
|
12
|
+
readonly failedStep: string;
|
|
13
|
+
readonly completedSteps: string[];
|
|
14
|
+
readonly context: FlowContext;
|
|
15
|
+
readonly cause: Error;
|
|
16
|
+
constructor(failedStep: string, completedSteps: string[], context: FlowContext, cause: Error);
|
|
17
|
+
}
|
|
18
|
+
export declare class PipelineDataFlow {
|
|
19
|
+
private readonly steps;
|
|
20
|
+
private readonly initiallyAvailable;
|
|
21
|
+
constructor(steps: PipelineStep[], initiallyAvailable: Set<string>);
|
|
22
|
+
deadData(): Set<string>;
|
|
23
|
+
stepOrder(): string[];
|
|
24
|
+
availableAfter(stepName: string): Set<string>;
|
|
25
|
+
toMermaid(): string;
|
|
26
|
+
}
|
|
27
|
+
export declare class Pipeline {
|
|
28
|
+
readonly name: string;
|
|
29
|
+
private readonly steps;
|
|
30
|
+
private readonly _initiallyAvailable;
|
|
31
|
+
private readonly _dataFlow;
|
|
32
|
+
private strictMode;
|
|
33
|
+
private transitionLogger?;
|
|
34
|
+
private stateLogger?;
|
|
35
|
+
private errorLogger?;
|
|
36
|
+
private constructor();
|
|
37
|
+
dataFlow(): PipelineDataFlow;
|
|
38
|
+
setStrictMode(strict: boolean): void;
|
|
39
|
+
setTransitionLogger(l: ((e: TransitionLogEntry) => void) | null): void;
|
|
40
|
+
setStateLogger(l: ((e: StateLogEntry) => void) | null): void;
|
|
41
|
+
setErrorLogger(l: ((e: ErrorLogEntry) => void) | null): void;
|
|
42
|
+
removeAllLoggers(): void;
|
|
43
|
+
execute(initialData: Map<string, unknown>): Promise<FlowContext>;
|
|
44
|
+
asStep(): PipelineStep;
|
|
45
|
+
static builder(name: string): PipelineBuilder;
|
|
46
|
+
}
|
|
47
|
+
export declare class PipelineBuilder {
|
|
48
|
+
private readonly name;
|
|
49
|
+
private readonly steps;
|
|
50
|
+
private readonly _initiallyAvailable;
|
|
51
|
+
constructor(name: string);
|
|
52
|
+
initiallyAvailable(...keys: FlowKey<unknown>[]): this;
|
|
53
|
+
step(s: PipelineStep): this;
|
|
54
|
+
build(): Pipeline;
|
|
55
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PipelineBuilder = exports.Pipeline = exports.PipelineDataFlow = exports.PipelineException = void 0;
|
|
4
|
+
const flow_context_js_1 = require("./flow-context");
|
|
5
|
+
const flow_error_js_1 = require("./flow-error");
|
|
6
|
+
class PipelineException extends flow_error_js_1.FlowError {
|
|
7
|
+
failedStep;
|
|
8
|
+
completedSteps;
|
|
9
|
+
context;
|
|
10
|
+
cause;
|
|
11
|
+
constructor(failedStep, completedSteps, context, cause) {
|
|
12
|
+
super('PIPELINE_STEP_FAILED', `Pipeline step '${failedStep}' failed: ${cause.message}`);
|
|
13
|
+
this.failedStep = failedStep;
|
|
14
|
+
this.completedSteps = completedSteps;
|
|
15
|
+
this.context = context;
|
|
16
|
+
this.cause = cause;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.PipelineException = PipelineException;
|
|
20
|
+
class PipelineDataFlow {
|
|
21
|
+
steps;
|
|
22
|
+
initiallyAvailable;
|
|
23
|
+
constructor(steps, initiallyAvailable) {
|
|
24
|
+
this.steps = steps;
|
|
25
|
+
this.initiallyAvailable = initiallyAvailable;
|
|
26
|
+
}
|
|
27
|
+
deadData() {
|
|
28
|
+
const allProduced = new Set(this.initiallyAvailable);
|
|
29
|
+
const allConsumed = new Set();
|
|
30
|
+
for (const s of this.steps) {
|
|
31
|
+
for (const r of s.requires)
|
|
32
|
+
allConsumed.add(r);
|
|
33
|
+
for (const p of s.produces)
|
|
34
|
+
allProduced.add(p);
|
|
35
|
+
}
|
|
36
|
+
const dead = new Set();
|
|
37
|
+
for (const p of allProduced) {
|
|
38
|
+
if (!allConsumed.has(p))
|
|
39
|
+
dead.add(p);
|
|
40
|
+
}
|
|
41
|
+
return dead;
|
|
42
|
+
}
|
|
43
|
+
stepOrder() { return this.steps.map(s => s.name); }
|
|
44
|
+
availableAfter(stepName) {
|
|
45
|
+
const available = new Set(this.initiallyAvailable);
|
|
46
|
+
for (const s of this.steps) {
|
|
47
|
+
for (const p of s.produces)
|
|
48
|
+
available.add(p);
|
|
49
|
+
if (s.name === stepName)
|
|
50
|
+
return available;
|
|
51
|
+
}
|
|
52
|
+
return available;
|
|
53
|
+
}
|
|
54
|
+
toMermaid() {
|
|
55
|
+
const lines = ['flowchart LR'];
|
|
56
|
+
for (const s of this.steps) {
|
|
57
|
+
for (const r of s.requires)
|
|
58
|
+
lines.push(` ${r} -->|requires| ${s.name}`);
|
|
59
|
+
for (const p of s.produces)
|
|
60
|
+
lines.push(` ${s.name} -->|produces| ${p}`);
|
|
61
|
+
}
|
|
62
|
+
return lines.join('\n') + '\n';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.PipelineDataFlow = PipelineDataFlow;
|
|
66
|
+
class Pipeline {
|
|
67
|
+
name;
|
|
68
|
+
steps;
|
|
69
|
+
_initiallyAvailable;
|
|
70
|
+
_dataFlow;
|
|
71
|
+
strictMode = false;
|
|
72
|
+
transitionLogger;
|
|
73
|
+
stateLogger;
|
|
74
|
+
errorLogger;
|
|
75
|
+
constructor(name, steps, initiallyAvailable) {
|
|
76
|
+
this.name = name;
|
|
77
|
+
this.steps = [...steps];
|
|
78
|
+
this._initiallyAvailable = new Set(initiallyAvailable);
|
|
79
|
+
this._dataFlow = new PipelineDataFlow(steps, initiallyAvailable);
|
|
80
|
+
}
|
|
81
|
+
dataFlow() { return this._dataFlow; }
|
|
82
|
+
setStrictMode(strict) { this.strictMode = strict; }
|
|
83
|
+
setTransitionLogger(l) { this.transitionLogger = l ?? undefined; }
|
|
84
|
+
setStateLogger(l) { this.stateLogger = l ?? undefined; }
|
|
85
|
+
setErrorLogger(l) { this.errorLogger = l ?? undefined; }
|
|
86
|
+
removeAllLoggers() { this.transitionLogger = undefined; this.stateLogger = undefined; this.errorLogger = undefined; }
|
|
87
|
+
async execute(initialData) {
|
|
88
|
+
const flowId = crypto.randomUUID();
|
|
89
|
+
const ctx = new flow_context_js_1.FlowContext(flowId);
|
|
90
|
+
for (const [k, v] of initialData)
|
|
91
|
+
ctx.put(k, v);
|
|
92
|
+
const completed = [];
|
|
93
|
+
let prev = 'initial';
|
|
94
|
+
for (const step of this.steps) {
|
|
95
|
+
this.transitionLogger?.({ flowId, from: prev, to: step.name, trigger: step.name });
|
|
96
|
+
const keysBefore = this.stateLogger ? new Set(ctx.snapshot().keys()) : null;
|
|
97
|
+
try {
|
|
98
|
+
await step.process(ctx);
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
102
|
+
this.errorLogger?.({ flowId, from: prev, to: step.name, trigger: step.name, cause: err });
|
|
103
|
+
throw new PipelineException(step.name, [...completed], ctx, err);
|
|
104
|
+
}
|
|
105
|
+
if (this.strictMode) {
|
|
106
|
+
for (const prod of step.produces) {
|
|
107
|
+
if (!ctx.has(prod)) {
|
|
108
|
+
const err = new flow_error_js_1.FlowError('PRODUCES_VIOLATION', `Step '${step.name}' declares produces ${prod} but did not put it`);
|
|
109
|
+
throw new PipelineException(step.name, [...completed], ctx, err);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (this.stateLogger && keysBefore) {
|
|
114
|
+
for (const [k] of ctx.snapshot()) {
|
|
115
|
+
if (!keysBefore.has(k)) {
|
|
116
|
+
this.stateLogger({ flowId, state: step.name, key: k, value: ctx.snapshot().get(k) });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
completed.push(step.name);
|
|
121
|
+
prev = step.name;
|
|
122
|
+
}
|
|
123
|
+
return ctx;
|
|
124
|
+
}
|
|
125
|
+
asStep() {
|
|
126
|
+
const self = this;
|
|
127
|
+
return {
|
|
128
|
+
name: self.name,
|
|
129
|
+
requires: [...self._initiallyAvailable],
|
|
130
|
+
produces: self.steps.flatMap(s => s.produces),
|
|
131
|
+
async process(ctx) {
|
|
132
|
+
for (const s of self.steps)
|
|
133
|
+
await s.process(ctx);
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
static builder(name) { return new PipelineBuilder(name); }
|
|
138
|
+
}
|
|
139
|
+
exports.Pipeline = Pipeline;
|
|
140
|
+
class PipelineBuilder {
|
|
141
|
+
name;
|
|
142
|
+
steps = [];
|
|
143
|
+
_initiallyAvailable = new Set();
|
|
144
|
+
constructor(name) {
|
|
145
|
+
this.name = name;
|
|
146
|
+
}
|
|
147
|
+
initiallyAvailable(...keys) {
|
|
148
|
+
for (const k of keys)
|
|
149
|
+
this._initiallyAvailable.add(k);
|
|
150
|
+
return this;
|
|
151
|
+
}
|
|
152
|
+
step(s) { this.steps.push(s); return this; }
|
|
153
|
+
build() {
|
|
154
|
+
const errors = [];
|
|
155
|
+
const available = new Set(this._initiallyAvailable);
|
|
156
|
+
for (const s of this.steps) {
|
|
157
|
+
for (const req of s.requires) {
|
|
158
|
+
if (!available.has(req))
|
|
159
|
+
errors.push(`Step '${s.name}' requires ${req} but it may not be available`);
|
|
160
|
+
}
|
|
161
|
+
for (const p of s.produces)
|
|
162
|
+
available.add(p);
|
|
163
|
+
}
|
|
164
|
+
if (errors.length > 0) {
|
|
165
|
+
throw new flow_error_js_1.FlowError('INVALID_PIPELINE', `Pipeline '${this.name}' has ${errors.length} error(s):\n - ${errors.join('\n - ')}`);
|
|
166
|
+
}
|
|
167
|
+
return Pipeline.builder(this.name);
|
|
168
|
+
// Use private constructor via reflection-like pattern
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
exports.PipelineBuilder = PipelineBuilder;
|
|
172
|
+
// Fix: expose Pipeline constructor to builder
|
|
173
|
+
Object.defineProperty(PipelineBuilder.prototype, 'build', {
|
|
174
|
+
value: function () {
|
|
175
|
+
const errors = [];
|
|
176
|
+
const available = new Set(this._initiallyAvailable);
|
|
177
|
+
for (const s of this.steps) {
|
|
178
|
+
for (const req of s.requires) {
|
|
179
|
+
if (!available.has(req))
|
|
180
|
+
errors.push(`Step '${s.name}' requires ${req} but it may not be available`);
|
|
181
|
+
}
|
|
182
|
+
for (const p of s.produces)
|
|
183
|
+
available.add(p);
|
|
184
|
+
}
|
|
185
|
+
if (errors.length > 0) {
|
|
186
|
+
throw new flow_error_js_1.FlowError('INVALID_PIPELINE', `Pipeline '${this.name}' has ${errors.length} error(s):\n - ${errors.join('\n - ')}`);
|
|
187
|
+
}
|
|
188
|
+
const pipeline = Object.create(Pipeline.prototype);
|
|
189
|
+
Object.assign(pipeline, {
|
|
190
|
+
name: this.name,
|
|
191
|
+
steps: [...this.steps],
|
|
192
|
+
_initiallyAvailable: new Set(this._initiallyAvailable),
|
|
193
|
+
_dataFlow: new PipelineDataFlow([...this.steps], new Set(this._initiallyAvailable)),
|
|
194
|
+
strictMode: false,
|
|
195
|
+
});
|
|
196
|
+
return pipeline;
|
|
197
|
+
},
|
|
198
|
+
});
|
package/dist/cjs/tramli.d.ts
CHANGED
|
@@ -2,9 +2,11 @@ import type { StateConfig } from './types.js';
|
|
|
2
2
|
import { Builder } from './flow-definition.js';
|
|
3
3
|
import { FlowEngine } from './flow-engine.js';
|
|
4
4
|
import type { InMemoryFlowStore } from './in-memory-flow-store.js';
|
|
5
|
+
import { PipelineBuilder } from './pipeline.js';
|
|
5
6
|
export declare class Tramli {
|
|
6
7
|
static define<S extends string>(name: string, stateConfig: Record<S, StateConfig>): Builder<S>;
|
|
7
8
|
static engine(store: InMemoryFlowStore, options?: {
|
|
8
9
|
strictMode?: boolean;
|
|
9
10
|
}): FlowEngine;
|
|
11
|
+
static pipeline(name: string): PipelineBuilder;
|
|
10
12
|
}
|
package/dist/cjs/tramli.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Tramli = void 0;
|
|
4
4
|
const flow_definition_js_1 = require("./flow-definition");
|
|
5
5
|
const flow_engine_js_1 = require("./flow-engine");
|
|
6
|
+
const pipeline_js_1 = require("./pipeline");
|
|
6
7
|
class Tramli {
|
|
7
8
|
static define(name, stateConfig) {
|
|
8
9
|
return new flow_definition_js_1.Builder(name, stateConfig);
|
|
@@ -10,5 +11,8 @@ class Tramli {
|
|
|
10
11
|
static engine(store, options) {
|
|
11
12
|
return new flow_engine_js_1.FlowEngine(store, options);
|
|
12
13
|
}
|
|
14
|
+
static pipeline(name) {
|
|
15
|
+
return new pipeline_js_1.PipelineBuilder(name);
|
|
16
|
+
}
|
|
13
17
|
}
|
|
14
18
|
exports.Tramli = Tramli;
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export { MermaidGenerator } from './mermaid-generator.js';
|
|
|
11
11
|
export { SkeletonGenerator } from './skeleton-generator.js';
|
|
12
12
|
export type { TargetLanguage } from './skeleton-generator.js';
|
|
13
13
|
export { DataFlowGraph } from './data-flow-graph.js';
|
|
14
|
+
export { Pipeline, PipelineBuilder, PipelineDataFlow, PipelineException } from './pipeline.js';
|
|
15
|
+
export type { PipelineStep } from './pipeline.js';
|
|
14
16
|
export type { NodeInfo } from './data-flow-graph.js';
|
|
15
17
|
export { flowKey } from './flow-key.js';
|
|
16
18
|
export type { FlowKey } from './flow-key.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -8,4 +8,5 @@ export { InMemoryFlowStore } from './in-memory-flow-store.js';
|
|
|
8
8
|
export { MermaidGenerator } from './mermaid-generator.js';
|
|
9
9
|
export { SkeletonGenerator } from './skeleton-generator.js';
|
|
10
10
|
export { DataFlowGraph } from './data-flow-graph.js';
|
|
11
|
+
export { Pipeline, PipelineBuilder, PipelineDataFlow, PipelineException } from './pipeline.js';
|
|
11
12
|
export { flowKey } from './flow-key.js';
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { FlowContext } from './flow-context.js';
|
|
2
|
+
import { FlowError } from './flow-error.js';
|
|
3
|
+
import type { FlowKey } from './flow-key.js';
|
|
4
|
+
import type { TransitionLogEntry, StateLogEntry, ErrorLogEntry } from './flow-engine.js';
|
|
5
|
+
export interface PipelineStep {
|
|
6
|
+
name: string;
|
|
7
|
+
requires: FlowKey<unknown>[];
|
|
8
|
+
produces: FlowKey<unknown>[];
|
|
9
|
+
process(ctx: FlowContext): Promise<void> | void;
|
|
10
|
+
}
|
|
11
|
+
export declare class PipelineException extends FlowError {
|
|
12
|
+
readonly failedStep: string;
|
|
13
|
+
readonly completedSteps: string[];
|
|
14
|
+
readonly context: FlowContext;
|
|
15
|
+
readonly cause: Error;
|
|
16
|
+
constructor(failedStep: string, completedSteps: string[], context: FlowContext, cause: Error);
|
|
17
|
+
}
|
|
18
|
+
export declare class PipelineDataFlow {
|
|
19
|
+
private readonly steps;
|
|
20
|
+
private readonly initiallyAvailable;
|
|
21
|
+
constructor(steps: PipelineStep[], initiallyAvailable: Set<string>);
|
|
22
|
+
deadData(): Set<string>;
|
|
23
|
+
stepOrder(): string[];
|
|
24
|
+
availableAfter(stepName: string): Set<string>;
|
|
25
|
+
toMermaid(): string;
|
|
26
|
+
}
|
|
27
|
+
export declare class Pipeline {
|
|
28
|
+
readonly name: string;
|
|
29
|
+
private readonly steps;
|
|
30
|
+
private readonly _initiallyAvailable;
|
|
31
|
+
private readonly _dataFlow;
|
|
32
|
+
private strictMode;
|
|
33
|
+
private transitionLogger?;
|
|
34
|
+
private stateLogger?;
|
|
35
|
+
private errorLogger?;
|
|
36
|
+
private constructor();
|
|
37
|
+
dataFlow(): PipelineDataFlow;
|
|
38
|
+
setStrictMode(strict: boolean): void;
|
|
39
|
+
setTransitionLogger(l: ((e: TransitionLogEntry) => void) | null): void;
|
|
40
|
+
setStateLogger(l: ((e: StateLogEntry) => void) | null): void;
|
|
41
|
+
setErrorLogger(l: ((e: ErrorLogEntry) => void) | null): void;
|
|
42
|
+
removeAllLoggers(): void;
|
|
43
|
+
execute(initialData: Map<string, unknown>): Promise<FlowContext>;
|
|
44
|
+
asStep(): PipelineStep;
|
|
45
|
+
static builder(name: string): PipelineBuilder;
|
|
46
|
+
}
|
|
47
|
+
export declare class PipelineBuilder {
|
|
48
|
+
private readonly name;
|
|
49
|
+
private readonly steps;
|
|
50
|
+
private readonly _initiallyAvailable;
|
|
51
|
+
constructor(name: string);
|
|
52
|
+
initiallyAvailable(...keys: FlowKey<unknown>[]): this;
|
|
53
|
+
step(s: PipelineStep): this;
|
|
54
|
+
build(): Pipeline;
|
|
55
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { FlowContext } from './flow-context.js';
|
|
2
|
+
import { FlowError } from './flow-error.js';
|
|
3
|
+
export class PipelineException extends FlowError {
|
|
4
|
+
failedStep;
|
|
5
|
+
completedSteps;
|
|
6
|
+
context;
|
|
7
|
+
cause;
|
|
8
|
+
constructor(failedStep, completedSteps, context, cause) {
|
|
9
|
+
super('PIPELINE_STEP_FAILED', `Pipeline step '${failedStep}' failed: ${cause.message}`);
|
|
10
|
+
this.failedStep = failedStep;
|
|
11
|
+
this.completedSteps = completedSteps;
|
|
12
|
+
this.context = context;
|
|
13
|
+
this.cause = cause;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export class PipelineDataFlow {
|
|
17
|
+
steps;
|
|
18
|
+
initiallyAvailable;
|
|
19
|
+
constructor(steps, initiallyAvailable) {
|
|
20
|
+
this.steps = steps;
|
|
21
|
+
this.initiallyAvailable = initiallyAvailable;
|
|
22
|
+
}
|
|
23
|
+
deadData() {
|
|
24
|
+
const allProduced = new Set(this.initiallyAvailable);
|
|
25
|
+
const allConsumed = new Set();
|
|
26
|
+
for (const s of this.steps) {
|
|
27
|
+
for (const r of s.requires)
|
|
28
|
+
allConsumed.add(r);
|
|
29
|
+
for (const p of s.produces)
|
|
30
|
+
allProduced.add(p);
|
|
31
|
+
}
|
|
32
|
+
const dead = new Set();
|
|
33
|
+
for (const p of allProduced) {
|
|
34
|
+
if (!allConsumed.has(p))
|
|
35
|
+
dead.add(p);
|
|
36
|
+
}
|
|
37
|
+
return dead;
|
|
38
|
+
}
|
|
39
|
+
stepOrder() { return this.steps.map(s => s.name); }
|
|
40
|
+
availableAfter(stepName) {
|
|
41
|
+
const available = new Set(this.initiallyAvailable);
|
|
42
|
+
for (const s of this.steps) {
|
|
43
|
+
for (const p of s.produces)
|
|
44
|
+
available.add(p);
|
|
45
|
+
if (s.name === stepName)
|
|
46
|
+
return available;
|
|
47
|
+
}
|
|
48
|
+
return available;
|
|
49
|
+
}
|
|
50
|
+
toMermaid() {
|
|
51
|
+
const lines = ['flowchart LR'];
|
|
52
|
+
for (const s of this.steps) {
|
|
53
|
+
for (const r of s.requires)
|
|
54
|
+
lines.push(` ${r} -->|requires| ${s.name}`);
|
|
55
|
+
for (const p of s.produces)
|
|
56
|
+
lines.push(` ${s.name} -->|produces| ${p}`);
|
|
57
|
+
}
|
|
58
|
+
return lines.join('\n') + '\n';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export class Pipeline {
|
|
62
|
+
name;
|
|
63
|
+
steps;
|
|
64
|
+
_initiallyAvailable;
|
|
65
|
+
_dataFlow;
|
|
66
|
+
strictMode = false;
|
|
67
|
+
transitionLogger;
|
|
68
|
+
stateLogger;
|
|
69
|
+
errorLogger;
|
|
70
|
+
constructor(name, steps, initiallyAvailable) {
|
|
71
|
+
this.name = name;
|
|
72
|
+
this.steps = [...steps];
|
|
73
|
+
this._initiallyAvailable = new Set(initiallyAvailable);
|
|
74
|
+
this._dataFlow = new PipelineDataFlow(steps, initiallyAvailable);
|
|
75
|
+
}
|
|
76
|
+
dataFlow() { return this._dataFlow; }
|
|
77
|
+
setStrictMode(strict) { this.strictMode = strict; }
|
|
78
|
+
setTransitionLogger(l) { this.transitionLogger = l ?? undefined; }
|
|
79
|
+
setStateLogger(l) { this.stateLogger = l ?? undefined; }
|
|
80
|
+
setErrorLogger(l) { this.errorLogger = l ?? undefined; }
|
|
81
|
+
removeAllLoggers() { this.transitionLogger = undefined; this.stateLogger = undefined; this.errorLogger = undefined; }
|
|
82
|
+
async execute(initialData) {
|
|
83
|
+
const flowId = crypto.randomUUID();
|
|
84
|
+
const ctx = new FlowContext(flowId);
|
|
85
|
+
for (const [k, v] of initialData)
|
|
86
|
+
ctx.put(k, v);
|
|
87
|
+
const completed = [];
|
|
88
|
+
let prev = 'initial';
|
|
89
|
+
for (const step of this.steps) {
|
|
90
|
+
this.transitionLogger?.({ flowId, from: prev, to: step.name, trigger: step.name });
|
|
91
|
+
const keysBefore = this.stateLogger ? new Set(ctx.snapshot().keys()) : null;
|
|
92
|
+
try {
|
|
93
|
+
await step.process(ctx);
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
97
|
+
this.errorLogger?.({ flowId, from: prev, to: step.name, trigger: step.name, cause: err });
|
|
98
|
+
throw new PipelineException(step.name, [...completed], ctx, err);
|
|
99
|
+
}
|
|
100
|
+
if (this.strictMode) {
|
|
101
|
+
for (const prod of step.produces) {
|
|
102
|
+
if (!ctx.has(prod)) {
|
|
103
|
+
const err = new FlowError('PRODUCES_VIOLATION', `Step '${step.name}' declares produces ${prod} but did not put it`);
|
|
104
|
+
throw new PipelineException(step.name, [...completed], ctx, err);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (this.stateLogger && keysBefore) {
|
|
109
|
+
for (const [k] of ctx.snapshot()) {
|
|
110
|
+
if (!keysBefore.has(k)) {
|
|
111
|
+
this.stateLogger({ flowId, state: step.name, key: k, value: ctx.snapshot().get(k) });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
completed.push(step.name);
|
|
116
|
+
prev = step.name;
|
|
117
|
+
}
|
|
118
|
+
return ctx;
|
|
119
|
+
}
|
|
120
|
+
asStep() {
|
|
121
|
+
const self = this;
|
|
122
|
+
return {
|
|
123
|
+
name: self.name,
|
|
124
|
+
requires: [...self._initiallyAvailable],
|
|
125
|
+
produces: self.steps.flatMap(s => s.produces),
|
|
126
|
+
async process(ctx) {
|
|
127
|
+
for (const s of self.steps)
|
|
128
|
+
await s.process(ctx);
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
static builder(name) { return new PipelineBuilder(name); }
|
|
133
|
+
}
|
|
134
|
+
export class PipelineBuilder {
|
|
135
|
+
name;
|
|
136
|
+
steps = [];
|
|
137
|
+
_initiallyAvailable = new Set();
|
|
138
|
+
constructor(name) {
|
|
139
|
+
this.name = name;
|
|
140
|
+
}
|
|
141
|
+
initiallyAvailable(...keys) {
|
|
142
|
+
for (const k of keys)
|
|
143
|
+
this._initiallyAvailable.add(k);
|
|
144
|
+
return this;
|
|
145
|
+
}
|
|
146
|
+
step(s) { this.steps.push(s); return this; }
|
|
147
|
+
build() {
|
|
148
|
+
const errors = [];
|
|
149
|
+
const available = new Set(this._initiallyAvailable);
|
|
150
|
+
for (const s of this.steps) {
|
|
151
|
+
for (const req of s.requires) {
|
|
152
|
+
if (!available.has(req))
|
|
153
|
+
errors.push(`Step '${s.name}' requires ${req} but it may not be available`);
|
|
154
|
+
}
|
|
155
|
+
for (const p of s.produces)
|
|
156
|
+
available.add(p);
|
|
157
|
+
}
|
|
158
|
+
if (errors.length > 0) {
|
|
159
|
+
throw new FlowError('INVALID_PIPELINE', `Pipeline '${this.name}' has ${errors.length} error(s):\n - ${errors.join('\n - ')}`);
|
|
160
|
+
}
|
|
161
|
+
return Pipeline.builder(this.name);
|
|
162
|
+
// Use private constructor via reflection-like pattern
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Fix: expose Pipeline constructor to builder
|
|
166
|
+
Object.defineProperty(PipelineBuilder.prototype, 'build', {
|
|
167
|
+
value: function () {
|
|
168
|
+
const errors = [];
|
|
169
|
+
const available = new Set(this._initiallyAvailable);
|
|
170
|
+
for (const s of this.steps) {
|
|
171
|
+
for (const req of s.requires) {
|
|
172
|
+
if (!available.has(req))
|
|
173
|
+
errors.push(`Step '${s.name}' requires ${req} but it may not be available`);
|
|
174
|
+
}
|
|
175
|
+
for (const p of s.produces)
|
|
176
|
+
available.add(p);
|
|
177
|
+
}
|
|
178
|
+
if (errors.length > 0) {
|
|
179
|
+
throw new FlowError('INVALID_PIPELINE', `Pipeline '${this.name}' has ${errors.length} error(s):\n - ${errors.join('\n - ')}`);
|
|
180
|
+
}
|
|
181
|
+
const pipeline = Object.create(Pipeline.prototype);
|
|
182
|
+
Object.assign(pipeline, {
|
|
183
|
+
name: this.name,
|
|
184
|
+
steps: [...this.steps],
|
|
185
|
+
_initiallyAvailable: new Set(this._initiallyAvailable),
|
|
186
|
+
_dataFlow: new PipelineDataFlow([...this.steps], new Set(this._initiallyAvailable)),
|
|
187
|
+
strictMode: false,
|
|
188
|
+
});
|
|
189
|
+
return pipeline;
|
|
190
|
+
},
|
|
191
|
+
});
|
package/dist/esm/tramli.d.ts
CHANGED
|
@@ -2,9 +2,11 @@ import type { StateConfig } from './types.js';
|
|
|
2
2
|
import { Builder } from './flow-definition.js';
|
|
3
3
|
import { FlowEngine } from './flow-engine.js';
|
|
4
4
|
import type { InMemoryFlowStore } from './in-memory-flow-store.js';
|
|
5
|
+
import { PipelineBuilder } from './pipeline.js';
|
|
5
6
|
export declare class Tramli {
|
|
6
7
|
static define<S extends string>(name: string, stateConfig: Record<S, StateConfig>): Builder<S>;
|
|
7
8
|
static engine(store: InMemoryFlowStore, options?: {
|
|
8
9
|
strictMode?: boolean;
|
|
9
10
|
}): FlowEngine;
|
|
11
|
+
static pipeline(name: string): PipelineBuilder;
|
|
10
12
|
}
|
package/dist/esm/tramli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Builder } from './flow-definition.js';
|
|
2
2
|
import { FlowEngine } from './flow-engine.js';
|
|
3
|
+
import { PipelineBuilder } from './pipeline.js';
|
|
3
4
|
export class Tramli {
|
|
4
5
|
static define(name, stateConfig) {
|
|
5
6
|
return new Builder(name, stateConfig);
|
|
@@ -7,4 +8,7 @@ export class Tramli {
|
|
|
7
8
|
static engine(store, options) {
|
|
8
9
|
return new FlowEngine(store, options);
|
|
9
10
|
}
|
|
11
|
+
static pipeline(name) {
|
|
12
|
+
return new PipelineBuilder(name);
|
|
13
|
+
}
|
|
10
14
|
}
|