@likec4/core 1.17.1 → 1.18.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 (112) hide show
  1. package/dist/compute-view/index.d.mts +91 -0
  2. package/dist/compute-view/index.d.ts +91 -0
  3. package/dist/compute-view/index.mjs +79 -0
  4. package/dist/index.d.mts +246 -434
  5. package/dist/index.d.ts +246 -434
  6. package/dist/index.mjs +418 -1322
  7. package/dist/model/index.d.mts +642 -0
  8. package/dist/model/index.d.ts +642 -0
  9. package/dist/model/index.mjs +2 -0
  10. package/dist/shared/core.BBgiAAv1.mjs +144 -0
  11. package/dist/shared/core.BPyJvuMU.mjs +2420 -0
  12. package/dist/shared/core.BQ4AbfvB.mjs +9228 -0
  13. package/dist/shared/{core.CoLQkcYL.d.mts → core.DMYY7C8s.d.mts} +398 -249
  14. package/dist/shared/{core.CoLQkcYL.d.ts → core.DMYY7C8s.d.ts} +398 -249
  15. package/dist/shared/core.k12s_keD.mjs +203 -0
  16. package/dist/types/index.d.mts +69 -1
  17. package/dist/types/index.d.ts +69 -1
  18. package/dist/types/index.mjs +21 -1
  19. package/package.json +38 -40
  20. package/src/builder/Builder.deployment.ts +219 -0
  21. package/src/builder/Builder.deploymentModel.ts +360 -0
  22. package/src/builder/Builder.element.ts +17 -19
  23. package/src/builder/Builder.model.ts +13 -2
  24. package/src/builder/Builder.test-d.ts +59 -4
  25. package/src/builder/Builder.ts +238 -53
  26. package/src/builder/Builder.view.ts +238 -53
  27. package/src/builder/_types.ts +87 -15
  28. package/src/compute-view/LikeC4ModelGraph.ts +330 -0
  29. package/src/compute-view/compute-view.ts +92 -0
  30. package/src/compute-view/deployment-view/Memory.ts +108 -0
  31. package/src/compute-view/deployment-view/Stage.ts +145 -0
  32. package/src/compute-view/deployment-view/__test__/fixture.ts +210 -0
  33. package/src/compute-view/deployment-view/_types.ts +29 -0
  34. package/src/compute-view/deployment-view/clean-connections.ts +229 -0
  35. package/src/compute-view/deployment-view/compute.ts +111 -0
  36. package/src/compute-view/deployment-view/predicates/deployment-ref.ts +146 -0
  37. package/src/compute-view/deployment-view/predicates/relation-direct.ts +130 -0
  38. package/src/compute-view/deployment-view/predicates/relation-in-out.ts +43 -0
  39. package/src/compute-view/deployment-view/predicates/relation-incoming.ts +46 -0
  40. package/src/compute-view/deployment-view/predicates/relation-outgoing.ts +49 -0
  41. package/src/compute-view/deployment-view/predicates/wildcard.ts +29 -0
  42. package/src/compute-view/deployment-view/utils.ts +212 -0
  43. package/src/compute-view/dynamic-view/__test__/fixture.ts +58 -0
  44. package/src/compute-view/dynamic-view/compute.ts +310 -0
  45. package/src/compute-view/element-view/__test__/__snapshots__/legacy.spec.ts.snap +456 -0
  46. package/src/compute-view/element-view/__test__/fixture.ts +645 -0
  47. package/src/compute-view/element-view/compute.ts +734 -0
  48. package/src/compute-view/element-view/predicates.ts +517 -0
  49. package/src/compute-view/index.ts +5 -0
  50. package/src/compute-view/utils/__snapshots__/sortNodes.spec.ts.snap +113 -0
  51. package/src/compute-view/utils/ancestorsOfNode.ts +19 -0
  52. package/src/compute-view/utils/applyCustomElementProperties.ts +66 -0
  53. package/src/compute-view/utils/applyCustomRelationProperties.ts +41 -0
  54. package/src/compute-view/utils/applyViewRuleStyles.ts +53 -0
  55. package/src/compute-view/utils/buildComputedNodes.ts +148 -0
  56. package/src/compute-view/utils/buildElementNotations.ts +63 -0
  57. package/src/compute-view/utils/derive-edge-props-from-relationships.ts +97 -0
  58. package/src/compute-view/utils/elementExpressionToPredicate.ts +52 -0
  59. package/src/compute-view/utils/link-nodes-with-edges.ts +58 -0
  60. package/src/compute-view/utils/relationExpressionToPredicates.ts +55 -0
  61. package/src/compute-view/utils/resolve-extended-views.ts +65 -0
  62. package/src/compute-view/utils/resolve-global-rules.ts +83 -0
  63. package/src/compute-view/utils/sortNodes.ts +101 -0
  64. package/src/compute-view/utils/topological-sort.ts +160 -0
  65. package/src/compute-view/utils/uniqueTags.test.ts +42 -0
  66. package/src/compute-view/utils/uniqueTags.ts +21 -0
  67. package/src/compute-view/utils/view-hash.ts +27 -0
  68. package/src/compute-view/utils/with-readable-edges.ts +31 -0
  69. package/src/index.ts +5 -1
  70. package/src/model/DeploymentElementModel.ts +534 -0
  71. package/src/model/DeploymentModel.ts +364 -0
  72. package/src/model/ElementModel.ts +195 -0
  73. package/src/model/LikeC4Model.test-d.ts +151 -0
  74. package/src/model/LikeC4Model.ts +280 -457
  75. package/src/model/RelationModel.ts +84 -0
  76. package/src/model/__test__/fixture.ts +153 -338
  77. package/src/model/connection/ConnectionModel.ts +94 -0
  78. package/src/model/connection/DeploymentConnectionModel.ts +99 -0
  79. package/src/model/connection/deployment.ts +124 -0
  80. package/src/model/connection/model.ts +123 -0
  81. package/src/model/index.ts +4 -2
  82. package/src/model/types.ts +95 -15
  83. package/src/model/view/EdgeModel.ts +103 -0
  84. package/src/model/view/LikeC4ViewModel.ts +220 -0
  85. package/src/model/view/NodeModel.ts +228 -0
  86. package/src/types/_common.ts +2 -0
  87. package/src/types/deployments.ts +237 -0
  88. package/src/types/element.ts +7 -3
  89. package/src/types/expression.ts +8 -6
  90. package/src/types/index.ts +1 -0
  91. package/src/types/model.ts +68 -9
  92. package/src/types/overview-graph.ts +2 -2
  93. package/src/types/relation.ts +4 -4
  94. package/src/types/view-notation.ts +2 -2
  95. package/src/types/view.ts +104 -30
  96. package/src/utils/{commonHead.ts → common-head.ts} +2 -0
  97. package/src/utils/fqn.ts +83 -27
  98. package/src/utils/getOrCreate.ts +42 -0
  99. package/src/utils/graphlib.ts +10 -0
  100. package/src/utils/index.ts +4 -1
  101. package/src/utils/object-hash.ts +3 -0
  102. package/src/utils/set.ts +76 -0
  103. package/src/utils/string-hash.ts +16 -0
  104. package/dist/index.cjs +0 -5452
  105. package/dist/index.d.cts +0 -1053
  106. package/dist/shared/core.BXTs-8n7.mjs +0 -293
  107. package/dist/shared/core.CoLQkcYL.d.cts +0 -734
  108. package/dist/shared/core.DIYZvpVg.cjs +0 -353
  109. package/dist/types/index.cjs +0 -70
  110. package/dist/types/index.d.cts +0 -2
  111. package/src/model/LikeC4DiagramModel.ts +0 -338
  112. package/src/model/LikeC4ViewModel.ts +0 -320
@@ -0,0 +1,364 @@
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 AnyLikeC4Model,
7
+ type ComputedDeploymentView,
8
+ DeploymentElement,
9
+ type DeploymentRef,
10
+ type DeploymentRelation,
11
+ type Tag as C4Tag
12
+ } from '../types'
13
+ import { ancestorsFqn, parentFqn } from '../utils/fqn'
14
+ import { getOrCreate } from '../utils/getOrCreate'
15
+ import { isString } from '../utils/guards'
16
+ import {
17
+ DeployedInstanceModel,
18
+ type DeployedInstancesIterator,
19
+ DeploymentElementModel,
20
+ type DeploymentElementsIterator,
21
+ DeploymentNodeModel,
22
+ type DeploymentNodesIterator,
23
+ DeploymentRelationModel,
24
+ NestedElementOfDeployedInstanceModel
25
+ } from './DeploymentElementModel'
26
+ import type { LikeC4Model } from './LikeC4Model'
27
+ import { type AnyAux, getId, type IncomingFilter, type IteratorLike, type OutgoingFilter } from './types'
28
+ import type { LikeC4ViewModel } from './view/LikeC4ViewModel'
29
+
30
+ export class LikeC4DeploymentModel<M extends AnyAux = AnyAux> {
31
+ readonly #elements = new Map<M['Deployment'], DeploymentElementModel<M>>()
32
+ // Parent element for given FQN
33
+ readonly #parents = new Map<M['Deployment'], DeploymentNodeModel<M>>()
34
+ // Children elements for given FQN
35
+ readonly #children = new DefaultMap<M['Deployment'], Set<DeploymentElementModel<M>>>(() => new Set())
36
+
37
+ // Keep track of instances of the logical element
38
+ readonly #instancesOf = new DefaultMap<M['Element'], Set<DeployedInstanceModel<M>>>(() => new Set())
39
+
40
+ readonly #rootElements = new Set<DeploymentNodeModel<M>>()
41
+
42
+ readonly #relations = new Map<M['RelationId'], DeploymentRelationModel<M>>()
43
+
44
+ // Incoming to an element or its descendants
45
+ readonly #incoming = new DefaultMap<M['Deployment'], Set<DeploymentRelationModel<M>>>(() => new Set())
46
+
47
+ // Outgoing from an element or its descendants
48
+ readonly #outgoing = new DefaultMap<M['Deployment'], Set<DeploymentRelationModel<M>>>(() => new Set())
49
+
50
+ // Relationships inside the element, among descendants
51
+ readonly #internal = new DefaultMap<M['Deployment'], Set<DeploymentRelationModel<M>>>(() => new Set())
52
+
53
+ // readonly #views = new Map<ViewID, LikeC4ViewModel<M>>()
54
+
55
+ readonly #allTags = new DefaultMap<C4Tag, Set<DeploymentElementModel<M> | DeploymentRelationModel<M>>>(() =>
56
+ new Set()
57
+ )
58
+
59
+ readonly #nestedElementsOfDeployment = new Map<
60
+ `${M['Deployment']}@${M['Element']}`,
61
+ NestedElementOfDeployedInstanceModel<M>
62
+ >()
63
+
64
+ constructor(
65
+ public readonly $model: LikeC4Model<M>,
66
+ public readonly $deployments: AnyLikeC4Model['deployments']
67
+ ) {
68
+ for (const element of values($deployments.elements)) {
69
+ const el = this.addElement(element)
70
+ for (const tag of el.tags) {
71
+ this.#allTags.get(tag).add(el)
72
+ }
73
+ if (el.isInstance()) {
74
+ this.#instancesOf.get(el.$element.id).add(el)
75
+ }
76
+ }
77
+ for (const relation of values($deployments.relations)) {
78
+ const el = this.addRelation(relation)
79
+ for (const tag of el.tags) {
80
+ this.#allTags.get(tag).add(el)
81
+ }
82
+ }
83
+ }
84
+
85
+ public element(el: M['DeploymentOrFqn']): DeploymentElementModel<M> {
86
+ if (el instanceof DeploymentElementModel) {
87
+ return el
88
+ }
89
+ const id = getId(el)
90
+ return nonNullable(this.#elements.get(id), `Element ${id} not found`)
91
+ }
92
+ public findElement(el: M['Deployment']): DeploymentElementModel<M> | null {
93
+ return this.#elements.get(el) ?? null
94
+ }
95
+
96
+ public node(el: M['DeploymentOrFqn']): DeploymentNodeModel<M> {
97
+ const element = this.element(el)
98
+ invariant(element.isDeploymentNode(), `Element ${element.id} is not a deployment node`)
99
+ return element
100
+ }
101
+ public findNode(el: M['Deployment']): DeploymentNodeModel<M> | null {
102
+ const element = this.findElement(el)
103
+ if (!element) {
104
+ return null
105
+ }
106
+ invariant(element.isDeploymentNode(), `Element ${element?.id} is not a deployment node`)
107
+ return element
108
+ }
109
+
110
+ public instance(el: M['DeploymentOrFqn']): DeployedInstanceModel<M> {
111
+ const element = this.element(el)
112
+ invariant(element.isInstance(), `Element ${element.id} is not a deployed instance`)
113
+ return element
114
+ }
115
+ public findInstance(el: M['Deployment']): DeployedInstanceModel<M> | null {
116
+ const element = this.findElement(el)
117
+ if (!element) {
118
+ return null
119
+ }
120
+ invariant(element.isInstance(), `Element ${element?.id} is not a deployed instance`)
121
+ return element
122
+ }
123
+
124
+ /**
125
+ * Returns the root elements of the model.
126
+ */
127
+ public roots(): DeploymentNodesIterator<M> {
128
+ return this.#rootElements.values()
129
+ }
130
+
131
+ /**
132
+ * Returns all elements in the model.
133
+ */
134
+ public elements(): DeploymentElementsIterator<M> {
135
+ return this.#elements.values()
136
+ }
137
+
138
+ /**
139
+ * Returns all elements in the model.
140
+ */
141
+ public *nodes(): DeploymentNodesIterator<M> {
142
+ for (const element of this.#elements.values()) {
143
+ if (element.isDeploymentNode()) {
144
+ yield element
145
+ }
146
+ }
147
+ return
148
+ }
149
+
150
+ public *instances(): DeployedInstancesIterator<M> {
151
+ for (const element of this.#elements.values()) {
152
+ if (element.isInstance()) {
153
+ yield element
154
+ }
155
+ }
156
+ return
157
+ }
158
+
159
+ /**
160
+ * Iterate over all instances of the given logical element.
161
+ */
162
+ public *instancesOf(element: M['ElementOrFqn']): DeployedInstancesIterator<M> {
163
+ const id = getId(element)
164
+ const instances = this.#instancesOf.get(id)
165
+ if (instances) {
166
+ yield* instances
167
+ }
168
+ return
169
+ }
170
+
171
+ public deploymentRef(ref: DeploymentRef): DeploymentElementModel<M> | NestedElementOfDeployedInstanceModel<M> {
172
+ if ('element' in ref) {
173
+ const { id, element } = ref
174
+ return getOrCreate(this.#nestedElementsOfDeployment, `${id}@${element}`, () => {
175
+ return new NestedElementOfDeployedInstanceModel(this.instance(id), this.$model.element(element))
176
+ })
177
+ }
178
+ return this.element(ref)
179
+ }
180
+
181
+ /**
182
+ * Returns all relationships in the model.
183
+ */
184
+ public relationships(): IteratorLike<DeploymentRelationModel<M>> {
185
+ return this.#relations.values()
186
+ }
187
+
188
+ /**
189
+ * Returns a specific relationship by its ID.
190
+ */
191
+ public relationship(id: M['RelationId']): DeploymentRelationModel<M> {
192
+ return nonNullable(this.#relations.get(id), `DeploymentRelationModel ${id} not found`)
193
+ }
194
+ public findRelationship(id: LiteralUnion<M['RelationId'], string>): DeploymentRelationModel<M> | null {
195
+ return this.#relations.get(id as M['RelationId']) ?? null
196
+ }
197
+
198
+ /**
199
+ * Returns all deployment views in the model.
200
+ */
201
+ public *views(): IteratorLike<LikeC4ViewModel<M, ComputedDeploymentView>> {
202
+ for (const view of this.$model.views()) {
203
+ if (view.isDeploymentView()) {
204
+ yield view
205
+ }
206
+ }
207
+ return
208
+ }
209
+
210
+ /**
211
+ * Returns the parent element of given element.
212
+ * @see ancestors
213
+ */
214
+ public parent(element: M['DeploymentOrFqn']): DeploymentNodeModel<M> | null {
215
+ const id = getId(element)
216
+ return this.#parents.get(id) || null
217
+ }
218
+
219
+ /**
220
+ * Get all children of the element (only direct children),
221
+ * @see descendants
222
+ */
223
+ public children(element: M['DeploymentOrFqn']): ReadonlySet<DeploymentElementModel<M>> {
224
+ const id = getId(element)
225
+ return this.#children.get(id)
226
+ }
227
+
228
+ /**
229
+ * Get all sibling (i.e. same parent)
230
+ */
231
+ public *siblings(element: M['DeploymentOrFqn']): DeploymentElementsIterator<M> {
232
+ const id = getId(element)
233
+ const siblings = this.parent(element)?.children() ?? this.roots()
234
+ for (const sibling of siblings) {
235
+ if (sibling.id !== id) {
236
+ yield sibling
237
+ }
238
+ }
239
+ return
240
+ }
241
+
242
+ /**
243
+ * Get all ancestor elements (i.e. parent, parent’s parent, etc.)
244
+ * (from closest to root)
245
+ */
246
+ public *ancestors(element: M['DeploymentOrFqn']): DeploymentNodesIterator<M> {
247
+ let id = isString(element) ? element : element.id
248
+ let parent
249
+ while (parent = this.#parents.get(id)) {
250
+ yield parent
251
+ id = parent.id
252
+ }
253
+ return
254
+ }
255
+
256
+ /**
257
+ * Get all descendant elements (i.e. children, children’s children, etc.)
258
+ */
259
+ public *descendants(element: M['DeploymentOrFqn']): DeploymentElementsIterator<M> {
260
+ for (const child of this.children(element)) {
261
+ yield child
262
+ yield* this.descendants(child.id)
263
+ }
264
+ return
265
+ }
266
+
267
+ /**
268
+ * Incoming relationships to the element and its descendants
269
+ * @see incomers
270
+ */
271
+ public *incoming(
272
+ element: M['DeploymentOrFqn'],
273
+ filter: IncomingFilter = 'all'
274
+ ): IteratorLike<DeploymentRelationModel<M>> {
275
+ const id = getId(element)
276
+ for (const rel of this.#incoming.get(id)) {
277
+ switch (true) {
278
+ case filter === 'all':
279
+ case filter === 'direct' && rel.target.id === id:
280
+ case filter === 'to-descendants' && rel.target.id !== id:
281
+ yield rel
282
+ break
283
+ }
284
+ }
285
+ return
286
+ }
287
+
288
+ /**
289
+ * Outgoing relationships from the element and its descendants
290
+ * @see outgoers
291
+ */
292
+ public *outgoing(
293
+ element: M['DeploymentOrFqn'],
294
+ filter: OutgoingFilter = 'all'
295
+ ): IteratorLike<DeploymentRelationModel<M>> {
296
+ const id = getId(element)
297
+ for (const rel of this.#outgoing.get(id)) {
298
+ switch (true) {
299
+ case filter === 'all':
300
+ case filter === 'direct' && rel.source.id === id:
301
+ case filter === 'from-descendants' && rel.source.id !== id:
302
+ yield rel
303
+ break
304
+ }
305
+ }
306
+ return
307
+ }
308
+
309
+ private addElement(element: DeploymentElement) {
310
+ if (this.#elements.has(element.id)) {
311
+ throw new Error(`Element ${element.id} already exists`)
312
+ }
313
+ const el = DeploymentElement.isDeploymentNode(element)
314
+ ? new DeploymentNodeModel(this, element)
315
+ : new DeployedInstanceModel(this, element, this.$model.element(element.element))
316
+ this.#elements.set(el.id, el)
317
+ const parentId = parentFqn(el.id)
318
+ if (parentId) {
319
+ invariant(this.#elements.has(parentId), `Parent ${parentId} of ${el.id} not found`)
320
+ this.#parents.set(el.id, this.node(parentId))
321
+ this.#children.get(parentId).add(el)
322
+ } else {
323
+ invariant(el.isDeploymentNode(), `Root element ${el.id} is not a deployment node`)
324
+ this.#rootElements.add(el)
325
+ }
326
+ return el
327
+ }
328
+
329
+ private addRelation(relation: DeploymentRelation) {
330
+ if (this.#relations.has(relation.id)) {
331
+ throw new Error(`Relation ${relation.id} already exists`)
332
+ }
333
+ const rel = new DeploymentRelationModel(
334
+ this,
335
+ relation
336
+ )
337
+ this.#relations.set(rel.id, rel)
338
+ this.#incoming.get(rel.target.id).add(rel)
339
+ this.#outgoing.get(rel.source.id).add(rel)
340
+
341
+ const relParent = rel.boundary?.id ?? null
342
+ // Process internal relationships
343
+ if (relParent) {
344
+ for (const ancestor of [relParent, ...ancestorsFqn(relParent)]) {
345
+ this.#internal.get(ancestor).add(rel)
346
+ }
347
+ }
348
+ // Process source hierarchy
349
+ for (const sourceAncestor of ancestorsFqn(rel.source.id)) {
350
+ if (sourceAncestor === relParent) {
351
+ break
352
+ }
353
+ this.#outgoing.get(sourceAncestor).add(rel)
354
+ }
355
+ // Process target hierarchy
356
+ for (const targetAncestor of ancestorsFqn(rel.target.id)) {
357
+ if (targetAncestor === relParent) {
358
+ break
359
+ }
360
+ this.#incoming.get(targetAncestor).add(rel)
361
+ }
362
+ return rel
363
+ }
364
+ }
@@ -0,0 +1,195 @@
1
+ import { isTruthy } from 'remeda'
2
+ import {
3
+ DefaultElementShape,
4
+ DefaultThemeColor,
5
+ type Element as C4Element,
6
+ type ElementKind as C4ElementKind,
7
+ type ElementShape as C4ElementShape,
8
+ type Link,
9
+ type Tag as C4Tag,
10
+ type ThemeColor
11
+ } from '../types'
12
+ import { commonAncestor } from '../utils'
13
+ import { type DeployedInstancesIterator } from './DeploymentElementModel'
14
+ import type { LikeC4Model } from './LikeC4Model'
15
+ import type { RelationshipModel, RelationshipsIterator } from './RelationModel'
16
+ import type { AnyAux, IncomingFilter, IteratorLike, OutgoingFilter } from './types'
17
+ import type { LikeC4ViewModel, ViewsIterator } from './view/LikeC4ViewModel'
18
+
19
+ export type ElementsIterator<M extends AnyAux> = IteratorLike<ElementModel<M>>
20
+
21
+ export class ElementModel<M extends AnyAux> {
22
+ constructor(
23
+ public readonly model: LikeC4Model<M>,
24
+ public readonly $element: C4Element
25
+ ) {
26
+ }
27
+
28
+ get id(): M['Fqn'] {
29
+ return this.$element.id
30
+ }
31
+
32
+ get kind(): C4ElementKind {
33
+ return this.$element.kind
34
+ }
35
+
36
+ get shape(): C4ElementShape {
37
+ return this.$element.shape ?? DefaultElementShape
38
+ }
39
+
40
+ get color(): ThemeColor {
41
+ return this.$element.color as ThemeColor ?? DefaultThemeColor
42
+ }
43
+
44
+ get tags(): ReadonlyArray<C4Tag> {
45
+ return this.$element.tags ?? []
46
+ }
47
+
48
+ get title(): string {
49
+ return this.$element.title
50
+ }
51
+
52
+ get description(): string | null {
53
+ return this.$element.description
54
+ }
55
+
56
+ get technology(): string | null {
57
+ return this.$element.technology
58
+ }
59
+
60
+ get links(): ReadonlyArray<Link> {
61
+ return this.$element.links ?? []
62
+ }
63
+
64
+ get parent(): ElementModel<M> | null {
65
+ return this.model.parent(this)
66
+ }
67
+
68
+ get defaultView(): LikeC4ViewModel<M> | null {
69
+ return this.scopedViews().next().value ?? null
70
+ }
71
+
72
+ /**
73
+ * Get all ancestor elements (i.e. parent, parent’s parent, etc.)
74
+ * (from closest to root)
75
+ */
76
+ public ancestors(): ElementsIterator<M> {
77
+ return this.model.ancestors(this)
78
+ }
79
+
80
+ /**
81
+ * Returns the common ancestor of this element and another element.
82
+ */
83
+ public commonAncestor(another: ElementModel<M>): ElementModel<M> | null {
84
+ const common = commonAncestor(this.id, another.id)
85
+ return common ? this.model.element(common) : null
86
+ }
87
+
88
+ public children(): ReadonlySet<ElementModel<M>> {
89
+ return this.model.children(this)
90
+ }
91
+
92
+ /**
93
+ * Get all descendant elements (i.e. children, children’s children, etc.)
94
+ */
95
+ public descendants(): ElementsIterator<M> {
96
+ return this.model.descendants(this)
97
+ }
98
+
99
+ /**
100
+ * Get all sibling (i.e. same parent)
101
+ */
102
+ public siblings(): ElementsIterator<M> {
103
+ return this.model.siblings(this)
104
+ }
105
+
106
+ /**
107
+ * Resolve siblings of the element and its ancestors
108
+ * (from closest to root)
109
+ */
110
+ public *ascendingSiblings(): ElementsIterator<M> {
111
+ yield* this.siblings()
112
+ for (const ancestor of this.ancestors()) {
113
+ yield* ancestor.siblings()
114
+ }
115
+ return
116
+ }
117
+
118
+ public incoming(filter: IncomingFilter = 'all'): RelationshipsIterator<M> {
119
+ return this.model.incoming(this, filter)
120
+ }
121
+ public *incomers(filter: IncomingFilter = 'all'): ElementsIterator<M> {
122
+ const unique = new Set<M['Fqn']>()
123
+ for (const r of this.incoming(filter)) {
124
+ if (unique.has(r.source.id)) {
125
+ continue
126
+ }
127
+ unique.add(r.source.id)
128
+ yield r.source
129
+ }
130
+ return
131
+ }
132
+ public outgoing(filter: OutgoingFilter = 'all'): RelationshipsIterator<M> {
133
+ return this.model.outgoing(this, filter)
134
+ }
135
+ public *outgoers(filter: OutgoingFilter = 'all'): ElementsIterator<M> {
136
+ const unique = new Set<M['Fqn']>()
137
+ for (const r of this.outgoing(filter)) {
138
+ if (unique.has(r.target.id)) {
139
+ continue
140
+ }
141
+ unique.add(r.target.id)
142
+ yield r.target
143
+ }
144
+ return
145
+ }
146
+
147
+ protected cachedOutgoing: Set<RelationshipModel<M>> | null = null
148
+ protected cachedIncoming: Set<RelationshipModel<M>> | null = null
149
+
150
+ public get allOutgoing(): ReadonlySet<RelationshipModel<M>> {
151
+ this.cachedOutgoing ??= new Set(this.outgoing())
152
+ return this.cachedOutgoing
153
+ }
154
+
155
+ public get allIncoming(): ReadonlySet<RelationshipModel<M>> {
156
+ this.cachedIncoming ??= new Set(this.incoming())
157
+ return this.cachedIncoming
158
+ }
159
+
160
+ /**
161
+ * Iterate over all views that include this element.
162
+ */
163
+ public *views(): ViewsIterator<M> {
164
+ for (const view of this.model.views()) {
165
+ if (view.includesElement(this.id)) {
166
+ yield view
167
+ }
168
+ }
169
+ return
170
+ }
171
+
172
+ /**
173
+ * Iterate over all views that scope this element.
174
+ * It is possible that element is not included in the view.
175
+ */
176
+ public *scopedViews(): ViewsIterator<M> {
177
+ for (const vm of this.model.views()) {
178
+ if (vm.isElementView() && vm.$view.viewOf === this.id) {
179
+ yield vm
180
+ }
181
+ }
182
+ return
183
+ }
184
+
185
+ /**
186
+ * @returns true if the element is deployed
187
+ */
188
+ public isDeployed(): boolean {
189
+ return isTruthy(this.deployments().next().value)
190
+ }
191
+
192
+ public deployments(): DeployedInstancesIterator<M> {
193
+ return this.model.deployment.instancesOf(this)
194
+ }
195
+ }
@@ -0,0 +1,151 @@
1
+ import { expectTypeOf, test } from 'vitest'
2
+ import { Builder } from '../builder'
3
+ import { computeViews } from '../compute-view/compute-view'
4
+ import type { ComputedView, Fqn, ViewId } from '../types'
5
+ import { LikeC4Model } from './LikeC4Model'
6
+
7
+ test('LikeC4Model.create: should have types', () => {
8
+ const {
9
+ model: {
10
+ model,
11
+ actor,
12
+ system,
13
+ component,
14
+ relTo
15
+ },
16
+ deployment: {
17
+ deployment,
18
+ instanceOf,
19
+ env,
20
+ vm
21
+ },
22
+ views: {
23
+ deploymentView,
24
+ views,
25
+ view,
26
+ $include
27
+ },
28
+ builder
29
+ } = Builder.forSpecification({
30
+ elements: {
31
+ actor: {
32
+ style: {
33
+ shape: 'person'
34
+ }
35
+ },
36
+ system: {},
37
+ component: {}
38
+ },
39
+ deployments: {
40
+ env: {},
41
+ vm: {}
42
+ },
43
+ relationships: {
44
+ like: {},
45
+ dislike: {}
46
+ },
47
+ tags: ['tag1', 'tag2', 'tag1']
48
+ })
49
+
50
+ const source = builder
51
+ .with(
52
+ model(
53
+ actor('alice'),
54
+ actor('bob'),
55
+ system('cloud').with(
56
+ component('backend').with(
57
+ component('api'),
58
+ component('db')
59
+ ),
60
+ component('frontend').with(
61
+ relTo('cloud.backend.api')
62
+ )
63
+ )
64
+ ),
65
+ deployment(
66
+ env('prod').with(
67
+ vm('vm1'),
68
+ vm('vm2')
69
+ ),
70
+ env('dev').with(
71
+ vm('vm1'),
72
+ instanceOf('api', 'cloud.backend.api'),
73
+ vm('vm2')
74
+ )
75
+ ),
76
+ views(
77
+ view('index', $include('*')),
78
+ deploymentView('prodview', $include('*'))
79
+ )
80
+ )
81
+ .build()
82
+
83
+ const computed = computeViews(source)
84
+ const m = LikeC4Model.create(computed)
85
+
86
+ expectTypeOf(m.Aux.Element).toEqualTypeOf(
87
+ '' as 'alice' | 'bob' | 'cloud' | 'cloud.backend' | 'cloud.backend.api' | 'cloud.backend.db' | 'cloud.frontend'
88
+ )
89
+ expectTypeOf(m.Aux.Fqn).toEqualTypeOf(
90
+ '' as Fqn<'alice' | 'bob' | 'cloud' | 'cloud.backend' | 'cloud.backend.api' | 'cloud.backend.db' | 'cloud.frontend'>
91
+ )
92
+ expectTypeOf(m.Aux.ViewId).toEqualTypeOf(
93
+ '' as ViewId<'index' | 'prodview'>
94
+ )
95
+ expectTypeOf(m.Aux.ViewType).toEqualTypeOf(
96
+ {} as ComputedView<ViewId<'index' | 'prodview'>>
97
+ )
98
+ expectTypeOf(m.Aux.Deployment).toEqualTypeOf(
99
+ '' as 'prod' | 'dev' | 'prod.vm1' | 'prod.vm2' | 'dev.vm1' | 'dev.vm2' | 'dev.api'
100
+ )
101
+ expectTypeOf(m.Aux.DeploymentFqn).toEqualTypeOf(
102
+ '' as Fqn<'prod' | 'dev' | 'prod.vm1' | 'prod.vm2' | 'dev.vm1' | 'dev.vm2' | 'dev.api'>
103
+ )
104
+ })
105
+
106
+ test('LikeC4Model.fromDump: should have types', () => {
107
+ const m = LikeC4Model.fromDump({
108
+ specification: {
109
+ tags: [],
110
+ elements: {},
111
+ relationships: {},
112
+ deployments: {}
113
+ },
114
+ elements: {
115
+ el1: {},
116
+ el2: {}
117
+ },
118
+ relations: {},
119
+ globals: {
120
+ predicates: {},
121
+ dynamicPredicates: {},
122
+ styles: {}
123
+ },
124
+ views: {
125
+ v1: {},
126
+ v2: {}
127
+ },
128
+ deployments: {
129
+ elements: {
130
+ d1: {},
131
+ d2: {}
132
+ },
133
+ relations: {}
134
+ }
135
+ })
136
+ expectTypeOf(m.Aux.Element).toEqualTypeOf(
137
+ '' as 'el1' | 'el2'
138
+ )
139
+ expectTypeOf(m.Aux.Fqn).toEqualTypeOf(
140
+ '' as Fqn<'el1' | 'el2'>
141
+ )
142
+ expectTypeOf(m.Aux.ViewId).toEqualTypeOf(
143
+ '' as ViewId<'v1' | 'v2'>
144
+ )
145
+ expectTypeOf(m.Aux.Deployment).toEqualTypeOf(
146
+ '' as 'd1' | 'd2'
147
+ )
148
+ expectTypeOf(m.Aux.DeploymentFqn).toEqualTypeOf(
149
+ '' as Fqn<'d1' | 'd2'>
150
+ )
151
+ })