@likec4/core 1.18.0 → 1.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) 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 +9 -78
  6. package/dist/compute-view/index.d.ts +9 -78
  7. package/dist/compute-view/index.js +78 -0
  8. package/dist/index.d.mts +9 -730
  9. package/dist/index.d.ts +9 -730
  10. package/dist/{index.mjs → index.js} +7 -784
  11. package/dist/model/index.d.mts +218 -606
  12. package/dist/model/index.d.ts +218 -606
  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.DMYY7C8s.d.mts → core.C05RDLhi.d.ts} +179 -230
  18. package/dist/shared/{core.DMYY7C8s.d.ts → core.CmYMqgha.d.mts} +179 -230
  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.DafVOuVd.js +7443 -0
  24. package/dist/shared/object_hash.CE_oF3I3.js +1222 -0
  25. package/dist/shared/view.CyZrG8BJ.js +354 -0
  26. package/dist/types/index.d.mts +4 -2
  27. package/dist/types/index.d.ts +4 -2
  28. package/dist/types/index.js +20 -0
  29. package/dist/utils/index.d.mts +419 -0
  30. package/dist/utils/index.d.ts +419 -0
  31. package/dist/utils/index.js +347 -0
  32. package/package.json +47 -33
  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 +25 -20
  36. package/src/builder/Builder.deploymentModel.ts +80 -55
  37. package/src/builder/Builder.element.ts +2 -0
  38. package/src/builder/Builder.model.ts +57 -43
  39. package/src/builder/Builder.ts +412 -431
  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 +28 -5
  49. package/src/builder/index.ts +10 -4
  50. package/src/colors/index.ts +2 -2
  51. package/src/compute-view/compute-view.ts +45 -54
  52. package/src/compute-view/deployment-view/__test__/TestHelper.ts +236 -0
  53. package/src/compute-view/deployment-view/__test__/fixture.ts +50 -62
  54. package/src/compute-view/deployment-view/_types.ts +26 -18
  55. package/src/compute-view/deployment-view/clean-connections.ts +173 -164
  56. package/src/compute-view/deployment-view/compute.ts +51 -37
  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 +183 -76
  61. package/src/compute-view/deployment-view/predicates/relation-in-out.ts +38 -20
  62. package/src/compute-view/deployment-view/predicates/relation-incoming.ts +72 -23
  63. package/src/compute-view/deployment-view/predicates/relation-outgoing.ts +61 -22
  64. package/src/compute-view/deployment-view/predicates/wildcard.ts +15 -15
  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 +95 -31
  69. package/src/compute-view/dynamic-view/__test__/fixture.ts +4 -4
  70. package/src/compute-view/dynamic-view/compute.ts +59 -45
  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 +64 -62
  73. package/src/compute-view/element-view/__test__/fixture.ts +147 -117
  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 +264 -683
  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 +3 -4
  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/buildComputedNodes.ts +4 -4
  101. package/src/compute-view/utils/elementExpressionToPredicate.ts +1 -3
  102. package/src/compute-view/utils/link-nodes-with-edges.ts +3 -7
  103. package/src/compute-view/utils/{derive-edge-props-from-relationships.ts → merge-props-from-relationships.ts} +54 -23
  104. package/src/compute-view/utils/relationExpressionToPredicates.ts +2 -2
  105. package/src/compute-view/utils/resolve-extended-views.ts +44 -40
  106. package/src/compute-view/utils/resolve-global-rules.ts +6 -6
  107. package/src/compute-view/utils/topological-sort.ts +167 -71
  108. package/src/compute-view/utils/with-readable-edges.ts +14 -9
  109. package/src/index.ts +22 -16
  110. package/src/model/DeploymentElementModel.ts +150 -85
  111. package/src/model/DeploymentModel.ts +27 -21
  112. package/src/model/ElementModel.ts +51 -25
  113. package/src/model/LikeC4Model.test-d.ts +4 -3
  114. package/src/model/LikeC4Model.ts +98 -42
  115. package/src/model/RelationModel.ts +5 -4
  116. package/src/model/__test__/fixture.ts +5 -9
  117. package/src/model/connection/Connection.ts +72 -0
  118. package/src/model/connection/deployment/DeploymentConnectionModel.ts +178 -0
  119. package/src/model/connection/{deployment.ts → deployment/find.ts} +14 -18
  120. package/src/model/connection/deployment/index.ts +8 -0
  121. package/src/model/connection/index.ts +22 -0
  122. package/src/model/connection/model/ConnectionModel.ts +157 -0
  123. package/src/model/connection/{model.ts → model/find.ts} +43 -49
  124. package/src/model/connection/model/index.ts +8 -0
  125. package/src/model/connection/ops.ts +274 -0
  126. package/src/model/guards.ts +9 -0
  127. package/src/model/index.ts +45 -3
  128. package/src/model/types.ts +35 -18
  129. package/src/model/view/EdgeModel.ts +6 -6
  130. package/src/model/view/LikeC4ViewModel.ts +14 -8
  131. package/src/model/view/NodeModel.ts +6 -5
  132. package/src/types/_common.ts +53 -1
  133. package/src/types/deployments.ts +88 -156
  134. package/src/types/element.ts +2 -2
  135. package/src/types/expression-v2.ts +172 -0
  136. package/src/types/expression.ts +9 -5
  137. package/src/types/global.ts +3 -3
  138. package/src/types/index.ts +220 -13
  139. package/src/types/model.ts +28 -24
  140. package/src/types/operators.ts +1 -1
  141. package/src/types/relation.ts +14 -10
  142. package/src/types/theme.ts +0 -1
  143. package/src/types/view.ts +20 -18
  144. package/src/utils/const.ts +1 -0
  145. package/src/utils/fqn.ts +140 -49
  146. package/src/utils/getOrCreate.ts +1 -1
  147. package/src/utils/index.ts +75 -9
  148. package/src/utils/iterable/_types.ts +5 -0
  149. package/src/utils/iterable/filter.ts +55 -0
  150. package/src/utils/iterable/find.ts +36 -0
  151. package/src/utils/iterable/flat.ts +17 -0
  152. package/src/utils/iterable/index.ts +13 -0
  153. package/src/utils/iterable/map.ts +40 -0
  154. package/src/utils/iterable/reduce.ts +24 -0
  155. package/src/utils/iterable/some.ts +36 -0
  156. package/src/utils/iterable/to.ts +19 -0
  157. package/src/utils/iterable/unique.ts +29 -0
  158. package/src/utils/relations.ts +3 -64
  159. package/src/utils/set.ts +11 -9
  160. package/src/utils/string-hash.ts +1 -1
  161. package/dist/compute-view/index.mjs +0 -79
  162. package/dist/model/index.mjs +0 -2
  163. package/dist/shared/core.BBgiAAv1.mjs +0 -144
  164. package/dist/shared/core.BPyJvuMU.mjs +0 -2420
  165. package/dist/shared/core.BQ4AbfvB.mjs +0 -9228
  166. package/dist/shared/core.k12s_keD.mjs +0 -203
  167. package/dist/types/index.mjs +0 -21
  168. package/src/builder/Builder.test-d.ts +0 -124
  169. package/src/builder/Builder.view.ts +0 -543
  170. package/src/compute-view/LikeC4ModelGraph.ts +0 -330
  171. package/src/compute-view/deployment-view/Memory.ts +0 -108
  172. package/src/compute-view/deployment-view/Stage.ts +0 -145
  173. package/src/compute-view/deployment-view/predicates/deployment-ref.ts +0 -146
  174. package/src/compute-view/element-view/predicates.ts +0 -517
  175. package/src/compute-view/utils/sortNodes.ts +0 -101
  176. package/src/model/connection/ConnectionModel.ts +0 -94
  177. package/src/model/connection/DeploymentConnectionModel.ts +0 -99
@@ -1,36 +1,49 @@
1
1
  import DefaultMap from 'mnemonist/default-map'
2
- import { dropWhile, filter, flatMap, forEach, forEachObj, groupBy, hasAtLeast, map, pipe, prop, sort } from 'remeda'
3
- import { mergeConnections } from '../../model/connection/deployment'
4
- import { DeploymentConnectionModel } from '../../model/connection/DeploymentConnectionModel'
2
+ import {
3
+ dropWhile,
4
+ filter,
5
+ flatMap,
6
+ forEach,
7
+ groupBy,
8
+ hasAtLeast,
9
+ map,
10
+ pipe,
11
+ piped,
12
+ prop,
13
+ reduce,
14
+ values,
15
+ } from 'remeda'
16
+ import { invariant } from '../../errors'
17
+ import {
18
+ DeploymentConnectionModel,
19
+ differenceConnections,
20
+ isNestedConnection,
21
+ mergeConnections,
22
+ sortConnectionsByBoundaryHierarchy,
23
+ } from '../../model/connection'
24
+ import { findConnection } from '../../model/connection/deployment'
5
25
  import { RelationshipsAccum } from '../../model/DeploymentElementModel'
6
26
  import type { RelationshipModel } from '../../model/RelationModel'
7
- import type { AnyAux } from '../../model/types'
8
- import { compareFqnHierarchically, isAncestor } from '../../utils'
9
- import { difference } from '../../utils/set'
10
- import type { Connections, Elem } from './_types'
11
- import { type Patch } from './Memory'
27
+ import { imap, toArray } from '../../utils/iterable'
28
+ import { intersection, union } from '../../utils/set'
29
+ import type { Connection, Connections } from './_types'
12
30
 
13
31
  const filterEmptyConnection = filter((c: DeploymentConnectionModel<any>) => c.nonEmpty())
14
32
 
15
- // We add '.' to distinguish between connections with boundary and without
16
- // This way we can sort them hierarchically
17
- const connectionBoundary = (conn: DeploymentConnectionModel<any>) => conn.boundary?.id ? `.${conn.boundary.id}` : ''
18
-
19
- function cleanCrossBoundary<M extends AnyAux>(connections: Connections<M>): Connections<M> {
20
- // Keep only connections between leafs
33
+ type MapOfExcludesFromConnection = DefaultMap<DeploymentConnectionModel, Set<RelationshipModel>>
34
+ function findCrossBoundarySameSourceOrTarget(connections: Connections): MapOfExcludesFromConnection {
21
35
  // Also find connections based on same relation
22
- const groupedByRelation = new DefaultMap<RelationshipModel<M>, Array<DeploymentConnectionModel<M>>>(() => [])
36
+ const groupedByRelation = new DefaultMap<RelationshipModel, Array<DeploymentConnectionModel>>(() => [])
23
37
  for (const conn of connections) {
24
38
  for (const relation of conn.relations.model) {
25
39
  groupedByRelation.get(relation).push(conn)
26
40
  }
27
41
  }
28
42
 
29
- // DeploymentConnectionModel is immutables
30
- // So we create new instances without excluded relations
43
+ // Aggreate excluded relations for each connection
31
44
  const excludedRelations = new DefaultMap<
32
- DeploymentConnectionModel<M>,
33
- Set<RelationshipModel<M>>
45
+ DeploymentConnectionModel,
46
+ Set<RelationshipModel>
34
47
  >(() => new Set())
35
48
 
36
49
  // In each group, find connected to same leaf
@@ -44,186 +57,182 @@ function cleanCrossBoundary<M extends AnyAux>(connections: Connections<M>): Conn
44
57
  sameRelationGroup,
45
58
  flatMap(conn => [
46
59
  { group: `$source-${conn.source.id}`, conn },
47
- { group: `$target-${conn.target.id}`, conn }
60
+ { group: `$target-${conn.target.id}`, conn },
48
61
  ]),
49
62
  groupBy(prop('group')),
50
- forEachObj((connections) => {
51
- if (connections.length < 2) {
52
- return
53
- }
54
- pipe(
55
- connections,
63
+ values(),
64
+ filter(hasAtLeast(2)),
65
+ forEach(
66
+ // In each group, sort by hierarchy, first are deepest
67
+ piped(
56
68
  map(prop('conn')),
57
- // Sort by hierarchy, first are deepest
58
- sort((a, b) => compareFqnHierarchically(connectionBoundary(a), connectionBoundary(b)) * -1),
59
- // Skip connections until we reach another boundary
69
+ sortConnectionsByBoundaryHierarchy('desc'),
70
+ // Drop first, as it is the deepest
71
+ // Drop if boundary is same as previous
60
72
  dropWhile((conn, i, all) => i === 0 || conn.boundary === all[i - 1]!.boundary),
61
- // Clean relations from the rest
73
+ // Drop relations from boundaries above
62
74
  forEach((conn) => {
63
75
  excludedRelations.get(conn).add(relation)
64
- })
65
- )
66
- })
76
+ }),
77
+ ),
78
+ ),
67
79
  )
68
80
  }
69
81
 
82
+ return excludedRelations
83
+ }
84
+
85
+ /**
86
+ * Identifies and processes cross-boundary connections in a deployment diagram.
87
+ * This function analyzes connections between nodes and removes redundant relations
88
+ * when multiple connections exist between the same elements across different boundaries.
89
+ *
90
+ * The function performs the following:
91
+ * 1. Groups connections by their relationship type
92
+ * 2. For each relationship group, identifies connections that:
93
+ * - Share the same source
94
+ * - Share the same target
95
+ * 3. When multiple connections are found in the same group, keeps only the most specific
96
+ * (deepest in hierarchy) connection and removes the relationship from others
97
+ *
98
+ * @param connections - The input collection of deployment connections to analyze
99
+ * @returns connections with redundant cross-boundary relationships
100
+ */
101
+ export function findCrossBoundaryConnections(connections: Connections): Array<Connection> {
102
+ // Ensure connections are merged
103
+ connections = mergeConnections(connections)
104
+
105
+ const excludedRelations = findCrossBoundarySameSourceOrTarget(connections)
106
+
107
+ for (const c of connections) {
108
+ const { source, target } = c
109
+ const connectionModelRelations = c.relations.model
110
+ // Connections between node and instance
111
+ // Exclude internal relations of the node (otherwise they are cross-boundary)
112
+ if (source.isDeploymentNode() !== target.isDeploymentNode()) {
113
+ const node = source.isDeploymentNode() ? source : c.target
114
+ invariant(node.isDeploymentNode())
115
+ const nodeInternals = node.internalModelRelationships()
116
+ const toExclude = intersection(
117
+ connectionModelRelations,
118
+ nodeInternals,
119
+ )
120
+ for (const relation of toExclude) {
121
+ excludedRelations.get(c).add(relation)
122
+ }
123
+
124
+ continue
125
+ }
126
+
127
+ // Connection between nodes
128
+ if (source.isDeploymentNode() && target.isDeploymentNode()) {
129
+ const toExclude = union(
130
+ // Exclude node internals (otherwise thay are cross-boundary)
131
+ intersection(
132
+ c.relations.model,
133
+ source.internalModelRelationships(),
134
+ ),
135
+ intersection(
136
+ c.relations.model,
137
+ target.internalModelRelationships(),
138
+ ),
139
+ )
140
+ for (const relation of toExclude) {
141
+ excludedRelations.get(c).add(relation)
142
+ }
143
+ }
144
+ }
145
+
70
146
  return pipe(
147
+ excludedRelations.entries(),
148
+ imap(([c, excluded]) =>
149
+ c.update({
150
+ model: excluded,
151
+ deployment: null,
152
+ })
153
+ ),
154
+ toArray(),
155
+ )
156
+ }
157
+
158
+ /**
159
+ * Cleans connections that cross boundaries by removing overlapping parts.
160
+ * @example
161
+ * ```ts
162
+ * const connections = getConnections();
163
+ * const cleanedConnections = cleanCrossBoundary(connections);
164
+ * ```
165
+ */
166
+ export function cleanCrossBoundary(connections: Connections): Array<Connection> {
167
+ return differenceConnections(
71
168
  connections,
72
- map(conn => {
73
- const excluded = excludedRelations.get(conn)
74
- if (excluded && excluded.size > 0) {
75
- const updated = new DeploymentConnectionModel(
76
- conn.source,
77
- conn.target,
78
- new RelationshipsAccum(
79
- difference(conn.relations.model, excluded),
80
- conn.relations.deployment
81
- )
82
- )
83
- return updated
84
- }
85
- return conn
86
- }),
87
- filterEmptyConnection
169
+ findCrossBoundaryConnections(connections),
88
170
  )
89
171
  }
90
172
 
91
173
  /**
92
- * Remove relationships from connection model, that are already included in the connections between descendants.
93
- * In other words - if there is same connection down the hierarchy.
174
+ * Identifies and returns redundant connections in a deployment view.
175
+ * A connection is considered redundant if its relationships are already implied
176
+ * through other connections
94
177
  *
95
- * @returns New connection without redundant relationships
96
- * Connection may be empty if all relationships are redundant, in this case it should be removed
178
+ * @returns Array of redundant connections that can be safely removed
97
179
  */
98
- function excludeRedundantRelationships<M extends AnyAux>(
99
- connections: Connections<M>
100
- ): Array<DeploymentConnectionModel<M>> {
180
+ export function findRedundantConnections(connections: Connections): Array<Connection> {
101
181
  return pipe(
102
182
  connections,
103
- map(connection => {
104
- const { source, target } = connection
183
+ mergeConnections,
184
+ reduce((redundants, connection) => {
185
+ const { source, target, relations } = connection
105
186
 
106
187
  // Connection between instances is always explicit
107
188
  if (source.isInstance() && target.isInstance()) {
108
- return connection
189
+ return redundants
109
190
  }
110
191
 
111
- let modelRelations = connection.relations.model
112
- let deploymentRelations = connection.relations.deployment
192
+ let redundantAccum = RelationshipsAccum.empty()
113
193
 
114
- for (const c of connections) {
115
- if (
116
- isAncestor(source.id, c.source.id) && isAncestor(target.id, c.target.id)
117
- || isAncestor(source.id, c.source.id) && c.target.id === target.id
118
- || isAncestor(target.id, c.target.id) && c.source.id === source.id
119
- ) {
120
- modelRelations = difference(modelRelations, c.relations.model)
121
- deploymentRelations = difference(deploymentRelations, c.relations.deployment)
194
+ if (source.isDeploymentNode() && target.isDeploymentNode()) {
195
+ const [reversed] = findConnection(target, source, 'directed')
196
+ if (reversed) {
197
+ redundantAccum = relations.intersect(reversed.relations)
122
198
  }
123
199
  }
124
200
 
125
- // if any set changed, return new connection
126
- if (modelRelations !== connection.relations.model || deploymentRelations !== connection.relations.deployment) {
127
- return new DeploymentConnectionModel(
128
- source,
129
- target,
130
- new RelationshipsAccum(
131
- modelRelations,
132
- deploymentRelations
201
+ for (const c of connections) {
202
+ if (isNestedConnection(c, connection)) {
203
+ redundantAccum = redundantAccum.union(
204
+ relations.intersect(c.relations),
133
205
  )
206
+ }
207
+ }
208
+
209
+ if (redundantAccum.nonEmpty) {
210
+ redundants.push(
211
+ new DeploymentConnectionModel(
212
+ source,
213
+ target,
214
+ redundantAccum,
215
+ ),
134
216
  )
135
217
  }
136
218
 
137
- return connection
138
- }),
139
- filterEmptyConnection
219
+ return redundants
220
+ }, [] as DeploymentConnectionModel[]),
140
221
  )
141
222
  }
223
+
142
224
  /**
143
- * This patch:
144
- * 1. Keeps connections between leafs or having direct deployment relations
145
- * 2. Removes cross-boundary model relations, that already exist inside boundaries
146
- * (e.g. prefer relations inside same deployment node over relations between nodes)
147
- * 3. Removes implicit connections between elements, if their descendants have same connection
225
+ * Remove relationships from connection model, that are already included in the connections between descendants.
226
+ * In other words - if there is same connection down the hierarchy.
227
+ *
228
+ * @returns New connection without redundant relationships
229
+ * Connection may be empty if all relationships are redundant, in this case it should be removed
148
230
  */
149
- export const cleanConnections: Patch = (memory) => {
150
- if (memory.connections.length < 2) {
151
- return memory
152
- }
153
-
154
- const leafs = new Set<Elem>()
155
-
156
- for (const element of memory.elements) {
157
- // Instance is always leaf
158
- if (element.isInstance()) {
159
- leafs.add(element)
160
- continue
161
- }
162
- // Check if element is ancestor of any element in elements
163
- let isLeaf = true
164
- for (const el of memory.elements) {
165
- if (isAncestor(element.id, el.id)) {
166
- isLeaf = false
167
- break
168
- }
169
- }
170
- if (isLeaf) {
171
- leafs.add(element)
172
- }
173
- }
174
-
175
- const connectedElements = new Set<Elem>()
176
-
177
- const connections = pipe(
178
- memory.connections,
179
- mergeConnections,
180
- // Keep connections
181
- // - between leafs
182
- // - has direct deployment relation
183
- filter(c => {
184
- return (leafs.has(c.source) && leafs.has(c.target)) || c.hasDirectDeploymentRelation()
185
- }),
186
- cleanCrossBoundary,
187
- excludeRedundantRelationships,
188
- forEach((c) => {
189
- connectedElements.add(c.source)
190
- connectedElements.add(c.target)
191
- if (c.boundary) {
192
- connectedElements.add(c.boundary)
193
- }
194
- })
231
+ export function cleanRedundantRelationships(
232
+ connections: Connections,
233
+ ): Array<Connection> {
234
+ return differenceConnections(
235
+ connections,
236
+ findRedundantConnections(connections),
195
237
  )
196
-
197
- const isAncestorOfConnected = (el: Elem) => {
198
- if (el.isInstance()) {
199
- return false
200
- }
201
- for (const connected of connectedElements) {
202
- if (isAncestor(el.id, connected.id)) {
203
- return true
204
- }
205
- }
206
- return false
207
- }
208
-
209
- // Update memory
210
- const newMemory = memory.clone()
211
- newMemory.connections = connections
212
-
213
- // Iterate over elements (preserving order) and pick:
214
- // - explicitly included
215
- // - connected elements
216
- // - ancestors of connected elements
217
- const finalElements = new Set<Elem>()
218
- for (const el of newMemory.finalElements) {
219
- if (
220
- newMemory.isExplicit(el)
221
- || connectedElements.has(el)
222
- || isAncestorOfConnected(el)
223
- ) {
224
- finalElements.add(el)
225
- }
226
- }
227
- newMemory.finalElements = finalElements
228
- return newMemory
229
238
  }
@@ -1,73 +1,73 @@
1
- import { findLast } from 'remeda'
1
+ import { findLast, map } from 'remeda'
2
2
  import { nonexhaustive } from '../../errors'
3
3
  import { type LikeC4DeploymentModel, LikeC4Model } from '../../model'
4
4
  import type { AnyAux } from '../../model/types'
5
5
  import type { DeploymentViewRule } from '../../types'
6
6
  import {
7
7
  type ComputedDeploymentView,
8
- DeploymentElementExpression,
9
- DeploymentRelationExpression,
10
8
  type DeploymentView,
9
+ FqnExpr,
11
10
  isViewRuleAutoLayout,
12
- isViewRulePredicate
11
+ isViewRulePredicate,
13
12
  } from '../../types'
13
+ import { RelationExpr } from '../../types/expression-v2'
14
+ import { buildElementNotations } from '../utils/buildElementNotations'
14
15
  import { linkNodesWithEdges } from '../utils/link-nodes-with-edges'
15
16
  import { topologicalSort } from '../utils/topological-sort'
16
17
  import { calcViewLayoutHash } from '../utils/view-hash'
17
- import { cleanConnections } from './clean-connections'
18
- import { MutableMemory, type Patch } from './Memory'
19
- import { DeploymentRefPredicate } from './predicates/deployment-ref'
18
+ import { Memory } from './memory'
19
+ import { DeploymentRefPredicate } from './predicates/elements'
20
20
  import { DirectRelationPredicate } from './predicates/relation-direct'
21
21
  import { InOutRelationPredicate } from './predicates/relation-in-out'
22
22
  import { IncomingRelationPredicate } from './predicates/relation-incoming'
23
23
  import { OutgoingRelationPredicate } from './predicates/relation-outgoing'
24
24
  import { WildcardPredicate } from './predicates/wildcard'
25
- import { Stage } from './Stage'
25
+ import { StageFinal } from './stages/stage-final'
26
26
  import { applyDeploymentViewRuleStyles, buildNodes, toComputedEdges } from './utils'
27
27
 
28
- function processPredicates<M extends AnyAux>(
28
+ export function processPredicates<M extends AnyAux>(
29
29
  model: LikeC4DeploymentModel<M>,
30
- rules: DeploymentViewRule[]
30
+ rules: DeploymentViewRule[],
31
31
  ) {
32
- let memory = MutableMemory.empty()
33
- let stage: Stage | null = null
32
+ let memory = Memory.empty()
34
33
 
35
34
  for (const rule of rules) {
36
35
  if (isViewRulePredicate(rule)) {
37
36
  const op = 'include' in rule ? 'include' : 'exclude'
38
37
  const exprs = rule.include ?? rule.exclude
39
38
  for (const expr of exprs) {
40
- stage = new Stage(stage)
39
+ let stage = op === 'include' ? memory.stageInclude(expr) : memory.stageExclude(expr)
41
40
  const ctx = { model, stage, memory }
42
- let patch: Patch | undefined
43
41
  switch (true) {
44
- case DeploymentElementExpression.isRef(expr):
45
- patch = DeploymentRefPredicate[op](expr, ctx)
42
+ case FqnExpr.isModelRef(expr):
43
+ // Ignore model refs in deployment view
46
44
  break
47
- case DeploymentElementExpression.isWildcard(expr):
48
- patch = WildcardPredicate[op](expr, ctx)
45
+ case FqnExpr.isDeploymentRef(expr):
46
+ stage = DeploymentRefPredicate[op]({ ...ctx, expr } as any) ?? stage
49
47
  break
50
- case DeploymentRelationExpression.isDirect(expr):
51
- patch = DirectRelationPredicate[op](expr, ctx)
48
+ case FqnExpr.isWildcard(expr):
49
+ stage = WildcardPredicate[op]({ ...ctx, expr } as any) ?? stage
52
50
  break
53
- case DeploymentRelationExpression.isInOut(expr):
54
- patch = InOutRelationPredicate[op](expr, ctx)
51
+ case RelationExpr.isDirect(expr):
52
+ stage = DirectRelationPredicate[op]({ ...ctx, expr } as any) ?? stage
55
53
  break
56
- case DeploymentRelationExpression.isOutgoing(expr):
57
- patch = OutgoingRelationPredicate[op](expr, ctx)
54
+ case RelationExpr.isInOut(expr):
55
+ stage = InOutRelationPredicate[op]({ ...ctx, expr } as any) ?? stage
58
56
  break
59
- case DeploymentRelationExpression.isIncoming(expr):
60
- patch = IncomingRelationPredicate[op](expr, ctx)
57
+ case RelationExpr.isOutgoing(expr):
58
+ stage = OutgoingRelationPredicate[op]({ ...ctx, expr } as any) ?? stage
59
+ break
60
+ case RelationExpr.isIncoming(expr):
61
+ stage = IncomingRelationPredicate[op]({ ...ctx, expr } as any) ?? stage
61
62
  break
62
63
  default:
63
64
  nonexhaustive(expr)
64
65
  }
65
- patch ??= stage.patch()
66
- memory = patch(memory)
66
+ memory = stage.commit()
67
67
  }
68
68
  }
69
69
  }
70
- return cleanConnections(memory)
70
+ return StageFinal.for(memory).commit()
71
71
  }
72
72
 
73
73
  export function computeDeploymentView<M extends AnyAux>(
@@ -76,9 +76,9 @@ export function computeDeploymentView<M extends AnyAux>(
76
76
  docUri: _docUri, // exclude docUri
77
77
  rules, // exclude rules
78
78
  ...view
79
- }: DeploymentView
79
+ }: DeploymentView,
80
80
  ): ComputedDeploymentView<M['ViewId']> {
81
- const memory = processPredicates<M>(likec4model.deployment, rules)
81
+ let memory = processPredicates<M>(likec4model.deployment, rules)
82
82
 
83
83
  const nodesMap = buildNodes(memory)
84
84
 
@@ -87,25 +87,39 @@ export function computeDeploymentView<M extends AnyAux>(
87
87
  linkNodesWithEdges(nodesMap, computedEdges)
88
88
 
89
89
  const sorted = topologicalSort({
90
- nodes: [...nodesMap.values()],
91
- edges: computedEdges
90
+ nodes: nodesMap,
91
+ edges: computedEdges,
92
92
  })
93
93
 
94
94
  const nodes = applyDeploymentViewRuleStyles(
95
95
  rules,
96
- sorted.nodes
96
+ sorted.nodes,
97
97
  )
98
98
 
99
99
  const autoLayoutRule = findLast(rules, isViewRuleAutoLayout)
100
100
 
101
+ const elementNotations = buildElementNotations(nodes)
102
+
101
103
  return calcViewLayoutHash({
102
104
  ...view,
103
105
  autoLayout: {
104
106
  direction: autoLayoutRule?.direction ?? 'TB',
105
107
  ...(autoLayoutRule?.nodeSep && { nodeSep: autoLayoutRule.nodeSep }),
106
- ...(autoLayoutRule?.rankSep && { rankSep: autoLayoutRule.rankSep })
108
+ ...(autoLayoutRule?.rankSep && { rankSep: autoLayoutRule.rankSep }),
107
109
  },
108
- nodes,
109
- edges: sorted.edges
110
+ edges: sorted.edges,
111
+ nodes: map(nodes, n => {
112
+ // omit notation
113
+ delete n.notation
114
+ if (n.icon === 'none') {
115
+ delete n.icon
116
+ }
117
+ return n
118
+ }),
119
+ ...(elementNotations.length > 0 && {
120
+ notation: {
121
+ elements: elementNotations,
122
+ },
123
+ }),
110
124
  })
111
125
  }
@@ -0,0 +1,9 @@
1
+ import type { StageExclude } from '../stages/stage-exclude'
2
+ import type { StageInclude } from '../stages/stage-include'
3
+
4
+ export { StageExclude } from '../stages/stage-exclude'
5
+ export { StageInclude } from '../stages/stage-include'
6
+ export { Memory } from './memory'
7
+ export type { Ctx } from './memory'
8
+
9
+ export type Stage = StageInclude | StageExclude
@@ -0,0 +1,97 @@
1
+ import { isDeepEqual } from 'remeda'
2
+ import { type DeploymentConnectionModel, differenceConnections } from '../../../model/connection'
3
+ import type { DeploymentElementModel } from '../../../model/DeploymentElementModel'
4
+ import type { AnyAux } from '../../../model/types'
5
+ import type { ExpressionV2 } from '../../../types'
6
+ import { difference as differenceSet } from '../../../utils'
7
+ import { customInspectSymbol } from '../../../utils/const'
8
+ import { toArray } from '../../../utils/iterable'
9
+ import { AbstractMemory, type ComputeCtx, type StageExpression } from '../../memory'
10
+ import { StageExclude } from '../stages/stage-exclude'
11
+ import { StageInclude } from '../stages/stage-include'
12
+
13
+ export type Ctx = ComputeCtx<
14
+ DeploymentElementModel<AnyAux>,
15
+ DeploymentConnectionModel<AnyAux>,
16
+ Memory,
17
+ StageInclude,
18
+ StageExclude,
19
+ ExpressionV2
20
+ >
21
+
22
+ export class Memory extends AbstractMemory<Ctx> {
23
+ static empty(): Memory {
24
+ return new Memory({
25
+ elements: new Set(),
26
+ explicits: new Set(),
27
+ final: new Set(),
28
+ connections: [],
29
+ })
30
+ }
31
+
32
+ override stageInclude(expr: StageExpression<Ctx>): StageInclude {
33
+ return new StageInclude(this, expr)
34
+ }
35
+ override stageExclude(expr: StageExpression<Ctx>): StageExclude {
36
+ return new StageExclude(this, expr)
37
+ }
38
+
39
+ override mutableState(): Ctx['MutableState'] {
40
+ return ({
41
+ elements: new Set(this.state.elements),
42
+ explicits: new Set(this.state.explicits),
43
+ final: new Set(this.state.final),
44
+ connections: [...this.state.connections],
45
+ })
46
+ }
47
+
48
+ override update(newstate: Partial<Ctx['MutableState']>): Memory {
49
+ return new Memory({
50
+ ...this.state,
51
+ ...newstate,
52
+ })
53
+ }
54
+
55
+ equals(other: unknown): boolean {
56
+ return other instanceof Memory && isDeepEqual(this.state, other.state)
57
+ }
58
+
59
+ diff(state: Memory) {
60
+ const current = this
61
+ return {
62
+ added: {
63
+ elements: toArray(differenceSet(state.elements, current.elements)),
64
+ explicits: toArray(differenceSet(state.explicits, current.explicits)),
65
+ final: toArray(differenceSet(state.final, current.final)),
66
+ connections: toArray(differenceConnections(state.connections, current.connections)),
67
+ },
68
+ removed: {
69
+ elements: toArray(differenceSet(current.elements, state.elements)),
70
+ explicits: toArray(differenceSet(current.explicits, state.explicits)),
71
+ final: toArray(differenceSet(current.final, state.final)),
72
+ connections: differenceConnections(current.connections, state.connections),
73
+ },
74
+ }
75
+ }
76
+
77
+ public override toString(): string {
78
+ return [
79
+ 'final:',
80
+ ...[...this.final].map(e => ' ' + e.id),
81
+ 'connections:',
82
+ ...this.connections.map(c => ' ' + c.expression),
83
+ ].join('\n')
84
+ }
85
+
86
+ [customInspectSymbol](_depth: unknown, _inspectOptions: unknown, _inspect: unknown) {
87
+ const asString = this.toString()
88
+
89
+ // // Trick so that node displays the name of the constructor
90
+ // Object.defineProperty(asString, 'constructor', {
91
+ // value: this.constructor,
92
+ // enumerable: false,
93
+ // })
94
+
95
+ return asString
96
+ }
97
+ }