wabachi 0.1.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 (117) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +84 -0
  3. package/dist/architecture/canon/authority.d.ts +35 -0
  4. package/dist/architecture/canon/authority.js +76 -0
  5. package/dist/architecture/canon/authority.js.map +1 -0
  6. package/dist/architecture/canon/boundaries.d.ts +31 -0
  7. package/dist/architecture/canon/boundaries.js +73 -0
  8. package/dist/architecture/canon/boundaries.js.map +1 -0
  9. package/dist/architecture/canon/codec.d.ts +10 -0
  10. package/dist/architecture/canon/codec.js +516 -0
  11. package/dist/architecture/canon/codec.js.map +1 -0
  12. package/dist/architecture/canon/constraints.d.ts +60 -0
  13. package/dist/architecture/canon/constraints.js +116 -0
  14. package/dist/architecture/canon/constraints.js.map +1 -0
  15. package/dist/architecture/canon/decisions.d.ts +62 -0
  16. package/dist/architecture/canon/decisions.js +192 -0
  17. package/dist/architecture/canon/decisions.js.map +1 -0
  18. package/dist/architecture/canon/deployment.d.ts +89 -0
  19. package/dist/architecture/canon/deployment.js +156 -0
  20. package/dist/architecture/canon/deployment.js.map +1 -0
  21. package/dist/architecture/canon/document.d.ts +60 -0
  22. package/dist/architecture/canon/document.js +106 -0
  23. package/dist/architecture/canon/document.js.map +1 -0
  24. package/dist/architecture/canon/elements.d.ts +21 -0
  25. package/dist/architecture/canon/elements.js +87 -0
  26. package/dist/architecture/canon/elements.js.map +1 -0
  27. package/dist/architecture/canon/flows.d.ts +37 -0
  28. package/dist/architecture/canon/flows.js +82 -0
  29. package/dist/architecture/canon/flows.js.map +1 -0
  30. package/dist/architecture/canon/identity.d.ts +45 -0
  31. package/dist/architecture/canon/identity.js +83 -0
  32. package/dist/architecture/canon/identity.js.map +1 -0
  33. package/dist/architecture/canon/relationships.d.ts +48 -0
  34. package/dist/architecture/canon/relationships.js +181 -0
  35. package/dist/architecture/canon/relationships.js.map +1 -0
  36. package/dist/architecture/canon/repository-mappings.d.ts +51 -0
  37. package/dist/architecture/canon/repository-mappings.js +134 -0
  38. package/dist/architecture/canon/repository-mappings.js.map +1 -0
  39. package/dist/architecture/canon/responsibilities.d.ts +40 -0
  40. package/dist/architecture/canon/responsibilities.js +89 -0
  41. package/dist/architecture/canon/responsibilities.js.map +1 -0
  42. package/dist/architecture/canon/validate.d.ts +14 -0
  43. package/dist/architecture/canon/validate.js +389 -0
  44. package/dist/architecture/canon/validate.js.map +1 -0
  45. package/dist/architecture/canon/views.d.ts +101 -0
  46. package/dist/architecture/canon/views.js +182 -0
  47. package/dist/architecture/canon/views.js.map +1 -0
  48. package/dist/architecture/cli.d.ts +2 -0
  49. package/dist/architecture/cli.js +158 -0
  50. package/dist/architecture/cli.js.map +1 -0
  51. package/dist/architecture/documentation/html.d.ts +3 -0
  52. package/dist/architecture/documentation/html.js +143 -0
  53. package/dist/architecture/documentation/html.js.map +1 -0
  54. package/dist/architecture/documentation/model.d.ts +46 -0
  55. package/dist/architecture/documentation/model.js +13 -0
  56. package/dist/architecture/documentation/model.js.map +1 -0
  57. package/dist/architecture/documentation/project.d.ts +4 -0
  58. package/dist/architecture/documentation/project.js +166 -0
  59. package/dist/architecture/documentation/project.js.map +1 -0
  60. package/dist/architecture/documentation/site.d.ts +43 -0
  61. package/dist/architecture/documentation/site.js +147 -0
  62. package/dist/architecture/documentation/site.js.map +1 -0
  63. package/dist/architecture/projection/structurizr-export.d.ts +65 -0
  64. package/dist/architecture/projection/structurizr-export.js +136 -0
  65. package/dist/architecture/projection/structurizr-export.js.map +1 -0
  66. package/dist/architecture/projection/structurizr.d.ts +34 -0
  67. package/dist/architecture/projection/structurizr.js +667 -0
  68. package/dist/architecture/projection/structurizr.js.map +1 -0
  69. package/dist/cli.d.ts +1 -0
  70. package/dist/cli.js +143 -0
  71. package/dist/cli.js.map +1 -0
  72. package/dist/index.d.ts +2 -0
  73. package/dist/index.js +6 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/runtime/correlation.d.ts +143 -0
  76. package/dist/runtime/correlation.js +919 -0
  77. package/dist/runtime/correlation.js.map +1 -0
  78. package/dist/runtime/facts.d.ts +138 -0
  79. package/dist/runtime/facts.js +614 -0
  80. package/dist/runtime/facts.js.map +1 -0
  81. package/dist/runtime/fixtureProvider.d.ts +10 -0
  82. package/dist/runtime/fixtureProvider.js +30 -0
  83. package/dist/runtime/fixtureProvider.js.map +1 -0
  84. package/dist/runtime/graftProvider.d.ts +8 -0
  85. package/dist/runtime/graftProvider.js +107 -0
  86. package/dist/runtime/graftProvider.js.map +1 -0
  87. package/dist/runtime/graftWiring.d.ts +30 -0
  88. package/dist/runtime/graftWiring.js +3 -0
  89. package/dist/runtime/graftWiring.js.map +1 -0
  90. package/dist/runtime/manifest.d.ts +17 -0
  91. package/dist/runtime/manifest.js +39 -0
  92. package/dist/runtime/manifest.js.map +1 -0
  93. package/dist/runtime/matrix.d.ts +337 -0
  94. package/dist/runtime/matrix.js +1485 -0
  95. package/dist/runtime/matrix.js.map +1 -0
  96. package/dist/runtime/observation.d.ts +36 -0
  97. package/dist/runtime/observation.js +3 -0
  98. package/dist/runtime/observation.js.map +1 -0
  99. package/dist/runtime/provider.d.ts +34 -0
  100. package/dist/runtime/provider.js +2 -0
  101. package/dist/runtime/provider.js.map +1 -0
  102. package/dist/runtime/repository.d.ts +20 -0
  103. package/dist/runtime/repository.js +55 -0
  104. package/dist/runtime/repository.js.map +1 -0
  105. package/dist/runtime/run.d.ts +21 -0
  106. package/dist/runtime/run.js +80 -0
  107. package/dist/runtime/run.js.map +1 -0
  108. package/dist/runtime/scipProvider.d.ts +11 -0
  109. package/dist/runtime/scipProvider.js +126 -0
  110. package/dist/runtime/scipProvider.js.map +1 -0
  111. package/dist/runtime/typescriptProvider.d.ts +10 -0
  112. package/dist/runtime/typescriptProvider.js +289 -0
  113. package/dist/runtime/typescriptProvider.js.map +1 -0
  114. package/dist/runtime/workflow.d.ts +30 -0
  115. package/dist/runtime/workflow.js +153 -0
  116. package/dist/runtime/workflow.js.map +1 -0
  117. package/package.json +72 -0
@@ -0,0 +1,667 @@
1
+ import { validateArchitectureDocument } from "../canon/validate.js";
2
+ export const PROJECTION_LOSS_CODES = [
3
+ "unsupported-authority",
4
+ "unsupported-ownership",
5
+ "unsupported-boundary",
6
+ "unsupported-responsibility",
7
+ "unsupported-constraint",
8
+ "unsupported-repository-mapping",
9
+ "unsupported-decision",
10
+ "unsupported-reference",
11
+ "unsupported-reference-attachment",
12
+ "unsupported-interface",
13
+ "unsupported-containment",
14
+ "unsupported-flow",
15
+ "unsupported-deployment-instance",
16
+ "unsupported-deployment-mapping",
17
+ "unsupported-infrastructure-reference",
18
+ "unsupported-view-reference",
19
+ "unsupported-view-exclusion",
20
+ "unsupported-view-scope",
21
+ "unsupported-presentation",
22
+ "ambiguous-flow-step",
23
+ ];
24
+ class LossCollector {
25
+ values = [];
26
+ keys = new Set();
27
+ add(code, path, canonIds, message) {
28
+ const key = `${code}\u0000${path}`;
29
+ if (this.keys.has(key))
30
+ return;
31
+ this.keys.add(key);
32
+ this.values.push(Object.freeze({
33
+ code,
34
+ path,
35
+ canonIds: Object.freeze([...canonIds]),
36
+ message,
37
+ }));
38
+ }
39
+ finish() {
40
+ const losses = [...this.values].sort((left, right) => {
41
+ return compareStrings(left.path, right.path) || compareStrings(left.code, right.code);
42
+ });
43
+ return Object.freeze(losses);
44
+ }
45
+ }
46
+ function quote(value) {
47
+ return JSON.stringify(value);
48
+ }
49
+ /** Encode every non-alphanumeric character, including `_`, so the mapping is collision-free. */
50
+ function identifierPart(value) {
51
+ return [...value]
52
+ .map((character) => {
53
+ if (/^[A-Za-z0-9]$/u.test(character))
54
+ return character;
55
+ return `_x${character.codePointAt(0)?.toString(16)}_`;
56
+ })
57
+ .join("");
58
+ }
59
+ function structurizrIdentifier(namespace, id) {
60
+ return `canon_${identifierPart(namespace)}_${identifierPart(id)}`;
61
+ }
62
+ function viewKey(key) {
63
+ return `canon_view_${identifierPart(key)}`;
64
+ }
65
+ function compareStrings(left, right) {
66
+ if (left < right)
67
+ return -1;
68
+ if (left > right)
69
+ return 1;
70
+ return 0;
71
+ }
72
+ function relationshipEntries(document) {
73
+ return Object.freeze(document.relationships.map((record) => {
74
+ const candidate = record;
75
+ return Object.freeze({
76
+ record,
77
+ ...(typeof candidate.id === "string" ? { explicitId: candidate.id } : {}),
78
+ });
79
+ }));
80
+ }
81
+ function indexById(records) {
82
+ return new Map(records.map((record) => [record.id, record]));
83
+ }
84
+ function mapIds(namespace, records) {
85
+ return new Map(records.map((record) => [record.id, structurizrIdentifier(namespace, record.id)]));
86
+ }
87
+ function addIdentityMappings(document) {
88
+ const mappings = [
89
+ { canonId: document.root.id, namespace: "architecture", structurizrId: "workspace" },
90
+ ...document.elements.map((record) => ({
91
+ canonId: record.id,
92
+ namespace: "element",
93
+ structurizrId: structurizrIdentifier("element", record.id),
94
+ })),
95
+ ...document.interfaces.map((record) => ({
96
+ canonId: record.id,
97
+ namespace: "interface",
98
+ structurizrId: structurizrIdentifier("interface", record.id),
99
+ })),
100
+ ...document.responsibilities.responsibilities.map((record) => ({
101
+ canonId: record.id,
102
+ namespace: "responsibility",
103
+ structurizrId: structurizrIdentifier("responsibility", record.id),
104
+ })),
105
+ ...document.boundaries.map((record) => ({
106
+ canonId: record.id,
107
+ namespace: "boundary",
108
+ structurizrId: structurizrIdentifier("boundary", record.id),
109
+ })),
110
+ ...document.flows.map((record) => ({
111
+ canonId: record.id,
112
+ namespace: "flow",
113
+ structurizrId: structurizrIdentifier("flow", record.id),
114
+ })),
115
+ ...document.decisions.decisions.map((record) => ({
116
+ canonId: record.id,
117
+ namespace: "decision",
118
+ structurizrId: structurizrIdentifier("decision", record.id),
119
+ })),
120
+ ...document.decisions.references.map((record) => ({
121
+ canonId: record.id,
122
+ namespace: "reference",
123
+ structurizrId: structurizrIdentifier("reference", record.id),
124
+ })),
125
+ ...document.deployment.runtimeEnvironments.map((record) => ({
126
+ canonId: record.id,
127
+ namespace: "runtime-environment",
128
+ structurizrId: structurizrIdentifier("runtime-environment", record.id),
129
+ })),
130
+ ...document.deployment.deploymentNodes.map((record) => ({
131
+ canonId: record.id,
132
+ namespace: "deployment-node",
133
+ structurizrId: structurizrIdentifier("deployment-node", record.id),
134
+ })),
135
+ ...document.deployment.deploymentInstances.map((record) => ({
136
+ canonId: record.id,
137
+ namespace: "deployment-instance",
138
+ structurizrId: structurizrIdentifier("deployment-instance", record.id),
139
+ })),
140
+ ...document.deployment.infrastructureReferences.map((record) => ({
141
+ canonId: record.id,
142
+ namespace: "infrastructure-reference",
143
+ structurizrId: structurizrIdentifier("infrastructure-reference", record.id),
144
+ })),
145
+ ];
146
+ mappings.sort((left, right) => compareStrings(left.namespace, right.namespace) || compareStrings(left.canonId, right.canonId));
147
+ return Object.freeze(mappings.map((mapping) => Object.freeze(mapping)));
148
+ }
149
+ function createContext(document, losses) {
150
+ const entries = relationshipEntries(document);
151
+ const explicit = new Map();
152
+ const byInterface = new Map();
153
+ for (const entry of entries) {
154
+ if (entry.explicitId !== undefined)
155
+ explicit.set(entry.explicitId, entry);
156
+ if (entry.record.interfaceId !== undefined) {
157
+ const matches = byInterface.get(entry.record.interfaceId) ?? [];
158
+ matches.push(entry);
159
+ byInterface.set(entry.record.interfaceId, matches);
160
+ }
161
+ }
162
+ return {
163
+ document,
164
+ elementIdentifiers: mapIds("element", document.elements),
165
+ interfaceIdentifiers: mapIds("interface", document.interfaces),
166
+ deploymentNodeIdentifiers: mapIds("deployment-node", document.deployment.deploymentNodes),
167
+ deploymentInstanceIdentifiers: mapIds("deployment-instance", document.deployment.deploymentInstances),
168
+ elementDslKinds: new Map(),
169
+ relationshipEntries: entries,
170
+ relationshipsByExplicitId: explicit,
171
+ relationshipsByInterface: new Map([...byInterface.entries()].map(([id, matches]) => [id, Object.freeze([...matches])])),
172
+ losses,
173
+ };
174
+ }
175
+ function elementTag(element) {
176
+ return `CanonElement,CanonKind-${identifierPart(element.kind)}`;
177
+ }
178
+ function childKind(parent, element) {
179
+ if (parent === "softwareSystem")
180
+ return "container";
181
+ if (parent === "container")
182
+ return "component";
183
+ if (parent === "component" || parent === "element")
184
+ return "element";
185
+ return element.kind === "system" || element.kind === "service" ? "softwareSystem" : "element";
186
+ }
187
+ function rootKind(element) {
188
+ return element.kind === "system" || element.kind === "service" ? "softwareSystem" : "element";
189
+ }
190
+ function renderElement(context, element, children, indent, parentKind) {
191
+ const kind = parentKind === undefined ? rootKind(element) : childKind(parentKind, element);
192
+ context.elementDslKinds.set(element.id, kind);
193
+ if (element.parentId !== undefined && kind === "element") {
194
+ const elementIndex = context.document.elements.findIndex((candidate) => candidate.id === element.id);
195
+ context.losses.add("unsupported-containment", `elements[${elementIndex}].parentId`, [element.id, element.parentId], `Structurizr cannot nest Canon element ${element.id} below ${element.parentId} at this abstraction level`);
196
+ }
197
+ const isUnnested = element.parentId !== undefined && kind === "element";
198
+ const declarationIndent = isUnnested ? 2 : indent;
199
+ const identifier = context.elementIdentifiers.get(element.id);
200
+ const lines = [`${" ".repeat(declarationIndent)}${identifier} = ${kind} ${quote(element.id)} {`];
201
+ lines.push(`${" ".repeat(declarationIndent + 1)}tags ${quote(elementTag(element))}`);
202
+ const unnestedChildren = [];
203
+ if (kind !== "element") {
204
+ for (const child of children.get(element.id) ?? []) {
205
+ if (childKind(kind, child) === "element")
206
+ unnestedChildren.push(child);
207
+ else
208
+ lines.push(...renderElement(context, child, children, declarationIndent + 1, kind));
209
+ }
210
+ }
211
+ lines.push(`${" ".repeat(declarationIndent)}}`);
212
+ if (kind === "element")
213
+ unnestedChildren.push(...(children.get(element.id) ?? []));
214
+ for (const child of unnestedChildren) {
215
+ lines.push(...renderElement(context, child, children, 2, kind));
216
+ }
217
+ return lines;
218
+ }
219
+ function renderElements(context) {
220
+ const children = new Map();
221
+ const roots = [];
222
+ for (const element of context.document.elements) {
223
+ if (element.parentId === undefined)
224
+ roots.push(element);
225
+ else {
226
+ const siblings = children.get(element.parentId) ?? [];
227
+ siblings.push(element);
228
+ children.set(element.parentId, siblings);
229
+ }
230
+ }
231
+ for (const siblings of children.values())
232
+ siblings.sort((left, right) => compareStrings(left.id, right.id));
233
+ return roots
234
+ .sort((left, right) => compareStrings(left.id, right.id))
235
+ .flatMap((element) => renderElement(context, element, children, 2));
236
+ }
237
+ function relationshipDescription(relationship, interfaceRecord) {
238
+ const parts = [`kind: ${relationship.kind}`];
239
+ if (relationship.interfaceId !== undefined)
240
+ parts.push(`interface: ${relationship.interfaceId}`);
241
+ if (interfaceRecord !== undefined)
242
+ parts.push(`owner: ${interfaceRecord.owner}`);
243
+ return parts.join("; ");
244
+ }
245
+ function interfaceTechnology(interfaceRecord) {
246
+ if (interfaceRecord === undefined)
247
+ return undefined;
248
+ return ([interfaceRecord.protocol, interfaceRecord.technology]
249
+ .filter((value) => value !== undefined)
250
+ .join("; ") || undefined);
251
+ }
252
+ function renderRelationships(context) {
253
+ const lines = [];
254
+ const interfaces = indexById(context.document.interfaces);
255
+ for (const entry of context.relationshipEntries) {
256
+ const relationship = entry.record;
257
+ const source = context.elementIdentifiers.get(relationship.source);
258
+ const target = context.elementIdentifiers.get(relationship.target);
259
+ const contract = relationship.interfaceId === undefined ? undefined : interfaces.get(relationship.interfaceId);
260
+ const technology = interfaceTechnology(contract);
261
+ const fields = [source, "->", target, quote(relationshipDescription(relationship, contract))];
262
+ if (technology !== undefined)
263
+ fields.push(quote(technology));
264
+ lines.push(` ${fields.join(" ")} {`);
265
+ const tags = ["CanonRelationship", `CanonRelationshipKind-${identifierPart(relationship.kind)}`];
266
+ if (contract !== undefined)
267
+ tags.push(`CanonInterface-${context.interfaceIdentifiers.get(contract.id)}`);
268
+ lines.push(` tags ${quote(tags.join(","))}`);
269
+ lines.push(" }");
270
+ }
271
+ return lines;
272
+ }
273
+ function renderDeploymentInstance(context, node, instance, mappings, indent) {
274
+ const lines = [];
275
+ const instancePath = `deployment.deploymentInstances[${context.document.deployment.deploymentInstances.findIndex((candidate) => candidate.id === instance.id)}]`;
276
+ const instanceTag = `CanonDeploymentInstance:${context.deploymentInstanceIdentifiers.get(instance.id)}`;
277
+ if (mappings.length === 0) {
278
+ context.losses.add("unsupported-deployment-instance", instancePath, [instance.id, node.id], `Deployment instance ${instance.id} has no Canon software-element mapping that Structurizr can instantiate`);
279
+ return lines;
280
+ }
281
+ const elements = indexById(context.document.elements);
282
+ for (const mapping of mappings) {
283
+ const element = elements.get(mapping.softwareElementId);
284
+ const elementKind = context.elementDslKinds.get(mapping.softwareElementId);
285
+ const elementIdentifier = context.elementIdentifiers.get(mapping.softwareElementId);
286
+ if (element === undefined || elementKind === undefined || elementIdentifier === undefined)
287
+ continue;
288
+ const instanceKeyword = elementKind === "softwareSystem"
289
+ ? "softwareSystemInstance"
290
+ : elementKind === "container"
291
+ ? "containerInstance"
292
+ : undefined;
293
+ if (instanceKeyword === undefined) {
294
+ const mappingIndex = context.document.deployment.mappings.findIndex((candidate) => candidate.softwareElementId === mapping.softwareElementId &&
295
+ candidate.deploymentInstanceId === mapping.deploymentInstanceId);
296
+ context.losses.add("unsupported-deployment-mapping", `deployment.mappings[${mappingIndex}]`, [mapping.softwareElementId, mapping.deploymentInstanceId], `Structurizr cannot instantiate Canon element ${element.id} of projected kind ${elementKind}`);
297
+ continue;
298
+ }
299
+ const prefix = " ".repeat(indent);
300
+ lines.push(`${prefix}${instanceKeyword} ${elementIdentifier} {`);
301
+ lines.push(`${prefix} tags ${quote(instanceTag)}`);
302
+ lines.push(`${prefix}}`);
303
+ }
304
+ return lines;
305
+ }
306
+ function renderDeployment(context) {
307
+ const lines = [];
308
+ const environments = context.document.deployment.runtimeEnvironments;
309
+ const nodes = context.document.deployment.deploymentNodes;
310
+ const instancesByNode = new Map();
311
+ const mappingsByInstance = new Map();
312
+ for (const instance of context.document.deployment.deploymentInstances) {
313
+ const values = instancesByNode.get(instance.nodeId) ?? [];
314
+ values.push(instance);
315
+ instancesByNode.set(instance.nodeId, values);
316
+ }
317
+ for (const mapping of context.document.deployment.mappings) {
318
+ const values = mappingsByInstance.get(mapping.deploymentInstanceId) ?? [];
319
+ values.push(mapping);
320
+ mappingsByInstance.set(mapping.deploymentInstanceId, values);
321
+ }
322
+ const nodesByEnvironment = new Map();
323
+ for (const node of nodes) {
324
+ const values = nodesByEnvironment.get(node.environmentId) ?? [];
325
+ values.push(node);
326
+ nodesByEnvironment.set(node.environmentId, values);
327
+ }
328
+ for (const environment of environments) {
329
+ const environmentIdentifier = context.document.deployment.runtimeEnvironments.length > 0
330
+ ? structurizrIdentifier("runtime-environment", environment.id)
331
+ : "";
332
+ const environmentLabel = environment.displayName ?? environment.id;
333
+ lines.push(` ${environmentIdentifier} = deploymentEnvironment ${quote(environmentLabel)} {`);
334
+ for (const node of nodesByEnvironment.get(environment.id) ?? []) {
335
+ const nodeIdentifier = context.deploymentNodeIdentifiers.get(node.id);
336
+ const nodeLabel = node.displayName ?? node.id;
337
+ lines.push(` ${nodeIdentifier} = deploymentNode ${quote(nodeLabel)} {`);
338
+ for (const instance of instancesByNode.get(node.id) ?? []) {
339
+ lines.push(...renderDeploymentInstance(context, node, instance, mappingsByInstance.get(instance.id) ?? [], 3));
340
+ }
341
+ lines.push(" }");
342
+ }
343
+ lines.push(" }");
344
+ }
345
+ for (const reference of context.document.deployment.infrastructureReferences) {
346
+ const index = context.document.deployment.infrastructureReferences.findIndex((candidate) => candidate.id === reference.id);
347
+ context.losses.add("unsupported-infrastructure-reference", `deployment.infrastructureReferences[${index}]`, [reference.id], `Structurizr has no lossless placement for Canon infrastructure reference ${reference.id}: ${reference.reference}`);
348
+ }
349
+ return lines;
350
+ }
351
+ function direction(value) {
352
+ if (value === undefined)
353
+ return undefined;
354
+ return ["tb", "bt", "lr", "rl"].includes(value) ? value : undefined;
355
+ }
356
+ function renderPresentation(context, view, viewIndex, lines) {
357
+ const presentation = view.presentation;
358
+ if (presentation === undefined)
359
+ return;
360
+ const path = `views[${viewIndex}].presentation`;
361
+ const layout = presentation.layout;
362
+ const viewDirection = direction(presentation.direction);
363
+ if (presentation.direction !== undefined && viewDirection === undefined) {
364
+ context.losses.add("unsupported-presentation", `${path}.direction`, [view.key], `Structurizr autoLayout does not support Canon direction ${presentation.direction}`);
365
+ }
366
+ if (layout !== undefined && layout !== "auto" && layout !== "automatic") {
367
+ context.losses.add("unsupported-presentation", `${path}.layout`, [view.key], `Structurizr DSL cannot represent Canon layout hint ${layout}`);
368
+ }
369
+ if (presentation.grouping !== undefined) {
370
+ context.losses.add("unsupported-presentation", `${path}.grouping`, [view.key], `Structurizr DSL cannot represent Canon grouping hint ${presentation.grouping}`);
371
+ }
372
+ if ((layout === "auto" || layout === "automatic" || viewDirection !== undefined) && viewDirection !== undefined) {
373
+ lines.push(` autoLayout ${viewDirection}`);
374
+ }
375
+ else if (layout === "auto" || layout === "automatic") {
376
+ lines.push(" autoLayout");
377
+ }
378
+ }
379
+ function viewPath(viewIndex, mode, referenceIndex) {
380
+ return `views[${viewIndex}].scope.${mode}[${referenceIndex}]`;
381
+ }
382
+ function addUnsupportedViewReference(context, view, path, reference) {
383
+ context.losses.add("unsupported-view-reference", path, [view.key, reference.id], `Structurizr cannot represent ${reference.kind} ${reference.id} in a ${view.kind} view scope`);
384
+ }
385
+ function renderStructuralView(context, view, viewIndex) {
386
+ const lines = [` systemLandscape ${quote(viewKey(view.key))} {`];
387
+ if (view.title !== undefined)
388
+ lines.push(` title ${quote(view.title)}`);
389
+ renderPresentation(context, view, viewIndex, lines);
390
+ for (const [mode, references] of [
391
+ ["include", view.scope.include],
392
+ ["exclude", view.scope.exclude],
393
+ ]) {
394
+ for (let referenceIndex = 0; referenceIndex < references.length; referenceIndex += 1) {
395
+ const reference = references[referenceIndex];
396
+ if (reference.kind === "element") {
397
+ lines.push(` ${mode} ${context.elementIdentifiers.get(reference.id)}`);
398
+ }
399
+ else {
400
+ addUnsupportedViewReference(context, view, viewPath(viewIndex, mode, referenceIndex), reference);
401
+ }
402
+ }
403
+ }
404
+ lines.push(" }");
405
+ return lines;
406
+ }
407
+ function matchingFlowRelationships(context, step) {
408
+ if (step.relationshipId !== undefined) {
409
+ const relationship = context.relationshipsByExplicitId.get(step.relationshipId);
410
+ if (relationship !== undefined)
411
+ return [relationship];
412
+ return [];
413
+ }
414
+ if (step.interfaceId !== undefined)
415
+ return context.relationshipsByInterface.get(step.interfaceId) ?? [];
416
+ return [];
417
+ }
418
+ function dynamicStepDescription(flow, step, relationship) {
419
+ const parts = [`flow: ${flow.id}`, `kind: ${relationship.kind}`];
420
+ if (relationship.interfaceId !== undefined)
421
+ parts.push(`interface: ${relationship.interfaceId}`);
422
+ if (step.operation !== undefined)
423
+ parts.push(`operation: ${step.operation}`);
424
+ if (step.information !== undefined)
425
+ parts.push(`information: ${step.information}`);
426
+ return parts.join("; ");
427
+ }
428
+ function renderDynamicFlow(context, flow, flowIndex, order, lines, viewKeyValue) {
429
+ if (flow.steps.length === 0) {
430
+ context.losses.add("unsupported-flow", `flows[${flowIndex}]`, [flow.id, viewKeyValue], `Structurizr dynamic views cannot show an empty Canon flow: ${flow.id}`);
431
+ return;
432
+ }
433
+ for (let stepIndex = 0; stepIndex < flow.steps.length; stepIndex += 1) {
434
+ const step = flow.steps[stepIndex];
435
+ const matches = matchingFlowRelationships(context, step);
436
+ const path = `flows[${flowIndex}].steps[${stepIndex}]`;
437
+ if (matches.length === 0) {
438
+ context.losses.add("unsupported-flow", path, [flow.id, step.relationshipId ?? step.interfaceId ?? ""], `Structurizr dynamic views require a static relationship for Canon flow step ${flow.id}[${stepIndex}]`);
439
+ continue;
440
+ }
441
+ if (matches.length > 1) {
442
+ context.losses.add("ambiguous-flow-step", path, [flow.id, step.interfaceId ?? ""], `Canon flow step ${flow.id}[${stepIndex}] matches multiple Structurizr relationships`);
443
+ }
444
+ for (const match of matches) {
445
+ if (step.interfaceId !== undefined && match.record.interfaceId !== step.interfaceId) {
446
+ context.losses.add("unsupported-flow", path, [flow.id, step.interfaceId], `Canon flow step ${flow.id}[${stepIndex}] interface does not match its projected relationship`);
447
+ }
448
+ const source = context.elementIdentifiers.get(match.record.source);
449
+ const target = context.elementIdentifiers.get(match.record.target);
450
+ lines.push(` ${order.value}: ${source} -> ${target} ${quote(dynamicStepDescription(flow, step, match.record))}`);
451
+ order.value += 1;
452
+ }
453
+ }
454
+ }
455
+ function renderDynamicView(context, view, viewIndex) {
456
+ const flowIds = view.scope.include.filter((reference) => reference.kind === "flow").map((reference) => reference.id);
457
+ const includedElements = view.scope.include.filter((reference) => reference.kind === "element");
458
+ const supportedScopes = includedElements.filter((reference) => {
459
+ const kind = context.elementDslKinds.get(reference.id);
460
+ return kind === "softwareSystem" || kind === "container";
461
+ });
462
+ let scope = "*";
463
+ if (supportedScopes.length === 1 && includedElements.length === 1) {
464
+ scope = context.elementIdentifiers.get(supportedScopes[0].id);
465
+ }
466
+ else if (includedElements.length > 0) {
467
+ context.losses.add("unsupported-view-scope", `views[${viewIndex}].scope.include`, includedElements.map((reference) => reference.id), `Structurizr dynamic views have one scope, but Canon view ${view.key} declares multiple or incompatible element scopes`);
468
+ }
469
+ const lines = [` dynamic ${scope} ${quote(viewKey(view.key))} {`];
470
+ if (view.title !== undefined)
471
+ lines.push(` title ${quote(view.title)}`);
472
+ renderPresentation(context, view, viewIndex, lines);
473
+ for (let referenceIndex = 0; referenceIndex < view.scope.include.length; referenceIndex += 1) {
474
+ const reference = view.scope.include[referenceIndex];
475
+ if (reference.kind === "flow" || reference.kind === "element")
476
+ continue;
477
+ addUnsupportedViewReference(context, view, viewPath(viewIndex, "include", referenceIndex), reference);
478
+ }
479
+ for (let referenceIndex = 0; referenceIndex < view.scope.exclude.length; referenceIndex += 1) {
480
+ const reference = view.scope.exclude[referenceIndex];
481
+ context.losses.add("unsupported-view-exclusion", viewPath(viewIndex, "exclude", referenceIndex), [view.key, reference.id], `Structurizr dynamic views have no Canon-equivalent exclude scope for ${reference.kind} ${reference.id}`);
482
+ }
483
+ const flows = indexById(context.document.flows);
484
+ const order = { value: 1 };
485
+ for (const flowId of flowIds) {
486
+ const flow = flows.get(flowId);
487
+ const flowIndex = context.document.flows.findIndex((candidate) => candidate.id === flowId);
488
+ renderDynamicFlow(context, flow, flowIndex, order, lines, view.key);
489
+ }
490
+ lines.push(" }");
491
+ return lines;
492
+ }
493
+ function deploymentEnvironmentForReference(context, reference) {
494
+ if (reference.kind === "runtime-environment")
495
+ return reference.id;
496
+ if (reference.kind === "deployment-node") {
497
+ return context.document.deployment.deploymentNodes.find((node) => node.id === reference.id)?.environmentId;
498
+ }
499
+ if (reference.kind === "deployment-instance") {
500
+ const instance = context.document.deployment.deploymentInstances.find((candidate) => candidate.id === reference.id);
501
+ return context.document.deployment.deploymentNodes.find((node) => node.id === instance?.nodeId)?.environmentId;
502
+ }
503
+ return undefined;
504
+ }
505
+ function renderDeploymentView(context, view, viewIndex) {
506
+ const environmentIds = new Set();
507
+ for (const reference of view.scope.include) {
508
+ const environment = deploymentEnvironmentForReference(context, reference);
509
+ if (environment !== undefined)
510
+ environmentIds.add(environment);
511
+ }
512
+ if (environmentIds.size !== 1) {
513
+ context.losses.add("unsupported-view-scope", `views[${viewIndex}].scope`, [view.key, ...environmentIds], `Structurizr deployment views require exactly one Canon runtime environment for view ${view.key}`);
514
+ return [];
515
+ }
516
+ const environment = [...environmentIds][0];
517
+ const elementScopes = view.scope.include.filter((reference) => reference.kind === "element" && context.elementDslKinds.get(reference.id) === "softwareSystem");
518
+ const scope = elementScopes.length === 1 ? context.elementIdentifiers.get(elementScopes[0].id) : "*";
519
+ const environmentIdentifier = context.document.deployment.runtimeEnvironments.find((candidate) => candidate.id === environment);
520
+ const lines = [
521
+ ` deployment ${scope} ${context.document.deployment.runtimeEnvironments.length > 0 ? (context.document.deployment.runtimeEnvironments.find((candidate) => candidate.id === environment) ? structurizrIdentifier("runtime-environment", environment) : quote(environment)) : quote(environment)} ${quote(viewKey(view.key))} {`,
522
+ ];
523
+ if (view.title !== undefined)
524
+ lines.push(` title ${quote(view.title)}`);
525
+ renderPresentation(context, view, viewIndex, lines);
526
+ for (const [mode, references] of [
527
+ ["include", view.scope.include],
528
+ ["exclude", view.scope.exclude],
529
+ ]) {
530
+ for (let referenceIndex = 0; referenceIndex < references.length; referenceIndex += 1) {
531
+ const reference = references[referenceIndex];
532
+ if (reference.kind === "element" || reference.kind === "deployment-node") {
533
+ const identifier = reference.kind === "element"
534
+ ? context.elementIdentifiers.get(reference.id)
535
+ : context.deploymentNodeIdentifiers.get(reference.id);
536
+ if (identifier !== undefined)
537
+ lines.push(` ${mode} ${identifier}`);
538
+ continue;
539
+ }
540
+ if (reference.kind === "runtime-environment" && reference.id === environment)
541
+ continue;
542
+ addUnsupportedViewReference(context, view, viewPath(viewIndex, mode, referenceIndex), reference);
543
+ }
544
+ }
545
+ if (environmentIdentifier === undefined) {
546
+ context.losses.add("unsupported-view-scope", `views[${viewIndex}].scope`, [view.key, environment], `Runtime environment ${environment} is not available for Structurizr deployment view ${view.key}`);
547
+ }
548
+ lines.push(" }");
549
+ return lines;
550
+ }
551
+ function renderViews(context) {
552
+ const lines = [];
553
+ const mappings = [];
554
+ for (let viewIndex = 0; viewIndex < context.document.views.length; viewIndex += 1) {
555
+ const view = context.document.views[viewIndex];
556
+ mappings.push({ canonKey: view.key, kind: view.kind, structurizrKey: viewKey(view.key) });
557
+ const rendered = view.kind === "structural"
558
+ ? renderStructuralView(context, view, viewIndex)
559
+ : view.kind === "dynamic"
560
+ ? renderDynamicView(context, view, viewIndex)
561
+ : renderDeploymentView(context, view, viewIndex);
562
+ lines.push(...rendered);
563
+ }
564
+ return { lines: Object.freeze(lines), mappings: Object.freeze(mappings.map((mapping) => Object.freeze(mapping))) };
565
+ }
566
+ function addUnsupportedCanonSemantics(context) {
567
+ const { document, losses } = context;
568
+ for (let index = 0; index < document.responsibilities.responsibilities.length; index += 1) {
569
+ const fact = document.responsibilities.responsibilities[index];
570
+ losses.add("unsupported-responsibility", `responsibilities.responsibilities[${index}]`, [fact.id, fact.target.id], `Structurizr has no lossless responsibility construct for Canon concern ${fact.concern}`);
571
+ }
572
+ for (let index = 0; index < document.authority.authority.length; index += 1) {
573
+ const fact = document.authority.authority[index];
574
+ losses.add("unsupported-authority", `authority.authority[${index}]`, [fact.concern, fact.owner], `Structurizr has no lossless authority construct for Canon concern ${fact.concern}`);
575
+ }
576
+ for (let index = 0; index < document.authority.ownership.length; index += 1) {
577
+ const fact = document.authority.ownership[index];
578
+ losses.add("unsupported-ownership", `authority.ownership[${index}]`, [fact.resource, fact.owner], `Structurizr has no lossless ownership construct for Canon resource ${fact.resource}`);
579
+ }
580
+ for (let index = 0; index < document.boundaries.length; index += 1) {
581
+ const boundary = document.boundaries[index];
582
+ losses.add("unsupported-boundary", `boundaries[${index}]`, [boundary.id, ...boundary.memberIds], `Structurizr groups cannot preserve Canon ${boundary.kind} boundary semantics for ${boundary.id}`);
583
+ }
584
+ for (let index = 0; index < document.constraints.length; index += 1) {
585
+ const constraint = document.constraints[index];
586
+ const ids = constraint.kind === "single-authority"
587
+ ? [constraint.concern]
588
+ : constraint.kind === "must-go-through"
589
+ ? [constraint.source, constraint.target, constraint.through]
590
+ : [constraint.source, constraint.target];
591
+ losses.add("unsupported-constraint", `constraints[${index}]`, ids, `Structurizr has no lossless Canon constraint construct for ${constraint.kind}`);
592
+ }
593
+ for (let index = 0; index < document.repositoryMappings.length; index += 1) {
594
+ const mapping = document.repositoryMappings[index];
595
+ losses.add("unsupported-repository-mapping", `repositoryMappings[${index}]`, [mapping.canonId], `Structurizr has no lossless repository mapping construct for ${mapping.canonId}`);
596
+ }
597
+ for (let index = 0; index < document.decisions.decisions.length; index += 1) {
598
+ const decision = document.decisions.decisions[index];
599
+ losses.add("unsupported-decision", `decisions.decisions[${index}]`, [decision.id, ...decision.targetIds, ...decision.referenceIds, ...decision.supersedes], `Structurizr ADR extensions cannot preserve Canon decision semantics for ${decision.id}`);
600
+ }
601
+ for (let index = 0; index < document.decisions.references.length; index += 1) {
602
+ const reference = document.decisions.references[index];
603
+ losses.add("unsupported-reference", `decisions.references[${index}]`, [reference.id], `Structurizr cannot preserve Canon reference ${reference.id} without a renderer-specific document source`);
604
+ }
605
+ for (let index = 0; index < document.decisions.referenceAttachments.length; index += 1) {
606
+ const attachment = document.decisions.referenceAttachments[index];
607
+ losses.add("unsupported-reference-attachment", `decisions.referenceAttachments[${index}]`, [attachment.targetId, ...attachment.referenceIds], `Structurizr cannot preserve Canon reference attachment for ${attachment.targetId}`);
608
+ }
609
+ const projectedFlows = new Set();
610
+ for (const view of document.views) {
611
+ if (view.kind === "dynamic") {
612
+ for (const reference of view.scope.include)
613
+ if (reference.kind === "flow")
614
+ projectedFlows.add(reference.id);
615
+ }
616
+ }
617
+ for (let index = 0; index < document.flows.length; index += 1) {
618
+ const flow = document.flows[index];
619
+ if (!projectedFlows.has(flow.id)) {
620
+ losses.add("unsupported-flow", `flows[${index}]`, [flow.id], `Canon flow ${flow.id} is not declared in a projected dynamic view`);
621
+ }
622
+ }
623
+ }
624
+ function renderWorkspace(context, views) {
625
+ const document = context.document;
626
+ const lines = [
627
+ `workspace ${quote(document.documentId)} {`,
628
+ " properties {",
629
+ ` ${quote("canon.version")} ${quote(String(document.canonVersion))}`,
630
+ ` ${quote("canon.documentId")} ${quote(document.documentId)}`,
631
+ ` ${quote("canon.rootId")} ${quote(document.root.id)}`,
632
+ " }",
633
+ " model {",
634
+ " !identifiers flat",
635
+ ...renderElements(context),
636
+ ...renderRelationships(context),
637
+ ...renderDeployment(context),
638
+ " }",
639
+ " views {",
640
+ ...views.map((line) => line),
641
+ " }",
642
+ "}",
643
+ ];
644
+ return `${lines.join("\n")}\n`;
645
+ }
646
+ /** Project a validated Architecture Canon into current Structurizr DSL plus explicit losses. */
647
+ export function projectArchitectureDocumentToStructurizr(document) {
648
+ const validation = validateArchitectureDocument(document);
649
+ if (!validation.valid) {
650
+ const diagnostics = validation.diagnostics.map(({ code, path }) => `${code} at ${path}`).join(", ");
651
+ throw new Error(`cannot project invalid Architecture Canon: ${diagnostics}`);
652
+ }
653
+ const losses = new LossCollector();
654
+ const context = createContext(document, losses);
655
+ const renderedViews = renderViews(context);
656
+ addUnsupportedCanonSemantics(context);
657
+ return Object.freeze({
658
+ canonVersion: document.canonVersion,
659
+ documentId: document.documentId,
660
+ dsl: renderWorkspace(context, renderedViews.lines),
661
+ identityMappings: addIdentityMappings(document),
662
+ viewMappings: renderedViews.mappings,
663
+ losses: losses.finish(),
664
+ });
665
+ }
666
+ export const projectArchitectureToStructurizr = projectArchitectureDocumentToStructurizr;
667
+ //# sourceMappingURL=structurizr.js.map