@unlaxer/tramli 1.5.2 → 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.
- package/dist/cjs/data-flow-graph.d.ts +92 -0
- package/dist/cjs/data-flow-graph.js +451 -0
- package/dist/cjs/flow-context.d.ts +19 -0
- package/dist/cjs/flow-context.js +44 -0
- package/dist/cjs/flow-definition.d.ts +85 -0
- package/dist/cjs/flow-definition.js +488 -0
- package/dist/cjs/flow-engine.d.ts +13 -0
- package/dist/cjs/flow-engine.js +244 -0
- package/dist/cjs/flow-error.d.ts +14 -0
- package/dist/cjs/flow-error.js +34 -0
- package/dist/cjs/flow-instance.d.ts +48 -0
- package/dist/cjs/flow-instance.js +108 -0
- package/dist/cjs/flow-key.d.ts +10 -0
- package/dist/cjs/flow-key.js +6 -0
- package/dist/cjs/in-memory-flow-store.d.ts +19 -0
- package/dist/cjs/in-memory-flow-store.js +27 -0
- package/dist/cjs/index.d.ts +16 -0
- package/dist/cjs/index.js +29 -0
- package/dist/cjs/mermaid-generator.d.ts +9 -0
- package/dist/cjs/mermaid-generator.js +80 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/skeleton-generator.d.ts +10 -0
- package/dist/cjs/skeleton-generator.js +48 -0
- package/dist/cjs/tramli.d.ts +8 -0
- package/dist/cjs/tramli.js +14 -0
- package/dist/cjs/types.d.ts +75 -0
- package/dist/cjs/types.js +2 -0
- package/dist/esm/data-flow-graph.d.ts +92 -0
- package/dist/esm/data-flow-graph.js +447 -0
- package/dist/esm/flow-context.d.ts +19 -0
- package/dist/esm/flow-context.js +40 -0
- package/dist/esm/flow-definition.d.ts +85 -0
- package/dist/esm/flow-definition.js +480 -0
- package/dist/esm/flow-engine.d.ts +13 -0
- package/dist/esm/flow-engine.js +240 -0
- package/dist/esm/flow-error.d.ts +14 -0
- package/dist/esm/flow-error.js +30 -0
- package/dist/esm/flow-instance.d.ts +48 -0
- package/dist/esm/flow-instance.js +104 -0
- package/dist/esm/flow-key.d.ts +10 -0
- package/dist/esm/flow-key.js +3 -0
- package/dist/esm/in-memory-flow-store.d.ts +19 -0
- package/dist/esm/in-memory-flow-store.js +23 -0
- package/dist/esm/index.d.ts +16 -0
- package/dist/esm/index.js +11 -0
- package/dist/esm/mermaid-generator.d.ts +9 -0
- package/dist/esm/mermaid-generator.js +76 -0
- package/dist/esm/skeleton-generator.d.ts +10 -0
- package/dist/esm/skeleton-generator.js +44 -0
- package/dist/esm/tramli.d.ts +8 -0
- package/dist/esm/tramli.js +10 -0
- package/dist/esm/types.d.ts +75 -0
- package/dist/esm/types.js +1 -0
- package/package.json +8 -4
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FlowDefinition } from './flow-definition.js';
|
|
2
|
+
export type TargetLanguage = 'java' | 'typescript' | 'rust';
|
|
3
|
+
/**
|
|
4
|
+
* Generates Processor skeleton code from a FlowDefinition's requires/produces contracts.
|
|
5
|
+
*/
|
|
6
|
+
export declare class SkeletonGenerator {
|
|
7
|
+
static generate<S extends string>(def: FlowDefinition<S>, lang: TargetLanguage): string;
|
|
8
|
+
private static genProcessor;
|
|
9
|
+
private static genGuard;
|
|
10
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SkeletonGenerator = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Generates Processor skeleton code from a FlowDefinition's requires/produces contracts.
|
|
6
|
+
*/
|
|
7
|
+
class SkeletonGenerator {
|
|
8
|
+
static generate(def, lang) {
|
|
9
|
+
const lines = [
|
|
10
|
+
`// Skeleton generated from flow: ${def.name}`,
|
|
11
|
+
`// Language: ${lang}`,
|
|
12
|
+
'',
|
|
13
|
+
];
|
|
14
|
+
const seen = new Set();
|
|
15
|
+
for (const t of def.transitions) {
|
|
16
|
+
if (t.processor && !seen.has(t.processor.name)) {
|
|
17
|
+
seen.add(t.processor.name);
|
|
18
|
+
lines.push(this.genProcessor(t.processor.name, t.processor.requires, t.processor.produces, lang));
|
|
19
|
+
}
|
|
20
|
+
if (t.guard && !seen.has(t.guard.name)) {
|
|
21
|
+
seen.add(t.guard.name);
|
|
22
|
+
lines.push(this.genGuard(t.guard.name, t.guard.requires, t.guard.produces, lang));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return lines.join('\n');
|
|
26
|
+
}
|
|
27
|
+
static genProcessor(name, reqs, prods, lang) {
|
|
28
|
+
if (lang === 'typescript') {
|
|
29
|
+
return `const ${lcFirst(name)}: StateProcessor<S> = {\n name: '${name}',\n requires: [${reqs.join(', ')}],\n produces: [${prods.join(', ')}],\n process(ctx: FlowContext) {\n${reqs.map(r => ` const ${lcFirst(r)} = ctx.get(${r});`).join('\n')}\n // TODO: implement\n${prods.map(p => ` // ctx.put(${p}, { ... });`).join('\n')}\n },\n};\n`;
|
|
30
|
+
}
|
|
31
|
+
if (lang === 'java') {
|
|
32
|
+
return `static final StateProcessor ${name.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase()} = new StateProcessor() {\n @Override public String name() { return "${name}"; }\n @Override public Set<Class<?>> requires() { return Set.of(${reqs.map(r => r + '.class').join(', ')}); }\n @Override public Set<Class<?>> produces() { return Set.of(${prods.map(p => p + '.class').join(', ')}); }\n @Override public void process(FlowContext ctx) {\n // TODO: implement\n }\n};\n`;
|
|
33
|
+
}
|
|
34
|
+
// rust
|
|
35
|
+
return `struct ${name};\nimpl StateProcessor<S> for ${name} {\n fn name(&self) -> &str { "${name}" }\n fn requires(&self) -> Vec<TypeId> { requires![${reqs.join(', ')}] }\n fn produces(&self) -> Vec<TypeId> { requires![${prods.join(', ')}] }\n fn process(&self, ctx: &mut FlowContext) -> Result<(), FlowError> {\n todo!()\n }\n}\n`;
|
|
36
|
+
}
|
|
37
|
+
static genGuard(name, reqs, prods, lang) {
|
|
38
|
+
if (lang === 'typescript') {
|
|
39
|
+
return `const ${lcFirst(name)}: TransitionGuard<S> = {\n name: '${name}',\n requires: [${reqs.join(', ')}],\n produces: [${prods.join(', ')}],\n maxRetries: 3,\n validate(ctx: FlowContext): GuardOutput {\n // TODO: implement\n return { type: 'accepted' };\n },\n};\n`;
|
|
40
|
+
}
|
|
41
|
+
if (lang === 'java') {
|
|
42
|
+
return `static final TransitionGuard ${name.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase()} = new TransitionGuard() {\n @Override public String name() { return "${name}"; }\n @Override public Set<Class<?>> requires() { return Set.of(${reqs.map(r => r + '.class').join(', ')}); }\n @Override public Set<Class<?>> produces() { return Set.of(${prods.map(p => p + '.class').join(', ')}); }\n @Override public int maxRetries() { return 3; }\n @Override public GuardOutput validate(FlowContext ctx) {\n return new GuardOutput.Accepted();\n }\n};\n`;
|
|
43
|
+
}
|
|
44
|
+
return `struct ${name};\nimpl TransitionGuard<S> for ${name} {\n fn name(&self) -> &str { "${name}" }\n fn requires(&self) -> Vec<TypeId> { requires![${reqs.join(', ')}] }\n fn produces(&self) -> Vec<TypeId> { requires![${prods.join(', ')}] }\n fn validate(&self, ctx: &FlowContext) -> GuardOutput {\n GuardOutput::Accepted { data: HashMap::new() }\n }\n}\n`;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.SkeletonGenerator = SkeletonGenerator;
|
|
48
|
+
function lcFirst(s) { return s.charAt(0).toLowerCase() + s.slice(1); }
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { StateConfig } from './types.js';
|
|
2
|
+
import { Builder } from './flow-definition.js';
|
|
3
|
+
import { FlowEngine } from './flow-engine.js';
|
|
4
|
+
import type { InMemoryFlowStore } from './in-memory-flow-store.js';
|
|
5
|
+
export declare class Tramli {
|
|
6
|
+
static define<S extends string>(name: string, stateConfig: Record<S, StateConfig>): Builder<S>;
|
|
7
|
+
static engine(store: InMemoryFlowStore): FlowEngine;
|
|
8
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Tramli = void 0;
|
|
4
|
+
const flow_definition_js_1 = require("./flow-definition");
|
|
5
|
+
const flow_engine_js_1 = require("./flow-engine");
|
|
6
|
+
class Tramli {
|
|
7
|
+
static define(name, stateConfig) {
|
|
8
|
+
return new flow_definition_js_1.Builder(name, stateConfig);
|
|
9
|
+
}
|
|
10
|
+
static engine(store) {
|
|
11
|
+
return new flow_engine_js_1.FlowEngine(store);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.Tramli = Tramli;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { FlowKey } from './flow-key.js';
|
|
2
|
+
import type { FlowContext } from './flow-context.js';
|
|
3
|
+
/** State configuration: terminal and initial flags for each state. */
|
|
4
|
+
export type StateConfig = {
|
|
5
|
+
terminal: boolean;
|
|
6
|
+
initial: boolean;
|
|
7
|
+
};
|
|
8
|
+
/** Guard output — discriminated union (Java: sealed interface GuardOutput). */
|
|
9
|
+
export type GuardOutput = {
|
|
10
|
+
type: 'accepted';
|
|
11
|
+
data?: Map<string, unknown>;
|
|
12
|
+
} | {
|
|
13
|
+
type: 'rejected';
|
|
14
|
+
reason: string;
|
|
15
|
+
} | {
|
|
16
|
+
type: 'expired';
|
|
17
|
+
};
|
|
18
|
+
/** Transition types. */
|
|
19
|
+
export type TransitionType = 'auto' | 'external' | 'branch' | 'sub_flow';
|
|
20
|
+
/** A single transition in the flow definition. */
|
|
21
|
+
export interface Transition<S extends string> {
|
|
22
|
+
from: S;
|
|
23
|
+
to: S;
|
|
24
|
+
type: TransitionType;
|
|
25
|
+
processor?: StateProcessor<S>;
|
|
26
|
+
guard?: TransitionGuard<S>;
|
|
27
|
+
branch?: BranchProcessor<S>;
|
|
28
|
+
branchTargets: Map<string, S>;
|
|
29
|
+
subFlowDefinition?: import('./flow-definition.js').FlowDefinition<any>;
|
|
30
|
+
exitMappings?: Map<string, S>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Processes a state transition.
|
|
34
|
+
*
|
|
35
|
+
* Processors SHOULD be fast and avoid external I/O.
|
|
36
|
+
* If a processor throws, the engine restores context and routes to error transition.
|
|
37
|
+
*/
|
|
38
|
+
export interface StateProcessor<S extends string> {
|
|
39
|
+
name: string;
|
|
40
|
+
requires: FlowKey<unknown>[];
|
|
41
|
+
produces: FlowKey<unknown>[];
|
|
42
|
+
process(ctx: FlowContext): Promise<void> | void;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Guards an external transition. Pure function — must not mutate FlowContext.
|
|
46
|
+
*
|
|
47
|
+
* TTL vs GuardOutput.expired: FlowInstance TTL is checked at resumeAndExecute
|
|
48
|
+
* entry (flow-level). GuardOutput 'expired' is guard-level for business logic.
|
|
49
|
+
*/
|
|
50
|
+
export interface TransitionGuard<S extends string> {
|
|
51
|
+
name: string;
|
|
52
|
+
requires: FlowKey<unknown>[];
|
|
53
|
+
produces: FlowKey<unknown>[];
|
|
54
|
+
maxRetries: number;
|
|
55
|
+
validate(ctx: FlowContext): Promise<GuardOutput> | GuardOutput;
|
|
56
|
+
}
|
|
57
|
+
/** Decides which branch to take based on FlowContext state. */
|
|
58
|
+
export interface BranchProcessor<S extends string> {
|
|
59
|
+
name: string;
|
|
60
|
+
requires: FlowKey<unknown>[];
|
|
61
|
+
decide(ctx: FlowContext): Promise<string> | string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Persistence contract for flow instances.
|
|
65
|
+
*
|
|
66
|
+
* Threading: FlowEngine assumes single-threaded access per flow instance.
|
|
67
|
+
* Atomicity: create/save and recordTransition form a logical unit.
|
|
68
|
+
* If partial writes occur, save is authoritative over the transition log.
|
|
69
|
+
*/
|
|
70
|
+
export interface FlowStore {
|
|
71
|
+
create(flow: unknown): void | Promise<void>;
|
|
72
|
+
loadForUpdate<S extends string>(flowId: string): unknown | undefined | Promise<unknown | undefined>;
|
|
73
|
+
save(flow: unknown): void | Promise<void>;
|
|
74
|
+
recordTransition(flowId: string, from: string | null, to: string, trigger: string, ctx: FlowContext): void | Promise<void>;
|
|
75
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { FlowDefinition } from './flow-definition.js';
|
|
2
|
+
import type { FlowKey } from './flow-key.js';
|
|
3
|
+
import type { StateProcessor } from './types.js';
|
|
4
|
+
import type { FlowContext } from './flow-context.js';
|
|
5
|
+
export interface NodeInfo<S extends string> {
|
|
6
|
+
name: string;
|
|
7
|
+
fromState: S;
|
|
8
|
+
toState: S;
|
|
9
|
+
kind: 'processor' | 'guard' | 'branch' | 'initial';
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Bipartite graph of data types (FlowKey) and processors/guards.
|
|
13
|
+
* Built automatically during FlowDefinition.build().
|
|
14
|
+
*/
|
|
15
|
+
export declare class DataFlowGraph<S extends string> {
|
|
16
|
+
private readonly _availableAtState;
|
|
17
|
+
private readonly _producers;
|
|
18
|
+
private readonly _consumers;
|
|
19
|
+
private readonly _allProduced;
|
|
20
|
+
private readonly _allConsumed;
|
|
21
|
+
private constructor();
|
|
22
|
+
/** Data types available in context when the flow reaches the given state. */
|
|
23
|
+
availableAt(state: S): Set<string>;
|
|
24
|
+
/** Processors/guards that produce the given type. */
|
|
25
|
+
producersOf(key: FlowKey<unknown>): NodeInfo<S>[];
|
|
26
|
+
/** Processors/guards that consume (require) the given type. */
|
|
27
|
+
consumersOf(key: FlowKey<unknown>): NodeInfo<S>[];
|
|
28
|
+
/** Types produced but never required by any downstream processor/guard. */
|
|
29
|
+
deadData(): Set<string>;
|
|
30
|
+
/** Data lifetime: which states a type is first produced and last consumed. */
|
|
31
|
+
lifetime(key: FlowKey<unknown>): {
|
|
32
|
+
firstProduced: S;
|
|
33
|
+
lastConsumed: S;
|
|
34
|
+
} | null;
|
|
35
|
+
/** Context pruning hints: for each state, types available but not required at that state. */
|
|
36
|
+
pruningHints(): Map<S, Set<string>>;
|
|
37
|
+
/**
|
|
38
|
+
* Check if processor B can replace processor A without breaking data-flow.
|
|
39
|
+
* B is compatible with A if: B requires no more than A, and B produces at least what A produces.
|
|
40
|
+
*/
|
|
41
|
+
static isCompatible<S extends string>(a: {
|
|
42
|
+
requires: FlowKey<unknown>[];
|
|
43
|
+
produces: FlowKey<unknown>[];
|
|
44
|
+
}, b: {
|
|
45
|
+
requires: FlowKey<unknown>[];
|
|
46
|
+
produces: FlowKey<unknown>[];
|
|
47
|
+
}): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Verify a processor's declared requires/produces against actual context usage.
|
|
50
|
+
* Returns list of violations (empty = OK).
|
|
51
|
+
*/
|
|
52
|
+
static verifyProcessor<S extends string>(processor: StateProcessor<S>, ctx: FlowContext): Promise<string[]>;
|
|
53
|
+
/** All type nodes in the graph. */
|
|
54
|
+
allTypes(): Set<string>;
|
|
55
|
+
/**
|
|
56
|
+
* Assert that a flow instance's context satisfies the data-flow invariant.
|
|
57
|
+
* Returns list of missing type keys (empty = OK).
|
|
58
|
+
*/
|
|
59
|
+
assertDataFlow(ctx: FlowContext, currentState: S): string[];
|
|
60
|
+
/** Impact analysis: all producers and consumers of a given type. */
|
|
61
|
+
impactOf(key: FlowKey<unknown>): {
|
|
62
|
+
producers: NodeInfo<S>[];
|
|
63
|
+
consumers: NodeInfo<S>[];
|
|
64
|
+
};
|
|
65
|
+
/** Parallelism hints: pairs of processors with no data dependency. */
|
|
66
|
+
parallelismHints(): [string, string][];
|
|
67
|
+
/** Structured JSON representation. */
|
|
68
|
+
toJson(): string;
|
|
69
|
+
/** Generate Mermaid data-flow diagram. */
|
|
70
|
+
toMermaid(): string;
|
|
71
|
+
/** Recommended migration order: processors sorted by dependency (fewest first). */
|
|
72
|
+
migrationOrder(): string[];
|
|
73
|
+
/** Generate Markdown migration checklist. */
|
|
74
|
+
toMarkdown(): string;
|
|
75
|
+
/** Test scaffold: for each processor, list required type names. */
|
|
76
|
+
testScaffold(): Map<string, string[]>;
|
|
77
|
+
/** Generate data-flow invariant assertions as strings. */
|
|
78
|
+
generateInvariantAssertions(): string[];
|
|
79
|
+
/** Cross-flow map: types that one flow produces and another requires. */
|
|
80
|
+
static crossFlowMap(...graphs: DataFlowGraph<any>[]): string[];
|
|
81
|
+
/** Diff two data-flow graphs. */
|
|
82
|
+
static diff(before: DataFlowGraph<any>, after: DataFlowGraph<any>): {
|
|
83
|
+
addedTypes: Set<string>;
|
|
84
|
+
removedTypes: Set<string>;
|
|
85
|
+
addedEdges: Set<string>;
|
|
86
|
+
removedEdges: Set<string>;
|
|
87
|
+
};
|
|
88
|
+
private static collectEdges;
|
|
89
|
+
/** Version compatibility: check if v1 instances can resume on v2 definition. */
|
|
90
|
+
static versionCompatibility<S extends string>(before: DataFlowGraph<S>, after: DataFlowGraph<S>): string[];
|
|
91
|
+
static build<S extends string>(def: FlowDefinition<S>, initiallyAvailable: string[]): DataFlowGraph<S>;
|
|
92
|
+
}
|