wabachi 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/.codex-plugin/plugin.json +6 -0
  2. package/LICENSE +1 -1
  3. package/README.md +17 -21
  4. package/dist/architecture/cli.js +51 -36
  5. package/dist/architecture/cli.js.map +1 -1
  6. package/dist/architecture/documentation/react-flow.d.ts +23 -0
  7. package/dist/architecture/documentation/react-flow.js +436 -0
  8. package/dist/architecture/documentation/react-flow.js.map +1 -0
  9. package/dist/architecture/documentation/site.d.ts +10 -13
  10. package/dist/architecture/documentation/site.js +58 -24
  11. package/dist/architecture/documentation/site.js.map +1 -1
  12. package/dist/architecture/projection/react-flow.d.ts +96 -0
  13. package/dist/architecture/projection/react-flow.js +388 -0
  14. package/dist/architecture/projection/react-flow.js.map +1 -0
  15. package/dist/cli.js +53 -14
  16. package/dist/cli.js.map +1 -1
  17. package/dist/command-contract.d.ts +78 -0
  18. package/dist/command-contract.js +194 -0
  19. package/dist/command-contract.js.map +1 -0
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.js +1 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/runtime/repository.d.ts +3 -0
  24. package/dist/runtime/repository.js +140 -16
  25. package/dist/runtime/repository.js.map +1 -1
  26. package/dist/runtime/run.js +3 -1
  27. package/dist/runtime/run.js.map +1 -1
  28. package/dist/skill.d.ts +47 -0
  29. package/dist/skill.js +160 -0
  30. package/dist/skill.js.map +1 -0
  31. package/dist/working-set/codec.d.ts +7 -0
  32. package/dist/working-set/codec.js +53 -0
  33. package/dist/working-set/codec.js.map +1 -0
  34. package/dist/working-set/conflicts.d.ts +46 -0
  35. package/dist/working-set/conflicts.js +97 -0
  36. package/dist/working-set/conflicts.js.map +1 -0
  37. package/dist/working-set/derive.d.ts +34 -0
  38. package/dist/working-set/derive.js +634 -0
  39. package/dist/working-set/derive.js.map +1 -0
  40. package/dist/working-set/index.d.ts +3 -0
  41. package/dist/working-set/index.js +3 -0
  42. package/dist/working-set/index.js.map +1 -0
  43. package/dist/working-set/model.d.ts +93 -0
  44. package/dist/working-set/model.js +204 -0
  45. package/dist/working-set/model.js.map +1 -0
  46. package/dist/working-set/quality.d.ts +201 -0
  47. package/dist/working-set/quality.js +447 -0
  48. package/dist/working-set/quality.js.map +1 -0
  49. package/dist/working-set/seeds.d.ts +135 -0
  50. package/dist/working-set/seeds.js +433 -0
  51. package/dist/working-set/seeds.js.map +1 -0
  52. package/docs/USAGE.md +118 -0
  53. package/docs/examples/minimal-canon.json +24 -0
  54. package/package.json +14 -4
  55. package/skills/wabachi/SKILL.md +39 -0
@@ -1,12 +1,24 @@
1
1
  import { lstat, mkdir, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { renderDocumentationHtml } from "./html.js";
4
- import { runStructurizrStaticExport, } from "../projection/structurizr-export.js";
4
+ import { renderReactFlowStatic } from "./react-flow.js";
5
+ const HTML_ESCAPE_PATTERN = /[&<>"']/g;
6
+ const HTML_ESCAPES = {
7
+ "&": "&amp;",
8
+ "<": "&lt;",
9
+ ">": "&gt;",
10
+ '"': "&quot;",
11
+ "'": "&#39;",
12
+ };
13
+ function escapeHtml(value) {
14
+ return value.replace(HTML_ESCAPE_PATTERN, (character) => HTML_ESCAPES[character] ?? character);
15
+ }
5
16
  export const ARCHITECTURE_SITE_LAYOUT = Object.freeze({
6
17
  indexHtml: "index.html",
7
- workspaceDsl: "workspace.dsl",
8
18
  projectionLosses: "projection-losses.json",
9
19
  diagramsDirectory: "diagrams",
20
+ diagramsIndexHtml: "diagrams/index.html",
21
+ diagramsStylesheet: "diagrams/wabachi-react-flow.css",
10
22
  });
11
23
  export class ArchitectureSiteError extends Error {
12
24
  code;
@@ -45,9 +57,10 @@ function artifactPaths(outputRoot) {
45
57
  return Object.freeze({
46
58
  root: outputRoot,
47
59
  indexHtml: assertInsideRoot(outputRoot, ARCHITECTURE_SITE_LAYOUT.indexHtml),
48
- workspaceDsl: assertInsideRoot(outputRoot, ARCHITECTURE_SITE_LAYOUT.workspaceDsl),
49
60
  projectionLosses: assertInsideRoot(outputRoot, ARCHITECTURE_SITE_LAYOUT.projectionLosses),
50
61
  diagramsDirectory: assertInsideRoot(outputRoot, ARCHITECTURE_SITE_LAYOUT.diagramsDirectory),
62
+ diagramsIndexHtml: assertInsideRoot(outputRoot, ARCHITECTURE_SITE_LAYOUT.diagramsIndexHtml),
63
+ diagramsStylesheet: assertInsideRoot(outputRoot, ARCHITECTURE_SITE_LAYOUT.diagramsStylesheet),
51
64
  });
52
65
  }
53
66
  async function prepareOutput(artifacts) {
@@ -64,7 +77,12 @@ async function prepareOutput(artifacts) {
64
77
  if (verifiedRootKind !== "directory") {
65
78
  throw new ArchitectureSiteError("unsafe-path", "site output root is not a stable directory");
66
79
  }
67
- for (const target of [artifacts.indexHtml, artifacts.workspaceDsl, artifacts.projectionLosses]) {
80
+ for (const target of [
81
+ artifacts.indexHtml,
82
+ artifacts.projectionLosses,
83
+ artifacts.diagramsIndexHtml,
84
+ artifacts.diagramsStylesheet,
85
+ ]) {
68
86
  const kind = await entryKind(target);
69
87
  if (kind !== "missing") {
70
88
  throw new ArchitectureSiteError("unsafe-overwrite", `refusing to overwrite existing site artifact: ${target}`);
@@ -76,9 +94,9 @@ async function prepareOutput(artifacts) {
76
94
  }
77
95
  await mkdir(artifacts.diagramsDirectory);
78
96
  }
79
- function assertCompatibleGeneration(documentation, structurizr) {
80
- if (documentation.canonVersion !== structurizr.canonVersion || documentation.documentId !== structurizr.documentId) {
81
- throw new ArchitectureSiteError("generation-mismatch", "documentation and Structurizr projections must originate from the same Canon generation");
97
+ function assertCompatibleGeneration(documentation, reactFlow) {
98
+ if (documentation.canonVersion !== reactFlow.canonVersion || documentation.documentId !== reactFlow.documentId) {
99
+ throw new ArchitectureSiteError("generation-mismatch", "documentation and React Flow projections must originate from the same Canon generation");
82
100
  }
83
101
  }
84
102
  function siteNavigation(lossCount) {
@@ -86,7 +104,7 @@ function siteNavigation(lossCount) {
86
104
  <h2>Wabachi architecture site</h2>
87
105
  <nav aria-label="architecture-site"><ul>
88
106
  <li><a href="index.html">Documentation</a></li>
89
- <li><a href="diagrams/index.html">Structurizr diagrams</a></li>
107
+ <li><a href="diagrams/index.html">React Flow diagrams</a></li>
90
108
  <li><a href="projection-losses.json">Projection losses (${String(lossCount)})</a></li>
91
109
  </ul></nav>
92
110
  </aside>`;
@@ -100,6 +118,20 @@ function composeIndex(documentation, losses) {
100
118
  }
101
119
  return `${rendered.slice(0, insertionPoint)}${siteNavigation(losses.length)}\n${rendered.slice(insertionPoint)}`;
102
120
  }
121
+ function composeDiagrams(documentId, rendered) {
122
+ return `<!doctype html>
123
+ <html lang="en">
124
+ <head>
125
+ <meta charset="utf-8">
126
+ <meta name="viewport" content="width=device-width, initial-scale=1">
127
+ <title>${escapeHtml(documentId)} React Flow diagrams</title>
128
+ <link rel="stylesheet" href="wabachi-react-flow.css">
129
+ </head>
130
+ <body>
131
+ ${rendered.markup}
132
+ </body>
133
+ </html>`;
134
+ }
103
135
  function projectionLossReport(projection) {
104
136
  return `${JSON.stringify({
105
137
  canonVersion: projection.canonVersion,
@@ -107,40 +139,42 @@ function projectionLossReport(projection) {
107
139
  losses: projection.losses,
108
140
  }, null, 2)}\n`;
109
141
  }
110
- /** Assemble the independent documentation and Structurizr projections at one filesystem edge. */
142
+ /** Assemble documentation and the first-party React Flow projection at one filesystem edge. */
111
143
  export async function buildArchitectureSite(request) {
112
144
  if (!path.isAbsolute(request.outputRoot) || request.outputRoot.length === 0) {
113
145
  throw new ArchitectureSiteError("invalid-input", "site output root must be a non-empty absolute path");
114
146
  }
115
- assertCompatibleGeneration(request.documentation, request.structurizr);
147
+ assertCompatibleGeneration(request.documentation, request.reactFlow);
148
+ const rendered = renderReactFlowStatic(request.reactFlow);
116
149
  const outputRoot = path.resolve(request.outputRoot);
117
150
  const artifacts = artifactPaths(outputRoot);
118
151
  await prepareOutput(artifacts);
119
- await writeFile(artifacts.indexHtml, composeIndex(request.documentation, request.structurizr.losses), {
152
+ await writeFile(artifacts.indexHtml, composeIndex(request.documentation, request.reactFlow.losses), {
153
+ encoding: "utf8",
154
+ flag: "wx",
155
+ });
156
+ await writeFile(artifacts.projectionLosses, projectionLossReport(request.reactFlow), {
120
157
  encoding: "utf8",
121
158
  flag: "wx",
122
159
  });
123
- await writeFile(artifacts.workspaceDsl, request.structurizr.dsl, { encoding: "utf8", flag: "wx" });
124
- await writeFile(artifacts.projectionLosses, projectionLossReport(request.structurizr), {
160
+ await writeFile(artifacts.diagramsIndexHtml, composeDiagrams(request.reactFlow.documentId, rendered), {
125
161
  encoding: "utf8",
126
162
  flag: "wx",
127
163
  });
128
- const exportRequest = {
129
- launcher: request.launcher,
130
- workspacePath: artifacts.workspaceDsl,
131
- outputDirectory: artifacts.diagramsDirectory,
132
- };
133
- const exportResult = await (request.exportAdapter ?? runStructurizrStaticExport)(exportRequest);
134
- const complete = exportResult.status === "ok";
164
+ for (const asset of rendered.assets) {
165
+ await writeFile(assertInsideRoot(artifacts.diagramsDirectory, asset.path), asset.content, {
166
+ encoding: "utf8",
167
+ flag: "wx",
168
+ });
169
+ }
135
170
  return Object.freeze({
136
- status: complete ? "ok" : "export-failed",
137
- complete,
171
+ status: "ok",
172
+ complete: true,
138
173
  outputRoot,
139
174
  canonVersion: request.documentation.canonVersion,
140
175
  documentId: request.documentation.documentId,
141
- losses: request.structurizr.losses,
176
+ losses: request.reactFlow.losses,
142
177
  artifacts,
143
- export: exportResult,
144
178
  });
145
179
  }
146
180
  export const assembleArchitectureSite = buildArchitectureSite;
@@ -1 +1 @@
1
- {"version":3,"file":"site.js","sourceRoot":"","sources":["../../../src/architecture/documentation/site.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"site.js","sourceRoot":"","sources":["../../../src/architecture/documentation/site.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ import type { ArchitectureDocumentV1 } from "../canon/document.js";
2
+ import type { ElementKind } from "../canon/elements.js";
3
+ import type { RelationshipKind } from "../canon/relationships.js";
4
+ import type { ViewSpec } from "../canon/views.js";
5
+ export declare const REACT_FLOW_PROJECTION_LOSS_CODES: readonly ["unsupported-view-kind", "unsupported-view-reference", "unsupported-view-exclusion", "unsupported-view-root", "unsupported-presentation", "unsupported-containment", "unsupported-relationship"];
6
+ export type ReactFlowProjectionLossCode = (typeof REACT_FLOW_PROJECTION_LOSS_CODES)[number];
7
+ export interface ReactFlowProjectionLoss {
8
+ readonly code: ReactFlowProjectionLossCode;
9
+ readonly path: string;
10
+ readonly canonIds: readonly string[];
11
+ readonly message: string;
12
+ }
13
+ export interface ReactFlowPosition {
14
+ readonly x: number;
15
+ readonly y: number;
16
+ }
17
+ export interface ReactFlowNodeData {
18
+ readonly canonId: string;
19
+ readonly viewKey: string;
20
+ readonly kind: ElementKind;
21
+ readonly label: string;
22
+ }
23
+ /** The renderer-neutral subset of a React Flow node emitted by this projection. */
24
+ export interface ReactFlowNode {
25
+ readonly id: string;
26
+ readonly type: "architecture";
27
+ readonly data: ReactFlowNodeData;
28
+ readonly position: ReactFlowPosition;
29
+ readonly width: number;
30
+ readonly height: number;
31
+ readonly parentId?: string;
32
+ readonly extent?: "parent";
33
+ }
34
+ export interface ReactFlowEdgePoint extends ReactFlowPosition {
35
+ }
36
+ export interface ReactFlowEdgeSection {
37
+ readonly startPoint: ReactFlowEdgePoint;
38
+ readonly endPoint: ReactFlowEdgePoint;
39
+ readonly bendPoints: readonly ReactFlowEdgePoint[];
40
+ }
41
+ export interface ReactFlowEdgeData {
42
+ readonly viewKey: string;
43
+ readonly canonSourceId: string;
44
+ readonly canonTargetId: string;
45
+ readonly canonRelationshipId?: string;
46
+ readonly kind: RelationshipKind;
47
+ readonly interfaceId?: string;
48
+ readonly sections: readonly ReactFlowEdgeSection[];
49
+ }
50
+ /** The renderer-neutral subset of a React Flow edge emitted by this projection. */
51
+ export interface ReactFlowEdge {
52
+ readonly id: string;
53
+ readonly type: "default";
54
+ readonly source: string;
55
+ readonly target: string;
56
+ readonly label: string;
57
+ readonly data: ReactFlowEdgeData;
58
+ }
59
+ export type ReactFlowLayoutDirection = "RIGHT" | "LEFT" | "DOWN" | "UP";
60
+ export interface ReactFlowLayoutSummary {
61
+ readonly algorithm: "layered";
62
+ readonly direction: ReactFlowLayoutDirection;
63
+ readonly nodeWidth: number;
64
+ readonly nodeHeight: number;
65
+ }
66
+ export interface ReactFlowViewProjection {
67
+ readonly key: string;
68
+ readonly kind: ViewSpec["kind"];
69
+ readonly title?: string;
70
+ readonly description?: string;
71
+ readonly layout: ReactFlowLayoutSummary;
72
+ readonly nodes: readonly ReactFlowNode[];
73
+ readonly edges: readonly ReactFlowEdge[];
74
+ readonly losses: readonly ReactFlowProjectionLoss[];
75
+ }
76
+ export interface ReactFlowProjection {
77
+ readonly canonVersion: ArchitectureDocumentV1["canonVersion"];
78
+ readonly documentId: ArchitectureDocumentV1["documentId"];
79
+ readonly nodes: readonly ReactFlowNode[];
80
+ readonly edges: readonly ReactFlowEdge[];
81
+ readonly views: readonly ReactFlowViewProjection[];
82
+ readonly losses: readonly ReactFlowProjectionLoss[];
83
+ }
84
+ export interface ReactFlowProjectionOptions {
85
+ /** Project one declared view; when omitted, all declared views are projected. */
86
+ readonly viewKey?: string;
87
+ readonly nodeWidth?: number;
88
+ readonly nodeHeight?: number;
89
+ readonly layerSpacing?: number;
90
+ readonly nodeSpacing?: number;
91
+ }
92
+ /** Project validated Architecture Canon views into deterministic React Flow + ELK data. */
93
+ export declare function projectArchitectureDocumentToReactFlow(document: ArchitectureDocumentV1, options?: ReactFlowProjectionOptions): Promise<ReactFlowProjection>;
94
+ /** Project one declared Architecture Canon view for renderers that select views individually. */
95
+ export declare function projectArchitectureViewToReactFlow(document: ArchitectureDocumentV1, viewKey: string, options?: Omit<ReactFlowProjectionOptions, "viewKey">): Promise<ReactFlowViewProjection>;
96
+ export declare const projectArchitectureToReactFlow: typeof projectArchitectureDocumentToReactFlow;
@@ -0,0 +1,388 @@
1
+ import { validateArchitectureDocument } from "../canon/validate.js";
2
+ import ELK from "elkjs/lib/main.js";
3
+ const ElkLayout = ELK;
4
+ export const REACT_FLOW_PROJECTION_LOSS_CODES = [
5
+ "unsupported-view-kind",
6
+ "unsupported-view-reference",
7
+ "unsupported-view-exclusion",
8
+ "unsupported-view-root",
9
+ "unsupported-presentation",
10
+ "unsupported-containment",
11
+ "unsupported-relationship",
12
+ ];
13
+ class LossCollector {
14
+ values = [];
15
+ keys = new Set();
16
+ add(code, path, canonIds, message) {
17
+ const key = `${code}\u0000${path}`;
18
+ if (this.keys.has(key))
19
+ return;
20
+ this.keys.add(key);
21
+ this.values.push(Object.freeze({
22
+ code,
23
+ path,
24
+ canonIds: Object.freeze([...canonIds]),
25
+ message,
26
+ }));
27
+ }
28
+ finish() {
29
+ return Object.freeze([...this.values].sort((left, right) => compareStrings(left.path, right.path) || compareStrings(left.code, right.code)));
30
+ }
31
+ }
32
+ function compareStrings(left, right) {
33
+ if (left < right)
34
+ return -1;
35
+ if (left > right)
36
+ return 1;
37
+ return 0;
38
+ }
39
+ function identifierPart(value) {
40
+ return [...value]
41
+ .map((character) => {
42
+ if (/^[A-Za-z0-9]$/u.test(character))
43
+ return character;
44
+ return `_x${character.codePointAt(0)?.toString(16)}_`;
45
+ })
46
+ .join("");
47
+ }
48
+ function nodeId(viewKey, canonId) {
49
+ return `react_flow_${identifierPart(viewKey)}_element_${identifierPart(canonId)}`;
50
+ }
51
+ function relationshipKey(record) {
52
+ return [record.source, record.target, record.kind, record.interfaceId ?? ""].join("\u0000");
53
+ }
54
+ function relationshipEntries(document) {
55
+ return Object.freeze(document.relationships.map((record, index) => {
56
+ const candidate = record;
57
+ const identity = typeof candidate.id === "string" ? candidate.id : undefined;
58
+ return Object.freeze({
59
+ record,
60
+ index,
61
+ ...(identity === undefined ? {} : { identity }),
62
+ key: relationshipKey(record),
63
+ });
64
+ }));
65
+ }
66
+ function relationshipEdgeId(viewKey, relationship) {
67
+ return `react_flow_${identifierPart(viewKey)}_relationship_${identifierPart(relationship.key)}`;
68
+ }
69
+ function relationshipLabel(relationship) {
70
+ return relationship.kind;
71
+ }
72
+ function ensurePositiveFinite(value, fallback, label) {
73
+ const resolved = value ?? fallback;
74
+ if (!Number.isFinite(resolved) || resolved <= 0) {
75
+ throw new TypeError(`${label} must be a positive finite number`);
76
+ }
77
+ return Object.is(resolved, -0) ? 0 : resolved;
78
+ }
79
+ function readLayoutOptions(options) {
80
+ return {
81
+ nodeWidth: ensurePositiveFinite(options.nodeWidth, 180, "nodeWidth"),
82
+ nodeHeight: ensurePositiveFinite(options.nodeHeight, 80, "nodeHeight"),
83
+ layerSpacing: ensurePositiveFinite(options.layerSpacing, 80, "layerSpacing"),
84
+ nodeSpacing: ensurePositiveFinite(options.nodeSpacing, 40, "nodeSpacing"),
85
+ };
86
+ }
87
+ function directionFor(view, losses, viewIndex) {
88
+ const value = view.presentation?.direction;
89
+ if (value === undefined)
90
+ return "RIGHT";
91
+ switch (value.toLocaleLowerCase("en-US")) {
92
+ case "lr":
93
+ case "right":
94
+ return "RIGHT";
95
+ case "rl":
96
+ case "left":
97
+ return "LEFT";
98
+ case "tb":
99
+ case "down":
100
+ return "DOWN";
101
+ case "bt":
102
+ case "up":
103
+ return "UP";
104
+ default:
105
+ losses.add("unsupported-presentation", `views[${viewIndex}].presentation.direction`, [view.key], `React Flow + ELK cannot interpret view direction hint: ${value}`);
106
+ return "RIGHT";
107
+ }
108
+ }
109
+ function addPresentationLosses(view, viewIndex, losses) {
110
+ const presentation = view.presentation;
111
+ if (presentation === undefined)
112
+ return;
113
+ if (presentation.layout !== undefined &&
114
+ !["auto", "automatic", "layered"].includes(presentation.layout.toLocaleLowerCase("en-US"))) {
115
+ losses.add("unsupported-presentation", `views[${viewIndex}].presentation.layout`, [view.key], `React Flow + ELK cannot preserve view layout hint: ${presentation.layout}`);
116
+ }
117
+ if (presentation.grouping !== undefined) {
118
+ losses.add("unsupported-presentation", `views[${viewIndex}].presentation.grouping`, [view.key], `React Flow + ELK cannot preserve view grouping hint: ${presentation.grouping}`);
119
+ }
120
+ }
121
+ function viewPath(viewIndex, mode, referenceIndex) {
122
+ return `views[${viewIndex}].scope.${mode}[${referenceIndex}]`;
123
+ }
124
+ function addUnsupportedReference(view, viewIndex, mode, referenceIndex, reference, losses) {
125
+ const code = mode === "include" ? "unsupported-view-reference" : "unsupported-view-exclusion";
126
+ losses.add(code, viewPath(viewIndex, mode, referenceIndex), [view.key, reference.id], `React Flow + ELK cannot represent ${reference.kind} ${reference.id} in a structural view ${mode} scope`);
127
+ }
128
+ function explicitRelationshipId(reference) {
129
+ return reference.kind === "relationship" ? reference.id : undefined;
130
+ }
131
+ function relationshipByIdentity(relationships, identity) {
132
+ return relationships.find((relationship) => relationship.identity === identity);
133
+ }
134
+ function addSelectedElement(selected, elementId, elementsById) {
135
+ if (elementsById.has(elementId))
136
+ selected.add(elementId);
137
+ }
138
+ function finiteCoordinate(value) {
139
+ return value !== undefined && Number.isFinite(value) ? (Object.is(value, -0) ? 0 : value) : 0;
140
+ }
141
+ function asPoint(point) {
142
+ return Object.freeze({ x: finiteCoordinate(point.x), y: finiteCoordinate(point.y) });
143
+ }
144
+ function edgeSections(edge) {
145
+ return Object.freeze((edge.sections ?? []).map((section) => Object.freeze({
146
+ startPoint: asPoint(section.startPoint),
147
+ endPoint: asPoint(section.endPoint),
148
+ bendPoints: Object.freeze((section.bendPoints ?? []).map(asPoint)),
149
+ })));
150
+ }
151
+ function childElements(elements, parentId) {
152
+ return elements.filter((element) => element.parentId === parentId);
153
+ }
154
+ function buildElkNodeTree(elements, selected, parentId, layout) {
155
+ return childElements(elements, parentId)
156
+ .filter((element) => selected.has(element.id))
157
+ .map((element) => {
158
+ const children = buildElkNodeTree(elements, selected, element.id, layout);
159
+ return {
160
+ id: element.id,
161
+ width: layout.nodeWidth,
162
+ height: layout.nodeHeight,
163
+ ...(children.length === 0 ? {} : { children }),
164
+ };
165
+ });
166
+ }
167
+ function collectLaidOutNodes(node, result) {
168
+ for (const child of node.children ?? []) {
169
+ result.set(child.id, child);
170
+ collectLaidOutNodes(child, result);
171
+ }
172
+ }
173
+ function findElementIndex(document, id) {
174
+ return document.elements.findIndex((element) => element.id === id);
175
+ }
176
+ function projectViewGraph(document, view, viewIndex, relationships, layout) {
177
+ const losses = new LossCollector();
178
+ const elementsById = new Map(document.elements.map((element) => [element.id, element]));
179
+ const selected = new Set();
180
+ const includedRelationships = new Set();
181
+ const excludedElementIds = new Set();
182
+ const excludedRelationshipIds = new Set();
183
+ if (view.kind !== "structural") {
184
+ losses.add("unsupported-view-kind", `views[${viewIndex}]`, [view.key], `React Flow + ELK currently projects structural views only; ${view.kind} view ${view.key} is unsupported`);
185
+ return { selected, entries: Object.freeze([]), losses, direction: "RIGHT" };
186
+ }
187
+ addPresentationLosses(view, viewIndex, losses);
188
+ const direction = directionFor(view, losses, viewIndex);
189
+ if (view.root !== undefined) {
190
+ if (view.root.kind !== "element") {
191
+ losses.add("unsupported-view-root", `views[${viewIndex}].root`, [view.key, view.root.id], `React Flow + ELK structural views require an element root, not ${view.root.kind} ${view.root.id}`);
192
+ }
193
+ else {
194
+ addSelectedElement(selected, view.root.id, elementsById);
195
+ }
196
+ }
197
+ for (let referenceIndex = 0; referenceIndex < view.scope.include.length; referenceIndex += 1) {
198
+ const reference = view.scope.include[referenceIndex];
199
+ if (reference.kind === "element") {
200
+ addSelectedElement(selected, reference.id, elementsById);
201
+ }
202
+ else if (reference.kind === "relationship") {
203
+ const relationship = relationshipByIdentity(relationships, reference.id);
204
+ if (relationship !== undefined) {
205
+ includedRelationships.add(relationship);
206
+ addSelectedElement(selected, relationship.record.source, elementsById);
207
+ addSelectedElement(selected, relationship.record.target, elementsById);
208
+ }
209
+ }
210
+ else {
211
+ addUnsupportedReference(view, viewIndex, "include", referenceIndex, reference, losses);
212
+ }
213
+ }
214
+ for (let referenceIndex = 0; referenceIndex < view.scope.exclude.length; referenceIndex += 1) {
215
+ const reference = view.scope.exclude[referenceIndex];
216
+ if (reference.kind === "element") {
217
+ excludedElementIds.add(reference.id);
218
+ }
219
+ else if (reference.kind === "relationship") {
220
+ excludedRelationshipIds.add(reference.id);
221
+ }
222
+ else {
223
+ addUnsupportedReference(view, viewIndex, "exclude", referenceIndex, reference, losses);
224
+ }
225
+ }
226
+ for (const elementId of excludedElementIds)
227
+ selected.delete(elementId);
228
+ const projectedRelationships = relationships.filter((relationship) => {
229
+ const sourceSelected = selected.has(relationship.record.source);
230
+ const targetSelected = selected.has(relationship.record.target);
231
+ const explicitlyIncluded = includedRelationships.has(relationship);
232
+ const explicitlyExcluded = relationship.identity !== undefined && excludedRelationshipIds.has(relationship.identity);
233
+ if (explicitlyIncluded && !explicitlyExcluded && (!sourceSelected || !targetSelected)) {
234
+ losses.add("unsupported-relationship", `relationships[${relationship.index}]`, [relationship.record.source, relationship.record.target], `React Flow + ELK cannot preserve relationship endpoints omitted by view scope: ${relationship.record.source} -> ${relationship.record.target}`);
235
+ }
236
+ return sourceSelected && targetSelected && !explicitlyExcluded;
237
+ });
238
+ for (const element of document.elements) {
239
+ if (!selected.has(element.id) || element.parentId === undefined)
240
+ continue;
241
+ if (!selected.has(element.parentId)) {
242
+ losses.add("unsupported-containment", `elements[${findElementIndex(document, element.id)}].parentId`, [element.id, element.parentId], `React Flow + ELK cannot preserve containment parent omitted by view scope: ${element.id} -> ${element.parentId}`);
243
+ }
244
+ }
245
+ return {
246
+ selected,
247
+ entries: Object.freeze(projectedRelationships),
248
+ losses,
249
+ direction,
250
+ };
251
+ }
252
+ async function layoutGraph(document, view, selected, relationships, direction, layout) {
253
+ const elements = document.elements.filter((element) => selected.has(element.id));
254
+ const elkEdges = relationships.map((relationship) => ({
255
+ id: relationshipEdgeId(view.key, relationship),
256
+ sources: [relationship.record.source],
257
+ targets: [relationship.record.target],
258
+ }));
259
+ const graph = {
260
+ id: `react_flow_${identifierPart(view.key)}_root`,
261
+ layoutOptions: {
262
+ "elk.algorithm": "layered",
263
+ "elk.direction": direction,
264
+ "elk.edgeRouting": "ORTHOGONAL",
265
+ "elk.hierarchyHandling": "INCLUDE_CHILDREN",
266
+ "elk.layered.spacing.nodeNodeBetweenLayers": String(layout.layerSpacing),
267
+ "elk.spacing.nodeNode": String(layout.nodeSpacing),
268
+ "elk.padding": "[top=40,left=40,bottom=40,right=40]",
269
+ },
270
+ children: buildElkNodeTree(document.elements, selected, undefined, layout),
271
+ ...(elkEdges.length === 0 ? {} : { edges: elkEdges }),
272
+ };
273
+ const laidOut = await new ElkLayout().layout(graph);
274
+ const laidOutNodes = new Map();
275
+ collectLaidOutNodes(laidOut, laidOutNodes);
276
+ const nodeIds = new Map(elements.map((element) => [element.id, nodeId(view.key, element.id)]));
277
+ const nodes = elements.map((element) => {
278
+ const laidOutNode = laidOutNodes.get(element.id);
279
+ const parentNodeId = element.parentId === undefined ? undefined : nodeIds.get(element.parentId);
280
+ return Object.freeze({
281
+ id: nodeIds.get(element.id),
282
+ type: "architecture",
283
+ data: Object.freeze({
284
+ canonId: element.id,
285
+ viewKey: view.key,
286
+ kind: element.kind,
287
+ label: element.displayName ?? element.id,
288
+ }),
289
+ position: Object.freeze({
290
+ x: finiteCoordinate(laidOutNode?.x),
291
+ y: finiteCoordinate(laidOutNode?.y),
292
+ }),
293
+ width: layout.nodeWidth,
294
+ height: layout.nodeHeight,
295
+ ...(parentNodeId === undefined ? {} : { parentId: parentNodeId, extent: "parent" }),
296
+ });
297
+ });
298
+ const laidOutEdges = new Map((laidOut.edges ?? []).map((edge) => [edge.id, edge]));
299
+ const edges = relationships.map((relationship) => {
300
+ const edgeId = relationshipEdgeId(view.key, relationship);
301
+ const edge = laidOutEdges.get(edgeId);
302
+ return Object.freeze({
303
+ id: edgeId,
304
+ type: "default",
305
+ source: nodeIds.get(relationship.record.source),
306
+ target: nodeIds.get(relationship.record.target),
307
+ label: relationshipLabel(relationship.record),
308
+ data: Object.freeze({
309
+ viewKey: view.key,
310
+ canonSourceId: relationship.record.source,
311
+ canonTargetId: relationship.record.target,
312
+ ...(relationship.identity === undefined ? {} : { canonRelationshipId: relationship.identity }),
313
+ kind: relationship.record.kind,
314
+ ...(relationship.record.interfaceId === undefined ? {} : { interfaceId: relationship.record.interfaceId }),
315
+ sections: edgeSections(edge ?? {}),
316
+ }),
317
+ });
318
+ });
319
+ return { nodes: Object.freeze(nodes), edges: Object.freeze(edges) };
320
+ }
321
+ async function projectView(document, view, viewIndex, relationships, layout) {
322
+ const graph = projectViewGraph(document, view, viewIndex, relationships, layout);
323
+ const layoutSummary = Object.freeze({
324
+ algorithm: "layered",
325
+ direction: graph.direction,
326
+ nodeWidth: layout.nodeWidth,
327
+ nodeHeight: layout.nodeHeight,
328
+ });
329
+ const result = view.kind === "structural"
330
+ ? await layoutGraph(document, view, graph.selected, graph.entries, graph.direction, layout)
331
+ : { nodes: Object.freeze([]), edges: Object.freeze([]) };
332
+ return Object.freeze({
333
+ key: view.key,
334
+ kind: view.kind,
335
+ ...(view.title === undefined ? {} : { title: view.title }),
336
+ ...(view.description === undefined ? {} : { description: view.description }),
337
+ layout: layoutSummary,
338
+ nodes: result.nodes,
339
+ edges: result.edges,
340
+ losses: graph.losses.finish(),
341
+ });
342
+ }
343
+ function assertValidDocument(document) {
344
+ const validation = validateArchitectureDocument(document);
345
+ if (!validation.valid) {
346
+ const diagnostics = validation.diagnostics.map(({ code, path }) => `${code} at ${path}`).join(", ");
347
+ throw new Error(`cannot project invalid Architecture Canon: ${diagnostics}`);
348
+ }
349
+ }
350
+ /** Project validated Architecture Canon views into deterministic React Flow + ELK data. */
351
+ export async function projectArchitectureDocumentToReactFlow(document, options = {}) {
352
+ assertValidDocument(document);
353
+ const layout = readLayoutOptions(options);
354
+ const requestedView = options.viewKey;
355
+ const selectedViews = requestedView === undefined ? document.views : document.views.filter((view) => view.key === requestedView);
356
+ if (requestedView !== undefined && selectedViews.length === 0) {
357
+ throw new Error(`unknown Architecture view: ${requestedView}`);
358
+ }
359
+ const relationships = relationshipEntries(document);
360
+ const views = [];
361
+ for (let viewIndex = 0; viewIndex < document.views.length; viewIndex += 1) {
362
+ const view = document.views[viewIndex];
363
+ if (selectedViews.includes(view)) {
364
+ views.push(await projectView(document, view, viewIndex, relationships, layout));
365
+ }
366
+ }
367
+ const frozenViews = Object.freeze(views);
368
+ const nodes = Object.freeze(frozenViews.flatMap((view) => view.nodes));
369
+ const edges = Object.freeze(frozenViews.flatMap((view) => view.edges));
370
+ const losses = Object.freeze(frozenViews
371
+ .flatMap((view) => view.losses)
372
+ .sort((left, right) => compareStrings(left.path, right.path) || compareStrings(left.code, right.code)));
373
+ return Object.freeze({
374
+ canonVersion: document.canonVersion,
375
+ documentId: document.documentId,
376
+ nodes,
377
+ edges,
378
+ views: frozenViews,
379
+ losses,
380
+ });
381
+ }
382
+ /** Project one declared Architecture Canon view for renderers that select views individually. */
383
+ export async function projectArchitectureViewToReactFlow(document, viewKey, options = {}) {
384
+ const projection = await projectArchitectureDocumentToReactFlow(document, { ...options, viewKey });
385
+ return projection.views[0];
386
+ }
387
+ export const projectArchitectureToReactFlow = projectArchitectureDocumentToReactFlow;
388
+ //# sourceMappingURL=react-flow.js.map