@unlaxer/tramli 1.3.0 → 1.5.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/data-flow-graph.d.ts +29 -0
- package/dist/data-flow-graph.js +200 -0
- package/dist/flow-definition.d.ts +4 -0
- package/dist/flow-definition.js +36 -0
- package/dist/flow-instance.d.ts +4 -0
- package/dist/flow-instance.js +21 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/mermaid-generator.d.ts +2 -0
- package/dist/mermaid-generator.js +17 -0
- package/dist/skeleton-generator.d.ts +10 -0
- package/dist/skeleton-generator.js +44 -0
- package/package.json +1 -1
|
@@ -57,7 +57,36 @@ export declare class DataFlowGraph<S extends string> {
|
|
|
57
57
|
* Returns list of missing type keys (empty = OK).
|
|
58
58
|
*/
|
|
59
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;
|
|
60
69
|
/** Generate Mermaid data-flow diagram. */
|
|
61
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[];
|
|
62
91
|
static build<S extends string>(def: FlowDefinition<S>, initiallyAvailable: string[]): DataFlowGraph<S>;
|
|
63
92
|
}
|
package/dist/data-flow-graph.js
CHANGED
|
@@ -142,6 +142,63 @@ export class DataFlowGraph {
|
|
|
142
142
|
}
|
|
143
143
|
return missing;
|
|
144
144
|
}
|
|
145
|
+
/** Impact analysis: all producers and consumers of a given type. */
|
|
146
|
+
impactOf(key) {
|
|
147
|
+
return { producers: this.producersOf(key), consumers: this.consumersOf(key) };
|
|
148
|
+
}
|
|
149
|
+
/** Parallelism hints: pairs of processors with no data dependency. */
|
|
150
|
+
parallelismHints() {
|
|
151
|
+
const allNodes = new Set();
|
|
152
|
+
for (const nodes of this._producers.values())
|
|
153
|
+
for (const n of nodes)
|
|
154
|
+
allNodes.add(n.name);
|
|
155
|
+
for (const nodes of this._consumers.values())
|
|
156
|
+
for (const n of nodes)
|
|
157
|
+
allNodes.add(n.name);
|
|
158
|
+
const list = [...allNodes];
|
|
159
|
+
const hints = [];
|
|
160
|
+
for (let i = 0; i < list.length; i++) {
|
|
161
|
+
for (let j = i + 1; j < list.length; j++) {
|
|
162
|
+
const aProds = new Set(), bReqs = new Set();
|
|
163
|
+
const bProds = new Set(), aReqs = new Set();
|
|
164
|
+
for (const [t, ns] of this._producers) {
|
|
165
|
+
for (const n of ns) {
|
|
166
|
+
if (n.name === list[i])
|
|
167
|
+
aProds.add(t);
|
|
168
|
+
if (n.name === list[j])
|
|
169
|
+
bProds.add(t);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
for (const [t, ns] of this._consumers) {
|
|
173
|
+
for (const n of ns) {
|
|
174
|
+
if (n.name === list[i])
|
|
175
|
+
aReqs.add(t);
|
|
176
|
+
if (n.name === list[j])
|
|
177
|
+
bReqs.add(t);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const aDepB = [...aReqs].some(r => bProds.has(r));
|
|
181
|
+
const bDepA = [...bReqs].some(r => aProds.has(r));
|
|
182
|
+
if (!aDepB && !bDepA)
|
|
183
|
+
hints.push([list[i], list[j]]);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return hints;
|
|
187
|
+
}
|
|
188
|
+
/** Structured JSON representation. */
|
|
189
|
+
toJson() {
|
|
190
|
+
const types = [...this.allTypes()].map(t => {
|
|
191
|
+
const entry = { name: t };
|
|
192
|
+
const prods = this.producersOf(t);
|
|
193
|
+
if (prods.length)
|
|
194
|
+
entry.producers = prods.map(p => p.name);
|
|
195
|
+
const cons = this.consumersOf(t);
|
|
196
|
+
if (cons.length)
|
|
197
|
+
entry.consumers = cons.map(c => c.name);
|
|
198
|
+
return entry;
|
|
199
|
+
});
|
|
200
|
+
return JSON.stringify({ types, deadData: [...this.deadData()] }, null, 2);
|
|
201
|
+
}
|
|
145
202
|
/** Generate Mermaid data-flow diagram. */
|
|
146
203
|
toMermaid() {
|
|
147
204
|
const lines = ['flowchart LR'];
|
|
@@ -166,6 +223,149 @@ export class DataFlowGraph {
|
|
|
166
223
|
}
|
|
167
224
|
return lines.join('\n') + '\n';
|
|
168
225
|
}
|
|
226
|
+
/** Recommended migration order: processors sorted by dependency (fewest first). */
|
|
227
|
+
migrationOrder() {
|
|
228
|
+
const nodeReqs = new Map();
|
|
229
|
+
const nodeProds = new Map();
|
|
230
|
+
for (const [t, ns] of this._consumers)
|
|
231
|
+
for (const n of ns) {
|
|
232
|
+
if (!nodeReqs.has(n.name))
|
|
233
|
+
nodeReqs.set(n.name, new Set());
|
|
234
|
+
nodeReqs.get(n.name).add(t);
|
|
235
|
+
}
|
|
236
|
+
for (const [t, ns] of this._producers)
|
|
237
|
+
for (const n of ns) {
|
|
238
|
+
if (!nodeProds.has(n.name))
|
|
239
|
+
nodeProds.set(n.name, new Set());
|
|
240
|
+
nodeProds.get(n.name).add(t);
|
|
241
|
+
}
|
|
242
|
+
const order = [];
|
|
243
|
+
const available = new Set();
|
|
244
|
+
for (const [t, ns] of this._producers) {
|
|
245
|
+
if (ns.some(n => n.name === 'initial'))
|
|
246
|
+
available.add(t);
|
|
247
|
+
}
|
|
248
|
+
const remaining = new Set([...nodeReqs.keys(), ...nodeProds.keys()]);
|
|
249
|
+
remaining.delete('initial');
|
|
250
|
+
while (remaining.size > 0) {
|
|
251
|
+
let next = null;
|
|
252
|
+
for (const name of remaining) {
|
|
253
|
+
const reqs = nodeReqs.get(name) ?? new Set();
|
|
254
|
+
if ([...reqs].every(r => available.has(r))) {
|
|
255
|
+
next = name;
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (!next) {
|
|
260
|
+
order.push(...remaining);
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
order.push(next);
|
|
264
|
+
remaining.delete(next);
|
|
265
|
+
for (const p of nodeProds.get(next) ?? [])
|
|
266
|
+
available.add(p);
|
|
267
|
+
}
|
|
268
|
+
return order;
|
|
269
|
+
}
|
|
270
|
+
/** Generate Markdown migration checklist. */
|
|
271
|
+
toMarkdown() {
|
|
272
|
+
const lines = ['# Migration Checklist\n'];
|
|
273
|
+
const order = this.migrationOrder();
|
|
274
|
+
for (let i = 0; i < order.length; i++) {
|
|
275
|
+
const name = order[i];
|
|
276
|
+
const reqs = [];
|
|
277
|
+
for (const [t, ns] of this._consumers)
|
|
278
|
+
if (ns.some(n => n.name === name))
|
|
279
|
+
reqs.push(t);
|
|
280
|
+
const prods = [];
|
|
281
|
+
for (const [t, ns] of this._producers)
|
|
282
|
+
if (ns.some(n => n.name === name))
|
|
283
|
+
prods.push(t);
|
|
284
|
+
let line = `- [ ] **${i + 1}. ${name}**`;
|
|
285
|
+
if (reqs.length)
|
|
286
|
+
line += ` requires: [${reqs.join(', ')}]`;
|
|
287
|
+
if (prods.length)
|
|
288
|
+
line += ` produces: [${prods.join(', ')}]`;
|
|
289
|
+
lines.push(line);
|
|
290
|
+
}
|
|
291
|
+
const dead = this.deadData();
|
|
292
|
+
if (dead.size > 0) {
|
|
293
|
+
lines.push('\n## Dead Data\n');
|
|
294
|
+
for (const d of dead)
|
|
295
|
+
lines.push(`- ${d}`);
|
|
296
|
+
}
|
|
297
|
+
return lines.join('\n') + '\n';
|
|
298
|
+
}
|
|
299
|
+
/** Test scaffold: for each processor, list required type names. */
|
|
300
|
+
testScaffold() {
|
|
301
|
+
const scaffold = new Map();
|
|
302
|
+
for (const [typeName, nodes] of this._consumers) {
|
|
303
|
+
for (const node of nodes) {
|
|
304
|
+
if (!scaffold.has(node.name))
|
|
305
|
+
scaffold.set(node.name, []);
|
|
306
|
+
scaffold.get(node.name).push(typeName);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return scaffold;
|
|
310
|
+
}
|
|
311
|
+
/** Generate data-flow invariant assertions as strings. */
|
|
312
|
+
generateInvariantAssertions() {
|
|
313
|
+
const assertions = [];
|
|
314
|
+
for (const [state, types] of this._availableAtState) {
|
|
315
|
+
assertions.push(`At state ${state}: context must contain [${[...types].sort().join(', ')}]`);
|
|
316
|
+
}
|
|
317
|
+
return assertions;
|
|
318
|
+
}
|
|
319
|
+
// ─── Cross-flow / Versioning utilities ─────────────────────
|
|
320
|
+
/** Cross-flow map: types that one flow produces and another requires. */
|
|
321
|
+
static crossFlowMap(...graphs) {
|
|
322
|
+
const results = [];
|
|
323
|
+
for (let i = 0; i < graphs.length; i++) {
|
|
324
|
+
for (let j = 0; j < graphs.length; j++) {
|
|
325
|
+
if (i === j)
|
|
326
|
+
continue;
|
|
327
|
+
for (const produced of graphs[i]._allProduced) {
|
|
328
|
+
if (graphs[j]._allConsumed.has(produced)) {
|
|
329
|
+
results.push(`${produced}: flow ${i} produces → flow ${j} consumes`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return results;
|
|
335
|
+
}
|
|
336
|
+
/** Diff two data-flow graphs. */
|
|
337
|
+
static diff(before, after) {
|
|
338
|
+
const beforeTypes = before.allTypes(), afterTypes = after.allTypes();
|
|
339
|
+
const addedTypes = new Set([...afterTypes].filter(t => !beforeTypes.has(t)));
|
|
340
|
+
const removedTypes = new Set([...beforeTypes].filter(t => !afterTypes.has(t)));
|
|
341
|
+
const beforeEdges = DataFlowGraph.collectEdges(before), afterEdges = DataFlowGraph.collectEdges(after);
|
|
342
|
+
const addedEdges = new Set([...afterEdges].filter(e => !beforeEdges.has(e)));
|
|
343
|
+
const removedEdges = new Set([...beforeEdges].filter(e => !afterEdges.has(e)));
|
|
344
|
+
return { addedTypes, removedTypes, addedEdges, removedEdges };
|
|
345
|
+
}
|
|
346
|
+
static collectEdges(graph) {
|
|
347
|
+
const edges = new Set();
|
|
348
|
+
for (const [t, ns] of graph._producers)
|
|
349
|
+
for (const n of ns)
|
|
350
|
+
edges.add(`${n.name} --produces--> ${t}`);
|
|
351
|
+
for (const [t, ns] of graph._consumers)
|
|
352
|
+
for (const n of ns)
|
|
353
|
+
edges.add(`${t} --requires--> ${n.name}`);
|
|
354
|
+
return edges;
|
|
355
|
+
}
|
|
356
|
+
/** Version compatibility: check if v1 instances can resume on v2 definition. */
|
|
357
|
+
static versionCompatibility(before, after) {
|
|
358
|
+
const issues = [];
|
|
359
|
+
for (const [state, beforeAvail] of before._availableAtState) {
|
|
360
|
+
const afterAvail = after._availableAtState.get(state) ?? new Set();
|
|
361
|
+
for (const type of afterAvail) {
|
|
362
|
+
if (!beforeAvail.has(type)) {
|
|
363
|
+
issues.push(`State ${state}: v2 expects ${type} but v1 instances may not have it`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return issues;
|
|
368
|
+
}
|
|
169
369
|
// ─── Builder ─────────────────────────────────────────────
|
|
170
370
|
static build(def, initiallyAvailable) {
|
|
171
371
|
const stateAvail = new Map();
|
|
@@ -15,6 +15,10 @@ export declare class FlowDefinition<S extends string> {
|
|
|
15
15
|
transitionsFrom(state: S): Transition<S>[];
|
|
16
16
|
externalFrom(state: S): Transition<S> | undefined;
|
|
17
17
|
allStates(): S[];
|
|
18
|
+
/**
|
|
19
|
+
* Create a new FlowDefinition with a sub-flow inserted before a specific transition.
|
|
20
|
+
*/
|
|
21
|
+
withPlugin(from: S, to: S, pluginFlow: FlowDefinition<any>): FlowDefinition<S>;
|
|
18
22
|
static builder<S extends string>(name: string, stateConfig: Record<S, StateConfig>): Builder<S>;
|
|
19
23
|
}
|
|
20
24
|
export declare class Builder<S extends string> {
|
package/dist/flow-definition.js
CHANGED
|
@@ -37,6 +37,42 @@ export class FlowDefinition {
|
|
|
37
37
|
allStates() {
|
|
38
38
|
return Object.keys(this.stateConfig);
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Create a new FlowDefinition with a sub-flow inserted before a specific transition.
|
|
42
|
+
*/
|
|
43
|
+
withPlugin(from, to, pluginFlow) {
|
|
44
|
+
const newTransitions = [];
|
|
45
|
+
let replaced = false;
|
|
46
|
+
for (const t of this.transitions) {
|
|
47
|
+
if (t.from === from && t.to === to && !replaced) {
|
|
48
|
+
const exitMap = new Map();
|
|
49
|
+
for (const terminal of pluginFlow.terminalStates)
|
|
50
|
+
exitMap.set(terminal, to);
|
|
51
|
+
newTransitions.push({
|
|
52
|
+
from, to: from, type: 'sub_flow',
|
|
53
|
+
processor: t.processor, guard: undefined, branch: undefined,
|
|
54
|
+
branchTargets: new Map(),
|
|
55
|
+
subFlowDefinition: pluginFlow, exitMappings: exitMap,
|
|
56
|
+
});
|
|
57
|
+
replaced = true;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
newTransitions.push(t);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const result = Object.create(FlowDefinition.prototype);
|
|
64
|
+
Object.assign(result, {
|
|
65
|
+
name: this.name + '+plugin:' + pluginFlow.name,
|
|
66
|
+
stateConfig: this.stateConfig, ttl: this.ttl,
|
|
67
|
+
maxGuardRetries: this.maxGuardRetries,
|
|
68
|
+
transitions: newTransitions,
|
|
69
|
+
errorTransitions: new Map(this.errorTransitions),
|
|
70
|
+
initialState: this.initialState,
|
|
71
|
+
terminalStates: this.terminalStates,
|
|
72
|
+
dataFlowGraph: null, // will be rebuilt if needed
|
|
73
|
+
});
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
40
76
|
// ─── Builder ─────────────────────────────────────────────
|
|
41
77
|
static builder(name, stateConfig) {
|
|
42
78
|
return new Builder(name, stateConfig);
|
package/dist/flow-instance.d.ts
CHANGED
|
@@ -28,6 +28,10 @@ export declare class FlowInstance<S extends string> {
|
|
|
28
28
|
statePath(): string[];
|
|
29
29
|
/** State path as slash-separated string. */
|
|
30
30
|
statePathString(): string;
|
|
31
|
+
/** Data types available in context at current state (from data-flow graph). */
|
|
32
|
+
availableData(): Set<string>;
|
|
33
|
+
/** Data types that the next transition requires but are not yet in context. */
|
|
34
|
+
missingFor(): string[];
|
|
31
35
|
/** Types required by the next external transition (including in active sub-flows). */
|
|
32
36
|
waitingFor(): string[];
|
|
33
37
|
/** Return a copy with the given version. For FlowStore optimistic locking. */
|
package/dist/flow-instance.js
CHANGED
|
@@ -56,6 +56,27 @@ export class FlowInstance {
|
|
|
56
56
|
}
|
|
57
57
|
/** State path as slash-separated string. */
|
|
58
58
|
statePathString() { return this.statePath().join('/'); }
|
|
59
|
+
/** Data types available in context at current state (from data-flow graph). */
|
|
60
|
+
availableData() {
|
|
61
|
+
return this.definition.dataFlowGraph?.availableAt(this._currentState) ?? new Set();
|
|
62
|
+
}
|
|
63
|
+
/** Data types that the next transition requires but are not yet in context. */
|
|
64
|
+
missingFor() {
|
|
65
|
+
const missing = [];
|
|
66
|
+
for (const t of this.definition.transitionsFrom(this._currentState)) {
|
|
67
|
+
if (t.guard)
|
|
68
|
+
for (const r of t.guard.requires) {
|
|
69
|
+
if (!this.context.has(r))
|
|
70
|
+
missing.push(r);
|
|
71
|
+
}
|
|
72
|
+
if (t.processor)
|
|
73
|
+
for (const r of t.processor.requires) {
|
|
74
|
+
if (!this.context.has(r))
|
|
75
|
+
missing.push(r);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return [...new Set(missing)];
|
|
79
|
+
}
|
|
59
80
|
/** Types required by the next external transition (including in active sub-flows). */
|
|
60
81
|
waitingFor() {
|
|
61
82
|
if (this._activeSubFlow)
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export { FlowError } from './flow-error.js';
|
|
|
7
7
|
export { InMemoryFlowStore } from './in-memory-flow-store.js';
|
|
8
8
|
export type { TransitionRecord } from './in-memory-flow-store.js';
|
|
9
9
|
export { MermaidGenerator } from './mermaid-generator.js';
|
|
10
|
+
export { SkeletonGenerator } from './skeleton-generator.js';
|
|
11
|
+
export type { TargetLanguage } from './skeleton-generator.js';
|
|
10
12
|
export { DataFlowGraph } from './data-flow-graph.js';
|
|
11
13
|
export type { NodeInfo } from './data-flow-graph.js';
|
|
12
14
|
export { flowKey } from './flow-key.js';
|
package/dist/index.js
CHANGED
|
@@ -6,5 +6,6 @@ export { FlowDefinition, Builder, FromBuilder, BranchBuilder, SubFlowBuilder } f
|
|
|
6
6
|
export { FlowError } from './flow-error.js';
|
|
7
7
|
export { InMemoryFlowStore } from './in-memory-flow-store.js';
|
|
8
8
|
export { MermaidGenerator } from './mermaid-generator.js';
|
|
9
|
+
export { SkeletonGenerator } from './skeleton-generator.js';
|
|
9
10
|
export { DataFlowGraph } from './data-flow-graph.js';
|
|
10
11
|
export { flowKey } from './flow-key.js';
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { FlowDefinition } from './flow-definition.js';
|
|
2
2
|
export declare class MermaidGenerator {
|
|
3
3
|
static generate<S extends string>(def: FlowDefinition<S>): string;
|
|
4
|
+
/** Generate Mermaid diagram highlighting external transitions and their data contracts. */
|
|
5
|
+
static generateExternalContract<S extends string>(def: FlowDefinition<S>): string;
|
|
4
6
|
/** Generate Mermaid data-flow diagram from requires/produces declarations. */
|
|
5
7
|
static generateDataFlow<S extends string>(def: FlowDefinition<S>): string;
|
|
6
8
|
private static transitionLabel;
|
|
@@ -43,6 +43,23 @@ export class MermaidGenerator {
|
|
|
43
43
|
}
|
|
44
44
|
return lines.join('\n') + '\n';
|
|
45
45
|
}
|
|
46
|
+
/** Generate Mermaid diagram highlighting external transitions and their data contracts. */
|
|
47
|
+
static generateExternalContract(def) {
|
|
48
|
+
const lines = ['flowchart LR'];
|
|
49
|
+
for (const t of def.transitions) {
|
|
50
|
+
if (t.type !== 'external' || !t.guard)
|
|
51
|
+
continue;
|
|
52
|
+
lines.push(` subgraph ${t.from}_to_${t.to}`);
|
|
53
|
+
lines.push(' direction TB');
|
|
54
|
+
lines.push(` ${t.guard.name}{"[${t.guard.name}]"}`);
|
|
55
|
+
for (const req of t.guard.requires)
|
|
56
|
+
lines.push(` ${req} -->|client sends| ${t.guard.name}`);
|
|
57
|
+
for (const prod of t.guard.produces)
|
|
58
|
+
lines.push(` ${t.guard.name} -->|returns| ${prod}`);
|
|
59
|
+
lines.push(' end');
|
|
60
|
+
}
|
|
61
|
+
return lines.join('\n') + '\n';
|
|
62
|
+
}
|
|
46
63
|
/** Generate Mermaid data-flow diagram from requires/produces declarations. */
|
|
47
64
|
static generateDataFlow(def) {
|
|
48
65
|
return def.dataFlowGraph?.toMermaid() ?? '';
|
|
@@ -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,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); }
|