@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,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates Processor skeleton code from a FlowDefinition's requires/produces contracts.
|
|
3
|
+
*/
|
|
4
|
+
export class SkeletonGenerator {
|
|
5
|
+
static generate(def, lang) {
|
|
6
|
+
const lines = [
|
|
7
|
+
`// Skeleton generated from flow: ${def.name}`,
|
|
8
|
+
`// Language: ${lang}`,
|
|
9
|
+
'',
|
|
10
|
+
];
|
|
11
|
+
const seen = new Set();
|
|
12
|
+
for (const t of def.transitions) {
|
|
13
|
+
if (t.processor && !seen.has(t.processor.name)) {
|
|
14
|
+
seen.add(t.processor.name);
|
|
15
|
+
lines.push(this.genProcessor(t.processor.name, t.processor.requires, t.processor.produces, lang));
|
|
16
|
+
}
|
|
17
|
+
if (t.guard && !seen.has(t.guard.name)) {
|
|
18
|
+
seen.add(t.guard.name);
|
|
19
|
+
lines.push(this.genGuard(t.guard.name, t.guard.requires, t.guard.produces, lang));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return lines.join('\n');
|
|
23
|
+
}
|
|
24
|
+
static genProcessor(name, reqs, prods, lang) {
|
|
25
|
+
if (lang === 'typescript') {
|
|
26
|
+
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`;
|
|
27
|
+
}
|
|
28
|
+
if (lang === 'java') {
|
|
29
|
+
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`;
|
|
30
|
+
}
|
|
31
|
+
// rust
|
|
32
|
+
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`;
|
|
33
|
+
}
|
|
34
|
+
static genGuard(name, reqs, prods, lang) {
|
|
35
|
+
if (lang === 'typescript') {
|
|
36
|
+
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`;
|
|
37
|
+
}
|
|
38
|
+
if (lang === 'java') {
|
|
39
|
+
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`;
|
|
40
|
+
}
|
|
41
|
+
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`;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
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,10 @@
|
|
|
1
|
+
import { Builder } from './flow-definition.js';
|
|
2
|
+
import { FlowEngine } from './flow-engine.js';
|
|
3
|
+
export class Tramli {
|
|
4
|
+
static define(name, stateConfig) {
|
|
5
|
+
return new Builder(name, stateConfig);
|
|
6
|
+
}
|
|
7
|
+
static engine(store) {
|
|
8
|
+
return new FlowEngine(store);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -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 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unlaxer/tramli",
|
|
3
|
-
"version": "1.
|
|
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": {
|
|
7
7
|
".": {
|
|
8
|
-
"import": "./dist/index.js",
|
|
9
|
-
"
|
|
8
|
+
"import": "./dist/esm/index.js",
|
|
9
|
+
"require": "./dist/cjs/index.js",
|
|
10
|
+
"types": "./dist/esm/index.d.ts"
|
|
10
11
|
}
|
|
11
12
|
},
|
|
13
|
+
"main": "./dist/cjs/index.js",
|
|
14
|
+
"module": "./dist/esm/index.js",
|
|
15
|
+
"types": "./dist/esm/index.d.ts",
|
|
12
16
|
"files": ["dist"],
|
|
13
17
|
"scripts": {
|
|
14
|
-
"build": "tsc",
|
|
18
|
+
"build": "tsc && tsc -p tsconfig.cjs.json && node scripts/fix-cjs.cjs",
|
|
15
19
|
"test": "vitest run",
|
|
16
20
|
"prepublishOnly": "npm run build && npm test"
|
|
17
21
|
},
|