@unlaxer/tramli 0.1.0 → 0.2.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.
@@ -0,0 +1,33 @@
1
+ import type { FlowDefinition } from './flow-definition.js';
2
+ import type { FlowKey } from './flow-key.js';
3
+ export interface NodeInfo<S extends string> {
4
+ name: string;
5
+ fromState: S;
6
+ toState: S;
7
+ kind: 'processor' | 'guard' | 'branch' | 'initial';
8
+ }
9
+ /**
10
+ * Bipartite graph of data types (FlowKey) and processors/guards.
11
+ * Built automatically during FlowDefinition.build().
12
+ */
13
+ export declare class DataFlowGraph<S extends string> {
14
+ private readonly _availableAtState;
15
+ private readonly _producers;
16
+ private readonly _consumers;
17
+ private readonly _allProduced;
18
+ private readonly _allConsumed;
19
+ private constructor();
20
+ /** Data types available in context when the flow reaches the given state. */
21
+ availableAt(state: S): Set<string>;
22
+ /** Processors/guards that produce the given type. */
23
+ producersOf(key: FlowKey<unknown>): NodeInfo<S>[];
24
+ /** Processors/guards that consume (require) the given type. */
25
+ consumersOf(key: FlowKey<unknown>): NodeInfo<S>[];
26
+ /** Types produced but never required by any downstream processor/guard. */
27
+ deadData(): Set<string>;
28
+ /** All type nodes in the graph. */
29
+ allTypes(): Set<string>;
30
+ /** Generate Mermaid data-flow diagram. */
31
+ toMermaid(): string;
32
+ static build<S extends string>(def: FlowDefinition<S>, initiallyAvailable: string[]): DataFlowGraph<S>;
33
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Bipartite graph of data types (FlowKey) and processors/guards.
3
+ * Built automatically during FlowDefinition.build().
4
+ */
5
+ export class DataFlowGraph {
6
+ _availableAtState;
7
+ _producers;
8
+ _consumers;
9
+ _allProduced;
10
+ _allConsumed;
11
+ constructor(availableAtState, producers, consumers, allProduced, allConsumed) {
12
+ this._availableAtState = availableAtState;
13
+ this._producers = producers;
14
+ this._consumers = consumers;
15
+ this._allProduced = allProduced;
16
+ this._allConsumed = allConsumed;
17
+ }
18
+ /** Data types available in context when the flow reaches the given state. */
19
+ availableAt(state) {
20
+ return this._availableAtState.get(state) ?? new Set();
21
+ }
22
+ /** Processors/guards that produce the given type. */
23
+ producersOf(key) {
24
+ return this._producers.get(key) ?? [];
25
+ }
26
+ /** Processors/guards that consume (require) the given type. */
27
+ consumersOf(key) {
28
+ return this._consumers.get(key) ?? [];
29
+ }
30
+ /** Types produced but never required by any downstream processor/guard. */
31
+ deadData() {
32
+ const dead = new Set(this._allProduced);
33
+ for (const c of this._allConsumed)
34
+ dead.delete(c);
35
+ return dead;
36
+ }
37
+ /** All type nodes in the graph. */
38
+ allTypes() {
39
+ const types = new Set(this._allProduced);
40
+ for (const c of this._allConsumed)
41
+ types.add(c);
42
+ return types;
43
+ }
44
+ /** Generate Mermaid data-flow diagram. */
45
+ toMermaid() {
46
+ const lines = ['flowchart LR'];
47
+ const seen = new Set();
48
+ for (const [typeName, nodes] of this._producers) {
49
+ for (const node of nodes) {
50
+ const edge = `${node.name} -->|produces| ${typeName}`;
51
+ if (!seen.has(edge)) {
52
+ seen.add(edge);
53
+ lines.push(` ${edge}`);
54
+ }
55
+ }
56
+ }
57
+ for (const [typeName, nodes] of this._consumers) {
58
+ for (const node of nodes) {
59
+ const edge = `${typeName} -->|requires| ${node.name}`;
60
+ if (!seen.has(edge)) {
61
+ seen.add(edge);
62
+ lines.push(` ${edge}`);
63
+ }
64
+ }
65
+ }
66
+ return lines.join('\n') + '\n';
67
+ }
68
+ // ─── Builder ─────────────────────────────────────────────
69
+ static build(def, initiallyAvailable) {
70
+ const stateAvail = new Map();
71
+ const producers = new Map();
72
+ const consumers = new Map();
73
+ const allProduced = new Set(initiallyAvailable);
74
+ const allConsumed = new Set();
75
+ if (def.initialState) {
76
+ traverse(def, def.initialState, new Set(initiallyAvailable), stateAvail, producers, consumers, allProduced, allConsumed);
77
+ // Mark initially available types as produced by "initial"
78
+ for (const key of initiallyAvailable) {
79
+ if (!producers.has(key))
80
+ producers.set(key, []);
81
+ producers.get(key).push({
82
+ name: 'initial', fromState: def.initialState, toState: def.initialState, kind: 'initial',
83
+ });
84
+ }
85
+ }
86
+ return new DataFlowGraph(stateAvail, producers, consumers, allProduced, allConsumed);
87
+ }
88
+ }
89
+ function traverse(def, state, available, stateAvail, producers, consumers, allProduced, allConsumed) {
90
+ if (stateAvail.has(state)) {
91
+ const existing = stateAvail.get(state);
92
+ let isSubset = true;
93
+ for (const a of available) {
94
+ if (!existing.has(a)) {
95
+ isSubset = false;
96
+ break;
97
+ }
98
+ }
99
+ if (isSubset)
100
+ return;
101
+ for (const a of [...existing]) {
102
+ if (!available.has(a))
103
+ existing.delete(a);
104
+ }
105
+ }
106
+ else {
107
+ stateAvail.set(state, new Set(available));
108
+ }
109
+ for (const t of def.transitionsFrom(state)) {
110
+ const newAvail = new Set(stateAvail.get(state));
111
+ if (t.guard) {
112
+ for (const req of t.guard.requires) {
113
+ addTo(consumers, req, { name: t.guard.name, fromState: t.from, toState: t.to, kind: 'guard' });
114
+ allConsumed.add(req);
115
+ }
116
+ for (const prod of t.guard.produces) {
117
+ addTo(producers, prod, { name: t.guard.name, fromState: t.from, toState: t.to, kind: 'guard' });
118
+ allProduced.add(prod);
119
+ newAvail.add(prod);
120
+ }
121
+ }
122
+ if (t.branch) {
123
+ for (const req of t.branch.requires) {
124
+ addTo(consumers, req, { name: t.branch.name, fromState: t.from, toState: t.to, kind: 'branch' });
125
+ allConsumed.add(req);
126
+ }
127
+ }
128
+ if (t.processor) {
129
+ for (const req of t.processor.requires) {
130
+ addTo(consumers, req, { name: t.processor.name, fromState: t.from, toState: t.to, kind: 'processor' });
131
+ allConsumed.add(req);
132
+ }
133
+ for (const prod of t.processor.produces) {
134
+ addTo(producers, prod, { name: t.processor.name, fromState: t.from, toState: t.to, kind: 'processor' });
135
+ allProduced.add(prod);
136
+ newAvail.add(prod);
137
+ }
138
+ }
139
+ traverse(def, t.to, newAvail, stateAvail, producers, consumers, allProduced, allConsumed);
140
+ }
141
+ }
142
+ function addTo(map, key, info) {
143
+ if (!map.has(key))
144
+ map.set(key, []);
145
+ map.get(key).push(info);
146
+ }
@@ -1,5 +1,6 @@
1
1
  import type { FlowKey } from './flow-key.js';
2
2
  import type { StateConfig, Transition, StateProcessor, TransitionGuard, BranchProcessor } from './types.js';
3
+ import { DataFlowGraph } from './data-flow-graph.js';
3
4
  export declare class FlowDefinition<S extends string> {
4
5
  readonly name: string;
5
6
  readonly stateConfig: Record<S, StateConfig>;
@@ -9,6 +10,7 @@ export declare class FlowDefinition<S extends string> {
9
10
  readonly errorTransitions: Map<S, S>;
10
11
  readonly initialState: S | null;
11
12
  readonly terminalStates: Set<S>;
13
+ readonly dataFlowGraph: DataFlowGraph<S> | null;
12
14
  private constructor();
13
15
  transitionsFrom(state: S): Transition<S>[];
14
16
  externalFrom(state: S): Transition<S> | undefined;
@@ -1,4 +1,5 @@
1
1
  import { FlowError } from './flow-error.js';
2
+ import { DataFlowGraph } from './data-flow-graph.js';
2
3
  export class FlowDefinition {
3
4
  name;
4
5
  stateConfig;
@@ -8,6 +9,7 @@ export class FlowDefinition {
8
9
  errorTransitions;
9
10
  initialState;
10
11
  terminalStates;
12
+ dataFlowGraph;
11
13
  constructor(name, stateConfig, ttl, maxGuardRetries, transitions, errorTransitions) {
12
14
  this.name = name;
13
15
  this.stateConfig = stateConfig;
@@ -98,7 +100,9 @@ export class Builder {
98
100
  }
99
101
  result.initialState = initial;
100
102
  result.terminalStates = terminals;
103
+ result.dataFlowGraph = null;
101
104
  this.validate(result);
105
+ result.dataFlowGraph = DataFlowGraph.build(result, this.initiallyAvailableKeys);
102
106
  return result;
103
107
  }
104
108
  validate(def) {
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 { DataFlowGraph } from './data-flow-graph.js';
11
+ export type { NodeInfo } from './data-flow-graph.js';
10
12
  export { flowKey } from './flow-key.js';
11
13
  export type { FlowKey } from './flow-key.js';
12
14
  export type { StateConfig, GuardOutput, TransitionType, Transition, StateProcessor, TransitionGuard, BranchProcessor, } from './types.js';
package/dist/index.js CHANGED
@@ -6,4 +6,5 @@ export { FlowDefinition, Builder, FromBuilder, BranchBuilder } from './flow-defi
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 { DataFlowGraph } from './data-flow-graph.js';
9
10
  export { flowKey } from './flow-key.js';
@@ -1,5 +1,7 @@
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 data-flow diagram from requires/produces declarations. */
5
+ static generateDataFlow<S extends string>(def: FlowDefinition<S>): string;
4
6
  private static transitionLabel;
5
7
  }
@@ -24,6 +24,10 @@ export class MermaidGenerator {
24
24
  }
25
25
  return lines.join('\n') + '\n';
26
26
  }
27
+ /** Generate Mermaid data-flow diagram from requires/produces declarations. */
28
+ static generateDataFlow(def) {
29
+ return def.dataFlowGraph?.toMermaid() ?? '';
30
+ }
27
31
  static transitionLabel(t) {
28
32
  if (t.type === 'auto')
29
33
  return t.processor?.name ?? '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unlaxer/tramli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Constrained flow engine — state machines that prevent invalid transitions at build time",
5
5
  "type": "module",
6
6
  "exports": {