@beehexa/hexasync-template-frontend-flow 2608.15.1

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 ADDED
@@ -0,0 +1,78 @@
1
+ import { type FrontendWorkflowKey, type FrontendSurface } from './workflows.js';
2
+ import { type JunctionKind } from './routes.js';
3
+ /** Where a node came from in the authored source. Supplied by the caller; see the worker package's AC-4b note. */
4
+ export interface NodeLocation {
5
+ readonly file?: string;
6
+ readonly range?: {
7
+ readonly start: {
8
+ readonly line: number;
9
+ readonly character: number;
10
+ };
11
+ readonly end: {
12
+ readonly line: number;
13
+ readonly character: number;
14
+ };
15
+ };
16
+ readonly sourceText?: string;
17
+ }
18
+ export interface LocateRequest {
19
+ readonly workflow: FrontendWorkflowKey;
20
+ readonly stepKey?: string;
21
+ readonly address: string;
22
+ }
23
+ /** One node in a frontend flow. */
24
+ export interface FrontendFlowNode extends NodeLocation {
25
+ readonly address: string;
26
+ /**
27
+ * `frontend` on every node — AC-4's whole purpose.
28
+ *
29
+ * *"so no consumer can merge the two domains by accident"*. The worker package carries `runtime: 'worker'` for the
30
+ * same reason, and the pair only works if both sides do it.
31
+ */
32
+ readonly runtime: 'frontend';
33
+ readonly workflow: FrontendWorkflowKey;
34
+ /** Which document declares this workflow. */
35
+ readonly surface: FrontendSurface;
36
+ readonly type?: string;
37
+ /** The step's human name, where it declares one — what the CLI baseline writes inside the node. */
38
+ readonly name?: string;
39
+ readonly decides: boolean;
40
+ readonly label?: string;
41
+ readonly junction?: JunctionKind;
42
+ readonly stepKey?: string;
43
+ readonly owner?: string;
44
+ }
45
+ export interface FrontendFlowEdge {
46
+ readonly from: string;
47
+ readonly to: string;
48
+ readonly label?: string;
49
+ readonly workflow: FrontendWorkflowKey;
50
+ }
51
+ /** One workflow's drawing, with its own title (AC-1). */
52
+ export interface FrontendWorkflowFlow {
53
+ readonly workflow: FrontendWorkflowKey;
54
+ readonly title: string;
55
+ readonly surface: FrontendSurface;
56
+ readonly address: string;
57
+ readonly nodes: readonly FrontendFlowNode[];
58
+ readonly edges: readonly FrontendFlowEdge[];
59
+ readonly duplicateStepKeys?: readonly string[];
60
+ /** Keys carrying a character the address algebra refuses, e.g. a `:`. Present only when it happened. */
61
+ readonly unaddressableStepKeys?: readonly string[];
62
+ }
63
+ export interface FrontendFlow {
64
+ readonly address: string;
65
+ readonly workflows: readonly FrontendWorkflowFlow[];
66
+ }
67
+ export interface FrontendFlowInput {
68
+ /** The parsed connector definition or template manifest. */
69
+ readonly document: Record<string, unknown>;
70
+ /** Its `id`, authored — AD-33 keeps the token rather than a composed GUID. */
71
+ readonly documentId?: unknown;
72
+ /** The collection segment the document sits under, e.g. `connectors`. */
73
+ readonly collection?: string;
74
+ readonly locate?: (request: LocateRequest) => NodeLocation | undefined;
75
+ }
76
+ /** The flow model for every frontend workflow a document declares. */
77
+ export declare function frontendFlow(input: FrontendFlowInput): FrontendFlow;
78
+ //# sourceMappingURL=flow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow.d.ts","sourceRoot":"","sources":["../src/flow.ts"],"names":[],"mappings":"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"}
package/dist/flow.js ADDED
@@ -0,0 +1,222 @@
1
+ /**
2
+ * A frontend workflow's flow and its node index — Story 4.2, one entry point per document.
3
+ *
4
+ * Mirrors `hexasync-template-worker-flow`'s shape on purpose: nodes and edges from one computation, AD-33 addresses on
5
+ * every node including synthesised ones, and source location arriving as data. What it does NOT mirror is the
6
+ * vocabulary — see `workflows.ts` for the four measured differences and why unifying them would be wrong.
7
+ *
8
+ * ⛔ **CORE (AD-3)**: no filesystem, no `vscode`, no Node builtin.
9
+ */
10
+ import { keySegment, identitySegment, nodeAddress, normalizeComponentKey, } from '@beehexa/hexasync-template-model';
11
+ import { workflowsPresent, surfaceOf, FRONTEND_WORKFLOW_TITLES, } from './workflows.js';
12
+ import { frontendRouteGraph, } from './routes.js';
13
+ /** A step, read defensively. */
14
+ function stepOf(raw) {
15
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
16
+ return undefined;
17
+ const step = raw;
18
+ /**
19
+ * A key is REQUIRED, and there is no positional fallback.
20
+ *
21
+ * ⚠️ The reference implementation falls back to `STEP_${index + 1}` (`connectorWorkflowMermaid.ts`'s `stepKeys`
22
+ * build). That is a fifth positional identity, and AD-33 exists to remove them: a keyless step would acquire an
23
+ * address that shifts when a step is inserted above it. A step with no key is skipped instead, exactly as the worker
24
+ * package does — the corpus contains one such step and it is correctly dropped there.
25
+ */
26
+ const key = typeof step.key === 'string' ? step.key : undefined;
27
+ if (key === undefined || key.trim() === '')
28
+ return undefined;
29
+ // A step the composer deletes is not in the flow — the worker package's HIGH-2 finding, applied here from the start.
30
+ if (normalizeComponentKey(key)?.removes === true)
31
+ return undefined;
32
+ const type = typeof step.type === 'string' ? step.type : undefined;
33
+ return {
34
+ key,
35
+ ...(step.next !== undefined ? { next: step.next } : {}),
36
+ ...(type !== undefined ? { type } : {}),
37
+ ...(step.metadata !== undefined ? { metadata: step.metadata } : {}),
38
+ ...(step.onCancelled !== undefined
39
+ ? { onCancelled: step.onCancelled }
40
+ : {}),
41
+ // The ENTRY flag. Dropped by the first version, with the whole START/entry concept (review H4).
42
+ ...(step.root !== undefined ? { root: step.root } : {}),
43
+ /**
44
+ * The human NAME the CLI baseline labels a node with — `SHOW_CREATE_PROFILE_ERROR["Couldn't Start Profile
45
+ * Creation"]`. Dropped by the first version (review L1), so a renderer had only the key.
46
+ */
47
+ ...(typeof step.name === 'string' && step.name.trim() !== ''
48
+ ? { name: step.name.trim() }
49
+ : {}),
50
+ };
51
+ }
52
+ /** The flow model for every frontend workflow a document declares. */
53
+ export function frontendFlow(input) {
54
+ const { document, documentId, collection, locate } = input;
55
+ const base = nodeAddress([
56
+ ...(collection !== undefined ? [keySegment(collection)] : []),
57
+ identitySegment(documentId, 'id'),
58
+ ]);
59
+ // Emptiness measured on what SURVIVES `stepOf`, not on raw item count — see `workflowsPresent`.
60
+ const workflows = workflowsPresent(document, (items) => items.reduce((count, item) => (stepOf(item) ? count + 1 : count), 0)).map((workflow) => oneWorkflow(workflow, document[workflow], base, locate));
61
+ return { address: base, workflows };
62
+ }
63
+ function oneWorkflow(workflow, raw, base, locate) {
64
+ const surface = surfaceOf(workflow);
65
+ /**
66
+ * An EMPTY base contributes nothing (Story 4.6).
67
+ *
68
+ * `collection` and `documentId` are both optional, and a caller that supplies neither — the CLI report's
69
+ * `creationSteps`, whose document is the composed root — leaves `base` as `''`. That used to produce a leading hole
70
+ * in every address: `:creationSteps:key_FORM`, rendering `__creationSteps__key_FORM`. An address with an empty
71
+ * segment is not a shorter address, it is a wrong one.
72
+ *
73
+ * Handled in `nodeAddress` now rather than here — a review pointed out that a `collection: ''` with a present
74
+ * `documentId` reproduced the same hole one segment along, so the special case fixed a symptom and left the class
75
+ * (Story 4.6 review, LOW-1). Written plainly again on purpose: nothing local guards it, `nodeAddress` does.
76
+ */
77
+ const address = nodeAddress([keySegment(base), keySegment(workflow)]);
78
+ const nodes = [];
79
+ const edges = [];
80
+ const duplicates = [];
81
+ /** Keys the address algebra refused — reported rather than swallowed, like a duplicate. */
82
+ const unaddressable = [];
83
+ const steps = [];
84
+ for (const step of raw.map(stepOf)) {
85
+ if (step === undefined)
86
+ continue;
87
+ // De-duplicated for the reason the worker package records: two nodes with one address make a click resolve to
88
+ // nothing, and the runtime cannot address the second one either.
89
+ if (steps.some((s) => s.key === step.key)) {
90
+ duplicates.push(step.key);
91
+ continue;
92
+ }
93
+ steps.push(step);
94
+ }
95
+ /**
96
+ * A step whose key the ADDRESS ALGEBRA refuses is skipped (review H1).
97
+ *
98
+ * `identitySegment` returns undefined for a key carrying `:` — and `nodeAddress` DROPS an undefined segment, so the
99
+ * step silently inherited the workflow's own address. Two such steps then shared one address, and `addressMatches`
100
+ * made the phantom an ancestor of every real sibling. That is verbatim the consequence chain Story 4.1's HIGH-2
101
+ * records, re-opened by deduping on KEY rather than on address.
102
+ *
103
+ * 0 of 191 corpus frontend keys contain `:`, so this is structural — which is the argument `nodeAddress.ts` makes
104
+ * for refusing the segment in the first place.
105
+ */
106
+ const addressOf = new Map();
107
+ const drawable = [];
108
+ for (const step of steps) {
109
+ const segment = identitySegment(step.key, 'key');
110
+ if (segment === undefined) {
111
+ unaddressable.push(step.key);
112
+ continue;
113
+ }
114
+ addressOf.set(step.key, nodeAddress([keySegment(address), segment]));
115
+ drawable.push(step);
116
+ }
117
+ const shared = { runtime: 'frontend', workflow, surface };
118
+ for (const step of drawable) {
119
+ const own = addressOf.get(step.key);
120
+ /**
121
+ * An `IF`/`SWITCH` STEP decides — and here that differs from the worker side, faithfully.
122
+ *
123
+ * The frontend reference routes an `IF`'s arms **straight off the step node** (`addStepEdges`: `addTargetEdge(ctx,
124
+ * nodeId, step.metadata.then, 'YES')`) and only synthesises a `NEXT_IF` diamond for a nested branch OBJECT. So
125
+ * there is no junction to carry `decides` for the common case, and the CLI's own `isDecision` — `stepType === 'IF'
126
+ * || 'SWITCH' || object next` — draws the STEP as the diamond.
127
+ *
128
+ * On the worker side `decides` is false on every step, because there a branch always gets its own junction. Both
129
+ * are right for their runtime, and getting this wrong in either direction leaves a renderer unable to tell a
130
+ * branch point from a plain step — which is what the first version of this file did: nothing at all was marked.
131
+ */
132
+ /**
133
+ * A step is a diamond only when its ARMS LEAVE THE STEP — i.e. an `IF`/`SWITCH` routing through `metadata`.
134
+ *
135
+ * ⛔ Story 4.3 review, HIGH-2. Story 4.2's review widened this to `IF || SWITCH || object-next`, quoting the CLI's
136
+ * `isDecision`; but a step whose `next` is an OBJECT also gets a `…:next:if` junction, and that junction is already
137
+ * `decides: true`. Result on fixture 04: **9 diamonds where dashboard's baseline has 7** — `GET_LOCAL_STORAGE` and
138
+ * `WAIT_EXECUTION_TIME` each drawn as a diamond AND given a diamond beside them.
139
+ *
140
+ * That is exactly the defect Story 4.1's review fixed on the worker side — *"`decides` used to be true on a
141
+ * branching step as well, so a renderer keyed on it drew two diamonds per branch"* — left unfixed in the twin, and
142
+ * invisible until a renderer existed to make it visible.
143
+ *
144
+ * The two references genuinely disagree, and the disagreement resolves once junctions exist: the CLI has no
145
+ * junctions, so its `isDecision` must put the diamond on the step; dashboard synthesises `NEXT_IF` and draws the
146
+ * step as a rectangle. This model has junctions, so dashboard's split is the coherent one — and it is the baseline
147
+ * whose count is recorded.
148
+ */
149
+ const type = String(step.type ?? '').toUpperCase();
150
+ const metadataBranches = (type === 'IF' || type === 'SWITCH') &&
151
+ step.metadata !== null &&
152
+ typeof step.metadata === 'object' &&
153
+ !Array.isArray(step.metadata);
154
+ nodes.push({
155
+ ...shared,
156
+ address: own,
157
+ ...(step.type !== undefined ? { type: step.type } : {}),
158
+ ...(step.name !== undefined ? { name: step.name } : {}),
159
+ decides: metadataBranches,
160
+ stepKey: step.key,
161
+ ...(locate?.({ workflow, stepKey: step.key, address: own }) ?? {}),
162
+ });
163
+ }
164
+ const graph = frontendRouteGraph(drawable, FRONTEND_WORKFLOW_TITLES[workflow]);
165
+ const junctionOf = new Map();
166
+ for (const point of graph.junctions) {
167
+ /**
168
+ * A junction owned by the WORKFLOW rather than by a step — the START terminal, and an unresolved target that every
169
+ * route naming it converges on. Its base is the workflow's address.
170
+ *
171
+ * ⚠️ Without this branch the lookup returned undefined and the node was silently `continue`d away, so the START
172
+ * node and every ghost vanished while their edges were dropped too. Caught by the convergence test.
173
+ */
174
+ const owner = point.owner === '' ? address : addressOf.get(point.owner);
175
+ if (owner === undefined)
176
+ continue;
177
+ const own = nodeAddress([
178
+ keySegment(owner),
179
+ ...point.path.map((element) => keySegment(element)),
180
+ ]);
181
+ junctionOf.set(`${point.owner} ${point.path.join(' ')}`, own);
182
+ nodes.push({
183
+ ...shared,
184
+ address: own,
185
+ // An `end` is a terminal and an `unknown` is a step-shaped box; neither branches.
186
+ decides: point.kind === 'if',
187
+ label: point.label,
188
+ junction: point.kind,
189
+ // A workflow-owned junction has no owning STEP to point at, and claiming one would be false.
190
+ ...(point.owner === '' ? {} : { owner }),
191
+ ...(locate?.({ workflow, address: own }) ?? {}),
192
+ });
193
+ }
194
+ const endAddress = (end) => end.kind === 'step'
195
+ ? addressOf.get(end.key)
196
+ : junctionOf.get(`${end.owner} ${end.path.join(' ')}`);
197
+ for (const edge of graph.edges) {
198
+ const from = endAddress(edge.from);
199
+ const to = endAddress(edge.to);
200
+ if (from === undefined || to === undefined)
201
+ continue;
202
+ edges.push({
203
+ from,
204
+ to,
205
+ ...(edge.label !== undefined ? { label: edge.label } : {}),
206
+ workflow,
207
+ });
208
+ }
209
+ return {
210
+ workflow,
211
+ title: FRONTEND_WORKFLOW_TITLES[workflow],
212
+ surface,
213
+ address,
214
+ nodes,
215
+ edges,
216
+ ...(duplicates.length > 0 ? { duplicateStepKeys: duplicates } : {}),
217
+ ...(unaddressable.length > 0
218
+ ? { unaddressableStepKeys: unaddressable }
219
+ : {}),
220
+ };
221
+ }
222
+ //# sourceMappingURL=flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow.js","sourceRoot":"","sources":["../src/flow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The flow model for a frontend onboarding workflow — `creationSteps` and the five connector workflows (FR-16, FR-17).
3
+ *
4
+ * Story 4.2's own words: *"so that the frontend workflows stop being the ones nothing can draw"*. That framing needed
5
+ * correcting before the work started — `epic-4-ground-truth.md` records that the CLI report DOES draw `creationSteps`
6
+ * and dashboard draws 3 of the 5 connector workflows, but only inside a `__dev` route. The accurate claim is that **no
7
+ * shipped product surface draws them**, and that `connectorWorkflowMermaid.ts` is a complete reference implementation
8
+ * rather than a blank page.
9
+ *
10
+ * ⛔ **CORE (AD-3)**: no filesystem, no `vscode`, no Node builtin.
11
+ *
12
+ * AD-21 forbids importing the worker flow package — the two vocabularies must not be able to reach each other. ⚠️
13
+ * `lint:boundaries` does **not** enforce that, and this file said it did (Story 4.2 review, M4): both packages are
14
+ * `core` and there is no core↔core rule, so a declared cross-dependency passes it and `depcruise` alike. What enforces
15
+ * it is `apps/cli/src/__tests__/packageLayer.spec.ts`, which checks both manifests **and sweeps both `src/` trees** —
16
+ * the sweep being the part that catches a relative-path import, the shape the accident would actually take.
17
+ */
18
+ export { CONNECTOR_WORKFLOW_KEYS, TEMPLATE_WORKFLOW_KEYS, FRONTEND_WORKFLOW_KEYS, FRONTEND_WORKFLOW_TITLES, workflowsPresent, surfaceOf, type ConnectorWorkflowKey, type TemplateWorkflowKey, type FrontendWorkflowKey, type FrontendSurface, } from './workflows.js';
19
+ export { frontendRouteGraph, branchArm, type FrontendStep, type FrontendRouteGraph, type Junction, type JunctionKind, type GraphEdge, type EdgeEnd, } from './routes.js';
20
+ export { frontendFlow, type FrontendFlow, type FrontendFlowInput, type FrontendWorkflowFlow, type FrontendFlowNode, type FrontendFlowEdge, type NodeLocation, type LocateRequest, } from './flow.js';
21
+ export { renderMermaid, renderMarkdown, renderDocumentMarkdown, } from './render.js';
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EACL,uBAAuB,EACvB,sBAAsB,EACtB,sBAAsB,EACtB,wBAAwB,EACxB,gBAAgB,EAChB,SAAS,EACT,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,eAAe,GACrB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,kBAAkB,EAClB,SAAS,EACT,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,QAAQ,EACb,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,OAAO,GACb,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,YAAY,EACZ,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,aAAa,GACnB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,aAAa,EACb,cAAc,EACd,sBAAsB,GACvB,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The flow model for a frontend onboarding workflow — `creationSteps` and the five connector workflows (FR-16, FR-17).
3
+ *
4
+ * Story 4.2's own words: *"so that the frontend workflows stop being the ones nothing can draw"*. That framing needed
5
+ * correcting before the work started — `epic-4-ground-truth.md` records that the CLI report DOES draw `creationSteps`
6
+ * and dashboard draws 3 of the 5 connector workflows, but only inside a `__dev` route. The accurate claim is that **no
7
+ * shipped product surface draws them**, and that `connectorWorkflowMermaid.ts` is a complete reference implementation
8
+ * rather than a blank page.
9
+ *
10
+ * ⛔ **CORE (AD-3)**: no filesystem, no `vscode`, no Node builtin.
11
+ *
12
+ * AD-21 forbids importing the worker flow package — the two vocabularies must not be able to reach each other. ⚠️
13
+ * `lint:boundaries` does **not** enforce that, and this file said it did (Story 4.2 review, M4): both packages are
14
+ * `core` and there is no core↔core rule, so a declared cross-dependency passes it and `depcruise` alike. What enforces
15
+ * it is `apps/cli/src/__tests__/packageLayer.spec.ts`, which checks both manifests **and sweeps both `src/` trees** —
16
+ * the sweep being the part that catches a relative-path import, the shape the accident would actually take.
17
+ */
18
+ export { CONNECTOR_WORKFLOW_KEYS, TEMPLATE_WORKFLOW_KEYS, FRONTEND_WORKFLOW_KEYS, FRONTEND_WORKFLOW_TITLES, workflowsPresent, surfaceOf, } from './workflows.js';
19
+ export { frontendRouteGraph, branchArm, } from './routes.js';
20
+ export { frontendFlow, } from './flow.js';
21
+ export { renderMermaid, renderMarkdown, renderDocumentMarkdown, } from './render.js';
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EACL,uBAAuB,EACvB,sBAAsB,EACtB,sBAAsB,EACtB,wBAAwB,EACxB,gBAAgB,EAChB,SAAS,GAKV,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,kBAAkB,EAClB,SAAS,GAOV,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,YAAY,GAQb,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,aAAa,EACb,cAAc,EACd,sBAAsB,GACvB,MAAM,aAAa,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * `renderMermaid` / `renderMarkdown` for a frontend workflow — Story 4.3, `PACKAGE-CONTRACT.md` §5.
3
+ *
4
+ * Thin, for the same reason as its worker twin: the emission rules live in `hexasync-template-model`, which is the one
5
+ * package both may import (AD-21). This file says what a FRONTEND node looks like — and the differences from the worker
6
+ * side are real, not incidental.
7
+ */
8
+ import { type RenderOptions } from '@beehexa/hexasync-template-model';
9
+ import type { FrontendWorkflowFlow, FrontendFlow } from './flow.js';
10
+ /** ONE workflow, which is what a diagram shows — a document with five of them is five diagrams, not one. */
11
+ export declare function renderMermaid(one: FrontendWorkflowFlow, opts?: RenderOptions): string;
12
+ /** Markdown for one workflow — `(flow, opts)`, the same shape as the worker twin (review MEDIUM-1). */
13
+ export declare function renderMarkdown(one: FrontendWorkflowFlow, opts?: RenderOptions & {
14
+ readonly title?: string;
15
+ }): string;
16
+ /**
17
+ * Every workflow a document declares, each its own titled section.
18
+ *
19
+ * A connector definition carries up to five, and `epic-4-ground-truth.md` records that no shipped surface draws any of
20
+ * them — so a document-level entry point is what makes the set usable rather than a per-workflow call a consumer has
21
+ * to discover.
22
+ */
23
+ export declare function renderDocumentMarkdown(flow: FrontendFlow, opts?: RenderOptions & {
24
+ readonly title?: string;
25
+ }): string;
26
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AA4BpE,4GAA4G;AAC5G,wBAAgB,aAAa,CAC3B,GAAG,EAAE,oBAAoB,EACzB,IAAI,GAAE,aAAkB,GACvB,MAAM,CAOR;AAED,uGAAuG;AACvG,wBAAgB,cAAc,CAC5B,GAAG,EAAE,oBAAoB,EACzB,IAAI,GAAE,aAAa,GAAG;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GACrD,MAAM,CAUR;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,YAAY,EAClB,IAAI,GAAE,aAAa,GAAG;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GACrD,MAAM,CAWR"}
package/dist/render.js ADDED
@@ -0,0 +1,68 @@
1
+ /**
2
+ * `renderMermaid` / `renderMarkdown` for a frontend workflow — Story 4.3, `PACKAGE-CONTRACT.md` §5.
3
+ *
4
+ * Thin, for the same reason as its worker twin: the emission rules live in `hexasync-template-model`, which is the one
5
+ * package both may import (AD-21). This file says what a FRONTEND node looks like — and the differences from the worker
6
+ * side are real, not incidental.
7
+ */
8
+ import { renderFlowMermaid, renderFlowMarkdown, } from '@beehexa/hexasync-template-model';
9
+ /**
10
+ * A node's label.
11
+ *
12
+ * ⚠️ A step reads as its **name** where it has one, falling back to its key — the CLI baseline draws
13
+ * `SHOW_CREATE_PROFILE_ERROR["Couldn't Start Profile Creation"]`, and dropping `name` was Story 4.2's L1 finding. The
14
+ * worker side has no equivalent: a worker step's identity in that corpus IS its key.
15
+ */
16
+ function labelOf(node) {
17
+ return node.label ?? node.name ?? node.stepKey ?? node.address;
18
+ }
19
+ function groupOf(one) {
20
+ return {
21
+ key: one.address,
22
+ title: one.title,
23
+ nodes: one.nodes.map((node) => ({
24
+ address: node.address,
25
+ label: labelOf(node),
26
+ decides: node.decides,
27
+ // START and END are stadiums, as both baselines draw them; an unresolved target is a plain box there, matching
28
+ // the reference, which ids it with the same shape as a real step.
29
+ terminal: node.junction === 'start' || node.junction === 'end',
30
+ })),
31
+ };
32
+ }
33
+ /** ONE workflow, which is what a diagram shows — a document with five of them is five diagrams, not one. */
34
+ export function renderMermaid(one, opts = {}) {
35
+ // A single workflow is a flat chart by default: its own title is the chart's, and a lone subgraph adds a box around
36
+ // everything for nothing.
37
+ return renderFlowMermaid([groupOf(one)], one.edges, {
38
+ multiStage: false,
39
+ ...opts,
40
+ });
41
+ }
42
+ /** Markdown for one workflow — `(flow, opts)`, the same shape as the worker twin (review MEDIUM-1). */
43
+ export function renderMarkdown(one, opts = {}) {
44
+ return renderFlowMarkdown(opts.title ?? one.title, [groupOf(one)], one.edges, {
45
+ multiStage: false,
46
+ ...opts,
47
+ });
48
+ }
49
+ /**
50
+ * Every workflow a document declares, each its own titled section.
51
+ *
52
+ * A connector definition carries up to five, and `epic-4-ground-truth.md` records that no shipped surface draws any of
53
+ * them — so a document-level entry point is what makes the set usable rather than a per-workflow call a consumer has
54
+ * to discover.
55
+ */
56
+ export function renderDocumentMarkdown(flow, opts = {}) {
57
+ /**
58
+ * A document with NO workflows says so (review MEDIUM-3).
59
+ *
60
+ * It returned `''`, so a caller could not tell "this connector declares no workflows" from "the renderer returned
61
+ * nothing" — the same failure the empty-diagram placeholder exists to prevent, one level up.
62
+ */
63
+ if (flow.workflows.length === 0) {
64
+ return `### ${opts.title ?? 'Workflows'}\n\nThis document declares no frontend workflows.\n`;
65
+ }
66
+ return flow.workflows.map((one) => renderMarkdown(one, opts)).join('\n');
67
+ }
68
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EACL,iBAAiB,EACjB,kBAAkB,GAGnB,MAAM,kCAAkC,CAAC;AAG1C;;;;;;GAMG;AACH,SAAS,OAAO,CAAC,IAA2C;IAC1D,OAAO,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC;AACjE,CAAC;AAED,SAAS,OAAO,CAAC,GAAyB;IACxC,OAAO;QACL,GAAG,EAAE,GAAG,CAAC,OAAO;QAChB,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAC9B,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC;YACpB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,+GAA+G;YAC/G,kEAAkE;YAClE,QAAQ,EAAE,IAAI,CAAC,QAAQ,KAAK,OAAO,IAAI,IAAI,CAAC,QAAQ,KAAK,KAAK;SAC/D,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC;AAED,4GAA4G;AAC5G,MAAM,UAAU,aAAa,CAC3B,GAAyB,EACzB,OAAsB,EAAE;IAExB,oHAAoH;IACpH,0BAA0B;IAC1B,OAAO,iBAAiB,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,KAAK,EAAE;QAClD,UAAU,EAAE,KAAK;QACjB,GAAG,IAAI;KACR,CAAC,CAAC;AACL,CAAC;AAED,uGAAuG;AACvG,MAAM,UAAU,cAAc,CAC5B,GAAyB,EACzB,OAAoD,EAAE;IAEtD,OAAO,kBAAkB,CACvB,IAAI,CAAC,KAAK,IAAI,GAAG,CAAC,KAAK,EACvB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EACd,GAAG,CAAC,KAAK,EACT;QACE,UAAU,EAAE,KAAK;QACjB,GAAG,IAAI;KACR,CACF,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CACpC,IAAkB,EAClB,OAAoD,EAAE;IAEtD;;;;;OAKG;IACH,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChC,OAAO,OAAO,IAAI,CAAC,KAAK,IAAI,WAAW,qDAAqD,CAAC;IAC/F,CAAC;IACD,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Where a frontend step goes next — Story 4.2 AC-2 and AC-3.
3
+ *
4
+ * Ported from `dashboard-v2/src/pages/__dev/lib/connectorWorkflowMermaid.ts`, which is a complete and working
5
+ * implementation; the port's job is to keep its semantics and drop its positional ids.
6
+ *
7
+ * ### AC-2: both spellings — on a `next` OBJECT only, which is where the reference aliases
8
+ *
9
+ * ⛔ **CORRECTED by the Story 4.2 review.** The first version applied the alias to an `IF` step's `metadata` as well,
10
+ * and that was an **invention**: dashboard reads `step.metadata.then` / `.else` *literally* (`addStepEdges`), and its
11
+ * `branchValue` is used only inside `addNextCondition` — i.e. only for a nested branch object under `next`. The CLI
12
+ * aliases the same single place. So `{ type: 'IF', metadata: { success: 'A', failed: 'B' } }` drew YES/NO edges here
13
+ * and **nothing at all** in either reference.
14
+ *
15
+ * That is precisely the failure the package split exists to prevent, committed by the package that exists to prevent
16
+ * it: accepting a document shape the runtime rejects. Measured: `success`/`failed` occur in **8 real `next` objects
17
+ * and 0 real `metadata` blocks**, so the alias was tested only on the surface where it does not belong, while the one
18
+ * place it genuinely applies had no test.
19
+ *
20
+ * Where it DOES apply, `hasOwnProperty` before falling back is load-bearing: `then: null` is a
21
+ * **declared-but-empty** arm (the frontend's way of saying "this branch ends") and is distinct from an absent `then`
22
+ * that should defer to `success`. Truthiness collapses the two and silently takes the wrong arm — which is what
23
+ * AC-2's *"does not silently lose half its edges"* is about.
24
+ *
25
+ * ### AC-3: `onCancelled` is not a route out of the happy path
26
+ *
27
+ * It is the cancel route for a pause-for-input step (`FORM`, `*_CHOICE_FORM`) and is emitted for ANY step that
28
+ * declares one, alongside whatever `next` says — the reference's comment records why: otherwise the cancel branch and
29
+ * its downstream navigation steps *"aren't left orphaned in the diagram"*.
30
+ *
31
+ * ### Expressions are NOT resolved — and the reason first given for that was false
32
+ *
33
+ * ⚠️ The original note claimed *"the frontend runtime has no such form"*. It has: **23 real `next` values** are Liquid
34
+ * templates, across `installStandaloneCallbackSteps` in LightspeedXSeries, SapoOmniV3PublicApp, ShopifyPublicApp,
35
+ * ShoplinePublicApp, TikTokShopGatewayV3 and Zalo — e.g.
36
+ * `{% assign codeSize = ... %}{% if codeSize > 0 %}CHECK_PROFILE_STATUS{% else %}GET_SETUP_INSTRUCTIONS{% endif %}`.
37
+ *
38
+ * Both the reference and this model render each as ONE unresolved node labelled with the whole template, so ~23
39
+ * genuinely reachable routes are undrawn on both sides. That is faithful, and it is a real gap.
40
+ *
41
+ * It is not closed here because the worker machinery does not transfer: the worker resolves `"QUOTED"` keys in a
42
+ * Scriban expression, whereas these name steps **bare** inside Liquid tags. Resolving them needs its own reader and
43
+ * its own decision about what to draw when a branch is undecidable statically. Deferred, with the premise corrected
44
+ * so nobody re-derives the false one.
45
+ */
46
+ /** A frontend step as routing needs to see it. */
47
+ export interface FrontendStep {
48
+ readonly key: string;
49
+ /** A step key, a `{ then|success, else|failed }` branch, or `null`/absent for the end of the workflow. */
50
+ readonly next?: unknown;
51
+ /** `IF`, `SWITCH`, `FORM`, … Compared case-insensitively. */
52
+ readonly type?: string;
53
+ /** Where an `IF`/`SWITCH` declares its branches — `metadata`, not `data`. */
54
+ readonly metadata?: unknown;
55
+ /** The cancel route. */
56
+ readonly onCancelled?: unknown;
57
+ /**
58
+ * An ENTRY point. `true` or the STRING `'true'` — the reference's `isTruthyFlag` accepts both, and 3 real
59
+ * declarations use the string form.
60
+ */
61
+ readonly root?: unknown;
62
+ }
63
+ /** What a synthesised node is. */
64
+ export type JunctionKind = 'if' | 'unknown' | 'end' | 'start';
65
+ /** A node the document does not declare. `path` is relative to the owning step. */
66
+ export interface Junction {
67
+ readonly path: readonly string[];
68
+ readonly kind: JunctionKind;
69
+ readonly label: string;
70
+ readonly owner: string;
71
+ }
72
+ export type EdgeEnd = {
73
+ readonly kind: 'step';
74
+ readonly key: string;
75
+ } | {
76
+ readonly kind: 'junction';
77
+ readonly owner: string;
78
+ readonly path: readonly string[];
79
+ };
80
+ export interface GraphEdge {
81
+ readonly from: EdgeEnd;
82
+ readonly to: EdgeEnd;
83
+ readonly label?: string;
84
+ }
85
+ export interface FrontendRouteGraph {
86
+ readonly junctions: readonly Junction[];
87
+ readonly edges: readonly GraphEdge[];
88
+ }
89
+ /**
90
+ * A branch arm, by either spelling.
91
+ *
92
+ * `hasOwnProperty` on the primary before falling back — see the module note. Returns a sentinel-free result: the
93
+ * caller cannot distinguish "absent" from "declared null" from the value alone, so both are returned as-is and the
94
+ * caller treats `null` as an end.
95
+ */
96
+ export declare function branchArm(branch: Record<string, unknown>, primary: 'then' | 'else', alternate: 'success' | 'failed'): unknown;
97
+ export declare function frontendRouteGraph(steps: readonly FrontendStep[],
98
+ /** Drawn inside the START terminal. The workflow's title, where the caller has one. */
99
+ startLabel?: string): FrontendRouteGraph;
100
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,kDAAkD;AAClD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,0GAA0G;IAC1G,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,wBAAwB;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,kCAAkC;AAClC,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,SAAS,GAAG,KAAK,GAAG,OAAO,CAAC;AAE9D,mFAAmF;AACnF,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;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,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,WAAW,kBAAkB;IACjC,QAAQ,CAAC,SAAS,EAAE,SAAS,QAAQ,EAAE,CAAC;IACxC,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;CACtC;AAqCD;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,SAAS,EAAE,SAAS,GAAG,QAAQ,GAC9B,OAAO,CAIT;AAYD,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,SAAS,YAAY,EAAE;AAC9B,uFAAuF;AACvF,UAAU,SAAU,GACnB,kBAAkB,CA6PpB"}
package/dist/routes.js ADDED
@@ -0,0 +1,322 @@
1
+ /**
2
+ * Where a frontend step goes next — Story 4.2 AC-2 and AC-3.
3
+ *
4
+ * Ported from `dashboard-v2/src/pages/__dev/lib/connectorWorkflowMermaid.ts`, which is a complete and working
5
+ * implementation; the port's job is to keep its semantics and drop its positional ids.
6
+ *
7
+ * ### AC-2: both spellings — on a `next` OBJECT only, which is where the reference aliases
8
+ *
9
+ * ⛔ **CORRECTED by the Story 4.2 review.** The first version applied the alias to an `IF` step's `metadata` as well,
10
+ * and that was an **invention**: dashboard reads `step.metadata.then` / `.else` *literally* (`addStepEdges`), and its
11
+ * `branchValue` is used only inside `addNextCondition` — i.e. only for a nested branch object under `next`. The CLI
12
+ * aliases the same single place. So `{ type: 'IF', metadata: { success: 'A', failed: 'B' } }` drew YES/NO edges here
13
+ * and **nothing at all** in either reference.
14
+ *
15
+ * That is precisely the failure the package split exists to prevent, committed by the package that exists to prevent
16
+ * it: accepting a document shape the runtime rejects. Measured: `success`/`failed` occur in **8 real `next` objects
17
+ * and 0 real `metadata` blocks**, so the alias was tested only on the surface where it does not belong, while the one
18
+ * place it genuinely applies had no test.
19
+ *
20
+ * Where it DOES apply, `hasOwnProperty` before falling back is load-bearing: `then: null` is a
21
+ * **declared-but-empty** arm (the frontend's way of saying "this branch ends") and is distinct from an absent `then`
22
+ * that should defer to `success`. Truthiness collapses the two and silently takes the wrong arm — which is what
23
+ * AC-2's *"does not silently lose half its edges"* is about.
24
+ *
25
+ * ### AC-3: `onCancelled` is not a route out of the happy path
26
+ *
27
+ * It is the cancel route for a pause-for-input step (`FORM`, `*_CHOICE_FORM`) and is emitted for ANY step that
28
+ * declares one, alongside whatever `next` says — the reference's comment records why: otherwise the cancel branch and
29
+ * its downstream navigation steps *"aren't left orphaned in the diagram"*.
30
+ *
31
+ * ### Expressions are NOT resolved — and the reason first given for that was false
32
+ *
33
+ * ⚠️ The original note claimed *"the frontend runtime has no such form"*. It has: **23 real `next` values** are Liquid
34
+ * templates, across `installStandaloneCallbackSteps` in LightspeedXSeries, SapoOmniV3PublicApp, ShopifyPublicApp,
35
+ * ShoplinePublicApp, TikTokShopGatewayV3 and Zalo — e.g.
36
+ * `{% assign codeSize = ... %}{% if codeSize > 0 %}CHECK_PROFILE_STATUS{% else %}GET_SETUP_INSTRUCTIONS{% endif %}`.
37
+ *
38
+ * Both the reference and this model render each as ONE unresolved node labelled with the whole template, so ~23
39
+ * genuinely reachable routes are undrawn on both sides. That is faithful, and it is a real gap.
40
+ *
41
+ * It is not closed here because the worker machinery does not transfer: the worker resolves `"QUOTED"` keys in a
42
+ * Scriban expression, whereas these name steps **bare** inside Liquid tags. Resolving them needs its own reader and
43
+ * its own decision about what to draw when a branch is undecidable statically. Deferred, with the premise corrected
44
+ * so nobody re-derives the false one.
45
+ */
46
+ const stepEnd = (key) => ({ kind: 'step', key });
47
+ /**
48
+ * A short, stable digest of an unresolved target — used as its ADDRESS segment instead of the target text.
49
+ *
50
+ * ⛔ Story 4.4 review. The segment used to be the raw target, and `renderedNodeId` sanitises everything outside
51
+ * `[A-Za-z0-9_]` to `_` — so two Liquid templates differing only in punctuation collapsed onto one mermaid id.
52
+ * Reproduced: `{% if x == 1 %}TARGET{% endif %}` and `{% if x != 1 %}TARGET{% endif %}` produce ONE node, which takes
53
+ * the second label and inherits both incoming edges as a false join, while `addressForRenderedId` then declines so the
54
+ * click resolves to nothing. It also produced 221-character ids carrying whole templates.
55
+ *
56
+ * A digest, not a counter: it is derived from the CONTENT, so it is stable under any insertion or reorder — which is
57
+ * what AD-33 actually requires — and two routes naming the same target still converge on one node, which is the
58
+ * behaviour the references have. The reader loses nothing: the junction's LABEL still carries the target verbatim.
59
+ *
60
+ * FNV-1a, because `node:crypto` is a host module this core package may not touch (AD-3).
61
+ */
62
+ function targetDigest(target) {
63
+ let hash = 0x811c9dc5;
64
+ for (let index = 0; index < target.length; index += 1) {
65
+ hash ^= target.charCodeAt(index);
66
+ hash = Math.imul(hash, 0x01000193) >>> 0;
67
+ }
68
+ return hash.toString(16).padStart(8, '0');
69
+ }
70
+ /**
71
+ * The owner of a junction that belongs to the WORKFLOW rather than to one step — an unresolved target, which every
72
+ * route naming it shares. Not a valid step key, so it cannot collide with one.
73
+ */
74
+ const WORKFLOW_OWNED = '';
75
+ const isRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
76
+ /**
77
+ * A branch arm, by either spelling.
78
+ *
79
+ * `hasOwnProperty` on the primary before falling back — see the module note. Returns a sentinel-free result: the
80
+ * caller cannot distinguish "absent" from "declared null" from the value alone, so both are returned as-is and the
81
+ * caller treats `null` as an end.
82
+ */
83
+ export function branchArm(branch, primary, alternate) {
84
+ return Object.prototype.hasOwnProperty.call(branch, primary)
85
+ ? branch[primary]
86
+ : branch[alternate];
87
+ }
88
+ /**
89
+ * Every junction and edge for ONE workflow's steps.
90
+ *
91
+ * Iterative, never recursive — a branch arm can itself be a branch, without bound, and a document is untrusted input.
92
+ */
93
+ /** `true` or the string `'true'` — the reference's `isTruthyFlag`, which 3 real declarations depend on. */
94
+ function isTruthyFlag(value) {
95
+ return value === true || String(value).trim().toLowerCase() === 'true';
96
+ }
97
+ export function frontendRouteGraph(steps,
98
+ /** Drawn inside the START terminal. The workflow's title, where the caller has one. */
99
+ startLabel = 'START') {
100
+ const known = new Set(steps.map((s) => String(s.key)));
101
+ const junctions = [];
102
+ const edges = [];
103
+ const seen = new Set();
104
+ const junction = (owner, path, kind, label) => {
105
+ /**
106
+ * `JSON.stringify`, not a space join (Story 4.2 review, MEDIUM-1).
107
+ *
108
+ * A SWITCH case label may contain spaces, and `['metadata','cases','q','if','then','unknown']` and
109
+ * `['metadata','cases','q if then','unknown']` joined to the SAME string — so one junction was never created and
110
+ * an edge resolved to the other one. The `q` diamond's YES arm pointed at the wrong box. 0 corpus labels contain a
111
+ * space, which is why this is a structural guard rather than a live fix.
112
+ */
113
+ const id = JSON.stringify([owner, ...path]);
114
+ if (!seen.has(id)) {
115
+ seen.add(id);
116
+ junctions.push({ owner, path, kind, label });
117
+ }
118
+ return { kind: 'junction', owner, path };
119
+ };
120
+ /**
121
+ * The ENTRY points, and an arrow into each — Story 4.2 review, HIGH-1 / H4.
122
+ *
123
+ * ⛔ The first version dropped `root` entirely: no field, no node, no edge. Measured: **84** `root: true` in the
124
+ * corpus, **3** of them written as the STRING `'true'`, and **two workflows declare TWO roots** —
125
+ * `GoogleSheets.yaml` and `Hubspot.yaml` `authorizationCallbackSteps` each mark a second, genuine direct-entry step
126
+ * for the OAuth callback.
127
+ *
128
+ * Not deferrable to the renderer: with no `root` bit in the model a renderer can only guess "the first step", which
129
+ * is wrong for those two and for the four workflows whose root is not first. The fact is unrecoverable downstream.
130
+ *
131
+ * Fallback to the first step, as the reference does — a workflow with no `root` still has an entry.
132
+ */
133
+ const roots = steps.filter((step) => isTruthyFlag(step.root));
134
+ const entries = roots.length > 0 ? roots : steps.slice(0, 1);
135
+ if (entries.length > 0) {
136
+ const start = junction(WORKFLOW_OWNED, ['start'], 'start', startLabel);
137
+ for (const entry of entries) {
138
+ edges.push({ from: start, to: stepEnd(String(entry.key)) });
139
+ }
140
+ }
141
+ for (const raw of steps) {
142
+ const owner = String(raw.key);
143
+ const type = String(raw.type ?? '').toUpperCase();
144
+ const metadata = isRecord(raw.metadata) ? raw.metadata : undefined;
145
+ /**
146
+ * Branch objects already entered on THIS step's walk — the cycle guard.
147
+ *
148
+ * ⛔ Without it a cyclic branch does not loop, it **kills the process**: exit 134, `FATAL ERROR: heap out of
149
+ * memory`, in seconds. The path grows two segments per iteration and the junction id is keyed by that path, so it
150
+ * never repeats and both lists grow without bound. Reachable from YAML with the parser the CLI already uses:
151
+ *
152
+ * ```yaml
153
+ * next: &loop
154
+ * if: "x"
155
+ * then: *loop
156
+ * ```
157
+ *
158
+ * The module note argued *"iterative, never recursive — a document is untrusted input"*, which was right and
159
+ * removed the only bound that existed: the reference recurses and throws a **catchable** `RangeError`. An
160
+ * uncatchable V8 fatal is strictly worse, and this package is bundled into the VS Code extension host.
161
+ *
162
+ * Identity, not depth: the same object reached twice is the cycle, however long the path. The sibling worker
163
+ * package had the identical defect and is fixed the same way.
164
+ */
165
+ const entered = new WeakSet();
166
+ /** Work list: where the arrow leaves, the route value, the path that reaches it, the label. */
167
+ const pending = [];
168
+ // ── AC-3: the cancel route, independent of the happy path and emitted first so it reads as a side exit ──
169
+ if (typeof raw.onCancelled === 'string' && raw.onCancelled.trim() !== '') {
170
+ pending.push({
171
+ from: stepEnd(owner),
172
+ value: raw.onCancelled.trim(),
173
+ path: ['onCancelled'],
174
+ label: 'Cancel',
175
+ });
176
+ }
177
+ if (type === 'IF' && metadata) {
178
+ /**
179
+ * `metadata.then` / `.else`, read LITERALLY — no alias. See the module note: aliasing here was an invention that
180
+ * accepted a shape both references reject. `metadata`, not `data`, is the frontend vocabulary; the alias is a
181
+ * property of a `next` OBJECT, and only there.
182
+ */
183
+ pending.push({
184
+ from: stepEnd(owner),
185
+ value: metadata.then,
186
+ path: ['metadata', 'then'],
187
+ label: 'YES',
188
+ });
189
+ pending.push({
190
+ from: stepEnd(owner),
191
+ value: metadata.else,
192
+ path: ['metadata', 'else'],
193
+ label: 'NO',
194
+ });
195
+ }
196
+ else if (type === 'SWITCH' && metadata) {
197
+ /**
198
+ * A frontend SWITCH reads `metadata.cases` — a RECORD of label → target — plus an optional `metadata.default`.
199
+ * The worker's SWITCH branches on its `data` KEYS instead. Two runtimes, two shapes.
200
+ */
201
+ if (isRecord(metadata.cases)) {
202
+ for (const [label, target] of Object.entries(metadata.cases)) {
203
+ pending.push({
204
+ from: stepEnd(owner),
205
+ value: target,
206
+ path: ['metadata', 'cases', label],
207
+ label,
208
+ });
209
+ }
210
+ }
211
+ if (typeof metadata.default === 'string') {
212
+ pending.push({
213
+ from: stepEnd(owner),
214
+ value: metadata.default,
215
+ path: ['metadata', 'default'],
216
+ label: 'default',
217
+ });
218
+ }
219
+ }
220
+ else if (typeof raw.next === 'string' || isRecord(raw.next)) {
221
+ pending.push({ from: stepEnd(owner), value: raw.next, path: ['next'] });
222
+ }
223
+ else {
224
+ /**
225
+ * No usable `next` ⇒ the workflow ENDS here, drawn as a terminal.
226
+ *
227
+ * The worker model has no equivalent: a worker step with no `next` simply has no outgoing edge. The frontend
228
+ * reference draws `END` explicitly, and it is worth keeping — an onboarding workflow that stops is a fact the
229
+ * reader wants stated, not inferred from an absence of arrows.
230
+ */
231
+ const end = junction(owner, ['next', 'end'], 'end', 'END');
232
+ edges.push({ from: stepEnd(owner), to: end });
233
+ }
234
+ while (pending.length > 0) {
235
+ const { from, value, path, label } = pending.shift();
236
+ const labelled = label !== undefined ? { label } : {};
237
+ if (typeof value === 'string' && value.trim() !== '') {
238
+ const target = value.trim();
239
+ if (known.has(target)) {
240
+ edges.push({ from, to: stepEnd(target), ...labelled });
241
+ continue;
242
+ }
243
+ /**
244
+ * An unresolved target is a NODE, and routes naming the SAME target CONVERGE on it.
245
+ *
246
+ * ⚠️ The first version gave each route its own node, so two steps routing to `GONE` drew two boxes labelled
247
+ * `GONE` where both references draw one with two incoming arrows (the reference ids the ghost by target, not
248
+ * by route). Convergence restored — but keyed on the target TEXT rather than the reference's
249
+ * `mermaidId(target)`, which also fixes a reference bug: two different Liquid expressions differing only in
250
+ * punctuation collapse onto one node there, and a ghost target `A-B` hijacks a real step keyed `A_B`.
251
+ *
252
+ * Owned by the WORKFLOW rather than a step, since that is what "the same target" means.
253
+ */
254
+ edges.push({
255
+ from,
256
+ // Digest, not the raw target — see `targetDigest`. Convergence is preserved: one target, one node.
257
+ to: junction(WORKFLOW_OWNED, ['unknown', targetDigest(target)], 'unknown', target),
258
+ ...labelled,
259
+ });
260
+ continue;
261
+ }
262
+ if (isRecord(value)) {
263
+ if (entered.has(value)) {
264
+ const point = junction(owner, [...path, 'cycle'], 'unknown', 'cycle: this branch refers to itself');
265
+ edges.push({ from, to: point, ...labelled });
266
+ continue;
267
+ }
268
+ entered.add(value);
269
+ /**
270
+ * A nested condition, labelled with its CONDITION where the document gives one.
271
+ *
272
+ * ⚠️ The first version labelled every one of these the literal `"IF"`, on the stated grounds that *"the
273
+ * frontend branch object carries no `if` field to draw"*. False: **all 61** real `next` branch objects in the
274
+ * corpus carry an `if:` holding the Liquid condition. The reference does label them `"IF"`, so this is an
275
+ * *"accept the change"* against the baseline rather than a bug fix — and the change is worth making, because
276
+ * the worker twin already carries its condition text and a diagram that says `IF` three times tells a reader
277
+ * nothing about which branch is which.
278
+ */
279
+ const condition = value.if;
280
+ const label = typeof condition === 'string' && condition.trim() !== ''
281
+ ? condition.trim()
282
+ : 'IF';
283
+ const point = junction(owner, [...path, 'if'], 'if', label);
284
+ edges.push({ from, to: point, ...labelled });
285
+ pending.push({
286
+ from: point,
287
+ value: branchArm(value, 'then', 'success'),
288
+ path: [...path, 'if', 'then'],
289
+ label: 'YES',
290
+ });
291
+ pending.push({
292
+ from: point,
293
+ value: branchArm(value, 'else', 'failed'),
294
+ path: [...path, 'if', 'else'],
295
+ label: 'NO',
296
+ });
297
+ continue;
298
+ }
299
+ if (value === null) {
300
+ /**
301
+ * A DECLARED end — `then: null` says this arm stops.
302
+ *
303
+ * ONE end per owning step, not one per arm (review M3): `{ then: null, else: null }` drew two, where the
304
+ * reference collapses them to a single unlabelled terminal. The label is dropped for the same reason — a
305
+ * terminal reached by both arms cannot honestly carry either arm's label.
306
+ */
307
+ const end = junction(owner, ['next', 'end'], 'end', 'END');
308
+ edges.push({ from, to: end });
309
+ continue;
310
+ }
311
+ /**
312
+ * Anything else — `undefined`, a number, an array — yields nothing, matching the reference exactly.
313
+ *
314
+ * ⚠️ Recorded rather than improved: an arm the document wrote as `then: 42` disappears silently here. Changing
315
+ * it would change the shipped diagram, which is Story 4.4's golden-suite decision to make deliberately, not a
316
+ * port's to make in passing.
317
+ */
318
+ }
319
+ }
320
+ return { junctions, edges };
321
+ }
322
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The frontend workflows, and their titles — Story 4.2 AC-1.
3
+ *
4
+ * ### A separate package, deliberately (AD-21)
5
+ *
6
+ * A frontend step is never resolved with the worker vocabulary, and this is where that becomes structural rather than
7
+ * a convention. The two runtimes genuinely differ, in four ways measured off the reference implementation
8
+ * (`dashboard-v2/src/pages/__dev/lib/connectorWorkflowMermaid.ts`):
9
+ *
10
+ * | | worker | frontend |
11
+ * | --- | --- | --- |
12
+ * | branch arms | `then` / `else` | `then` **or** `success` / `else` **or** `failed` |
13
+ * | `IF` reads | `data` | `metadata` |
14
+ * | `SWITCH` reads | `data`'s KEYS | `metadata.cases` (a record) + `metadata.default` |
15
+ * | extra route | `onError` | `onCancelled`, labelled `Cancel` |
16
+ *
17
+ * Two more the worker side has no equivalent for: an explicit **`END`** terminal when a step declares no `next`, and
18
+ * no expression handling at all — a frontend `next` is a key, a branch object, or nothing.
19
+ *
20
+ * ⛔ **So the two packages must NOT share a branch reader.** The temptation to unify them is the thing to resist:
21
+ * teaching the worker model about `success`/`failed` would make it accept documents the worker runtime rejects, which
22
+ * is exactly the invention NFR-1 forbids.
23
+ */
24
+ /** The five workflows a CONNECTOR DEFINITION may declare, in the order the reference implementation lists them. */
25
+ export declare const CONNECTOR_WORKFLOW_KEYS: readonly ["appRegistrationSteps", "authorizationSteps", "authorizationCallbackSteps", "installStandaloneCallbackSteps", "setupSteps"];
26
+ /** The one a TEMPLATE declares. Separate because it lives on a different document. */
27
+ export declare const TEMPLATE_WORKFLOW_KEYS: readonly ["creationSteps"];
28
+ export type ConnectorWorkflowKey = (typeof CONNECTOR_WORKFLOW_KEYS)[number];
29
+ export type TemplateWorkflowKey = (typeof TEMPLATE_WORKFLOW_KEYS)[number];
30
+ export type FrontendWorkflowKey = ConnectorWorkflowKey | TemplateWorkflowKey;
31
+ export declare const FRONTEND_WORKFLOW_KEYS: readonly FrontendWorkflowKey[];
32
+ /**
33
+ * A human title per workflow — AC-1's *"drawn with its own title"*.
34
+ *
35
+ * ⚠️ **PRESERVED from the recorded baseline** (Story 4.2 review, M2). The first version renamed five of the six —
36
+ * `App Registration` → `App registration`, `Profile Setup` → `Setup`, `Profile Creation` → `Creation` and so on — on
37
+ * the reasoning that the word *Steps* is noise and sentence case reads better. Two things were wrong with that: the
38
+ * baseline README records these titles as rendered output, so changing them needs an adopt/preserve/accept verdict per
39
+ * `golden-flows/README.md`, and nothing was asserting them — **setting all six to the same string left all 27 tests
40
+ * green**, because only `authorizationSteps` (the one title that happened not to change) was compared exactly.
41
+ *
42
+ * PRESERVE is the verdict: these are the titles two shipped surfaces already draw, and the case argument is a
43
+ * preference, not a defect. Each is now asserted exactly.
44
+ */
45
+ export declare const FRONTEND_WORKFLOW_TITLES: Readonly<Record<FrontendWorkflowKey, string>>;
46
+ /** Where a workflow may be declared. Reported on every node so a consumer can tell the two documents apart. */
47
+ export type FrontendSurface = 'connector' | 'template';
48
+ export declare function surfaceOf(key: FrontendWorkflowKey): FrontendSurface;
49
+ /**
50
+ * The workflows a document actually declares, in the order above.
51
+ *
52
+ * Non-empty, for the same reason the worker package requires it: a declared-but-empty workflow is a fact about the
53
+ * document, not a diagram a reader can be sent to. (The worker side shipped the looser rule and the review measured
54
+ * 4,519 corpus occurrences of an arrow into nothing.)
55
+ */
56
+ export declare function workflowsPresent(document: Record<string, unknown>,
57
+ /**
58
+ * How many of an array's items are actually DRAWABLE. Injected so this module stays free of step semantics while
59
+ * still measuring the right thing.
60
+ */
61
+ drawableCount?: (items: readonly unknown[]) => number): readonly FrontendWorkflowKey[];
62
+ //# sourceMappingURL=workflows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflows.d.ts","sourceRoot":"","sources":["../src/workflows.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,mHAAmH;AACnH,eAAO,MAAM,uBAAuB,uIAM1B,CAAC;AAEX,sFAAsF;AACtF,eAAO,MAAM,sBAAsB,4BAA6B,CAAC;AAEjE,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAC;AAC5E,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAC1E,MAAM,MAAM,mBAAmB,GAAG,oBAAoB,GAAG,mBAAmB,CAAC;AAE7E,eAAO,MAAM,sBAAsB,EAAE,SAAS,mBAAmB,EAGhE,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,wBAAwB,EAAE,QAAQ,CAC7C,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAQpC,CAAC;AAEF,+GAA+G;AAC/G,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,UAAU,CAAC;AAEvD,wBAAgB,SAAS,CAAC,GAAG,EAAE,mBAAmB,GAAG,eAAe,CAInE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;AACjC;;;GAGG;AACH,aAAa,GAAE,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,KAAK,MAChC,GACb,SAAS,mBAAmB,EAAE,CAKhC"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The frontend workflows, and their titles — Story 4.2 AC-1.
3
+ *
4
+ * ### A separate package, deliberately (AD-21)
5
+ *
6
+ * A frontend step is never resolved with the worker vocabulary, and this is where that becomes structural rather than
7
+ * a convention. The two runtimes genuinely differ, in four ways measured off the reference implementation
8
+ * (`dashboard-v2/src/pages/__dev/lib/connectorWorkflowMermaid.ts`):
9
+ *
10
+ * | | worker | frontend |
11
+ * | --- | --- | --- |
12
+ * | branch arms | `then` / `else` | `then` **or** `success` / `else` **or** `failed` |
13
+ * | `IF` reads | `data` | `metadata` |
14
+ * | `SWITCH` reads | `data`'s KEYS | `metadata.cases` (a record) + `metadata.default` |
15
+ * | extra route | `onError` | `onCancelled`, labelled `Cancel` |
16
+ *
17
+ * Two more the worker side has no equivalent for: an explicit **`END`** terminal when a step declares no `next`, and
18
+ * no expression handling at all — a frontend `next` is a key, a branch object, or nothing.
19
+ *
20
+ * ⛔ **So the two packages must NOT share a branch reader.** The temptation to unify them is the thing to resist:
21
+ * teaching the worker model about `success`/`failed` would make it accept documents the worker runtime rejects, which
22
+ * is exactly the invention NFR-1 forbids.
23
+ */
24
+ /** The five workflows a CONNECTOR DEFINITION may declare, in the order the reference implementation lists them. */
25
+ export const CONNECTOR_WORKFLOW_KEYS = [
26
+ 'appRegistrationSteps',
27
+ 'authorizationSteps',
28
+ 'authorizationCallbackSteps',
29
+ 'installStandaloneCallbackSteps',
30
+ 'setupSteps',
31
+ ];
32
+ /** The one a TEMPLATE declares. Separate because it lives on a different document. */
33
+ export const TEMPLATE_WORKFLOW_KEYS = ['creationSteps'];
34
+ export const FRONTEND_WORKFLOW_KEYS = [
35
+ ...CONNECTOR_WORKFLOW_KEYS,
36
+ ...TEMPLATE_WORKFLOW_KEYS,
37
+ ];
38
+ /**
39
+ * A human title per workflow — AC-1's *"drawn with its own title"*.
40
+ *
41
+ * ⚠️ **PRESERVED from the recorded baseline** (Story 4.2 review, M2). The first version renamed five of the six —
42
+ * `App Registration` → `App registration`, `Profile Setup` → `Setup`, `Profile Creation` → `Creation` and so on — on
43
+ * the reasoning that the word *Steps* is noise and sentence case reads better. Two things were wrong with that: the
44
+ * baseline README records these titles as rendered output, so changing them needs an adopt/preserve/accept verdict per
45
+ * `golden-flows/README.md`, and nothing was asserting them — **setting all six to the same string left all 27 tests
46
+ * green**, because only `authorizationSteps` (the one title that happened not to change) was compared exactly.
47
+ *
48
+ * PRESERVE is the verdict: these are the titles two shipped surfaces already draw, and the case argument is a
49
+ * preference, not a defect. Each is now asserted exactly.
50
+ */
51
+ export const FRONTEND_WORKFLOW_TITLES = {
52
+ appRegistrationSteps: 'App Registration',
53
+ authorizationSteps: 'Authorization',
54
+ authorizationCallbackSteps: 'Authorization Callback',
55
+ installStandaloneCallbackSteps: 'Install Standalone Callback',
56
+ setupSteps: 'Profile Setup',
57
+ creationSteps: 'Profile Creation',
58
+ };
59
+ export function surfaceOf(key) {
60
+ return CONNECTOR_WORKFLOW_KEYS.includes(key)
61
+ ? 'connector'
62
+ : 'template';
63
+ }
64
+ /**
65
+ * The workflows a document actually declares, in the order above.
66
+ *
67
+ * Non-empty, for the same reason the worker package requires it: a declared-but-empty workflow is a fact about the
68
+ * document, not a diagram a reader can be sent to. (The worker side shipped the looser rule and the review measured
69
+ * 4,519 corpus occurrences of an arrow into nothing.)
70
+ */
71
+ export function workflowsPresent(document,
72
+ /**
73
+ * How many of an array's items are actually DRAWABLE. Injected so this module stays free of step semantics while
74
+ * still measuring the right thing.
75
+ */
76
+ drawableCount = (items) => items.length) {
77
+ return FRONTEND_WORKFLOW_KEYS.filter((key) => {
78
+ const value = document[key];
79
+ return Array.isArray(value) && drawableCount(value) > 0;
80
+ });
81
+ }
82
+ //# sourceMappingURL=workflows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflows.js","sourceRoot":"","sources":["../src/workflows.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,mHAAmH;AACnH,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,sBAAsB;IACtB,oBAAoB;IACpB,4BAA4B;IAC5B,gCAAgC;IAChC,YAAY;CACJ,CAAC;AAEX,sFAAsF;AACtF,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,eAAe,CAAU,CAAC;AAMjE,MAAM,CAAC,MAAM,sBAAsB,GAAmC;IACpE,GAAG,uBAAuB;IAC1B,GAAG,sBAAsB;CAC1B,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAEjC;IACF,oBAAoB,EAAE,kBAAkB;IACxC,kBAAkB,EAAE,eAAe;IACnC,0BAA0B,EAAE,wBAAwB;IACpD,8BAA8B,EAAE,6BAA6B;IAC7D,UAAU,EAAE,eAAe;IAC3B,aAAa,EAAE,kBAAkB;CAClC,CAAC;AAKF,MAAM,UAAU,SAAS,CAAC,GAAwB;IAChD,OAAQ,uBAA6C,CAAC,QAAQ,CAAC,GAAG,CAAC;QACjE,CAAC,CAAC,WAAW;QACb,CAAC,CAAC,UAAU,CAAC;AACjB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAAiC;AACjC;;;GAGG;AACH,gBAAuD,CAAC,KAAK,EAAE,EAAE,CAC/D,KAAK,CAAC,MAAM;IAEd,OAAO,sBAAsB,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE;QAC3C,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC5B,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;AACL,CAAC"}
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@beehexa/hexasync-template-frontend-flow",
3
+ "version": "2608.15.1",
4
+ "description": "The flow model for a frontend onboarding workflow — creationSteps and the five connector workflows.",
5
+ "license": "SEE LICENSE IN ../../LICENSE",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ }
12
+ },
13
+ "main": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "typecheck": "tsc --noEmit",
20
+ "build": "tsc -p tsconfig.build.json"
21
+ },
22
+ "dependencies": {
23
+ "@beehexa/hexasync-template-model": "2608.15.1"
24
+ },
25
+ "hexasync": {
26
+ "layer": "core"
27
+ },
28
+ "devDependencies": {
29
+ "yaml": "^2.8.0"
30
+ }
31
+ }