@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,370 @@
1
+ import DefaultMap from 'mnemonist/default-map'
2
+ import { values } from 'remeda'
3
+ import type { LiteralUnion } from 'type-fest'
4
+ import { invariant, nonNullable } from '../errors'
5
+ import {
6
+ type ComputedDeploymentView,
7
+ type DeploymentRef,
8
+ type DeploymentRelation,
9
+ type GenericLikeC4Model,
10
+ type IteratorLike,
11
+ type Tag as C4Tag,
12
+ DeploymentElement,
13
+ } from '../types'
14
+ import { ancestorsFqn, parentFqn, sortParentsFirst } from '../utils/fqn'
15
+ import { getOrCreate } from '../utils/getOrCreate'
16
+ import { isString } from '../utils/guards'
17
+ import {
18
+ type DeployedInstancesIterator,
19
+ type DeploymentElementModel,
20
+ type DeploymentElementsIterator,
21
+ type DeploymentNodesIterator,
22
+ DeployedInstanceModel,
23
+ DeploymentNodeModel,
24
+ DeploymentRelationModel,
25
+ NestedElementOfDeployedInstanceModel,
26
+ } from './DeploymentElementModel'
27
+ import type { LikeC4Model } from './LikeC4Model'
28
+ import { type AnyAux, type IncomingFilter, type OutgoingFilter, getId } from './types'
29
+ import type { LikeC4ViewModel } from './view/LikeC4ViewModel'
30
+
31
+ export class LikeC4DeploymentModel<M extends AnyAux = AnyAux> {
32
+ readonly #elements = new Map<M['Deployment'], DeploymentElementModel<M>>()
33
+ // Parent element for given FQN
34
+ readonly #parents = new Map<M['Deployment'], DeploymentNodeModel<M>>()
35
+ // Children elements for given FQN
36
+ readonly #children = new DefaultMap<M['Deployment'], Set<DeploymentElementModel<M>>>(() => new Set())
37
+
38
+ // Keep track of instances of the logical element
39
+ readonly #instancesOf = new DefaultMap<M['Element'], Set<DeployedInstanceModel<M>>>(() => new Set())
40
+
41
+ readonly #rootElements = new Set<DeploymentNodeModel<M>>()
42
+
43
+ readonly #relations = new Map<M['RelationId'], DeploymentRelationModel<M>>()
44
+
45
+ // Incoming to an element or its descendants
46
+ readonly #incoming = new DefaultMap<M['Deployment'], Set<DeploymentRelationModel<M>>>(() => new Set())
47
+
48
+ // Outgoing from an element or its descendants
49
+ readonly #outgoing = new DefaultMap<M['Deployment'], Set<DeploymentRelationModel<M>>>(() => new Set())
50
+
51
+ // Relationships inside the element, among descendants
52
+ readonly #internal = new DefaultMap<M['Deployment'], Set<DeploymentRelationModel<M>>>(() => new Set())
53
+
54
+ // readonly #views = new Map<ViewID, LikeC4ViewModel<M>>()
55
+
56
+ readonly #allTags = new DefaultMap<C4Tag, Set<DeploymentElementModel<M> | DeploymentRelationModel<M>>>(() =>
57
+ new Set()
58
+ )
59
+
60
+ readonly #nestedElementsOfDeployment = new Map<
61
+ `${M['Deployment']}@${M['Element']}`,
62
+ NestedElementOfDeployedInstanceModel<M>
63
+ >()
64
+
65
+ constructor(
66
+ public readonly $model: LikeC4Model<M>,
67
+ public readonly $deployments: GenericLikeC4Model['deployments'],
68
+ ) {
69
+ for (const element of sortParentsFirst(values($deployments.elements))) {
70
+ const el = this.addElement(element)
71
+ for (const tag of el.tags) {
72
+ this.#allTags.get(tag).add(el)
73
+ }
74
+ if (el.isInstance()) {
75
+ this.#instancesOf.get(el.element.id).add(el)
76
+ }
77
+ }
78
+ for (const relation of values($deployments.relations)) {
79
+ const el = this.addRelation(relation)
80
+ for (const tag of el.tags) {
81
+ this.#allTags.get(tag).add(el)
82
+ }
83
+ }
84
+ }
85
+
86
+ public element(el: M['DeploymentOrFqn']): DeploymentElementModel<M> {
87
+ if (el instanceof DeploymentNodeModel || el instanceof DeployedInstanceModel) {
88
+ return el
89
+ }
90
+ const id = getId(el)
91
+ return nonNullable(this.#elements.get(id), `Element ${id} not found`)
92
+ }
93
+ public findElement(el: M['Deployment']): DeploymentElementModel<M> | null {
94
+ return this.#elements.get(el) ?? null
95
+ }
96
+
97
+ public node(el: M['DeploymentOrFqn']): DeploymentNodeModel<M> {
98
+ const element = this.element(el)
99
+ invariant(element.isDeploymentNode(), `Element ${element.id} is not a deployment node`)
100
+ return element
101
+ }
102
+ public findNode(el: M['Deployment']): DeploymentNodeModel<M> | null {
103
+ const element = this.findElement(el)
104
+ if (!element) {
105
+ return null
106
+ }
107
+ invariant(element.isDeploymentNode(), `Element ${element?.id} is not a deployment node`)
108
+ return element
109
+ }
110
+
111
+ public instance(el: M['DeploymentOrFqn']): DeployedInstanceModel<M> {
112
+ const element = this.element(el)
113
+ invariant(element.isInstance(), `Element ${element.id} is not a deployed instance`)
114
+ return element
115
+ }
116
+ public findInstance(el: M['Deployment']): DeployedInstanceModel<M> | null {
117
+ const element = this.findElement(el)
118
+ if (!element) {
119
+ return null
120
+ }
121
+ invariant(element.isInstance(), `Element ${element?.id} is not a deployed instance`)
122
+ return element
123
+ }
124
+
125
+ /**
126
+ * Returns the root elements of the model.
127
+ */
128
+ public roots(): DeploymentNodesIterator<M> {
129
+ return this.#rootElements.values()
130
+ }
131
+
132
+ /**
133
+ * Returns all elements in the model.
134
+ */
135
+ public elements(): DeploymentElementsIterator<M> {
136
+ return this.#elements.values()
137
+ }
138
+
139
+ /**
140
+ * Returns all elements in the model.
141
+ */
142
+ public *nodes(): DeploymentNodesIterator<M> {
143
+ for (const element of this.#elements.values()) {
144
+ if (element.isDeploymentNode()) {
145
+ yield element
146
+ }
147
+ }
148
+ return
149
+ }
150
+
151
+ public *instances(): DeployedInstancesIterator<M> {
152
+ for (const element of this.#elements.values()) {
153
+ if (element.isInstance()) {
154
+ yield element
155
+ }
156
+ }
157
+ return
158
+ }
159
+
160
+ /**
161
+ * Iterate over all instances of the given logical element.
162
+ */
163
+ public *instancesOf(element: M['ElementOrFqn']): DeployedInstancesIterator<M> {
164
+ const id = getId(element)
165
+ const instances = this.#instancesOf.get(id)
166
+ if (instances) {
167
+ yield* instances
168
+ }
169
+ return
170
+ }
171
+
172
+ public deploymentRef(ref: DeploymentRef): DeploymentElementModel<M> | NestedElementOfDeployedInstanceModel<M> {
173
+ if ('element' in ref) {
174
+ const { id, element } = ref
175
+ return getOrCreate(this.#nestedElementsOfDeployment, `${id}@${element}`, () => {
176
+ return new NestedElementOfDeployedInstanceModel(this.instance(id), this.$model.element(element))
177
+ })
178
+ }
179
+ return this.element(ref)
180
+ }
181
+
182
+ /**
183
+ * Returns all relationships in the model.
184
+ */
185
+ public relationships(): IteratorLike<DeploymentRelationModel<M>> {
186
+ return this.#relations.values()
187
+ }
188
+
189
+ /**
190
+ * Returns a specific relationship by its ID.
191
+ */
192
+ public relationship(id: M['RelationId']): DeploymentRelationModel<M> {
193
+ return nonNullable(this.#relations.get(id), `DeploymentRelationModel ${id} not found`)
194
+ }
195
+ public findRelationship(id: LiteralUnion<M['RelationId'], string>): DeploymentRelationModel<M> | null {
196
+ return this.#relations.get(id as M['RelationId']) ?? null
197
+ }
198
+
199
+ /**
200
+ * Returns all deployment views in the model.
201
+ */
202
+ public *views(): IteratorLike<LikeC4ViewModel<M, ComputedDeploymentView>> {
203
+ for (const view of this.$model.views()) {
204
+ if (view.isDeploymentView()) {
205
+ yield view
206
+ }
207
+ }
208
+ return
209
+ }
210
+
211
+ /**
212
+ * Returns the parent element of given element.
213
+ * @see ancestors
214
+ */
215
+ public parent(element: M['DeploymentOrFqn']): DeploymentNodeModel<M> | null {
216
+ const id = getId(element)
217
+ return this.#parents.get(id) || null
218
+ }
219
+
220
+ /**
221
+ * Get all children of the element (only direct children),
222
+ * @see descendants
223
+ */
224
+ public children(element: M['DeploymentOrFqn']): ReadonlySet<DeploymentElementModel<M>> {
225
+ const id = getId(element)
226
+ return this.#children.get(id)
227
+ }
228
+
229
+ /**
230
+ * Get all sibling (i.e. same parent)
231
+ */
232
+ public *siblings(element: M['DeploymentOrFqn']): DeploymentElementsIterator<M> {
233
+ const id = getId(element)
234
+ const siblings = this.parent(element)?.children() ?? this.roots()
235
+ for (const sibling of siblings) {
236
+ if (sibling.id !== id) {
237
+ yield sibling
238
+ }
239
+ }
240
+ return
241
+ }
242
+
243
+ /**
244
+ * Get all ancestor elements (i.e. parent, parent’s parent, etc.)
245
+ * (from closest to root)
246
+ */
247
+ public *ancestors(element: M['DeploymentOrFqn']): DeploymentNodesIterator<M> {
248
+ let id = isString(element) ? element : element.id
249
+ let parent
250
+ while (parent = this.#parents.get(id)) {
251
+ yield parent
252
+ id = parent.id
253
+ }
254
+ return
255
+ }
256
+
257
+ /**
258
+ * Get all descendant elements (i.e. children, children’s children, etc.)
259
+ */
260
+ public *descendants(element: M['DeploymentOrFqn'], sort: 'asc' | 'desc' = 'desc'): DeploymentElementsIterator<M> {
261
+ for (const child of this.children(element)) {
262
+ if (sort === 'asc') {
263
+ yield child
264
+ yield* this.descendants(child.id)
265
+ } else {
266
+ yield* this.descendants(child.id)
267
+ yield child
268
+ }
269
+ }
270
+ return
271
+ }
272
+
273
+ /**
274
+ * Incoming relationships to the element and its descendants
275
+ * @see incomers
276
+ */
277
+ public *incoming(
278
+ element: M['DeploymentOrFqn'],
279
+ filter: IncomingFilter = 'all',
280
+ ): IteratorLike<DeploymentRelationModel<M>> {
281
+ const id = getId(element)
282
+ for (const rel of this.#incoming.get(id)) {
283
+ switch (true) {
284
+ case filter === 'all':
285
+ case filter === 'direct' && rel.target.id === id:
286
+ case filter === 'to-descendants' && rel.target.id !== id:
287
+ yield rel
288
+ break
289
+ }
290
+ }
291
+ return
292
+ }
293
+
294
+ /**
295
+ * Outgoing relationships from the element and its descendants
296
+ * @see outgoers
297
+ */
298
+ public *outgoing(
299
+ element: M['DeploymentOrFqn'],
300
+ filter: OutgoingFilter = 'all',
301
+ ): IteratorLike<DeploymentRelationModel<M>> {
302
+ const id = getId(element)
303
+ for (const rel of this.#outgoing.get(id)) {
304
+ switch (true) {
305
+ case filter === 'all':
306
+ case filter === 'direct' && rel.source.id === id:
307
+ case filter === 'from-descendants' && rel.source.id !== id:
308
+ yield rel
309
+ break
310
+ }
311
+ }
312
+ return
313
+ }
314
+
315
+ private addElement(element: DeploymentElement) {
316
+ if (this.#elements.has(element.id)) {
317
+ throw new Error(`Element ${element.id} already exists`)
318
+ }
319
+ const el = DeploymentElement.isDeploymentNode(element)
320
+ ? new DeploymentNodeModel(this, Object.freeze(element))
321
+ : new DeployedInstanceModel(this, Object.freeze(element), this.$model.element(element.element))
322
+ this.#elements.set(el.id, el)
323
+ const parentId = parentFqn(el.id)
324
+ if (parentId) {
325
+ invariant(this.#elements.has(parentId), `Parent ${parentId} of ${el.id} not found`)
326
+ this.#parents.set(el.id, this.node(parentId))
327
+ this.#children.get(parentId).add(el)
328
+ } else {
329
+ invariant(el.isDeploymentNode(), `Root element ${el.id} is not a deployment node`)
330
+ this.#rootElements.add(el)
331
+ }
332
+ return el
333
+ }
334
+
335
+ private addRelation(relation: DeploymentRelation) {
336
+ if (this.#relations.has(relation.id)) {
337
+ throw new Error(`Relation ${relation.id} already exists`)
338
+ }
339
+ const rel = new DeploymentRelationModel(
340
+ this,
341
+ Object.freeze(relation),
342
+ )
343
+ this.#relations.set(rel.id, rel)
344
+ this.#incoming.get(rel.target.id).add(rel)
345
+ this.#outgoing.get(rel.source.id).add(rel)
346
+
347
+ const relParent = rel.boundary?.id ?? null
348
+ // Process internal relationships
349
+ if (relParent) {
350
+ for (const ancestor of [relParent, ...ancestorsFqn(relParent)]) {
351
+ this.#internal.get(ancestor).add(rel)
352
+ }
353
+ }
354
+ // Process source hierarchy
355
+ for (const sourceAncestor of ancestorsFqn(rel.source.id)) {
356
+ if (sourceAncestor === relParent) {
357
+ break
358
+ }
359
+ this.#outgoing.get(sourceAncestor).add(rel)
360
+ }
361
+ // Process target hierarchy
362
+ for (const targetAncestor of ancestorsFqn(rel.target.id)) {
363
+ if (targetAncestor === relParent) {
364
+ break
365
+ }
366
+ this.#incoming.get(targetAncestor).add(rel)
367
+ }
368
+ return rel
369
+ }
370
+ }
@@ -0,0 +1,221 @@
1
+ import { isTruthy } from 'remeda'
2
+ import {
3
+ type Element as C4Element,
4
+ type ElementKind as C4ElementKind,
5
+ type ElementShape as C4ElementShape,
6
+ type IteratorLike,
7
+ type Link,
8
+ type Tag as C4Tag,
9
+ type ThemeColor,
10
+ DefaultElementShape,
11
+ DefaultThemeColor,
12
+ } from '../types'
13
+ import { commonAncestor, hierarchyLevel, isAncestor, sortNaturalByFqn } from '../utils'
14
+ import { type DeployedInstancesIterator } from './DeploymentElementModel'
15
+ import type { LikeC4Model } from './LikeC4Model'
16
+ import type { RelationshipModel, RelationshipsIterator } from './RelationModel'
17
+ import type { AnyAux, IncomingFilter, OutgoingFilter } from './types'
18
+ import type { LikeC4ViewModel, ViewsIterator } from './view/LikeC4ViewModel'
19
+
20
+ export type ElementsIterator<M extends AnyAux> = IteratorLike<ElementModel<M>>
21
+
22
+ export class ElementModel<M extends AnyAux = AnyAux> {
23
+ readonly id: M['Fqn']
24
+ readonly hierarchyLevel: number
25
+
26
+ constructor(
27
+ public readonly $model: LikeC4Model<M>,
28
+ public readonly $element: C4Element,
29
+ ) {
30
+ this.id = this.$element.id
31
+ this.hierarchyLevel = hierarchyLevel(this.id)
32
+ }
33
+
34
+ get parent(): ElementModel<M> | null {
35
+ return this.$model.parent(this)
36
+ }
37
+
38
+ get kind(): C4ElementKind {
39
+ return this.$element.kind
40
+ }
41
+
42
+ get shape(): C4ElementShape {
43
+ return this.$element.shape ?? DefaultElementShape
44
+ }
45
+
46
+ get color(): ThemeColor {
47
+ return this.$element.color as ThemeColor ?? DefaultThemeColor
48
+ }
49
+
50
+ get tags(): ReadonlyArray<C4Tag> {
51
+ return this.$element.tags ?? []
52
+ }
53
+
54
+ get title(): string {
55
+ return this.$element.title
56
+ }
57
+
58
+ get description(): string | null {
59
+ return this.$element.description
60
+ }
61
+
62
+ get technology(): string | null {
63
+ return this.$element.technology
64
+ }
65
+
66
+ get links(): ReadonlyArray<Link> {
67
+ return this.$element.links ?? []
68
+ }
69
+
70
+ get defaultView(): LikeC4ViewModel<M> | null {
71
+ return this.scopedViews().next().value ?? null
72
+ }
73
+
74
+ public isAncestorOf(another: ElementModel<M>): boolean {
75
+ return isAncestor(this, another)
76
+ }
77
+
78
+ public isDescendantOf(another: ElementModel<M>): boolean {
79
+ return isAncestor(another, this)
80
+ }
81
+
82
+ /**
83
+ * Get all ancestor elements (i.e. parent, parent’s parent, etc.)
84
+ * (from closest to root)
85
+ */
86
+ public ancestors(): ElementsIterator<M> {
87
+ return this.$model.ancestors(this)
88
+ }
89
+
90
+ /**
91
+ * Returns the common ancestor of this element and another element.
92
+ */
93
+ public commonAncestor(another: ElementModel<M>): ElementModel<M> | null {
94
+ const common = commonAncestor(this.id, another.id)
95
+ return common ? this.$model.element(common) : null
96
+ }
97
+
98
+ public children(): ReadonlySet<ElementModel<M>> {
99
+ return this.$model.children(this)
100
+ }
101
+
102
+ /**
103
+ * Get all descendant elements (i.e. children, children’s children, etc.)
104
+ */
105
+ public descendants(sort?: 'asc' | 'desc'): ElementsIterator<M> {
106
+ if (sort) {
107
+ const sorted = sortNaturalByFqn([...this.$model.descendants(this)], sort)
108
+ return sorted[Symbol.iterator]()
109
+ }
110
+ return this.$model.descendants(this)
111
+ }
112
+
113
+ /**
114
+ * Get all sibling (i.e. same parent)
115
+ */
116
+ public siblings(): ElementsIterator<M> {
117
+ return this.$model.siblings(this)
118
+ }
119
+
120
+ /**
121
+ * Resolve siblings of the element and its ancestors
122
+ * (from closest to root)
123
+ */
124
+ public *ascendingSiblings(): ElementsIterator<M> {
125
+ yield* this.siblings()
126
+ for (const ancestor of this.ancestors()) {
127
+ yield* ancestor.siblings()
128
+ }
129
+ return
130
+ }
131
+
132
+ /**
133
+ * Resolve siblings of the element and its ancestors
134
+ * (from root to closest)
135
+ */
136
+ public *descendingSiblings(): ElementsIterator<M> {
137
+ for (const ancestor of [...this.ancestors()].reverse()) {
138
+ yield* ancestor.siblings()
139
+ }
140
+ yield* this.siblings()
141
+ return
142
+ }
143
+
144
+ public incoming(filter: IncomingFilter = 'all'): RelationshipsIterator<M> {
145
+ return this.$model.incoming(this, filter)
146
+ }
147
+ public *incomers(filter: IncomingFilter = 'all'): ElementsIterator<M> {
148
+ const unique = new Set<M['Fqn']>()
149
+ for (const r of this.incoming(filter)) {
150
+ if (unique.has(r.source.id)) {
151
+ continue
152
+ }
153
+ unique.add(r.source.id)
154
+ yield r.source
155
+ }
156
+ return
157
+ }
158
+ public outgoing(filter: OutgoingFilter = 'all'): RelationshipsIterator<M> {
159
+ return this.$model.outgoing(this, filter)
160
+ }
161
+ public *outgoers(filter: OutgoingFilter = 'all'): ElementsIterator<M> {
162
+ const unique = new Set<M['Fqn']>()
163
+ for (const r of this.outgoing(filter)) {
164
+ if (unique.has(r.target.id)) {
165
+ continue
166
+ }
167
+ unique.add(r.target.id)
168
+ yield r.target
169
+ }
170
+ return
171
+ }
172
+
173
+ protected cachedOutgoing: Set<RelationshipModel<M>> | null = null
174
+ protected cachedIncoming: Set<RelationshipModel<M>> | null = null
175
+
176
+ public get allOutgoing(): ReadonlySet<RelationshipModel<M>> {
177
+ this.cachedOutgoing ??= new Set(this.outgoing())
178
+ return this.cachedOutgoing
179
+ }
180
+
181
+ public get allIncoming(): ReadonlySet<RelationshipModel<M>> {
182
+ this.cachedIncoming ??= new Set(this.incoming())
183
+ return this.cachedIncoming
184
+ }
185
+
186
+ /**
187
+ * Iterate over all views that include this element.
188
+ */
189
+ public *views(): ViewsIterator<M> {
190
+ for (const view of this.$model.views()) {
191
+ if (view.includesElement(this.id)) {
192
+ yield view
193
+ }
194
+ }
195
+ return
196
+ }
197
+
198
+ /**
199
+ * Iterate over all views that scope this element.
200
+ * It is possible that element is not included in the view.
201
+ */
202
+ public *scopedViews(): ViewsIterator<M> {
203
+ for (const vm of this.$model.views()) {
204
+ if (vm.isElementView() && vm.$view.viewOf === this.id) {
205
+ yield vm
206
+ }
207
+ }
208
+ return
209
+ }
210
+
211
+ /**
212
+ * @returns true if the element is deployed
213
+ */
214
+ public isDeployed(): boolean {
215
+ return isTruthy(this.deployments().next().value)
216
+ }
217
+
218
+ public deployments(): DeployedInstancesIterator<M> {
219
+ return this.$model.deployment.instancesOf(this)
220
+ }
221
+ }