@likec4/core 1.30.0 → 1.32.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 (186) hide show
  1. package/README.md +407 -5
  2. package/dist/builder/index.d.mts +221 -42
  3. package/dist/builder/index.mjs +118 -65
  4. package/dist/compute-view/index.d.mts +23 -7
  5. package/dist/compute-view/index.mjs +11 -6
  6. package/dist/compute-view/relationships-view/index.d.mts +9 -7
  7. package/dist/compute-view/relationships-view/index.mjs +3 -2
  8. package/dist/index.d.mts +10 -14
  9. package/dist/index.mjs +10 -6
  10. package/dist/model/index.d.mts +173 -19
  11. package/dist/model/index.mjs +6 -1
  12. package/dist/shared/core.BaYE_r29.d.mts +258 -0
  13. package/dist/shared/core.Bk7wEZFG.mjs +276 -0
  14. package/dist/shared/core.BrCnZFTn.mjs +32 -0
  15. package/dist/shared/core.BuO4ncfY.d.mts +146 -0
  16. package/dist/shared/{core.CAcZ6_cP.mjs → core.CBpKAlhj.mjs} +1742 -4128
  17. package/dist/shared/core.CCWXGc6G.d.mts +920 -0
  18. package/dist/shared/core.CUYDgv2-.mjs +214 -0
  19. package/dist/shared/{core.Cu-UdkSl.mjs → core.CpXlRyE5.mjs} +7 -494
  20. package/dist/shared/{core.C-YXI-43.mjs → core.Cy9smucx.mjs} +3 -1
  21. package/dist/shared/core.CywrQs86.d.mts +31 -0
  22. package/dist/shared/core.DoK86JEe.mjs +518 -0
  23. package/dist/shared/core.Dpq3P3YJ.d.mts +1265 -0
  24. package/dist/shared/{core.D2830qgg.d.mts → core.Pf5I9o0e.d.mts} +1 -1
  25. package/dist/shared/core.cbA9VZ02.mjs +2681 -0
  26. package/dist/shared/{core.CQXU9DF7.mjs → core.rmvsy0n3.mjs} +23 -32
  27. package/dist/types/aux.d.mts +3 -0
  28. package/dist/types/aux.mjs +1 -0
  29. package/dist/types/index.d.mts +7 -45
  30. package/dist/types/index.mjs +130 -3
  31. package/dist/utils/index.d.mts +221 -53
  32. package/dist/utils/index.mjs +14 -10
  33. package/package.json +19 -10
  34. package/src/builder/Builder.deploymentModel.ts +82 -18
  35. package/src/builder/Builder.element.ts +40 -20
  36. package/src/builder/Builder.model.ts +66 -17
  37. package/src/builder/Builder.ts +168 -76
  38. package/src/builder/Builder.view-common.ts +35 -17
  39. package/src/builder/Builder.view-deployment.ts +25 -25
  40. package/src/builder/Builder.view-element.ts +13 -12
  41. package/src/builder/Builder.views.ts +12 -7
  42. package/src/builder/__snapshots__/Builder-style1.spec.ts.snap +62 -34
  43. package/src/builder/__snapshots__/Builder-style2.compute-model.json5 +319 -0
  44. package/src/builder/__snapshots__/Builder-style2.spec.ts.snap +45 -24
  45. package/src/builder/_types.ts +34 -27
  46. package/src/compute-view/compute-view.ts +31 -34
  47. package/src/compute-view/deployment-view/__test__/TestHelper.ts +27 -14
  48. package/src/compute-view/deployment-view/__test__/fixture.ts +15 -13
  49. package/src/compute-view/deployment-view/_types.ts +10 -10
  50. package/src/compute-view/deployment-view/clean-connections.ts +5 -7
  51. package/src/compute-view/deployment-view/compute.ts +16 -13
  52. package/src/compute-view/deployment-view/memory/memory.ts +5 -6
  53. package/src/compute-view/deployment-view/predicates/deploymentRefs.ts +4 -5
  54. package/src/compute-view/deployment-view/predicates/relation-direct.ts +25 -50
  55. package/src/compute-view/deployment-view/predicates/relation-in-out.ts +5 -7
  56. package/src/compute-view/deployment-view/predicates/relation-incoming.ts +7 -11
  57. package/src/compute-view/deployment-view/predicates/relation-outgoing.ts +7 -10
  58. package/src/compute-view/deployment-view/predicates/utils.ts +37 -31
  59. package/src/compute-view/deployment-view/predicates/wildcard.ts +3 -2
  60. package/src/compute-view/deployment-view/stages/stage-final.ts +2 -2
  61. package/src/compute-view/deployment-view/stages/stage-include.ts +1 -1
  62. package/src/compute-view/deployment-view/utils.ts +79 -72
  63. package/src/compute-view/dynamic-view/__test__/fixture.ts +20 -11
  64. package/src/compute-view/dynamic-view/compute.ts +60 -53
  65. package/src/compute-view/element-view/__test__/TestHelper.ts +53 -29
  66. package/src/compute-view/element-view/__test__/__snapshots__/legacy.spec.ts.snap +26 -26
  67. package/src/compute-view/element-view/__test__/fixture.ts +134 -83
  68. package/src/compute-view/element-view/_types.ts +14 -19
  69. package/src/compute-view/element-view/clean-connections.ts +12 -8
  70. package/src/compute-view/element-view/compute.ts +61 -51
  71. package/src/compute-view/element-view/memory/NodeGroup.ts +8 -9
  72. package/src/compute-view/element-view/memory/memory.ts +22 -22
  73. package/src/compute-view/element-view/memory/stage-include.ts +5 -3
  74. package/src/compute-view/element-view/predicates/_utils.ts +18 -14
  75. package/src/compute-view/element-view/predicates/element-expand.ts +4 -5
  76. package/src/compute-view/element-view/predicates/element-kind-tag.ts +3 -4
  77. package/src/compute-view/element-view/predicates/element-ref.ts +3 -4
  78. package/src/compute-view/element-view/predicates/relation-direct.ts +16 -12
  79. package/src/compute-view/element-view/predicates/relation-in-out.ts +7 -8
  80. package/src/compute-view/element-view/predicates/relation-in.ts +19 -26
  81. package/src/compute-view/element-view/predicates/relation-out.ts +20 -21
  82. package/src/compute-view/element-view/predicates/wildcard.ts +3 -3
  83. package/src/compute-view/element-view/utils.ts +25 -23
  84. package/src/compute-view/index.ts +1 -1
  85. package/src/compute-view/memory/AbstractStageExclude.ts +3 -4
  86. package/src/compute-view/memory/AbstractStageInclude.ts +1 -1
  87. package/src/compute-view/memory/_types.ts +3 -2
  88. package/src/compute-view/relationships-view/_types.ts +3 -4
  89. package/src/compute-view/relationships-view/compute.ts +8 -11
  90. package/src/compute-view/relationships-view/layout.ts +6 -6
  91. package/src/compute-view/relationships-view/utils.ts +4 -4
  92. package/src/compute-view/utils/ancestorsOfNode.ts +7 -7
  93. package/src/compute-view/utils/applyCustomElementProperties.ts +16 -8
  94. package/src/compute-view/utils/applyCustomRelationProperties.ts +19 -9
  95. package/src/compute-view/utils/applyViewRuleStyles.ts +18 -10
  96. package/src/compute-view/utils/buildComputedNodes.ts +40 -40
  97. package/src/compute-view/utils/buildElementNotations.ts +14 -14
  98. package/src/compute-view/utils/elementExpressionToPredicate.ts +14 -15
  99. package/src/compute-view/utils/link-nodes-with-edges.ts +6 -4
  100. package/src/compute-view/utils/merge-props-from-relationships.ts +52 -20
  101. package/src/compute-view/utils/relationExpressionToPredicates.ts +19 -19
  102. package/src/compute-view/utils/resolve-extended-views.ts +8 -7
  103. package/src/compute-view/utils/resolve-global-rules.ts +31 -26
  104. package/src/compute-view/utils/topological-sort.ts +14 -14
  105. package/src/compute-view/utils/view-hash.ts +2 -2
  106. package/src/compute-view/utils/with-readable-edges.ts +9 -5
  107. package/src/index.ts +0 -32
  108. package/src/model/DeploymentElementModel.ts +182 -137
  109. package/src/model/DeploymentModel.ts +88 -65
  110. package/src/model/ElementModel.ts +92 -72
  111. package/src/model/LikeC4Model.ts +430 -241
  112. package/src/model/RelationModel.ts +69 -26
  113. package/src/model/__test__/fixture.ts +50 -36
  114. package/src/model/connection/Connection.ts +2 -2
  115. package/src/model/connection/deployment/DeploymentConnectionModel.ts +21 -24
  116. package/src/model/connection/deployment/find.ts +1 -1
  117. package/src/model/connection/model/ConnectionModel.ts +21 -20
  118. package/src/model/connection/model/find.ts +7 -7
  119. package/src/model/connection/ops.ts +6 -6
  120. package/src/model/guards.ts +66 -14
  121. package/src/model/index.ts +20 -7
  122. package/src/model/types.ts +41 -97
  123. package/src/model/view/EdgeModel.ts +47 -41
  124. package/src/model/view/LikeC4ViewModel.ts +130 -80
  125. package/src/model/view/NodeModel.ts +102 -96
  126. package/src/theme/element.ts +1 -1
  127. package/src/theme/index.ts +9 -9
  128. package/src/theme/relationships.ts +1 -1
  129. package/src/types/_common.ts +21 -15
  130. package/src/types/aux.ts +285 -0
  131. package/src/types/const.ts +34 -0
  132. package/src/types/expression-model.ts +292 -0
  133. package/src/types/expression.ts +311 -0
  134. package/src/types/fqnRef.ts +88 -0
  135. package/src/types/geometry.ts +131 -0
  136. package/src/types/global.ts +10 -8
  137. package/src/types/index.ts +39 -201
  138. package/src/types/model-data.ts +52 -84
  139. package/src/types/model-deployment.ts +87 -0
  140. package/src/types/model-dump.ts +72 -0
  141. package/src/types/model-logical.ts +111 -0
  142. package/src/types/model-spec.ts +81 -0
  143. package/src/types/operators.ts +51 -52
  144. package/src/types/scalar.ts +114 -0
  145. package/src/types/styles.ts +123 -0
  146. package/src/types/view-changes.ts +4 -5
  147. package/src/types/view-common.ts +157 -0
  148. package/src/types/view-computed.ts +113 -0
  149. package/src/types/view-layouted.ts +82 -0
  150. package/src/types/view-parsed.deployment.ts +40 -0
  151. package/src/types/view-parsed.dynamic.ts +78 -0
  152. package/src/types/view-parsed.element.ts +65 -0
  153. package/src/types/view.ts +122 -542
  154. package/src/utils/compare-natural.ts +43 -1
  155. package/src/utils/getOrCreate.ts +0 -34
  156. package/src/utils/index.ts +7 -12
  157. package/src/utils/iterable/filter.ts +1 -1
  158. package/src/utils/iterable/find.ts +3 -1
  159. package/src/utils/iterable/head.ts +22 -0
  160. package/src/utils/iterable/index.ts +1 -0
  161. package/src/utils/iterable/map.ts +1 -1
  162. package/src/utils/iterable/reduce.ts +1 -1
  163. package/src/utils/iterable/some.ts +1 -1
  164. package/src/utils/memoize-prop.ts +30 -0
  165. package/src/utils/object-hash.ts +1 -1
  166. package/src/utils/relations.ts +14 -11
  167. package/src/utils/string-hash.ts +1 -1
  168. package/dist/shared/core.CZR9J1Qx.d.mts +0 -146
  169. package/dist/shared/core.CiFKryGW.d.mts +0 -745
  170. package/dist/shared/core.DJvmfnEi.d.mts +0 -1153
  171. package/dist/shared/core.DgfwjBtu.mjs +0 -495
  172. package/dist/shared/core.SjLhMA7a.d.mts +0 -62
  173. package/src/builder/__snapshots__/Builder.spec.ts.snap +0 -374
  174. package/src/compute-view/element-view/predicates.ts +0 -520
  175. package/src/compute-view/utils/uniqueTags.ts +0 -21
  176. package/src/types/deployments.ts +0 -170
  177. package/src/types/element.ts +0 -109
  178. package/src/types/expression-v2-model.ts +0 -314
  179. package/src/types/expression-v2.ts +0 -392
  180. package/src/types/overview-graph.ts +0 -43
  181. package/src/types/relation.ts +0 -61
  182. package/src/types/scalars.ts +0 -39
  183. package/src/types/theme.ts +0 -82
  184. package/src/types/view-notation.ts +0 -9
  185. package/src/utils/graphlib.ts +0 -10
  186. /package/src/{errors/index.ts → utils/invariant.ts} +0 -0
@@ -1,18 +1,16 @@
1
1
  import { values } from 'remeda'
2
- import type { LiteralUnion } from 'type-fest'
3
- import { invariant, nonNullable } from '../errors'
4
2
  import {
5
- type ComputedDeploymentView,
6
- type DeploymentRef,
7
- type DeploymentRelation,
8
- type GenericLikeC4ModelData,
3
+ type Any,
4
+ type DeploymentElement,
5
+ type DeploymentRelationship,
9
6
  type IteratorLike,
10
- type Tag as C4Tag,
11
- DeploymentElement,
7
+ FqnRef,
8
+ isDeploymentNode,
12
9
  } from '../types'
10
+ import * as aux from '../types/aux'
11
+ import { invariant, nonNullable } from '../utils'
13
12
  import { ancestorsFqn, parentFqn, sortParentsFirst } from '../utils/fqn'
14
13
  import { getOrCreate } from '../utils/getOrCreate'
15
- import { isString } from '../utils/guards'
16
14
  import { DefaultMap } from '../utils/mnemonist'
17
15
  import {
18
16
  type DeployedInstancesIterator,
@@ -25,48 +23,59 @@ import {
25
23
  NestedElementOfDeployedInstanceModel,
26
24
  } from './DeploymentElementModel'
27
25
  import type { LikeC4Model } from './LikeC4Model'
28
- import { type AnyAux, type IncomingFilter, type OutgoingFilter, getId } from './types'
26
+ import {
27
+ type $ModelData,
28
+ type DeploymentOrFqn,
29
+ type ElementOrFqn,
30
+ type IncomingFilter,
31
+ type OutgoingFilter,
32
+ type RelationOrId,
33
+ getId,
34
+ } from './types'
29
35
  import type { LikeC4ViewModel } from './view/LikeC4ViewModel'
30
36
 
31
- export class LikeC4DeploymentModel<M extends AnyAux = AnyAux> {
32
- readonly #elements = new Map<M['Deployment'], DeploymentElementModel<M>>()
37
+ export class LikeC4DeploymentModel<A extends Any = Any> {
38
+ readonly #elements = new Map<aux.DeploymentFqn<A>, DeploymentElementModel<A>>()
33
39
  // Parent element for given FQN
34
- readonly #parents = new Map<M['Deployment'], DeploymentNodeModel<M>>()
40
+ readonly #parents = new Map<aux.DeploymentFqn<A>, DeploymentNodeModel<A>>()
35
41
  // Children elements for given FQN
36
- readonly #children = new DefaultMap<M['Deployment'], Set<DeploymentElementModel<M>>>(() => new Set())
42
+ readonly #children = new DefaultMap<aux.DeploymentFqn<A>, Set<DeploymentElementModel<A>>>(() => new Set())
37
43
 
38
44
  // Keep track of instances of the logical element
39
- readonly #instancesOf = new DefaultMap<M['Element'], Set<DeployedInstanceModel<M>>>(() => new Set())
45
+ readonly #instancesOf = new DefaultMap<aux.Fqn<A>, Set<DeployedInstanceModel<A>>>(() => new Set())
40
46
 
41
- readonly #rootElements = new Set<DeploymentNodeModel<M>>()
47
+ readonly #rootElements = new Set<DeploymentNodeModel<A>>()
42
48
 
43
- readonly #relations = new Map<M['RelationId'], DeploymentRelationModel<M>>()
49
+ readonly #relations = new Map<aux.RelationId, DeploymentRelationModel<A>>()
44
50
 
45
51
  // Incoming to an element or its descendants
46
- readonly #incoming = new DefaultMap<M['Deployment'], Set<DeploymentRelationModel<M>>>(() => new Set())
52
+ readonly #incoming = new DefaultMap<aux.DeploymentFqn<A>, Set<DeploymentRelationModel<A>>>(() => new Set())
47
53
 
48
54
  // Outgoing from an element or its descendants
49
- readonly #outgoing = new DefaultMap<M['Deployment'], Set<DeploymentRelationModel<M>>>(() => new Set())
55
+ readonly #outgoing = new DefaultMap<aux.DeploymentFqn<A>, Set<DeploymentRelationModel<A>>>(() => new Set())
50
56
 
51
57
  // Relationships inside the element, among descendants
52
- readonly #internal = new DefaultMap<M['Deployment'], Set<DeploymentRelationModel<M>>>(() => new Set())
58
+ readonly #internal = new DefaultMap<aux.DeploymentFqn<A>, Set<DeploymentRelationModel<A>>>(() => new Set())
53
59
 
54
- // readonly #views = new Map<ViewID, LikeC4ViewModel<M>>()
60
+ // readonly #views = new Map<ViewID, LikeC4ViewModel<A>>()
55
61
 
56
- readonly #allTags = new DefaultMap<C4Tag, Set<DeploymentElementModel<M> | DeploymentRelationModel<M>>>(() =>
57
- new Set()
62
+ readonly #allTags = new DefaultMap<aux.Tag<A>, Set<DeploymentElementModel<A> | DeploymentRelationModel<A>>>(
63
+ () => new Set(),
58
64
  )
59
65
 
60
66
  readonly #nestedElementsOfDeployment = new Map<
61
- `${M['Deployment']}@${M['Element']}`,
62
- NestedElementOfDeployedInstanceModel<M>
67
+ `${aux.DeploymentId<A>}@${aux.ElementId<A>}`,
68
+ NestedElementOfDeployedInstanceModel<A>
63
69
  >()
64
70
 
71
+ public readonly $deployments: $ModelData<A>['deployments']
72
+
65
73
  constructor(
66
- public readonly $model: LikeC4Model<M>,
67
- public readonly $deployments: GenericLikeC4ModelData['deployments'],
74
+ public readonly $model: LikeC4Model<A>,
68
75
  ) {
69
- for (const element of sortParentsFirst(values($deployments.elements))) {
76
+ const $deployments = this.$deployments = $model.$data.deployments
77
+ const elements = values($deployments.elements as Record<string, DeploymentElement<A>>)
78
+ for (const element of sortParentsFirst(elements)) {
70
79
  const el = this.addElement(element)
71
80
  for (const tag of el.tags) {
72
81
  this.#allTags.get(tag).add(el)
@@ -83,23 +92,23 @@ export class LikeC4DeploymentModel<M extends AnyAux = AnyAux> {
83
92
  }
84
93
  }
85
94
 
86
- public element(el: M['DeploymentOrFqn']): DeploymentElementModel<M> {
95
+ public element(el: DeploymentOrFqn<A>): DeploymentElementModel<A> {
87
96
  if (el instanceof DeploymentNodeModel || el instanceof DeployedInstanceModel) {
88
97
  return el
89
98
  }
90
99
  const id = getId(el)
91
100
  return nonNullable(this.#elements.get(id), `Element ${id} not found`)
92
101
  }
93
- public findElement(el: M['Deployment']): DeploymentElementModel<M> | null {
94
- return this.#elements.get(el) ?? null
102
+ public findElement(el: aux.LooseDeploymentId<A>): DeploymentElementModel<A> | null {
103
+ return this.#elements.get(el as aux.DeploymentFqn<A>) ?? null
95
104
  }
96
105
 
97
- public node(el: M['DeploymentOrFqn']): DeploymentNodeModel<M> {
106
+ public node(el: DeploymentOrFqn<A>): DeploymentNodeModel<A> {
98
107
  const element = this.element(el)
99
108
  invariant(element.isDeploymentNode(), `Element ${element.id} is not a deployment node`)
100
109
  return element
101
110
  }
102
- public findNode(el: M['Deployment']): DeploymentNodeModel<M> | null {
111
+ public findNode(el: aux.LooseDeploymentId<A>): DeploymentNodeModel<A> | null {
103
112
  const element = this.findElement(el)
104
113
  if (!element) {
105
114
  return null
@@ -108,12 +117,12 @@ export class LikeC4DeploymentModel<M extends AnyAux = AnyAux> {
108
117
  return element
109
118
  }
110
119
 
111
- public instance(el: M['DeploymentOrFqn']): DeployedInstanceModel<M> {
120
+ public instance(el: DeploymentOrFqn<A>): DeployedInstanceModel<A> {
112
121
  const element = this.element(el)
113
122
  invariant(element.isInstance(), `Element ${element.id} is not a deployed instance`)
114
123
  return element
115
124
  }
116
- public findInstance(el: M['Deployment']): DeployedInstanceModel<M> | null {
125
+ public findInstance(el: aux.LooseDeploymentId<A>): DeployedInstanceModel<A> | null {
117
126
  const element = this.findElement(el)
118
127
  if (!element) {
119
128
  return null
@@ -125,21 +134,21 @@ export class LikeC4DeploymentModel<M extends AnyAux = AnyAux> {
125
134
  /**
126
135
  * Returns the root elements of the model.
127
136
  */
128
- public roots(): DeploymentNodesIterator<M> {
137
+ public roots(): DeploymentNodesIterator<A> {
129
138
  return this.#rootElements.values()
130
139
  }
131
140
 
132
141
  /**
133
142
  * Returns all elements in the model.
134
143
  */
135
- public elements(): DeploymentElementsIterator<M> {
144
+ public elements(): DeploymentElementsIterator<A> {
136
145
  return this.#elements.values()
137
146
  }
138
147
 
139
148
  /**
140
149
  * Returns all elements in the model.
141
150
  */
142
- public *nodes(): DeploymentNodesIterator<M> {
151
+ public *nodes(): DeploymentNodesIterator<A> {
143
152
  for (const element of this.#elements.values()) {
144
153
  if (element.isDeploymentNode()) {
145
154
  yield element
@@ -148,7 +157,16 @@ export class LikeC4DeploymentModel<M extends AnyAux = AnyAux> {
148
157
  return
149
158
  }
150
159
 
151
- public *instances(): DeployedInstancesIterator<M> {
160
+ public *nodesOfKind(kind: aux.DeploymentKind<A>): DeploymentNodesIterator<A> {
161
+ for (const node of this.#elements.values()) {
162
+ if (node.isDeploymentNode() && node.kind === kind) {
163
+ yield node
164
+ }
165
+ }
166
+ return
167
+ }
168
+
169
+ public *instances(): DeployedInstancesIterator<A> {
152
170
  for (const element of this.#elements.values()) {
153
171
  if (element.isInstance()) {
154
172
  yield element
@@ -160,7 +178,7 @@ export class LikeC4DeploymentModel<M extends AnyAux = AnyAux> {
160
178
  /**
161
179
  * Iterate over all instances of the given logical element.
162
180
  */
163
- public *instancesOf(element: M['ElementOrFqn']): DeployedInstancesIterator<M> {
181
+ public *instancesOf(element: ElementOrFqn<A>): DeployedInstancesIterator<A> {
164
182
  const id = getId(element)
165
183
  const instances = this.#instancesOf.get(id)
166
184
  if (instances) {
@@ -169,37 +187,39 @@ export class LikeC4DeploymentModel<M extends AnyAux = AnyAux> {
169
187
  return
170
188
  }
171
189
 
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))
190
+ public deploymentRef(
191
+ ref: FqnRef.DeploymentRef<A>,
192
+ ): DeploymentElementModel<A> | NestedElementOfDeployedInstanceModel<A> {
193
+ if (FqnRef.isInsideInstanceRef(ref)) {
194
+ const { deployment, element } = ref
195
+ return getOrCreate(this.#nestedElementsOfDeployment, `${deployment}@${element}`, () => {
196
+ return new NestedElementOfDeployedInstanceModel(this.instance(deployment), this.$model.element(element))
177
197
  })
178
198
  }
179
- return this.element(ref)
199
+ return this.element(ref.deployment)
180
200
  }
181
201
 
182
202
  /**
183
203
  * Returns all relationships in the model.
184
204
  */
185
- public relationships(): IteratorLike<DeploymentRelationModel<M>> {
205
+ public relationships(): IteratorLike<DeploymentRelationModel<A>> {
186
206
  return this.#relations.values()
187
207
  }
188
208
 
189
209
  /**
190
210
  * Returns a specific relationship by its ID.
191
211
  */
192
- public relationship(id: M['RelationId']): DeploymentRelationModel<M> {
193
- return nonNullable(this.#relations.get(id), `DeploymentRelationModel ${id} not found`)
212
+ public relationship(id: RelationOrId): DeploymentRelationModel<A> {
213
+ return nonNullable(this.#relations.get(getId(id)), `DeploymentRelationModel ${id} not found`)
194
214
  }
195
- public findRelationship(id: LiteralUnion<M['RelationId'], string>): DeploymentRelationModel<M> | null {
196
- return this.#relations.get(id as M['RelationId']) ?? null
215
+ public findRelationship(id: string): DeploymentRelationModel<A> | null {
216
+ return this.#relations.get(id as aux.RelationId) ?? null
197
217
  }
198
218
 
199
219
  /**
200
220
  * Returns all deployment views in the model.
201
221
  */
202
- public *views(): IteratorLike<LikeC4ViewModel<M, ComputedDeploymentView>> {
222
+ public *views(): IteratorLike<LikeC4ViewModel.DeploymentView<A>> {
203
223
  for (const view of this.$model.views()) {
204
224
  if (view.isDeploymentView()) {
205
225
  yield view
@@ -212,7 +232,7 @@ export class LikeC4DeploymentModel<M extends AnyAux = AnyAux> {
212
232
  * Returns the parent element of given element.
213
233
  * @see ancestors
214
234
  */
215
- public parent(element: M['DeploymentOrFqn']): DeploymentNodeModel<M> | null {
235
+ public parent(element: DeploymentOrFqn<A>): DeploymentNodeModel<A> | null {
216
236
  const id = getId(element)
217
237
  return this.#parents.get(id) || null
218
238
  }
@@ -221,7 +241,7 @@ export class LikeC4DeploymentModel<M extends AnyAux = AnyAux> {
221
241
  * Get all children of the element (only direct children),
222
242
  * @see descendants
223
243
  */
224
- public children(element: M['DeploymentOrFqn']): ReadonlySet<DeploymentElementModel<M>> {
244
+ public children(element: DeploymentOrFqn<A>): ReadonlySet<DeploymentElementModel<A>> {
225
245
  const id = getId(element)
226
246
  return this.#children.get(id)
227
247
  }
@@ -229,7 +249,7 @@ export class LikeC4DeploymentModel<M extends AnyAux = AnyAux> {
229
249
  /**
230
250
  * Get all sibling (i.e. same parent)
231
251
  */
232
- public *siblings(element: M['DeploymentOrFqn']): DeploymentElementsIterator<M> {
252
+ public *siblings(element: DeploymentOrFqn<A>): DeploymentElementsIterator<A> {
233
253
  const id = getId(element)
234
254
  const siblings = this.parent(element)?.children() ?? this.roots()
235
255
  for (const sibling of siblings) {
@@ -244,8 +264,8 @@ export class LikeC4DeploymentModel<M extends AnyAux = AnyAux> {
244
264
  * Get all ancestor elements (i.e. parent, parent’s parent, etc.)
245
265
  * (from closest to root)
246
266
  */
247
- public *ancestors(element: M['DeploymentOrFqn']): DeploymentNodesIterator<M> {
248
- let id = isString(element) ? element : element.id
267
+ public *ancestors(element: DeploymentOrFqn<A>): DeploymentNodesIterator<A> {
268
+ let id = getId(element)
249
269
  let parent
250
270
  while (parent = this.#parents.get(id)) {
251
271
  yield parent
@@ -257,7 +277,10 @@ export class LikeC4DeploymentModel<M extends AnyAux = AnyAux> {
257
277
  /**
258
278
  * Get all descendant elements (i.e. children, children’s children, etc.)
259
279
  */
260
- public *descendants(element: M['DeploymentOrFqn'], sort: 'asc' | 'desc' = 'desc'): DeploymentElementsIterator<M> {
280
+ public *descendants(
281
+ element: DeploymentOrFqn<A>,
282
+ sort: 'asc' | 'desc' = 'desc',
283
+ ): DeploymentElementsIterator<A> {
261
284
  for (const child of this.children(element)) {
262
285
  if (sort === 'asc') {
263
286
  yield child
@@ -275,9 +298,9 @@ export class LikeC4DeploymentModel<M extends AnyAux = AnyAux> {
275
298
  * @see incomers
276
299
  */
277
300
  public *incoming(
278
- element: M['DeploymentOrFqn'],
301
+ element: DeploymentOrFqn<A>,
279
302
  filter: IncomingFilter = 'all',
280
- ): IteratorLike<DeploymentRelationModel<M>> {
303
+ ): IteratorLike<DeploymentRelationModel<A>> {
281
304
  const id = getId(element)
282
305
  for (const rel of this.#incoming.get(id)) {
283
306
  switch (true) {
@@ -296,9 +319,9 @@ export class LikeC4DeploymentModel<M extends AnyAux = AnyAux> {
296
319
  * @see outgoers
297
320
  */
298
321
  public *outgoing(
299
- element: M['DeploymentOrFqn'],
322
+ element: DeploymentOrFqn<A>,
300
323
  filter: OutgoingFilter = 'all',
301
- ): IteratorLike<DeploymentRelationModel<M>> {
324
+ ): IteratorLike<DeploymentRelationModel<A>> {
302
325
  const id = getId(element)
303
326
  for (const rel of this.#outgoing.get(id)) {
304
327
  switch (true) {
@@ -312,11 +335,11 @@ export class LikeC4DeploymentModel<M extends AnyAux = AnyAux> {
312
335
  return
313
336
  }
314
337
 
315
- private addElement(element: DeploymentElement) {
338
+ private addElement(element: DeploymentElement<A>) {
316
339
  if (this.#elements.has(element.id)) {
317
340
  throw new Error(`Element ${element.id} already exists`)
318
341
  }
319
- const el = DeploymentElement.isDeploymentNode(element)
342
+ const el = isDeploymentNode(element)
320
343
  ? new DeploymentNodeModel(this, Object.freeze(element))
321
344
  : new DeployedInstanceModel(this, Object.freeze(element), this.$model.element(element.element))
322
345
  this.#elements.set(el.id, el)
@@ -332,7 +355,7 @@ export class LikeC4DeploymentModel<M extends AnyAux = AnyAux> {
332
355
  return el
333
356
  }
334
357
 
335
- private addRelation(relation: DeploymentRelation) {
358
+ private addRelation(relation: DeploymentRelationship<A>) {
336
359
  if (this.#relations.has(relation.id)) {
337
360
  throw new Error(`Relation ${relation.id} already exists`)
338
361
  }
@@ -1,45 +1,41 @@
1
- import { isTruthy } from 'remeda'
1
+ import { isTruthy, unique } from 'remeda'
2
2
  import type { SetRequired } from 'type-fest'
3
- import type { IteratorLike } from '../types/_common'
3
+ import type { Any, AnyAux, Color, IteratorLike } from '../types'
4
4
  import {
5
5
  type Element as C4Element,
6
- type ElementKind as C4ElementKind,
7
6
  type ElementShape as C4ElementShape,
8
7
  type ElementStyle,
8
+ type IconUrl,
9
9
  type Link,
10
+ type ProjectId,
10
11
  DefaultElementShape,
12
+ DefaultShapeSize,
11
13
  DefaultThemeColor,
12
- } from '../types/element'
13
- import { DefaultShapeSize } from '../types/element'
14
- import {
15
- type IconUrl,
16
- type ProjectId,
17
- type Tag as C4Tag,
18
14
  splitGlobalFqn,
19
- } from '../types/scalars'
20
- import type { ThemeColor } from '../types/theme'
21
- import { commonAncestor, hierarchyLevel, isAncestor, sortNaturalByFqn } from '../utils'
15
+ } from '../types'
16
+ import * as aux from '../types/aux'
17
+ import { commonAncestor, hierarchyLevel, ihead, isAncestor, memoizeProp, sortNaturalByFqn } from '../utils'
22
18
  import { type DeployedInstancesIterator } from './DeploymentElementModel'
23
19
  import type { LikeC4Model } from './LikeC4Model'
24
20
  import type { RelationshipModel, RelationshipsIterator } from './RelationModel'
25
- import type { AnyAux, IncomingFilter, OutgoingFilter } from './types'
26
- import type { LikeC4ViewModel, ViewsIterator } from './view/LikeC4ViewModel'
21
+ import type { $ViewWithType, IncomingFilter, OutgoingFilter } from './types'
22
+ import type { LikeC4ViewModel } from './view/LikeC4ViewModel'
27
23
 
28
24
  export type ElementsIterator<M extends AnyAux> = IteratorLike<ElementModel<M>>
29
25
 
30
- export class ElementModel<M extends AnyAux = AnyAux> {
31
- readonly id: M['Fqn']
32
- readonly _literalId: M['Element']
26
+ export class ElementModel<A extends AnyAux = Any> {
27
+ readonly id: aux.Fqn<A>
28
+ readonly _literalId: aux.ElementId<A>
33
29
  readonly hierarchyLevel: number
34
30
 
35
31
  readonly imported: null | {
36
32
  from: ProjectId
37
- fqn: string
33
+ fqn: aux.Fqn<A>
38
34
  }
39
35
 
40
36
  constructor(
41
- public readonly $model: LikeC4Model<M>,
42
- public readonly $element: C4Element,
37
+ public readonly $model: LikeC4Model<A>,
38
+ public readonly $element: C4Element<A>,
43
39
  ) {
44
40
  this.id = this.$element.id
45
41
  this._literalId = this.$element.id
@@ -56,11 +52,11 @@ export class ElementModel<M extends AnyAux = AnyAux> {
56
52
  }
57
53
  }
58
54
 
59
- get parent(): ElementModel<M> | null {
55
+ get parent(): ElementModel<A> | null {
60
56
  return this.$model.parent(this)
61
57
  }
62
58
 
63
- get kind(): C4ElementKind {
59
+ get kind(): aux.ElementKind<A> {
64
60
  return this.$element.kind
65
61
  }
66
62
 
@@ -68,16 +64,25 @@ export class ElementModel<M extends AnyAux = AnyAux> {
68
64
  return this.$element.shape ?? DefaultElementShape
69
65
  }
70
66
 
71
- get color(): ThemeColor {
72
- return this.$element.color as ThemeColor ?? DefaultThemeColor
67
+ get color(): Color {
68
+ return this.$element.color as Color ?? DefaultThemeColor
73
69
  }
74
70
 
75
71
  get icon(): IconUrl | null {
76
72
  return this.$element.icon ?? null
77
73
  }
78
74
 
79
- get tags(): ReadonlyArray<C4Tag> {
80
- return this.$element.tags ?? []
75
+ /**
76
+ * Returns all tags of the element.
77
+ * It includes tags from the element and its kind.
78
+ */
79
+ get tags(): aux.Tags<A> {
80
+ return memoizeProp(this, Symbol.for('tags'), () => {
81
+ return unique([
82
+ ...(this.$element.tags ?? []),
83
+ ...(this.$model.specification.elements[this.$element.kind]?.tags ?? []),
84
+ ] as aux.Tags<A>)
85
+ })
81
86
  }
82
87
 
83
88
  get title(): string {
@@ -85,19 +90,19 @@ export class ElementModel<M extends AnyAux = AnyAux> {
85
90
  }
86
91
 
87
92
  get description(): string | null {
88
- return this.$element.description
93
+ return this.$element.description ?? null
89
94
  }
90
95
 
91
96
  get technology(): string | null {
92
- return this.$element.technology
97
+ return this.$element.technology ?? null
93
98
  }
94
99
 
95
100
  get links(): ReadonlyArray<Link> {
96
101
  return this.$element.links ?? []
97
102
  }
98
103
 
99
- get defaultView(): LikeC4ViewModel<M> | null {
100
- return this.scopedViews().next().value ?? null
104
+ get defaultView(): LikeC4ViewModel<A, $ViewWithType<A, 'element'> & { viewOf: aux.StrictFqn<A> }> | null {
105
+ return memoizeProp(this, Symbol.for('defaultView'), () => ihead(this.scopedViews()) ?? null)
101
106
  }
102
107
 
103
108
  get isRoot(): boolean {
@@ -111,11 +116,11 @@ export class ElementModel<M extends AnyAux = AnyAux> {
111
116
  }
112
117
  }
113
118
 
114
- public isAncestorOf(another: ElementModel<M>): boolean {
119
+ public isAncestorOf(another: ElementModel<A>): boolean {
115
120
  return isAncestor(this, another)
116
121
  }
117
122
 
118
- public isDescendantOf(another: ElementModel<M>): boolean {
123
+ public isDescendantOf(another: ElementModel<A>): boolean {
119
124
  return isAncestor(another, this)
120
125
  }
121
126
 
@@ -123,26 +128,26 @@ export class ElementModel<M extends AnyAux = AnyAux> {
123
128
  * Get all ancestor elements (i.e. parent, parent’s parent, etc.)
124
129
  * (from closest to root)
125
130
  */
126
- public ancestors(): ElementsIterator<M> {
131
+ public ancestors(): ElementsIterator<A> {
127
132
  return this.$model.ancestors(this)
128
133
  }
129
134
 
130
135
  /**
131
136
  * Returns the common ancestor of this element and another element.
132
137
  */
133
- public commonAncestor(another: ElementModel<M>): ElementModel<M> | null {
138
+ public commonAncestor(another: ElementModel<A>): ElementModel<A> | null {
134
139
  const common = commonAncestor(this.id, another.id)
135
140
  return common ? this.$model.element(common) : null
136
141
  }
137
142
 
138
- public children(): ReadonlySet<ElementModel<M>> {
143
+ public children(): ReadonlySet<ElementModel<A>> {
139
144
  return this.$model.children(this)
140
145
  }
141
146
 
142
147
  /**
143
148
  * Get all descendant elements (i.e. children, children’s children, etc.)
144
149
  */
145
- public descendants(sort?: 'asc' | 'desc'): ElementsIterator<M> {
150
+ public descendants(sort?: 'asc' | 'desc'): ElementsIterator<A> {
146
151
  if (sort) {
147
152
  const sorted = sortNaturalByFqn([...this.$model.descendants(this)], sort)
148
153
  return sorted[Symbol.iterator]()
@@ -153,15 +158,15 @@ export class ElementModel<M extends AnyAux = AnyAux> {
153
158
  /**
154
159
  * Get all sibling (i.e. same parent)
155
160
  */
156
- public siblings(): ElementsIterator<M> {
161
+ public siblings(): ElementsIterator<A> {
157
162
  return this.$model.siblings(this)
158
163
  }
159
164
 
160
165
  /**
161
166
  * Resolve siblings of the element and its ancestors
162
- * (from closest to root)
167
+ * (from closest parent to root)
163
168
  */
164
- public *ascendingSiblings(): ElementsIterator<M> {
169
+ public *ascendingSiblings(): ElementsIterator<A> {
165
170
  yield* this.siblings()
166
171
  for (const ancestor of this.ancestors()) {
167
172
  yield* ancestor.siblings()
@@ -173,7 +178,7 @@ export class ElementModel<M extends AnyAux = AnyAux> {
173
178
  * Resolve siblings of the element and its ancestors
174
179
  * (from root to closest)
175
180
  */
176
- public *descendingSiblings(): ElementsIterator<M> {
181
+ public *descendingSiblings(): ElementsIterator<A> {
177
182
  for (const ancestor of [...this.ancestors()].reverse()) {
178
183
  yield* ancestor.siblings()
179
184
  }
@@ -181,11 +186,11 @@ export class ElementModel<M extends AnyAux = AnyAux> {
181
186
  return
182
187
  }
183
188
 
184
- public incoming(filter: IncomingFilter = 'all'): RelationshipsIterator<M> {
189
+ public incoming(filter: IncomingFilter = 'all'): RelationshipsIterator<A> {
185
190
  return this.$model.incoming(this, filter)
186
191
  }
187
- public *incomers(filter: IncomingFilter = 'all'): ElementsIterator<M> {
188
- const unique = new Set<M['Fqn']>()
192
+ public *incomers(filter: IncomingFilter = 'all'): ElementsIterator<A> {
193
+ const unique = new Set<aux.StrictFqn<A>>()
189
194
  for (const r of this.incoming(filter)) {
190
195
  if (unique.has(r.source.id)) {
191
196
  continue
@@ -195,11 +200,11 @@ export class ElementModel<M extends AnyAux = AnyAux> {
195
200
  }
196
201
  return
197
202
  }
198
- public outgoing(filter: OutgoingFilter = 'all'): RelationshipsIterator<M> {
203
+ public outgoing(filter: OutgoingFilter = 'all'): RelationshipsIterator<A> {
199
204
  return this.$model.outgoing(this, filter)
200
205
  }
201
- public *outgoers(filter: OutgoingFilter = 'all'): ElementsIterator<M> {
202
- const unique = new Set<M['Fqn']>()
206
+ public *outgoers(filter: OutgoingFilter = 'all'): ElementsIterator<A> {
207
+ const unique = new Set<aux.StrictFqn<A>>()
203
208
  for (const r of this.outgoing(filter)) {
204
209
  if (unique.has(r.target.id)) {
205
210
  continue
@@ -210,56 +215,71 @@ export class ElementModel<M extends AnyAux = AnyAux> {
210
215
  return
211
216
  }
212
217
 
213
- protected cachedOutgoing: Set<RelationshipModel<M>> | null = null
214
- protected cachedIncoming: Set<RelationshipModel<M>> | null = null
215
-
216
- public get allOutgoing(): ReadonlySet<RelationshipModel<M>> {
217
- this.cachedOutgoing ??= new Set(this.outgoing())
218
- return this.cachedOutgoing
218
+ public get allOutgoing(): ReadonlySet<RelationshipModel<A>> {
219
+ return memoizeProp(this, Symbol.for('allOutgoing'), () => new Set(this.outgoing()))
219
220
  }
220
221
 
221
- public get allIncoming(): ReadonlySet<RelationshipModel<M>> {
222
- this.cachedIncoming ??= new Set(this.incoming())
223
- return this.cachedIncoming
222
+ public get allIncoming(): ReadonlySet<RelationshipModel<A>> {
223
+ return memoizeProp(this, Symbol.for('allIncoming'), () => new Set(this.incoming()))
224
224
  }
225
225
 
226
226
  /**
227
227
  * Iterate over all views that include this element.
228
228
  */
229
- public *views(): ViewsIterator<M> {
230
- for (const view of this.$model.views()) {
231
- if (view.includesElement(this.id)) {
232
- yield view
229
+ public views(): ReadonlySet<LikeC4ViewModel<A>> {
230
+ return memoizeProp(this, Symbol.for('views'), () => {
231
+ const views = new Set<LikeC4ViewModel<A>>()
232
+ for (const view of this.$model.views()) {
233
+ if (view.includesElement(this.id)) {
234
+ views.add(view)
235
+ }
233
236
  }
234
- }
235
- return
237
+ return views
238
+ })
236
239
  }
237
240
 
238
241
  /**
239
242
  * Iterate over all views that scope this element.
240
243
  * It is possible that element is not included in the view.
241
244
  */
242
- public *scopedViews(): ViewsIterator<M> {
243
- for (const vm of this.$model.views()) {
244
- if (vm.isElementView() && vm.$view.viewOf === this.id) {
245
- yield vm
245
+ public scopedViews(): ReadonlySet<
246
+ LikeC4ViewModel<A, $ViewWithType<A, 'element'> & { viewOf: aux.StrictFqn<A> }>
247
+ > {
248
+ return memoizeProp(this, Symbol.for('scopedViews'), () => {
249
+ const views = new Set<LikeC4ViewModel<A, $ViewWithType<A, 'element'> & { viewOf: aux.StrictFqn<A> }>>()
250
+ for (const vm of this.$model.views()) {
251
+ if (vm.isScopedElementView() && vm.viewOf.id === this.id) {
252
+ views.add(vm)
253
+ }
246
254
  }
247
- }
248
- return
255
+ return views
256
+ })
249
257
  }
250
258
 
251
259
  /**
252
260
  * @returns true if the element is deployed
253
261
  */
254
262
  public isDeployed(): boolean {
255
- return isTruthy(this.deployments().next().value)
263
+ return isTruthy(ihead(this.deployments()))
256
264
  }
257
265
 
258
- public deployments(): DeployedInstancesIterator<M> {
266
+ public deployments(): DeployedInstancesIterator<A> {
259
267
  return this.$model.deployment.instancesOf(this)
260
268
  }
261
- }
262
269
 
263
- export function isElementModel<M extends AnyAux = AnyAux>(element: any): element is ElementModel<M> {
264
- return element instanceof ElementModel
270
+ public getMetadata(): aux.Metadata<A>
271
+ public getMetadata(field: aux.MetadataKey<A>): string | undefined
272
+ public getMetadata(field?: aux.MetadataKey<A>) {
273
+ if (field) {
274
+ return this.$element.metadata?.[field]
275
+ }
276
+ return this.$element.metadata ?? {}
277
+ }
278
+
279
+ /**
280
+ * Checks if the element has the given tag.
281
+ */
282
+ public isTagged(tag: aux.LooseTag<A>): boolean {
283
+ return this.tags.includes(tag as aux.Tag<A>)
284
+ }
265
285
  }