@beehexa/hexasync-template-worker-flow 2608.20.18 → 2608.20.31
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/flow.d.ts +163 -0
- package/dist/flow.d.ts.map +1 -0
- package/dist/flow.js +251 -0
- package/dist/flow.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/render.d.ts +25 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +157 -0
- package/dist/render.js.map +1 -0
- package/dist/routeGraph.d.ts +41 -0
- package/dist/routeGraph.d.ts.map +1 -0
- package/dist/routeGraph.js +214 -0
- package/dist/routeGraph.js.map +1 -0
- package/dist/routes.d.ts +71 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +155 -0
- package/dist/routes.js.map +1 -0
- package/dist/stages.d.ts +112 -0
- package/dist/stages.d.ts.map +1 -0
- package/dist/stages.js +178 -0
- package/dist/stages.js.map +1 -0
- package/package.json +2 -2
package/dist/render.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `renderMermaid` / `renderMarkdown` for a worker flow — Story 4.3, `PACKAGE-CONTRACT.md` §5.
|
|
3
|
+
*
|
|
4
|
+
* Thin by design. The emission rules live in `hexasync-template-model`'s `flowRender`, because AD-21 forbids the two
|
|
5
|
+
* flow packages importing each other and the shared half has to live somewhere neither owns. This file's whole job is
|
|
6
|
+
* to say what a WORKER node looks like: which shape it takes, what its label reads, and how stages become subgraphs.
|
|
7
|
+
*/
|
|
8
|
+
import { renderFlowMermaid, composeNodeLabel, renderFlowMarkdown, } from '@beehexa/hexasync-template-model';
|
|
9
|
+
/**
|
|
10
|
+
* A node's label.
|
|
11
|
+
*
|
|
12
|
+
* A synthesised node reads as its own `label` — the condition, the expression, the unresolved target — which is why
|
|
13
|
+
* `FlowNode` carries one at all (Story 4.1 review, HIGH-3).
|
|
14
|
+
*
|
|
15
|
+
* ⚠️ A step reads as its **`name`**, falling back to its key. The first version used the key alone and defended it by
|
|
16
|
+
* citing a FRONTEND node that shows a name, so the evidence argued the other way. All three baselines draw the name:
|
|
17
|
+
* the CLI report `["Get Sales Return Detail"]`, the extension `{"GET_…<br/>Get Sales Return Detail"}`.
|
|
18
|
+
*
|
|
19
|
+
* The extension's `<br/>` form cannot be produced through this renderer, deliberately: `escapeLabel` escapes `<` and
|
|
20
|
+
* `>`, so a literal `<br/>` would render as text. A two-line label is a RENDERER capability, not a model one, and
|
|
21
|
+
* belongs in whatever option the extension needs rather than smuggled through a label string.
|
|
22
|
+
*/
|
|
23
|
+
function labelOf(node) {
|
|
24
|
+
// COMPOSED, not a fallback chain (2026-08-20). This was
|
|
25
|
+
// `node.label ?? node.name ?? node.stepKey ?? node.address`, so a step with a name drew its name and
|
|
26
|
+
// dropped the key and the type — both already on the node. A reader beside the YAML could not tell which
|
|
27
|
+
// `key:` a box was, and `next:`/`outputs:` reference steps BY KEY. One composer for both runtimes, in
|
|
28
|
+
// `template-model`, so the two diagrams cannot start labelling differently.
|
|
29
|
+
return composeNodeLabel(node);
|
|
30
|
+
}
|
|
31
|
+
/** Stages become subgraphs, in the order the model reports them. */
|
|
32
|
+
/**
|
|
33
|
+
* Stages become subgraphs — and a stage that drew NOTHING is omitted entirely (Story 4.3 review, HIGH-1).
|
|
34
|
+
*
|
|
35
|
+
* `stagesPresent` counts a stage present when its array is non-empty, but `stepOf` can drop every element (no `key`, or
|
|
36
|
+
* the composer's `!` removal). The stage then had zero nodes and was still emitted: an empty `subgraph … / direction TB
|
|
37
|
+
* / end`, and — because `firstOf` never got an entry — **every stage arrow touching it disappeared**, so the chart
|
|
38
|
+
* asserted the surviving stages were unrelated. Both authorities remove empty stages so their chain collapses instead.
|
|
39
|
+
*/
|
|
40
|
+
function groupsOf(flow) {
|
|
41
|
+
return flow.stages
|
|
42
|
+
.map((stage) => ({
|
|
43
|
+
key: `${flow.address}:${stage.key}`,
|
|
44
|
+
title: stage.label,
|
|
45
|
+
nodes: flow.nodes
|
|
46
|
+
.filter((node) => node.stage === stage.key)
|
|
47
|
+
.map((node) => ({
|
|
48
|
+
address: node.address,
|
|
49
|
+
label: labelOf(node),
|
|
50
|
+
decides: node.decides,
|
|
51
|
+
// A dead end and a cycle marker are terminals; a condition or an expression is a diamond.
|
|
52
|
+
terminal: node.junction === 'unknown',
|
|
53
|
+
})),
|
|
54
|
+
}))
|
|
55
|
+
.filter((group) => group.nodes.length > 0);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Stage-to-stage arrows: **every terminal of the source stage → every ENTRY of the target**, which is the CLI's
|
|
59
|
+
* geometry and therefore the one the composition report must reproduce (Story 4.6).
|
|
60
|
+
*
|
|
61
|
+
* ⛔ The first version drew ONE arrow, from each stage's first document-order step. That was a **fourth** geometry
|
|
62
|
+
* where `epic-4-ground-truth.md` records three and says one must be chosen — and it was semantically wrong: an
|
|
63
|
+
* `on error` arrow leaving only the first step reads as *"only that step can fail"*. The extension's own docblock
|
|
64
|
+
* argues exactly against it: *"picking one of them would draw a claim about ordering that the YAML does not make."*
|
|
65
|
+
* Worse, the file's comment cited *"node→node (CLI, terminal to entry)"* and then implemented entry→entry.
|
|
66
|
+
*
|
|
67
|
+
* A **terminal** is a step no route leaves — the honest source of "what happens after this stage". An **entry** is a
|
|
68
|
+
* step marked `rootStep`, falling back to the first, which is how dashboard chooses its target. Both facts now exist in
|
|
69
|
+
* the model; neither did before.
|
|
70
|
+
*/
|
|
71
|
+
function stageEdgesOf(flow) {
|
|
72
|
+
const stepsOf = (stage) => flow.nodes.filter((node) => node.stage === stage && node.stepKey !== undefined);
|
|
73
|
+
/** Steps with no outgoing route edge — where a stage actually finishes. */
|
|
74
|
+
const terminalsOf = (stage) => {
|
|
75
|
+
const steps = stepsOf(stage);
|
|
76
|
+
const leaves = new Set(flow.edges.filter((e) => e.stage === stage).map((e) => e.from));
|
|
77
|
+
const terminals = steps.filter((node) => !leaves.has(node.address));
|
|
78
|
+
// A stage whose every step routes onward (a loop) has no terminal; its last step is the honest stand-in.
|
|
79
|
+
return terminals.length > 0 ? terminals : steps.slice(-1);
|
|
80
|
+
};
|
|
81
|
+
const entriesOf = (stage) => {
|
|
82
|
+
const steps = stepsOf(stage);
|
|
83
|
+
const declared = steps.filter((node) => node.isEntry === true);
|
|
84
|
+
return declared.length > 0 ? declared : steps.slice(0, 1);
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* A stage that drew NOTHING is collapsed THROUGH, not merely omitted (Story 4.4 review, MED-6).
|
|
88
|
+
*
|
|
89
|
+
* ⛔ Story 4.3's fix omitted the empty subgraph and stopped there — so every stage arrow touching it still vanished,
|
|
90
|
+
* and the chart went on asserting that the stages either side were unrelated. Half a fix reads exactly like the whole
|
|
91
|
+
* one until something builds the case: no real corpus component has a stage whose every step is dropped, so only a
|
|
92
|
+
* hand-built one can falsify it.
|
|
93
|
+
*
|
|
94
|
+
* Both authorities collapse: dashboard's `pusherPhases` and the extension's `drawable` filter both REMOVE an empty
|
|
95
|
+
* stage, so `beforePull → afterPull` survives. The stage order is the model's, so following it forward is the same
|
|
96
|
+
* walk either of them does.
|
|
97
|
+
*/
|
|
98
|
+
const order = flow.stages.map((stage) => stage.key);
|
|
99
|
+
const drawn = (stage) => stepsOf(stage).length > 0;
|
|
100
|
+
/** The next stage at or after `stage` that actually drew something. */
|
|
101
|
+
const forwardTo = (stage) => {
|
|
102
|
+
for (let index = order.indexOf(stage); index < order.length; index += 1) {
|
|
103
|
+
const candidate = order[index];
|
|
104
|
+
if (drawn(candidate))
|
|
105
|
+
return candidate;
|
|
106
|
+
}
|
|
107
|
+
return undefined;
|
|
108
|
+
};
|
|
109
|
+
const seen = new Set();
|
|
110
|
+
return flow.stageEdges.flatMap((edge) => {
|
|
111
|
+
if (!drawn(edge.from))
|
|
112
|
+
return [];
|
|
113
|
+
const to = forwardTo(edge.to);
|
|
114
|
+
if (to === undefined || to === edge.from)
|
|
115
|
+
return [];
|
|
116
|
+
// Two collapsed hops can arrive at the same pair; one arrow is enough.
|
|
117
|
+
const key = `${edge.from} ${to} ${edge.label ?? ''}`;
|
|
118
|
+
if (seen.has(key))
|
|
119
|
+
return [];
|
|
120
|
+
seen.add(key);
|
|
121
|
+
return terminalsOf(edge.from).flatMap((from) => entriesOf(to).map((entry) => ({
|
|
122
|
+
from: from.address,
|
|
123
|
+
to: entry.address,
|
|
124
|
+
...(edge.label !== undefined ? { label: edge.label } : {}),
|
|
125
|
+
})));
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
export function renderMermaid(flow, opts = {}) {
|
|
129
|
+
return renderFlowMermaid(groupsOf(flow), [...flow.edges, ...stageEdgesOf(flow)], opts);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Markdown for one component — `(flow, opts)`, matching the frontend twin and `PACKAGE-CONTRACT.md` §5.
|
|
133
|
+
*
|
|
134
|
+
* ⛔ This took `(flow, title, opts)` (Story 4.3 review, MEDIUM-1). §5 declares `renderMarkdown(model, opts?)`
|
|
135
|
+
* *"exported from both flow packages"*, so a caller written to the contract passed the options object into the `title`
|
|
136
|
+
* slot and it reached `escapeLabel`: **`TypeError: value.replace is not a function`**. TypeScript caught it for TS
|
|
137
|
+
* consumers; JS consumers and the "one renderer set, callable uniformly" premise did not.
|
|
138
|
+
*
|
|
139
|
+
* The title now comes from the model, which has everything needed: the collection and the component's authored id. A
|
|
140
|
+
* caller wanting its own wording passes `title`.
|
|
141
|
+
*/
|
|
142
|
+
export function renderMarkdown(flow, opts = {}) {
|
|
143
|
+
const title = opts.title ?? titleOf(flow);
|
|
144
|
+
return renderFlowMarkdown(title, groupsOf(flow), [...flow.edges, ...stageEdgesOf(flow)], opts);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* A default title from the model.
|
|
148
|
+
*
|
|
149
|
+
* The authored id rather than a composed GUID, for AD-33's reason: it is the form that is identical across
|
|
150
|
+
* environments. `pullers:id_**XPullerId**` reads as `**XPullerId**`, which is what an author typed.
|
|
151
|
+
*/
|
|
152
|
+
function titleOf(flow) {
|
|
153
|
+
const identity = flow.address.split(':').at(-1) ?? flow.address;
|
|
154
|
+
const singular = flow.collection === 'pullers' ? 'Puller' : 'Pusher';
|
|
155
|
+
return `${singular} ${identity.replace(/^id_/, '')}`;
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type RoutableStep } from './routes.js';
|
|
2
|
+
/** What a synthesised node is, so a renderer picks a shape without inspecting the label. */
|
|
3
|
+
export type JunctionKind = 'if' | 'expression' | 'unknown';
|
|
4
|
+
/** A node the document does not declare, produced because a route needs somewhere to branch. */
|
|
5
|
+
export interface Junction {
|
|
6
|
+
/** Relative path from the owning step, e.g. `['next', 'if']`. The caller prefixes the step's address. */
|
|
7
|
+
readonly path: readonly string[];
|
|
8
|
+
readonly kind: JunctionKind;
|
|
9
|
+
/** The text a renderer draws in it: the condition, the expression, or the unresolved target. */
|
|
10
|
+
readonly label: string;
|
|
11
|
+
/** The step key that produced it. */
|
|
12
|
+
readonly owner: string;
|
|
13
|
+
}
|
|
14
|
+
/** An edge in the route graph. Ends are either a step key or a junction path, tagged so the caller can address it. */
|
|
15
|
+
export interface GraphEdge {
|
|
16
|
+
readonly from: EdgeEnd;
|
|
17
|
+
readonly to: EdgeEnd;
|
|
18
|
+
readonly label?: string;
|
|
19
|
+
}
|
|
20
|
+
export type EdgeEnd = {
|
|
21
|
+
readonly kind: 'step';
|
|
22
|
+
readonly key: string;
|
|
23
|
+
} | {
|
|
24
|
+
readonly kind: 'junction';
|
|
25
|
+
readonly owner: string;
|
|
26
|
+
readonly path: readonly string[];
|
|
27
|
+
};
|
|
28
|
+
export interface RouteGraph {
|
|
29
|
+
readonly junctions: readonly Junction[];
|
|
30
|
+
readonly edges: readonly GraphEdge[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Every junction and every edge for the steps of ONE stage.
|
|
34
|
+
*
|
|
35
|
+
* Iterative over an explicit work list, never recursive — `then`/`else` nest without bound and a document is
|
|
36
|
+
* untrusted input. The work list carries the PATH, which is what makes the addressing positional-free.
|
|
37
|
+
*/
|
|
38
|
+
export declare function routeGraph(steps: readonly RoutableStep[]): RouteGraph;
|
|
39
|
+
/** The address of a junction, given its owning step's address. AD-33 rule 4 / rule 5. */
|
|
40
|
+
export declare function junctionAddress(ownerAddress: string, path: readonly string[]): string;
|
|
41
|
+
//# sourceMappingURL=routeGraph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routeGraph.d.ts","sourceRoot":"","sources":["../src/routeGraph.ts"],"names":[],"mappings":"AAqCA,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,aAAa,CAAC;AAErB,4FAA4F;AAC5F,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,YAAY,GAAG,SAAS,CAAC;AAE3D,gGAAgG;AAChG,MAAM,WAAW,QAAQ;IACvB,yGAAyG;IACzG,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,gGAAgG;IAChG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,sHAAsH;AACtH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,OAAO,GACf;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC/C;IACE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC,CAAC;AAEN,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,SAAS,EAAE,SAAS,QAAQ,EAAE,CAAC;IACxC,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;CACtC;AAID;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,GAAG,UAAU,CAsMrE;AAED,yFAAyF;AACzF,wBAAgB,eAAe,CAC7B,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,SAAS,MAAM,EAAE,GACtB,MAAM,CAOR"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The route GRAPH — edges that pass THROUGH the nodes a branch synthesises.
|
|
3
|
+
*
|
|
4
|
+
* ### Why this exists alongside `routeEdges`, which is flat
|
|
5
|
+
*
|
|
6
|
+
* ⛔ The first version of this package had `routeEdges` only, and `flow.ts` synthesised a decision node beside it
|
|
7
|
+
* without connecting the two. The Story 4.1 review measured what that produced: a diamond with **zero** edges
|
|
8
|
+
* touching it, while the YES/NO arms left the step — so the diamond was unreachable and undrawable, and the step
|
|
9
|
+
* carried arms it does not have. Worse, it synthesised for the ~22 corpus steps with an object-valued `next` and
|
|
10
|
+
* produced **no** node for the **3,480** with an expression-valued one, which is exactly where both shipped
|
|
11
|
+
* surfaces DO synthesise (`_EXPR_<n>` in `flowChart.ts:145-155`, in the golden baseline for fixture 01).
|
|
12
|
+
*
|
|
13
|
+
* `routeEdges` is kept because it answers a genuinely different question — *"what routes where"*, flat, which is
|
|
14
|
+
* what `flowEdges` answers and what `routeParity.spec.ts` compares. This answers *"how is it drawn"*, and
|
|
15
|
+
* `routeGraph.spec.ts` asserts the two agree about reachability so they cannot drift.
|
|
16
|
+
*
|
|
17
|
+
* ### The three synthesised kinds, from the shipped baseline
|
|
18
|
+
*
|
|
19
|
+
* | kind | when | drawn as | label |
|
|
20
|
+
* | --- | --- | --- | --- |
|
|
21
|
+
* | `if` | an object route `{ if, then, else }` | diamond | the `if` text, or `?` |
|
|
22
|
+
* | `expression` | a Scriban route naming ≥1 real step | diamond | the expression |
|
|
23
|
+
* | `unknown` | a route naming no step at all | stadium | the route text |
|
|
24
|
+
*
|
|
25
|
+
* ### Addressed by PATH, per AD-33 rule 4
|
|
26
|
+
*
|
|
27
|
+
* A structural node is *"named by the keys that reach it"*. So the junction for a step's object `next` is
|
|
28
|
+
* `<step>:next:if` — AD-33's own example — a nested arm is `<step>:next:then:if`, an `IF` branching through `data`
|
|
29
|
+
* is `<step>:data:if`, and an expression is `<step>:next:expression`. No counter anywhere, so inserting a step above
|
|
30
|
+
* renumbers nothing. The path also disambiguates naturally: two dead ends leaving one step reach it by different
|
|
31
|
+
* keys (`:next:unknown` and `:onError:unknown`), so neither needs an index.
|
|
32
|
+
*/
|
|
33
|
+
import { keySegment, entrySegment, nodeAddress, } from '@beehexa/hexasync-template-model';
|
|
34
|
+
import { looksLikeExpression, quotedKeys, } from './routes.js';
|
|
35
|
+
const step_ = (key) => ({ kind: 'step', key });
|
|
36
|
+
/**
|
|
37
|
+
* Every junction and every edge for the steps of ONE stage.
|
|
38
|
+
*
|
|
39
|
+
* Iterative over an explicit work list, never recursive — `then`/`else` nest without bound and a document is
|
|
40
|
+
* untrusted input. The work list carries the PATH, which is what makes the addressing positional-free.
|
|
41
|
+
*/
|
|
42
|
+
export function routeGraph(steps) {
|
|
43
|
+
const known = new Set(steps.map((s) => String(s.key)));
|
|
44
|
+
const junctions = [];
|
|
45
|
+
const edges = [];
|
|
46
|
+
/** Junctions already created, so two routes reaching the same path share one node. */
|
|
47
|
+
const seen = new Set();
|
|
48
|
+
const junction = (owner, path, kind, label) => {
|
|
49
|
+
const id = `${owner} ${path.join(' ')}`;
|
|
50
|
+
if (!seen.has(id)) {
|
|
51
|
+
seen.add(id);
|
|
52
|
+
junctions.push({ owner, path, kind, label });
|
|
53
|
+
}
|
|
54
|
+
return { kind: 'junction', owner, path };
|
|
55
|
+
};
|
|
56
|
+
for (const step of steps) {
|
|
57
|
+
const owner = String(step.key);
|
|
58
|
+
const type = String(step.stepType ?? '').toUpperCase();
|
|
59
|
+
const isBranchObject = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
60
|
+
/**
|
|
61
|
+
* Branch objects already entered on THIS step's walk — the cycle guard.
|
|
62
|
+
*
|
|
63
|
+
* ⛔ Without it a cyclic branch does not merely loop, it **kills the process**. Found by the Story 4.2 review and
|
|
64
|
+
* confirmed here: `const a = {}; a.then = a; routeGraph([{ key: 'A', next: a }])` exits **134**, `FATAL ERROR:
|
|
65
|
+
* heap out of memory`, in a few seconds. The `path` grows by two segments per iteration, `seen` is keyed by that
|
|
66
|
+
* path so it never repeats, and both `junctions` and the work list grow without bound.
|
|
67
|
+
*
|
|
68
|
+
* This module's own docblock argued *"iterative, never recursive — a document is untrusted input"*. Iteration was
|
|
69
|
+
* the right call and it removed the only thing that had been bounding this: the reference implementation recurses
|
|
70
|
+
* and throws a **catchable** `RangeError`. An uncatchable V8 fatal is strictly worse, and this package is bundled
|
|
71
|
+
* into the VS Code extension host, so the blast radius is the editor process.
|
|
72
|
+
*
|
|
73
|
+
* REACHABLE FROM YAML with the parser the CLI already uses — a recursive anchor is all it takes:
|
|
74
|
+
*
|
|
75
|
+
* ```yaml
|
|
76
|
+
* next: &loop
|
|
77
|
+
* if: "x"
|
|
78
|
+
* then: *loop
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* Identity, not path: the same object reached twice is the cycle, however long the path to it.
|
|
82
|
+
*/
|
|
83
|
+
const entered = new WeakSet();
|
|
84
|
+
/** Work list of routes still to place: where the arrow leaves, the value, the path, the label. */
|
|
85
|
+
const pending = [];
|
|
86
|
+
// ── Which route(s) this step declares, and under which key ──
|
|
87
|
+
//
|
|
88
|
+
// An IF whose `data` is an object but declares NEITHER `then` NOR `else` falls back to `next`. Without the
|
|
89
|
+
// fallback a partly-written IF loses its only edge and its target is orphaned in the diagram — `data:
|
|
90
|
+
// { expression: '…' }` with `next: 'DONE'` drew `CHECK → <diamond>` and nothing else, leaving `DONE` floating.
|
|
91
|
+
//
|
|
92
|
+
// This mirrors `routes.ts`, the flat twin, which has always had the fallback and says why in the same words. The
|
|
93
|
+
// two disagreed until Story 4.7's review measured it; this module's own docblock claimed the case "routed
|
|
94
|
+
// correctly through its `next`", which was aspiration rather than behaviour. `type` comes from `displayType`
|
|
95
|
+
// first, and every UI-served step carries one, so the divergence was reachable from a real surface.
|
|
96
|
+
const branching = (type === 'IF' || type === 'SWITCH') && isBranchObject(step.data);
|
|
97
|
+
const ifWithoutBranch = type === 'IF' &&
|
|
98
|
+
branching &&
|
|
99
|
+
step.data.then == null &&
|
|
100
|
+
step.data.else == null;
|
|
101
|
+
if (branching && !ifWithoutBranch) {
|
|
102
|
+
pending.push({ from: step_(owner), value: step.data, path: ['data'] });
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
pending.push({ from: step_(owner), value: step.next, path: ['next'] });
|
|
106
|
+
}
|
|
107
|
+
if (step.onError !== null && step.onError !== undefined) {
|
|
108
|
+
pending.push({
|
|
109
|
+
from: step_(owner),
|
|
110
|
+
value: step.onError,
|
|
111
|
+
path: ['onError'],
|
|
112
|
+
label: 'onError',
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
while (pending.length > 0) {
|
|
116
|
+
const { from, value, path, label } = pending.pop();
|
|
117
|
+
if (value === null || value === undefined || value === '')
|
|
118
|
+
continue;
|
|
119
|
+
const labelled = label !== undefined ? { label } : {};
|
|
120
|
+
// ── An object route: a diamond, and its arms leave the DIAMOND, not the step ──
|
|
121
|
+
if (isBranchObject(value)) {
|
|
122
|
+
const branch = value;
|
|
123
|
+
if (entered.has(branch)) {
|
|
124
|
+
/**
|
|
125
|
+
* A cycle. Reported as a dead end labelled for what it is, rather than dropped: a route that loops back on
|
|
126
|
+
* itself is a real document defect, and a diagram that simply stops there tells the reader nothing.
|
|
127
|
+
*/
|
|
128
|
+
const point = junction(owner, [...path, 'cycle'], 'unknown', 'cycle: this branch refers to itself');
|
|
129
|
+
edges.push({ from, to: point, ...labelled });
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
entered.add(branch);
|
|
133
|
+
const isSwitch = type === 'SWITCH' && path.length === 1 && path[0] === 'data';
|
|
134
|
+
if (isSwitch) {
|
|
135
|
+
// A SWITCH's arms are its data KEYS. The junction is the switch point itself.
|
|
136
|
+
const point = junction(owner, [...path, 'switch'], 'if', 'SWITCH');
|
|
137
|
+
edges.push({ from, to: point, ...labelled });
|
|
138
|
+
for (const arm of Object.keys(branch)) {
|
|
139
|
+
pending.push({
|
|
140
|
+
from: point,
|
|
141
|
+
value: arm,
|
|
142
|
+
path: [...path, arm],
|
|
143
|
+
label: arm,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const condition = branch.if === null || branch.if === undefined
|
|
149
|
+
? '?'
|
|
150
|
+
: String(branch.if);
|
|
151
|
+
const point = junction(owner, [...path, 'if'], 'if', condition);
|
|
152
|
+
edges.push({ from, to: point, ...labelled });
|
|
153
|
+
// `else` first so `then` is placed first — a reader expects YES before NO.
|
|
154
|
+
pending.push({
|
|
155
|
+
from: point,
|
|
156
|
+
value: branch.else,
|
|
157
|
+
path: [...path, 'else'],
|
|
158
|
+
label: 'NO',
|
|
159
|
+
});
|
|
160
|
+
pending.push({
|
|
161
|
+
from: point,
|
|
162
|
+
value: branch.then,
|
|
163
|
+
path: [...path, 'then'],
|
|
164
|
+
label: 'YES',
|
|
165
|
+
});
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const text = String(value);
|
|
169
|
+
// ── A direct step key: a plain edge, no synthesis ──
|
|
170
|
+
if (known.has(text)) {
|
|
171
|
+
edges.push({ from, to: step_(text), ...labelled });
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
// ── An expression naming real steps: a diamond, then one arrow per target ──
|
|
175
|
+
if (looksLikeExpression(text)) {
|
|
176
|
+
const targets = quotedKeys(text).filter((k) => known.has(k));
|
|
177
|
+
if (targets.length > 0) {
|
|
178
|
+
const point = junction(owner, [...path, 'expression'], 'expression', text);
|
|
179
|
+
edges.push({ from, to: point, ...labelled });
|
|
180
|
+
// Unlabelled, as the baseline draws them: which target it takes is a run-time fact.
|
|
181
|
+
for (const target of targets) {
|
|
182
|
+
edges.push({ from: point, to: step_(target) });
|
|
183
|
+
}
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// ── Naming nothing: a terminal, kept so the reader sees the dead end ──
|
|
188
|
+
const point = junction(owner, [...path, 'unknown'], 'unknown', text);
|
|
189
|
+
edges.push({ from, to: point, ...labelled });
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* The exception BACK-REFERENCE, which is reversed: an edge INTO this step from the one that threw. No junction —
|
|
193
|
+
* it is a direct edge between two authored steps.
|
|
194
|
+
*/
|
|
195
|
+
if (step.fromStep && known.has(step.fromStep)) {
|
|
196
|
+
edges.push({
|
|
197
|
+
from: step_(step.fromStep),
|
|
198
|
+
to: step_(owner),
|
|
199
|
+
label: 'Exception',
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return { junctions, edges };
|
|
204
|
+
}
|
|
205
|
+
/** The address of a junction, given its owning step's address. AD-33 rule 4 / rule 5. */
|
|
206
|
+
export function junctionAddress(ownerAddress, path) {
|
|
207
|
+
return nodeAddress([
|
|
208
|
+
keySegment(ownerAddress),
|
|
209
|
+
// A path element is a document key where it is one (`next`, `then`, `if`) and a dictionary tail where it is a
|
|
210
|
+
// SWITCH arm — `entrySegment` normalises both and refuses a `!`-prefixed one.
|
|
211
|
+
...path.map((element) => entrySegment(element) ?? keySegment(element)),
|
|
212
|
+
]);
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=routeGraph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routeGraph.js","sourceRoot":"","sources":["../src/routeGraph.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EACL,UAAU,EACV,YAAY,EACZ,WAAW,GACZ,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,mBAAmB,EACnB,UAAU,GAEX,MAAM,aAAa,CAAC;AAoCrB,MAAM,KAAK,GAAG,CAAC,GAAW,EAAW,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;AAEhE;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,KAA8B;IACvD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACvD,MAAM,SAAS,GAAe,EAAE,CAAC;IACjC,MAAM,KAAK,GAAgB,EAAE,CAAC;IAC9B,sFAAsF;IACtF,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,MAAM,QAAQ,GAAG,CACf,KAAa,EACb,IAAuB,EACvB,IAAkB,EAClB,KAAa,EACJ,EAAE;QACX,MAAM,EAAE,GAAG,GAAG,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACxC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YAClB,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACb,SAAS,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC/C,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAC3C,CAAC,CAAC;IAEF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QACvD,MAAM,cAAc,GAAG,CAAC,CAAU,EAAE,EAAE,CACpC,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAE3D;;;;;;;;;;;;;;;;;;;;;;WAsBG;QACH,MAAM,OAAO,GAAG,IAAI,OAAO,EAAU,CAAC;QAEtC,kGAAkG;QAClG,MAAM,OAAO,GAKP,EAAE,CAAC;QAET,+DAA+D;QAC/D,EAAE;QACF,2GAA2G;QAC3G,sGAAsG;QACtG,+GAA+G;QAC/G,EAAE;QACF,iHAAiH;QACjH,0GAA0G;QAC1G,6GAA6G;QAC7G,oGAAoG;QACpG,MAAM,SAAS,GACb,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,QAAQ,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpE,MAAM,eAAe,GACnB,IAAI,KAAK,IAAI;YACb,SAAS;YACR,IAAI,CAAC,IAAgC,CAAC,IAAI,IAAI,IAAI;YAClD,IAAI,CAAC,IAAgC,CAAC,IAAI,IAAI,IAAI,CAAC;QACtD,IAAI,SAAS,IAAI,CAAC,eAAe,EAAE,CAAC;YAClC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACzE,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACzE,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YACxD,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC;gBAClB,KAAK,EAAE,IAAI,CAAC,OAAO;gBACnB,IAAI,EAAE,CAAC,SAAS,CAAC;gBACjB,KAAK,EAAE,SAAS;aACjB,CAAC,CAAC;QACL,CAAC;QAED,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1B,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,GAAG,EAAG,CAAC;YACpD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE;gBAAE,SAAS;YACpE,MAAM,QAAQ,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAEtD,iFAAiF;YACjF,IAAI,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC1B,MAAM,MAAM,GAAG,KAAgC,CAAC;gBAChD,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;oBACxB;;;uBAGG;oBACH,MAAM,KAAK,GAAG,QAAQ,CACpB,KAAK,EACL,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,EAClB,SAAS,EACT,qCAAqC,CACtC,CAAC;oBACF,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAC;oBAC7C,SAAS;gBACX,CAAC;gBACD,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBACpB,MAAM,QAAQ,GACZ,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC;gBAC/D,IAAI,QAAQ,EAAE,CAAC;oBACb,8EAA8E;oBAC9E,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;oBACnE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAC;oBAC7C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;wBACtC,OAAO,CAAC,IAAI,CAAC;4BACX,IAAI,EAAE,KAAK;4BACX,KAAK,EAAE,GAAG;4BACV,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,CAAC;4BACpB,KAAK,EAAE,GAAG;yBACX,CAAC,CAAC;oBACL,CAAC;oBACD,SAAS;gBACX,CAAC;gBACD,MAAM,SAAS,GACb,MAAM,CAAC,EAAE,KAAK,IAAI,IAAI,MAAM,CAAC,EAAE,KAAK,SAAS;oBAC3C,CAAC,CAAC,GAAG;oBACL,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBACxB,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;gBAChE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAC;gBAC7C,2EAA2E;gBAC3E,OAAO,CAAC,IAAI,CAAC;oBACX,IAAI,EAAE,KAAK;oBACX,KAAK,EAAE,MAAM,CAAC,IAAI;oBAClB,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC;oBACvB,KAAK,EAAE,IAAI;iBACZ,CAAC,CAAC;gBACH,OAAO,CAAC,IAAI,CAAC;oBACX,IAAI,EAAE,KAAK;oBACX,KAAK,EAAE,MAAM,CAAC,IAAI;oBAClB,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC;oBACvB,KAAK,EAAE,KAAK;iBACb,CAAC,CAAC;gBACH,SAAS;YACX,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;YAE3B,sDAAsD;YACtD,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBACpB,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAC;gBACnD,SAAS;YACX,CAAC;YAED,8EAA8E;YAC9E,IAAI,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC9B,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC7D,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACvB,MAAM,KAAK,GAAG,QAAQ,CACpB,KAAK,EACL,CAAC,GAAG,IAAI,EAAE,YAAY,CAAC,EACvB,YAAY,EACZ,IAAI,CACL,CAAC;oBACF,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAC;oBAC7C,oFAAoF;oBACpF,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;wBAC7B,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;oBACjD,CAAC;oBACD,SAAS;gBACX,CAAC;YACH,CAAC;YAED,yEAAyE;YACzE,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,SAAS,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;YACrE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAC;QAC/C,CAAC;QAED;;;WAGG;QACH,IAAI,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC9C,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAC1B,EAAE,EAAE,KAAK,CAAC,KAAK,CAAC;gBAChB,KAAK,EAAE,WAAW;aACnB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;AAC9B,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,eAAe,CAC7B,YAAoB,EACpB,IAAuB;IAEvB,OAAO,WAAW,CAAC;QACjB,UAAU,CAAC,YAAY,CAAC;QACxB,8GAA8G;QAC9G,8EAA8E;QAC9E,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,UAAU,CAAC,OAAO,CAAC,CAAC;KACvE,CAAC,CAAC;AACL,CAAC"}
|
package/dist/routes.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a step goes next — Story 4.1 AC-2, all five kinds, in the MODEL rather than in a renderer.
|
|
3
|
+
*
|
|
4
|
+
* The semantics are the runtime's: `core.api/hexasync.steps/Execution/NextStepBuilder.cs` and `IfStepExecutor`.
|
|
5
|
+
* This package does not invent them; NFR-1 says the editor invents no semantics the runtime lacks.
|
|
6
|
+
*
|
|
7
|
+
* | kind | where it is declared | who drew it before |
|
|
8
|
+
* | --- | --- | --- |
|
|
9
|
+
* | by step TYPE | `IF` / `SWITCH` read their `data` | CLI only — dashboard reads only `step.next` |
|
|
10
|
+
* | by step DATA | `{ if, then, else }` on `next` or `data` | CLI |
|
|
11
|
+
* | an EXPRESSION | a Scriban `next` naming keys in quotes | CLI |
|
|
12
|
+
* | an ERROR route | `onError` | CLI |
|
|
13
|
+
* | an exception BACK-REFERENCE | `fromStep` → this step | **nobody** — see below |
|
|
14
|
+
*
|
|
15
|
+
* ### The back-reference is why routing has to live here
|
|
16
|
+
*
|
|
17
|
+
* `fromStep` produces an edge *into* the exception step from the step that threw. It exists **only in the CLI's
|
|
18
|
+
* drawing path** (`flowChart.ts:199-201` emits `fromStep --|"Exception"|-> this`) and **not** in the shared
|
|
19
|
+
* `flowEdges`. The VS Code extension imports `flowEdges`, so it has never drawn that edge at all — a whole route
|
|
20
|
+
* kind missing from one surface because the renderer, not the model, owned it. Moving routing into the model is what
|
|
21
|
+
* makes AC-2's last clause true everywhere, and it is the concrete argument for this package existing.
|
|
22
|
+
*
|
|
23
|
+
* ### An unresolvable route is REPORTED, not dropped
|
|
24
|
+
*
|
|
25
|
+
* A route naming no step in the stage yields an edge with `raw` and no `to`. A reader needs to see the dead end:
|
|
26
|
+
* silently dropping it draws a flow that terminates where the document does not say it terminates, which is the
|
|
27
|
+
* more expensive error. Same for an expression whose quoted keys match nothing.
|
|
28
|
+
*/
|
|
29
|
+
/** A step as routing needs to see it. Deliberately structural — this package reads no schema and no file. */
|
|
30
|
+
export interface RoutableStep {
|
|
31
|
+
readonly key: string;
|
|
32
|
+
/** Raw `next`: a step key, a `{ if, then, else }` branch, a Scriban expression, or absent. */
|
|
33
|
+
readonly next?: unknown;
|
|
34
|
+
/** The step's declared type, e.g. `IF`, `SWITCH`. Compared case-insensitively. */
|
|
35
|
+
readonly stepType?: string;
|
|
36
|
+
/** For `IF`/`SWITCH`, the runtime passes `data` to the route builder. */
|
|
37
|
+
readonly data?: unknown;
|
|
38
|
+
/** A recovery target. */
|
|
39
|
+
readonly onError?: unknown;
|
|
40
|
+
/** An Exception step's back-reference: the step whose failure reaches this one. */
|
|
41
|
+
readonly fromStep?: string;
|
|
42
|
+
}
|
|
43
|
+
/** One arrow between two steps. */
|
|
44
|
+
export interface RouteEdge {
|
|
45
|
+
/** The step the arrow leaves. */
|
|
46
|
+
readonly from: string;
|
|
47
|
+
/** The step it reaches, when that step exists in this stage. */
|
|
48
|
+
readonly to?: string;
|
|
49
|
+
/** `YES`, `NO`, a SWITCH branch name, `onError`, `Exception`. */
|
|
50
|
+
readonly label?: string;
|
|
51
|
+
/**
|
|
52
|
+
* The route text when it does not name a known step — an expression, or a dead end.
|
|
53
|
+
*
|
|
54
|
+
* Carried alongside `to` for an expression that DOES resolve, because which branch it takes at run time is a fact
|
|
55
|
+
* a static view cannot know, and showing the expression is how the reader learns that.
|
|
56
|
+
*/
|
|
57
|
+
readonly raw?: string;
|
|
58
|
+
}
|
|
59
|
+
/** Every `"QUOTED_KEY"` in an expression. */
|
|
60
|
+
export declare function quotedKeys(expression: string): readonly string[];
|
|
61
|
+
/** Is this route text an expression rather than a step key? */
|
|
62
|
+
export declare function looksLikeExpression(text: string): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Every edge the steps of ONE stage declare.
|
|
65
|
+
*
|
|
66
|
+
* Iterative, never recursive: `then`/`else` nest without bound and a document is untrusted input, so a deep branch
|
|
67
|
+
* must not be able to exhaust the stack. (The same rule the puller step standards state: *no recursion — stack or
|
|
68
|
+
* loop*.)
|
|
69
|
+
*/
|
|
70
|
+
export declare function routeEdges(steps: readonly RoutableStep[]): readonly RouteEdge[];
|
|
71
|
+
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,6GAA6G;AAC7G,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,8FAA8F;IAC9F,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,kFAAkF;IAClF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,yBAAyB;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,mFAAmF;IACnF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,mCAAmC;AACnC,MAAM,WAAW,SAAS;IACxB,iCAAiC;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,iEAAiE;IACjE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,6CAA6C;AAC7C,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAEhE;AAED,+DAA+D;AAC/D,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAIzD;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,SAAS,YAAY,EAAE,GAC7B,SAAS,SAAS,EAAE,CAmHtB"}
|