@likec4/core 1.17.1 → 1.19.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 (184) hide show
  1. package/LICENSE +1 -1
  2. package/dist/builder/index.d.mts +502 -0
  3. package/dist/builder/index.d.ts +502 -0
  4. package/dist/builder/index.js +871 -0
  5. package/dist/compute-view/index.d.mts +22 -0
  6. package/dist/compute-view/index.d.ts +22 -0
  7. package/dist/compute-view/index.js +78 -0
  8. package/dist/index.d.mts +10 -919
  9. package/dist/index.d.ts +10 -919
  10. package/dist/{index.mjs → index.js} +7 -1688
  11. package/dist/model/index.d.mts +254 -0
  12. package/dist/model/index.d.ts +254 -0
  13. package/dist/model/index.js +1 -0
  14. package/dist/shared/chunk-RAAYCPUM.M-JWF7SS.js +21 -0
  15. package/dist/shared/core.BIfgabEw.d.ts +719 -0
  16. package/dist/shared/core.BZ9Msq7w.d.mts +719 -0
  17. package/dist/shared/{core.CoLQkcYL.d.cts → core.C05RDLhi.d.ts} +460 -362
  18. package/dist/shared/{core.CoLQkcYL.d.mts → core.CmYMqgha.d.mts} +460 -362
  19. package/dist/shared/core.D6-fWbM3.d.ts +127 -0
  20. package/dist/shared/core.D74xkKkG.d.mts +148 -0
  21. package/dist/shared/core.D74xkKkG.d.ts +148 -0
  22. package/dist/shared/core.sLaWHBjR.d.mts +127 -0
  23. package/dist/shared/index.-BdzdI2C.js +7443 -0
  24. package/dist/shared/object_hash.CE_oF3I3.js +1222 -0
  25. package/dist/shared/{core.BXTs-8n7.mjs → view.CyZrG8BJ.js} +108 -47
  26. package/dist/types/index.d.mts +71 -1
  27. package/dist/types/index.d.ts +71 -1
  28. package/dist/types/index.js +20 -0
  29. package/dist/utils/index.d.mts +404 -0
  30. package/dist/utils/index.d.ts +404 -0
  31. package/dist/utils/index.js +330 -0
  32. package/package.json +59 -47
  33. package/src/builder/Builder-style1.test-d.ts +190 -0
  34. package/src/builder/Builder-style2.test-d.ts +175 -0
  35. package/src/builder/Builder.deployment.ts +224 -0
  36. package/src/builder/Builder.deploymentModel.ts +385 -0
  37. package/src/builder/Builder.element.ts +19 -19
  38. package/src/builder/Builder.model.ts +67 -42
  39. package/src/builder/Builder.ts +533 -367
  40. package/src/builder/Builder.view-common.ts +234 -0
  41. package/src/builder/Builder.view-deployment.ts +135 -0
  42. package/src/builder/Builder.view-element.ts +189 -0
  43. package/src/builder/Builder.views.ts +109 -33
  44. package/src/builder/Builder.with.ts +475 -0
  45. package/src/builder/__snapshots__/Builder-style1.spec.ts.snap +374 -0
  46. package/src/builder/__snapshots__/Builder-style2.spec.ts.snap +183 -0
  47. package/src/builder/__snapshots__/Builder.spec.ts.snap +374 -0
  48. package/src/builder/_types.ts +113 -18
  49. package/src/builder/index.ts +10 -4
  50. package/src/colors/index.ts +2 -2
  51. package/src/compute-view/compute-view.ts +83 -0
  52. package/src/compute-view/deployment-view/__test__/TestHelper.ts +236 -0
  53. package/src/compute-view/deployment-view/__test__/fixture.ts +198 -0
  54. package/src/compute-view/deployment-view/_types.ts +37 -0
  55. package/src/compute-view/deployment-view/clean-connections.ts +238 -0
  56. package/src/compute-view/deployment-view/compute.ts +125 -0
  57. package/src/compute-view/deployment-view/memory/index.ts +9 -0
  58. package/src/compute-view/deployment-view/memory/memory.ts +97 -0
  59. package/src/compute-view/deployment-view/predicates/elements.ts +144 -0
  60. package/src/compute-view/deployment-view/predicates/relation-direct.ts +237 -0
  61. package/src/compute-view/deployment-view/predicates/relation-in-out.ts +61 -0
  62. package/src/compute-view/deployment-view/predicates/relation-incoming.ts +95 -0
  63. package/src/compute-view/deployment-view/predicates/relation-outgoing.ts +88 -0
  64. package/src/compute-view/deployment-view/predicates/wildcard.ts +29 -0
  65. package/src/compute-view/deployment-view/stages/stage-exclude.ts +5 -0
  66. package/src/compute-view/deployment-view/stages/stage-final.ts +245 -0
  67. package/src/compute-view/deployment-view/stages/stage-include.ts +95 -0
  68. package/src/compute-view/deployment-view/utils.ts +276 -0
  69. package/src/compute-view/dynamic-view/__test__/fixture.ts +58 -0
  70. package/src/compute-view/dynamic-view/compute.ts +324 -0
  71. package/src/compute-view/element-view/__test__/TestHelper.ts +258 -0
  72. package/src/compute-view/element-view/__test__/__snapshots__/legacy.spec.ts.snap +458 -0
  73. package/src/compute-view/element-view/__test__/fixture.ts +675 -0
  74. package/src/compute-view/element-view/_types.ts +48 -0
  75. package/src/compute-view/element-view/clean-connections.ts +113 -0
  76. package/src/compute-view/element-view/compute.ts +315 -0
  77. package/src/compute-view/element-view/memory/NodeGroup.ts +31 -0
  78. package/src/compute-view/element-view/memory/index.ts +10 -0
  79. package/src/compute-view/element-view/memory/memory.ts +177 -0
  80. package/src/compute-view/element-view/memory/stage-exclude.ts +59 -0
  81. package/src/compute-view/element-view/memory/stage-final.ts +129 -0
  82. package/src/compute-view/element-view/memory/stage-include.ts +128 -0
  83. package/src/compute-view/element-view/predicates/_utils.ts +86 -0
  84. package/src/compute-view/element-view/predicates/element-expand.ts +33 -0
  85. package/src/compute-view/element-view/predicates/element-kind-tag.ts +26 -0
  86. package/src/compute-view/element-view/predicates/element-ref.ts +37 -0
  87. package/src/compute-view/element-view/predicates/relation-direct.ts +309 -0
  88. package/src/compute-view/element-view/predicates/relation-in-out.ts +66 -0
  89. package/src/compute-view/element-view/predicates/relation-in.ts +118 -0
  90. package/src/compute-view/element-view/predicates/relation-out.ts +116 -0
  91. package/src/compute-view/element-view/predicates/wildcard.ts +88 -0
  92. package/src/compute-view/element-view/utils.ts +59 -0
  93. package/src/compute-view/index.ts +4 -0
  94. package/src/compute-view/memory/AbstractMemory.ts +45 -0
  95. package/src/compute-view/memory/AbstractStageExclude.ts +216 -0
  96. package/src/compute-view/memory/AbstractStageInclude.ts +200 -0
  97. package/src/compute-view/memory/_types.ts +147 -0
  98. package/src/compute-view/memory/index.ts +5 -0
  99. package/src/compute-view/memory/ops.ts +43 -0
  100. package/src/compute-view/utils/__snapshots__/sortNodes.spec.ts.snap +113 -0
  101. package/src/compute-view/utils/ancestorsOfNode.ts +19 -0
  102. package/src/compute-view/utils/applyCustomElementProperties.ts +66 -0
  103. package/src/compute-view/utils/applyCustomRelationProperties.ts +41 -0
  104. package/src/compute-view/utils/applyViewRuleStyles.ts +53 -0
  105. package/src/compute-view/utils/buildComputedNodes.ts +148 -0
  106. package/src/compute-view/utils/buildElementNotations.ts +63 -0
  107. package/src/compute-view/utils/elementExpressionToPredicate.ts +50 -0
  108. package/src/compute-view/utils/link-nodes-with-edges.ts +54 -0
  109. package/src/compute-view/utils/merge-props-from-relationships.ts +128 -0
  110. package/src/compute-view/utils/relationExpressionToPredicates.ts +55 -0
  111. package/src/compute-view/utils/resolve-extended-views.ts +69 -0
  112. package/src/compute-view/utils/resolve-global-rules.ts +83 -0
  113. package/src/compute-view/utils/topological-sort.ts +256 -0
  114. package/src/compute-view/utils/uniqueTags.test.ts +42 -0
  115. package/src/compute-view/utils/uniqueTags.ts +21 -0
  116. package/src/compute-view/utils/view-hash.ts +27 -0
  117. package/src/compute-view/utils/with-readable-edges.ts +36 -0
  118. package/src/index.ts +22 -12
  119. package/src/model/DeploymentElementModel.ts +599 -0
  120. package/src/model/DeploymentModel.ts +370 -0
  121. package/src/model/ElementModel.ts +221 -0
  122. package/src/model/LikeC4Model.test-d.ts +152 -0
  123. package/src/model/LikeC4Model.ts +336 -457
  124. package/src/model/RelationModel.ts +85 -0
  125. package/src/model/__test__/fixture.ts +149 -338
  126. package/src/model/connection/Connection.ts +72 -0
  127. package/src/model/connection/deployment/DeploymentConnectionModel.ts +178 -0
  128. package/src/model/connection/deployment/find.ts +120 -0
  129. package/src/model/connection/deployment/index.ts +8 -0
  130. package/src/model/connection/index.ts +22 -0
  131. package/src/model/connection/model/ConnectionModel.ts +157 -0
  132. package/src/model/connection/model/find.ts +117 -0
  133. package/src/model/connection/model/index.ts +8 -0
  134. package/src/model/connection/ops.ts +274 -0
  135. package/src/model/guards.ts +9 -0
  136. package/src/model/index.ts +46 -2
  137. package/src/model/types.ts +112 -15
  138. package/src/model/view/EdgeModel.ts +103 -0
  139. package/src/model/view/LikeC4ViewModel.ts +226 -0
  140. package/src/model/view/NodeModel.ts +229 -0
  141. package/src/types/_common.ts +55 -1
  142. package/src/types/deployments.ts +169 -0
  143. package/src/types/element.ts +9 -5
  144. package/src/types/expression-v2.ts +172 -0
  145. package/src/types/expression.ts +17 -11
  146. package/src/types/global.ts +3 -3
  147. package/src/types/index.ts +220 -12
  148. package/src/types/model.ts +75 -12
  149. package/src/types/operators.ts +1 -1
  150. package/src/types/overview-graph.ts +2 -2
  151. package/src/types/relation.ts +18 -14
  152. package/src/types/theme.ts +0 -1
  153. package/src/types/view-notation.ts +2 -2
  154. package/src/types/view.ts +117 -41
  155. package/src/utils/{commonHead.ts → common-head.ts} +2 -0
  156. package/src/utils/const.ts +1 -0
  157. package/src/utils/fqn.ts +203 -56
  158. package/src/utils/getOrCreate.ts +42 -0
  159. package/src/utils/graphlib.ts +10 -0
  160. package/src/utils/index.ts +74 -6
  161. package/src/utils/iterable/_types.ts +5 -0
  162. package/src/utils/iterable/filter.ts +55 -0
  163. package/src/utils/iterable/flat.ts +17 -0
  164. package/src/utils/iterable/index.ts +12 -0
  165. package/src/utils/iterable/map.ts +40 -0
  166. package/src/utils/iterable/reduce.ts +24 -0
  167. package/src/utils/iterable/some.ts +36 -0
  168. package/src/utils/iterable/to.ts +19 -0
  169. package/src/utils/iterable/unique.ts +29 -0
  170. package/src/utils/object-hash.ts +3 -0
  171. package/src/utils/relations.ts +3 -64
  172. package/src/utils/set.ts +78 -0
  173. package/src/utils/string-hash.ts +16 -0
  174. package/dist/index.cjs +0 -5452
  175. package/dist/index.d.cts +0 -1053
  176. package/dist/shared/core.CoLQkcYL.d.ts +0 -734
  177. package/dist/shared/core.DIYZvpVg.cjs +0 -353
  178. package/dist/types/index.cjs +0 -70
  179. package/dist/types/index.d.cts +0 -2
  180. package/dist/types/index.mjs +0 -1
  181. package/src/builder/Builder.test-d.ts +0 -69
  182. package/src/builder/Builder.view.ts +0 -358
  183. package/src/model/LikeC4DiagramModel.ts +0 -338
  184. package/src/model/LikeC4ViewModel.ts +0 -320
@@ -0,0 +1,245 @@
1
+ import DefaultMap from 'mnemonist/default-map'
2
+ import { forEach, only, pipe } from 'remeda'
3
+ import { differenceConnections } from '../../../model/connection'
4
+ import type { RelationshipModel } from '../../../model/RelationModel'
5
+ import { isAncestor, sortByFqnHierarchically } from '../../../utils/fqn'
6
+ import { ifilter, isome, toArray } from '../../../utils/iterable'
7
+ import { difference, union } from '../../../utils/set'
8
+ import { treeFromMemoryState } from '../../memory'
9
+ import { cleanCrossBoundary, cleanRedundantRelationships } from '../clean-connections'
10
+ import type { Ctx, Memory } from '../memory'
11
+
12
+ type Elem = Ctx['Element']
13
+ /**
14
+ * This patch:
15
+ * 1. Keeps connections between leafs or having direct deployment relations
16
+ * 2. Removes cross-boundary model relations, that already exist inside boundaries
17
+ * (e.g. prefer relations inside same deployment node over relations between nodes)
18
+ * 3. Removes implicit connections between elements, if their descendants have same connection
19
+ */
20
+ export class StageFinal {
21
+ static for(memory: Memory) {
22
+ return new StageFinal(memory)
23
+ }
24
+
25
+ private constructor(
26
+ protected readonly memory: Memory,
27
+ ) {
28
+ }
29
+
30
+ public step1CleanConnections(memory: Memory): Memory {
31
+ if (memory.connections.length < 2) {
32
+ return memory
33
+ }
34
+
35
+ // const leafs = new Set<Elem>()
36
+
37
+ // for (const element of memory.final) {
38
+ // // Instance is always leaf
39
+ // if (element.isInstance()) {
40
+ // leafs.add(element)
41
+ // continue
42
+ // }
43
+ // // Check if element is ancestor of any element in elements
44
+ // let isLeaf = true
45
+ // for (const el of memory.final) {
46
+ // if (isAncestor(element.id, el.id)) {
47
+ // isLeaf = false
48
+ // break
49
+ // }
50
+ // }
51
+ // if (isLeaf) {
52
+ // leafs.add(element)
53
+ // }
54
+ // }
55
+
56
+ const connections = pipe(
57
+ memory.connections,
58
+ // Keep connections
59
+ // - between leafs
60
+ // - has direct deployment relation
61
+ // filter(c => {
62
+ // return (leafs.has(c.source) && leafs.has(c.target)) || c.hasDirectDeploymentRelation()
63
+ // }),
64
+ cleanCrossBoundary,
65
+ cleanRedundantRelationships,
66
+ )
67
+
68
+ const connectionsToExclude = differenceConnections(
69
+ memory.connections,
70
+ connections,
71
+ )
72
+ if (connectionsToExclude.length === 0) {
73
+ return memory
74
+ }
75
+ const stage = memory.stageExclude({} as any)
76
+ stage.excludeConnections(connectionsToExclude, true)
77
+ return stage.commit()
78
+ }
79
+
80
+ public step2ProcessImplicits(memory: Memory): Memory {
81
+ const implicits = union(this.memory.elements, memory.elements)
82
+ const final = union(memory.final, this.memory.explicits)
83
+
84
+ // Find connections based on same relation but different boundaries
85
+ const groupedByRelation = new DefaultMap<RelationshipModel, Set<Elem>>(() => new Set())
86
+ for (const conn of memory.connections) {
87
+ if (conn.boundary) {
88
+ for (const relation of conn.relations.model) {
89
+ groupedByRelation.get(relation).add(conn.boundary)
90
+ }
91
+ }
92
+ }
93
+ for (const [_, boundaries] of groupedByRelation) {
94
+ if (boundaries.size < 2) {
95
+ continue
96
+ }
97
+ for (const boundary of boundaries) {
98
+ if (implicits.delete(boundary)) {
99
+ final.add(boundary)
100
+ }
101
+ }
102
+ }
103
+
104
+ const snapshot = new Set(final)
105
+ const isFinalOrHasIncludedDescendant = (el: Elem) => {
106
+ for (const final of snapshot) {
107
+ if (el === final || isAncestor(el, final)) {
108
+ return true
109
+ }
110
+ }
111
+ return false
112
+ }
113
+
114
+ // Pick from implicit elements
115
+ pipe(
116
+ difference(implicits, snapshot),
117
+ ifilter(e => e.isDeploymentNode()),
118
+ toArray(),
119
+ sortByFqnHierarchically,
120
+ forEach((el) => {
121
+ // If element has more 2 or more children included
122
+ // It can "box" around
123
+ const childrensToWrap = [...el.children()].filter(isFinalOrHasIncludedDescendant).length
124
+ if (childrensToWrap >= 2) {
125
+ final.add(el)
126
+ return
127
+ }
128
+ // If Element has only one child included
129
+ // and has "similar" sibling with included descendant
130
+ if (childrensToWrap === 1 && isome(el.siblings(), isFinalOrHasIncludedDescendant)) {
131
+ final.add(el)
132
+ }
133
+ }),
134
+ )
135
+
136
+ return memory.update({ final })
137
+ }
138
+
139
+ public step3ProcessBoundaries(memory: Memory): Memory {
140
+ const boundaries = new Set<Elem>()
141
+ for (const conn of memory.connections) {
142
+ if (conn.boundary) {
143
+ boundaries.add(conn.boundary)
144
+ }
145
+ }
146
+ const tree = treeFromMemoryState<Ctx>(memory, 'final')
147
+ const stage = memory.stageExclude({} as any)
148
+
149
+ const isRemovable = (el: Elem) =>
150
+ !(
151
+ boundaries.has(el)
152
+ || memory.explicits.has(el)
153
+ || tree.hasInOut(el)
154
+ || tree.root.has(el)
155
+ )
156
+
157
+ const singleRoot = only([...tree.root])
158
+ if (singleRoot && !memory.explicits.has(singleRoot)) {
159
+ stage.exclude(singleRoot)
160
+ }
161
+
162
+ for (const el of memory.final) {
163
+ const singleChild = only(tree.children(el))
164
+ if (singleChild && !tree.hasInOut(singleChild) && isRemovable(el)) {
165
+ stage.exclude(el)
166
+ }
167
+ }
168
+
169
+ if (stage.isDirty()) {
170
+ return stage.commit()
171
+ }
172
+ return memory
173
+ }
174
+
175
+ // TODO: Lot of corner cases to cover, skip for now
176
+ // public step3FlatNodes(memory: Memory): Memory {
177
+ // // final implicits
178
+ // const explicits = new Set<Elem>([
179
+ // ...memory.explicits,
180
+ // ...memory.connections.flatMap(c => [c.source, c.target]),
181
+ // ])
182
+ // const sorted = sortParentsFirst(toArray(memory.final))
183
+
184
+ // const toplevel = new Set<Elem>(sorted)
185
+ // const children = sorted.reduce((acc, el, index, all) => {
186
+ // acc.set(
187
+ // el,
188
+ // new Set(
189
+ // all
190
+ // .slice(index + 1)
191
+ // .filter(e => isAncestor(el, e))
192
+ // .reduce((acc, el) => {
193
+ // if (!acc.some(e => isAncestor(e, el))) {
194
+ // toplevel.delete(el)
195
+ // acc.push(el)
196
+ // }
197
+ // return acc
198
+ // }, [] as Elem[]),
199
+ // ),
200
+ // )
201
+ // return acc
202
+ // }, new DefaultMap<Elem, Set<Elem>>(() => new Set()))
203
+
204
+ // const state = memory.mutableState()
205
+
206
+ // function flattenNode(node: Elem) {
207
+ // const _children = [...children.get(node)]
208
+ // if (_children.length > 1) {
209
+ // for (const child of _children) {
210
+ // flattenNode(child)
211
+ // }
212
+ // return !explicits.has(node)
213
+ // }
214
+ // if (hasAtLeast(_children, 1)) {
215
+ // if (flattenNode(_children[0])) {
216
+ // state.final.delete(_children[0])
217
+ // }
218
+ // }
219
+ // return !explicits.has(node)
220
+ // }
221
+
222
+ // const root = [...toplevel]
223
+ // for (const node of root) {
224
+ // flattenNode(node)
225
+ // if (!explicits.has(node) && children.get(node).size === 1) {
226
+ // state.final.delete(node)
227
+ // }
228
+ // }
229
+ // if (root.length === 1 && !explicits.has(root[0]!)) {
230
+ // state.final.delete(root[0]!)
231
+ // }
232
+
233
+ // return memory.update(state)
234
+ // }
235
+
236
+ public commit(): Memory {
237
+ // return []
238
+ const step1 = this.step1CleanConnections(this.memory)
239
+ const step2 = this.step2ProcessImplicits(step1)
240
+ return this.step3ProcessBoundaries(step2)
241
+ // return step2memory
242
+ // const step3m?emory = this.step3FlatNodes(step2memory)
243
+ // return step3memory
244
+ }
245
+ }
@@ -0,0 +1,95 @@
1
+ import { dropWhile, forEach, pipe, take, zip } from 'remeda'
2
+ import { findConnection, findConnectionsBetween } from '../../../model/connection/deployment'
3
+ import { isIterable } from '../../../utils'
4
+ import { toArray } from '../../../utils/iterable'
5
+ import { type CtxConnection, type CtxElement, AbstractStageInclude } from '../../memory'
6
+ import { cleanCrossBoundary, cleanRedundantRelationships } from '../clean-connections'
7
+ import { type Ctx } from '../memory/memory'
8
+
9
+ type Elem = CtxElement<Ctx>
10
+ type Connection = CtxConnection<Ctx>
11
+
12
+ export class StageInclude extends AbstractStageInclude<Ctx> {
13
+ /**
14
+ * Connects elements with existing ones in the memory
15
+ */
16
+ public override connectWithExisting(
17
+ elements: Elem | Iterable<Elem>,
18
+ direction: 'in' | 'out' | 'both' = 'both',
19
+ ): boolean {
20
+ const before = this._connections.length
21
+ const hasChanged = () => this._connections.length > before
22
+ if (!isIterable(elements)) {
23
+ if (direction === 'in' || direction === 'both') {
24
+ for (const el of this.memory.elements) {
25
+ this.addConnections(
26
+ findConnection(el, elements, 'directed'),
27
+ )
28
+ }
29
+ }
30
+ if (direction === 'out' || direction === 'both') {
31
+ this.addConnections(
32
+ findConnectionsBetween(elements, this.memory.elements, 'directed'),
33
+ )
34
+ }
35
+ return hasChanged()
36
+ }
37
+
38
+ const targets = [...elements]
39
+ if (direction === 'in' || direction === 'both') {
40
+ for (const el of this.memory.elements) {
41
+ this.addConnections(
42
+ findConnectionsBetween(el, targets, 'directed'),
43
+ )
44
+ }
45
+ }
46
+ if (direction === 'out' || direction === 'both') {
47
+ for (const el of targets) {
48
+ this.addConnections(
49
+ findConnectionsBetween(el, this.memory.elements, 'directed'),
50
+ )
51
+ }
52
+ }
53
+ return hasChanged()
54
+ }
55
+
56
+ protected override processConnections(connections: Connection[]) {
57
+ const clean = pipe(
58
+ connections,
59
+ cleanCrossBoundary,
60
+ cleanRedundantRelationships,
61
+ )
62
+
63
+ pipe(
64
+ clean,
65
+ // Process only connection from this stage
66
+ // filter(c => this._connections.some(c2 => c2.id === c.id)),
67
+ forEach(({ source, target, boundary }) => {
68
+ pipe(
69
+ zip(
70
+ [...toArray(source.ancestors()).reverse(), source],
71
+ [...toArray(target.ancestors()).reverse(), target],
72
+ ),
73
+ // Filter out common ancestors
74
+ dropWhile(([sourceAncestor, targetAncestor]) => sourceAncestor === targetAncestor),
75
+ take(1),
76
+ forEach(([sourceAncestor, targetAncestor]) => {
77
+ if (source === sourceAncestor && target === targetAncestor) {
78
+ this.addImplicit(boundary)
79
+ return
80
+ }
81
+ if (sourceAncestor !== source && sourceAncestor.isDeploymentNode() && !sourceAncestor.onlyOneInstance()) {
82
+ // state.final.add(source)
83
+ this.addImplicit(sourceAncestor)
84
+ }
85
+ if (targetAncestor !== target && targetAncestor.isDeploymentNode() && !targetAncestor.onlyOneInstance()) {
86
+ // state.final.add(source)
87
+ this.addImplicit(targetAncestor)
88
+ }
89
+ }),
90
+ )
91
+ }),
92
+ )
93
+ return clean
94
+ }
95
+ }
@@ -0,0 +1,276 @@
1
+ import { hasAtLeast, isEmpty, isNumber, isString } from 'remeda'
2
+ import { invariant, nonexhaustive } from '../../errors'
3
+ import type { LikeC4DeploymentModel } from '../../model'
4
+ import type { DeploymentConnectionModel } from '../../model/connection/deployment'
5
+ import type { ElementModel } from '../../model/ElementModel'
6
+ import type { AnyAux } from '../../model/types'
7
+ import {
8
+ type ComputedEdge,
9
+ type ComputedNode,
10
+ DefaultArrowType,
11
+ type DeploymentNodeKind,
12
+ type DeploymentViewRule,
13
+ type Fqn,
14
+ FqnExpr,
15
+ isViewRuleStyle,
16
+ type NonEmptyArray,
17
+ type Tag,
18
+ } from '../../types'
19
+ import { nameFromFqn, parentFqn } from '../../utils'
20
+ import { applyViewRuleStyle } from '../utils/applyViewRuleStyles'
21
+ import { buildComputedNodes, type ComputedNodeSource } from '../utils/buildComputedNodes'
22
+ import { mergePropsFromRelationships } from '../utils/merge-props-from-relationships'
23
+ import { uniqueTags } from '../utils/uniqueTags'
24
+ import type { Elem, Memory } from './_types'
25
+
26
+ type Predicate<T> = (x: T) => boolean
27
+
28
+ export function resolveElements(
29
+ model: LikeC4DeploymentModel,
30
+ expr: FqnExpr.DeploymentRef,
31
+ ): Elem[] {
32
+ const ref = model.element(expr.ref.deployment)
33
+ if (ref.isDeploymentNode()) {
34
+ if (expr.selector === 'children') {
35
+ return [...ref.children()]
36
+ }
37
+ if (expr.selector === 'expanded') {
38
+ return [ref, ...ref.children()]
39
+ }
40
+ if (expr.selector === 'descendants') {
41
+ return [...ref.descendants()]
42
+ }
43
+ }
44
+ return [ref]
45
+ }
46
+
47
+ export function resolveModelElements(
48
+ model: LikeC4DeploymentModel,
49
+ expr: FqnExpr.ModelRef,
50
+ ): ElementModel[] {
51
+ const ref = model.$model.element(expr.ref.model)
52
+ if (expr.selector === 'children') {
53
+ return [...ref.children()]
54
+ }
55
+ if (expr.selector === 'expanded') {
56
+ return [ref, ...ref.children()]
57
+ }
58
+ if (expr.selector === 'descendants') {
59
+ return [...ref.descendants()]
60
+ }
61
+ return [ref]
62
+ }
63
+
64
+ export function deploymentExpressionToPredicate<T extends { id: string; modelRef?: number | string }>(
65
+ target: FqnExpr,
66
+ ): Predicate<T> {
67
+ if (FqnExpr.isWildcard(target)) {
68
+ return () => true
69
+ }
70
+ if (FqnExpr.isDeploymentRef(target)) {
71
+ const fqn = target.ref.deployment
72
+ if (target.selector === 'expanded') {
73
+ const fqnWithDot = fqn + '.'
74
+ return n => n.id === fqn || n.id.startsWith(fqnWithDot)
75
+ }
76
+ if (target.selector === 'descendants') {
77
+ const fqnWithDot = fqn + '.'
78
+ return n => n.id.startsWith(fqnWithDot)
79
+ }
80
+ if (target.selector === 'children') {
81
+ return n => parentFqn(n.id) === fqn
82
+ }
83
+ return n => n.id === fqn
84
+ }
85
+ if (FqnExpr.isModelRef(target)) {
86
+ const modelFqn = (node: T) => {
87
+ if (isString(node.modelRef)) {
88
+ return node.modelRef
89
+ }
90
+ if (isNumber(node.modelRef)) {
91
+ return node.id
92
+ }
93
+ return null
94
+ }
95
+
96
+ const fqn = target.ref.model
97
+ if (target.selector === 'expanded') {
98
+ const fqnWithDot = fqn + '.'
99
+ return (n) => {
100
+ const m = modelFqn(n)
101
+ if (!m) {
102
+ return true
103
+ }
104
+ return m === fqn || m.startsWith(fqnWithDot)
105
+ }
106
+ }
107
+ if (target.selector === 'descendants') {
108
+ const fqnWithDot = fqn + '.'
109
+ return (n) => {
110
+ const m = modelFqn(n)
111
+ if (!m) {
112
+ return true
113
+ }
114
+ return m.startsWith(fqnWithDot)
115
+ }
116
+ }
117
+ if (target.selector === 'children') {
118
+ return (n) => {
119
+ const m = modelFqn(n)
120
+ if (!m) {
121
+ return true
122
+ }
123
+ return parentFqn(m) === fqn
124
+ }
125
+ }
126
+ return (n) => {
127
+ const m = modelFqn(n)
128
+ if (!m) {
129
+ return true
130
+ }
131
+ return m === fqn
132
+ }
133
+ }
134
+ nonexhaustive(target)
135
+ }
136
+
137
+ export function toNodeSource(el: Elem): ComputedNodeSource {
138
+ if (el.isDeploymentNode()) {
139
+ const onlyOneInstance = el.onlyOneInstance()
140
+ let { title, kind, id, ...$node } = el.$node
141
+ const { icon, color, shape, ...style } = el.$node.style ?? {}
142
+
143
+ // If there is only one instance and title was not overriden
144
+ if (onlyOneInstance && title === nameFromFqn(el.id)) {
145
+ title = onlyOneInstance.title
146
+ }
147
+
148
+ return {
149
+ ...(onlyOneInstance && {
150
+ ...toNodeSource(onlyOneInstance),
151
+ modelRef: onlyOneInstance.element.id,
152
+ }),
153
+ title,
154
+ ...$node,
155
+ ...(icon && { icon }),
156
+ ...(color && { color }),
157
+ ...(shape && { shape }),
158
+ style: {
159
+ ...style,
160
+ },
161
+ deploymentRef: 1,
162
+ kind,
163
+ id,
164
+ }
165
+ }
166
+ invariant(el.isInstance(), 'Expected Instance')
167
+ const instance = el.$instance
168
+ const element = el.element.$element
169
+
170
+ const icon = instance.style?.icon ?? element.icon
171
+ const color = instance.style?.color ?? element.color
172
+ const shape = instance.style?.shape ?? element.shape
173
+
174
+ const links = [
175
+ ...(element.links ?? []),
176
+ ...(instance.links ?? []),
177
+ ]
178
+
179
+ const metadata = {
180
+ ...element.metadata,
181
+ ...instance.metadata,
182
+ }
183
+
184
+ const notation = instance.notation ?? element.notation
185
+
186
+ return {
187
+ id: el.id,
188
+ kind: 'instance' as DeploymentNodeKind,
189
+ title: instance.title ?? element.title,
190
+ description: instance.description ?? element.description,
191
+ technology: instance.technology ?? element.technology,
192
+ tags: uniqueTags([element, instance]) as NonEmptyArray<Tag>,
193
+ links: hasAtLeast(links, 1) ? links : null,
194
+ ...icon && { icon },
195
+ ...color && { color },
196
+ ...shape && { shape },
197
+ style: {
198
+ ...element.style,
199
+ ...instance.style,
200
+ },
201
+ deploymentRef: el.id === instance.id ? 1 : instance.id,
202
+ modelRef: el.id === element.id ? 1 : element.id,
203
+ ...notation && { notation },
204
+ ...!isEmpty(metadata) && ({ metadata }),
205
+ }
206
+ }
207
+
208
+ export function toComputedEdges<M extends AnyAux>(
209
+ connections: ReadonlyArray<DeploymentConnectionModel<M>>,
210
+ ): ComputedEdge[] {
211
+ return connections.reduce((acc, e) => {
212
+ // const modelRelations = []
213
+ // const deploymentRelations = []
214
+ const relations = [
215
+ ...e.relations.model,
216
+ ...e.relations.deployment,
217
+ ]
218
+ invariant(hasAtLeast(relations, 1), 'Edge must have at least one relation')
219
+
220
+ const source = e.source.id
221
+ const target = e.target.id
222
+
223
+ const {
224
+ title,
225
+ ...props
226
+ } = mergePropsFromRelationships(relations.map(r => r.$relationship)) // || relations.find(r => r.source === source && r.target === target)
227
+
228
+ const edge: ComputedEdge = {
229
+ id: e.id,
230
+ parent: e.boundary?.id ?? null,
231
+ source,
232
+ target,
233
+ label: title ?? null,
234
+ relations: relations.map((r) => r.id as M['RelationId']),
235
+ ...props,
236
+ }
237
+
238
+ // If exists same edge but in opposite direction
239
+ const existing = acc.find(e => e.source === target && e.target === source)
240
+ if (existing && edge.label === existing.label) {
241
+ existing.dir = 'both'
242
+ const head = existing.head ?? edge.head ?? DefaultArrowType
243
+ existing.head ??= head
244
+ existing.tail ??= head
245
+
246
+ if (edge.color) {
247
+ existing.color ??= edge.color
248
+ }
249
+ if (edge.line) {
250
+ existing.line ??= edge.line
251
+ }
252
+ return acc
253
+ }
254
+
255
+ acc.push(edge)
256
+ return acc
257
+ }, [] as ComputedEdge[])
258
+ }
259
+
260
+ export function buildNodes(memory: Memory): ReadonlyMap<Fqn, ComputedNode> {
261
+ return buildComputedNodes([...memory.final].map(toNodeSource))
262
+ }
263
+
264
+ export function applyDeploymentViewRuleStyles(
265
+ rules: DeploymentViewRule[],
266
+ nodes: ComputedNode[],
267
+ ): ComputedNode[] {
268
+ for (const rule of rules) {
269
+ if (!isViewRuleStyle(rule) || rule.targets.length === 0) {
270
+ continue
271
+ }
272
+ const predicates = rule.targets.map(deploymentExpressionToPredicate)
273
+ applyViewRuleStyle(rule, predicates, nodes)
274
+ }
275
+ return nodes
276
+ }
@@ -0,0 +1,58 @@
1
+ import { partition } from 'remeda'
2
+ import type { DynamicViewRule, DynamicViewStep, Fqn, ViewId, ViewRulePredicate } from '../../../types'
3
+ import { type FakeElementIds, fakeModel } from '../../element-view/__test__/fixture'
4
+ import { computeDynamicView } from '../compute'
5
+
6
+ const emptyView = {
7
+ __: 'dynamic' as const,
8
+ id: 'index' as ViewId,
9
+ title: null,
10
+ description: null,
11
+ tags: null,
12
+ links: null,
13
+ customColorDefinitions: {},
14
+ rules: []
15
+ }
16
+
17
+ type StepExpr = `${FakeElementIds} ${'->' | '<-'} ${FakeElementIds}`
18
+ type StepProps = Omit<DynamicViewStep, 'source' | 'target' | 'isBackward'>
19
+
20
+ export function $step(expr: StepExpr, props?: string | Partial<StepProps>): DynamicViewStep {
21
+ const title = typeof props === 'string' ? props : props?.title
22
+ if (expr.includes(' -> ')) {
23
+ const [source, target] = expr.split(' -> ')
24
+ return {
25
+ source: source as Fqn,
26
+ target: target as Fqn,
27
+ ...(typeof props === 'object' ? props : {}),
28
+ title: title ?? null
29
+ }
30
+ }
31
+ if (expr.includes(' <- ')) {
32
+ const [target, source] = expr.split(' <- ')
33
+ return {
34
+ source: source as Fqn,
35
+ target: target as Fqn,
36
+ ...(typeof props === 'object' ? props : {}),
37
+ title: title ?? null,
38
+ isBackward: true
39
+ }
40
+ }
41
+ throw new Error(`Invalid step expression: ${expr}`)
42
+ }
43
+
44
+ export function compute(stepsAndRules: (DynamicViewStep | ViewRulePredicate)[]) {
45
+ const [steps, rules] = partition(stepsAndRules, (s): s is DynamicViewStep => 'source' in s)
46
+ let view = computeDynamicView(
47
+ fakeModel,
48
+ {
49
+ ...emptyView,
50
+ steps,
51
+ rules: rules as DynamicViewRule[]
52
+ }
53
+ )
54
+ return Object.assign(view, {
55
+ nodeIds: view.nodes.map((node) => node.id) as string[],
56
+ edgeIds: view.edges.map((edge) => edge.id) as string[]
57
+ })
58
+ }