@likec4/core 1.18.0 → 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 (176) hide show
  1. package/LICENSE +1 -1
  2. package/dist/builder/index.d.mts +502 -0
  3. package/dist/builder/index.d.ts +502 -0
  4. package/dist/builder/index.js +871 -0
  5. package/dist/compute-view/index.d.mts +9 -78
  6. package/dist/compute-view/index.d.ts +9 -78
  7. package/dist/compute-view/index.js +78 -0
  8. package/dist/index.d.mts +9 -730
  9. package/dist/index.d.ts +9 -730
  10. package/dist/{index.mjs → index.js} +7 -784
  11. package/dist/model/index.d.mts +218 -606
  12. package/dist/model/index.d.ts +218 -606
  13. package/dist/model/index.js +1 -0
  14. package/dist/shared/chunk-RAAYCPUM.M-JWF7SS.js +21 -0
  15. package/dist/shared/core.BIfgabEw.d.ts +719 -0
  16. package/dist/shared/core.BZ9Msq7w.d.mts +719 -0
  17. package/dist/shared/{core.DMYY7C8s.d.mts → core.C05RDLhi.d.ts} +179 -230
  18. package/dist/shared/{core.DMYY7C8s.d.ts → core.CmYMqgha.d.mts} +179 -230
  19. package/dist/shared/core.D6-fWbM3.d.ts +127 -0
  20. package/dist/shared/core.D74xkKkG.d.mts +148 -0
  21. package/dist/shared/core.D74xkKkG.d.ts +148 -0
  22. package/dist/shared/core.sLaWHBjR.d.mts +127 -0
  23. package/dist/shared/index.-BdzdI2C.js +7443 -0
  24. package/dist/shared/object_hash.CE_oF3I3.js +1222 -0
  25. package/dist/shared/view.CyZrG8BJ.js +354 -0
  26. package/dist/types/index.d.mts +4 -2
  27. package/dist/types/index.d.ts +4 -2
  28. package/dist/types/index.js +20 -0
  29. package/dist/utils/index.d.mts +404 -0
  30. package/dist/utils/index.d.ts +404 -0
  31. package/dist/utils/index.js +330 -0
  32. package/package.json +47 -33
  33. package/src/builder/Builder-style1.test-d.ts +190 -0
  34. package/src/builder/Builder-style2.test-d.ts +175 -0
  35. package/src/builder/Builder.deployment.ts +25 -20
  36. package/src/builder/Builder.deploymentModel.ts +80 -55
  37. package/src/builder/Builder.element.ts +2 -0
  38. package/src/builder/Builder.model.ts +57 -43
  39. package/src/builder/Builder.ts +412 -431
  40. package/src/builder/Builder.view-common.ts +234 -0
  41. package/src/builder/Builder.view-deployment.ts +135 -0
  42. package/src/builder/Builder.view-element.ts +189 -0
  43. package/src/builder/Builder.views.ts +109 -33
  44. package/src/builder/Builder.with.ts +475 -0
  45. package/src/builder/__snapshots__/Builder-style1.spec.ts.snap +374 -0
  46. package/src/builder/__snapshots__/Builder-style2.spec.ts.snap +183 -0
  47. package/src/builder/__snapshots__/Builder.spec.ts.snap +374 -0
  48. package/src/builder/_types.ts +28 -5
  49. package/src/builder/index.ts +10 -4
  50. package/src/colors/index.ts +2 -2
  51. package/src/compute-view/compute-view.ts +45 -54
  52. package/src/compute-view/deployment-view/__test__/TestHelper.ts +236 -0
  53. package/src/compute-view/deployment-view/__test__/fixture.ts +50 -62
  54. package/src/compute-view/deployment-view/_types.ts +26 -18
  55. package/src/compute-view/deployment-view/clean-connections.ts +173 -164
  56. package/src/compute-view/deployment-view/compute.ts +51 -37
  57. package/src/compute-view/deployment-view/memory/index.ts +9 -0
  58. package/src/compute-view/deployment-view/memory/memory.ts +97 -0
  59. package/src/compute-view/deployment-view/predicates/elements.ts +144 -0
  60. package/src/compute-view/deployment-view/predicates/relation-direct.ts +183 -76
  61. package/src/compute-view/deployment-view/predicates/relation-in-out.ts +38 -20
  62. package/src/compute-view/deployment-view/predicates/relation-incoming.ts +72 -23
  63. package/src/compute-view/deployment-view/predicates/relation-outgoing.ts +61 -22
  64. package/src/compute-view/deployment-view/predicates/wildcard.ts +15 -15
  65. package/src/compute-view/deployment-view/stages/stage-exclude.ts +5 -0
  66. package/src/compute-view/deployment-view/stages/stage-final.ts +245 -0
  67. package/src/compute-view/deployment-view/stages/stage-include.ts +95 -0
  68. package/src/compute-view/deployment-view/utils.ts +95 -31
  69. package/src/compute-view/dynamic-view/__test__/fixture.ts +4 -4
  70. package/src/compute-view/dynamic-view/compute.ts +59 -45
  71. package/src/compute-view/element-view/__test__/TestHelper.ts +258 -0
  72. package/src/compute-view/element-view/__test__/__snapshots__/legacy.spec.ts.snap +64 -62
  73. package/src/compute-view/element-view/__test__/fixture.ts +147 -117
  74. package/src/compute-view/element-view/_types.ts +48 -0
  75. package/src/compute-view/element-view/clean-connections.ts +113 -0
  76. package/src/compute-view/element-view/compute.ts +264 -683
  77. package/src/compute-view/element-view/memory/NodeGroup.ts +31 -0
  78. package/src/compute-view/element-view/memory/index.ts +10 -0
  79. package/src/compute-view/element-view/memory/memory.ts +177 -0
  80. package/src/compute-view/element-view/memory/stage-exclude.ts +59 -0
  81. package/src/compute-view/element-view/memory/stage-final.ts +129 -0
  82. package/src/compute-view/element-view/memory/stage-include.ts +128 -0
  83. package/src/compute-view/element-view/predicates/_utils.ts +86 -0
  84. package/src/compute-view/element-view/predicates/element-expand.ts +33 -0
  85. package/src/compute-view/element-view/predicates/element-kind-tag.ts +26 -0
  86. package/src/compute-view/element-view/predicates/element-ref.ts +37 -0
  87. package/src/compute-view/element-view/predicates/relation-direct.ts +309 -0
  88. package/src/compute-view/element-view/predicates/relation-in-out.ts +66 -0
  89. package/src/compute-view/element-view/predicates/relation-in.ts +118 -0
  90. package/src/compute-view/element-view/predicates/relation-out.ts +116 -0
  91. package/src/compute-view/element-view/predicates/wildcard.ts +88 -0
  92. package/src/compute-view/element-view/utils.ts +59 -0
  93. package/src/compute-view/index.ts +3 -4
  94. package/src/compute-view/memory/AbstractMemory.ts +45 -0
  95. package/src/compute-view/memory/AbstractStageExclude.ts +216 -0
  96. package/src/compute-view/memory/AbstractStageInclude.ts +200 -0
  97. package/src/compute-view/memory/_types.ts +147 -0
  98. package/src/compute-view/memory/index.ts +5 -0
  99. package/src/compute-view/memory/ops.ts +43 -0
  100. package/src/compute-view/utils/buildComputedNodes.ts +4 -4
  101. package/src/compute-view/utils/elementExpressionToPredicate.ts +1 -3
  102. package/src/compute-view/utils/link-nodes-with-edges.ts +3 -7
  103. package/src/compute-view/utils/{derive-edge-props-from-relationships.ts → merge-props-from-relationships.ts} +54 -23
  104. package/src/compute-view/utils/relationExpressionToPredicates.ts +2 -2
  105. package/src/compute-view/utils/resolve-extended-views.ts +44 -40
  106. package/src/compute-view/utils/resolve-global-rules.ts +6 -6
  107. package/src/compute-view/utils/topological-sort.ts +167 -71
  108. package/src/compute-view/utils/with-readable-edges.ts +14 -9
  109. package/src/index.ts +22 -16
  110. package/src/model/DeploymentElementModel.ts +150 -85
  111. package/src/model/DeploymentModel.ts +27 -21
  112. package/src/model/ElementModel.ts +51 -25
  113. package/src/model/LikeC4Model.test-d.ts +4 -3
  114. package/src/model/LikeC4Model.ts +98 -42
  115. package/src/model/RelationModel.ts +5 -4
  116. package/src/model/__test__/fixture.ts +5 -9
  117. package/src/model/connection/Connection.ts +72 -0
  118. package/src/model/connection/deployment/DeploymentConnectionModel.ts +178 -0
  119. package/src/model/connection/{deployment.ts → deployment/find.ts} +14 -18
  120. package/src/model/connection/deployment/index.ts +8 -0
  121. package/src/model/connection/index.ts +22 -0
  122. package/src/model/connection/model/ConnectionModel.ts +157 -0
  123. package/src/model/connection/{model.ts → model/find.ts} +43 -49
  124. package/src/model/connection/model/index.ts +8 -0
  125. package/src/model/connection/ops.ts +274 -0
  126. package/src/model/guards.ts +9 -0
  127. package/src/model/index.ts +45 -3
  128. package/src/model/types.ts +35 -18
  129. package/src/model/view/EdgeModel.ts +6 -6
  130. package/src/model/view/LikeC4ViewModel.ts +14 -8
  131. package/src/model/view/NodeModel.ts +6 -5
  132. package/src/types/_common.ts +53 -1
  133. package/src/types/deployments.ts +88 -156
  134. package/src/types/element.ts +2 -2
  135. package/src/types/expression-v2.ts +172 -0
  136. package/src/types/expression.ts +9 -5
  137. package/src/types/global.ts +3 -3
  138. package/src/types/index.ts +220 -13
  139. package/src/types/model.ts +28 -24
  140. package/src/types/operators.ts +1 -1
  141. package/src/types/relation.ts +14 -10
  142. package/src/types/theme.ts +0 -1
  143. package/src/types/view.ts +20 -18
  144. package/src/utils/const.ts +1 -0
  145. package/src/utils/fqn.ts +140 -49
  146. package/src/utils/getOrCreate.ts +1 -1
  147. package/src/utils/index.ts +74 -9
  148. package/src/utils/iterable/_types.ts +5 -0
  149. package/src/utils/iterable/filter.ts +55 -0
  150. package/src/utils/iterable/flat.ts +17 -0
  151. package/src/utils/iterable/index.ts +12 -0
  152. package/src/utils/iterable/map.ts +40 -0
  153. package/src/utils/iterable/reduce.ts +24 -0
  154. package/src/utils/iterable/some.ts +36 -0
  155. package/src/utils/iterable/to.ts +19 -0
  156. package/src/utils/iterable/unique.ts +29 -0
  157. package/src/utils/relations.ts +3 -64
  158. package/src/utils/set.ts +11 -9
  159. package/src/utils/string-hash.ts +1 -1
  160. package/dist/compute-view/index.mjs +0 -79
  161. package/dist/model/index.mjs +0 -2
  162. package/dist/shared/core.BBgiAAv1.mjs +0 -144
  163. package/dist/shared/core.BPyJvuMU.mjs +0 -2420
  164. package/dist/shared/core.BQ4AbfvB.mjs +0 -9228
  165. package/dist/shared/core.k12s_keD.mjs +0 -203
  166. package/dist/types/index.mjs +0 -21
  167. package/src/builder/Builder.test-d.ts +0 -124
  168. package/src/builder/Builder.view.ts +0 -543
  169. package/src/compute-view/LikeC4ModelGraph.ts +0 -330
  170. package/src/compute-view/deployment-view/Memory.ts +0 -108
  171. package/src/compute-view/deployment-view/Stage.ts +0 -145
  172. package/src/compute-view/deployment-view/predicates/deployment-ref.ts +0 -146
  173. package/src/compute-view/element-view/predicates.ts +0 -517
  174. package/src/compute-view/utils/sortNodes.ts +0 -101
  175. package/src/model/connection/ConnectionModel.ts +0 -94
  176. package/src/model/connection/DeploymentConnectionModel.ts +0 -99
@@ -1,32 +1,38 @@
1
1
  import { isTruthy } from 'remeda'
2
2
  import {
3
- DefaultElementShape,
4
- DefaultThemeColor,
5
3
  type Element as C4Element,
6
4
  type ElementKind as C4ElementKind,
7
5
  type ElementShape as C4ElementShape,
6
+ type IteratorLike,
8
7
  type Link,
9
8
  type Tag as C4Tag,
10
- type ThemeColor
9
+ type ThemeColor,
10
+ DefaultElementShape,
11
+ DefaultThemeColor,
11
12
  } from '../types'
12
- import { commonAncestor } from '../utils'
13
+ import { commonAncestor, hierarchyLevel, isAncestor, sortNaturalByFqn } from '../utils'
13
14
  import { type DeployedInstancesIterator } from './DeploymentElementModel'
14
15
  import type { LikeC4Model } from './LikeC4Model'
15
16
  import type { RelationshipModel, RelationshipsIterator } from './RelationModel'
16
- import type { AnyAux, IncomingFilter, IteratorLike, OutgoingFilter } from './types'
17
+ import type { AnyAux, IncomingFilter, OutgoingFilter } from './types'
17
18
  import type { LikeC4ViewModel, ViewsIterator } from './view/LikeC4ViewModel'
18
19
 
19
20
  export type ElementsIterator<M extends AnyAux> = IteratorLike<ElementModel<M>>
20
21
 
21
- export class ElementModel<M extends AnyAux> {
22
+ export class ElementModel<M extends AnyAux = AnyAux> {
23
+ readonly id: M['Fqn']
24
+ readonly hierarchyLevel: number
25
+
22
26
  constructor(
23
- public readonly model: LikeC4Model<M>,
24
- public readonly $element: C4Element
27
+ public readonly $model: LikeC4Model<M>,
28
+ public readonly $element: C4Element,
25
29
  ) {
30
+ this.id = this.$element.id
31
+ this.hierarchyLevel = hierarchyLevel(this.id)
26
32
  }
27
33
 
28
- get id(): M['Fqn'] {
29
- return this.$element.id
34
+ get parent(): ElementModel<M> | null {
35
+ return this.$model.parent(this)
30
36
  }
31
37
 
32
38
  get kind(): C4ElementKind {
@@ -61,20 +67,24 @@ export class ElementModel<M extends AnyAux> {
61
67
  return this.$element.links ?? []
62
68
  }
63
69
 
64
- get parent(): ElementModel<M> | null {
65
- return this.model.parent(this)
66
- }
67
-
68
70
  get defaultView(): LikeC4ViewModel<M> | null {
69
71
  return this.scopedViews().next().value ?? null
70
72
  }
71
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
+
72
82
  /**
73
83
  * Get all ancestor elements (i.e. parent, parent’s parent, etc.)
74
84
  * (from closest to root)
75
85
  */
76
86
  public ancestors(): ElementsIterator<M> {
77
- return this.model.ancestors(this)
87
+ return this.$model.ancestors(this)
78
88
  }
79
89
 
80
90
  /**
@@ -82,25 +92,29 @@ export class ElementModel<M extends AnyAux> {
82
92
  */
83
93
  public commonAncestor(another: ElementModel<M>): ElementModel<M> | null {
84
94
  const common = commonAncestor(this.id, another.id)
85
- return common ? this.model.element(common) : null
95
+ return common ? this.$model.element(common) : null
86
96
  }
87
97
 
88
98
  public children(): ReadonlySet<ElementModel<M>> {
89
- return this.model.children(this)
99
+ return this.$model.children(this)
90
100
  }
91
101
 
92
102
  /**
93
103
  * Get all descendant elements (i.e. children, children’s children, etc.)
94
104
  */
95
- public descendants(): ElementsIterator<M> {
96
- return this.model.descendants(this)
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)
97
111
  }
98
112
 
99
113
  /**
100
114
  * Get all sibling (i.e. same parent)
101
115
  */
102
116
  public siblings(): ElementsIterator<M> {
103
- return this.model.siblings(this)
117
+ return this.$model.siblings(this)
104
118
  }
105
119
 
106
120
  /**
@@ -115,8 +129,20 @@ export class ElementModel<M extends AnyAux> {
115
129
  return
116
130
  }
117
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
+
118
144
  public incoming(filter: IncomingFilter = 'all'): RelationshipsIterator<M> {
119
- return this.model.incoming(this, filter)
145
+ return this.$model.incoming(this, filter)
120
146
  }
121
147
  public *incomers(filter: IncomingFilter = 'all'): ElementsIterator<M> {
122
148
  const unique = new Set<M['Fqn']>()
@@ -130,7 +156,7 @@ export class ElementModel<M extends AnyAux> {
130
156
  return
131
157
  }
132
158
  public outgoing(filter: OutgoingFilter = 'all'): RelationshipsIterator<M> {
133
- return this.model.outgoing(this, filter)
159
+ return this.$model.outgoing(this, filter)
134
160
  }
135
161
  public *outgoers(filter: OutgoingFilter = 'all'): ElementsIterator<M> {
136
162
  const unique = new Set<M['Fqn']>()
@@ -161,7 +187,7 @@ export class ElementModel<M extends AnyAux> {
161
187
  * Iterate over all views that include this element.
162
188
  */
163
189
  public *views(): ViewsIterator<M> {
164
- for (const view of this.model.views()) {
190
+ for (const view of this.$model.views()) {
165
191
  if (view.includesElement(this.id)) {
166
192
  yield view
167
193
  }
@@ -174,7 +200,7 @@ export class ElementModel<M extends AnyAux> {
174
200
  * It is possible that element is not included in the view.
175
201
  */
176
202
  public *scopedViews(): ViewsIterator<M> {
177
- for (const vm of this.model.views()) {
203
+ for (const vm of this.$model.views()) {
178
204
  if (vm.isElementView() && vm.$view.viewOf === this.id) {
179
205
  yield vm
180
206
  }
@@ -190,6 +216,6 @@ export class ElementModel<M extends AnyAux> {
190
216
  }
191
217
 
192
218
  public deployments(): DeployedInstancesIterator<M> {
193
- return this.model.deployment.instancesOf(this)
219
+ return this.$model.deployment.instancesOf(this)
194
220
  }
195
221
  }
@@ -1,6 +1,5 @@
1
1
  import { expectTypeOf, test } from 'vitest'
2
2
  import { Builder } from '../builder'
3
- import { computeViews } from '../compute-view/compute-view'
4
3
  import type { ComputedView, Fqn, ViewId } from '../types'
5
4
  import { LikeC4Model } from './LikeC4Model'
6
5
 
@@ -80,8 +79,10 @@ test('LikeC4Model.create: should have types', () => {
80
79
  )
81
80
  .build()
82
81
 
83
- const computed = computeViews(source)
84
- const m = LikeC4Model.create(computed)
82
+ const m = LikeC4Model.compute(source)
83
+
84
+ // @ts-expect-error
85
+ m.element('wrong')
85
86
 
86
87
  expectTypeOf(m.Aux.Element).toEqualTypeOf(
87
88
  '' as 'alice' | 'bob' | 'cloud' | 'cloud.backend' | 'cloud.backend.api' | 'cloud.backend.db' | 'cloud.frontend'
@@ -1,31 +1,33 @@
1
1
  import DefaultMap from 'mnemonist/default-map'
2
- import { pipe, sort, values } from 'remeda'
2
+ import { mapValues, pipe, sort, values } from 'remeda'
3
3
  import type { LiteralUnion } from 'type-fest'
4
+ import { computeView, unsafeComputeView } from '../compute-view'
5
+ import type { ComputeViewResult } from '../compute-view/compute-view'
4
6
  import { invariant, nonNullable } from '../errors'
5
- import type { ComputedView, DiagramView } from '../types'
7
+ import type { ComputedView, DiagramView, IteratorLike, LikeC4View, ModelGlobals } from '../types'
6
8
  import type { Element } from '../types/element'
7
9
  import { type Tag as C4Tag } from '../types/element'
8
- import type { AnyLikeC4Model, ParsedLikeC4ModelDump } from '../types/model'
9
- import type { Relation } from '../types/relation'
10
+ import type { AnyParsedLikeC4Model, GenericLikeC4Model, LikeC4ModelDump } from '../types/model'
11
+ import type { ModelRelation } from '../types/relation'
10
12
  import { compareNatural } from '../utils'
11
13
  import { ancestorsFqn, commonAncestor, parentFqn } from '../utils/fqn'
12
14
  import type {
13
15
  DeployedInstanceModel,
14
16
  DeploymentElementModel,
15
17
  DeploymentNodeModel,
16
- DeploymentRelationModel
18
+ DeploymentRelationModel,
17
19
  } from './DeploymentElementModel'
18
20
  import { LikeC4DeploymentModel } from './DeploymentModel'
19
- import { ElementModel, type ElementsIterator } from './ElementModel'
20
- import { RelationshipModel, type RelationshipsIterator } from './RelationModel'
21
- import { type AnyAux, type Aux, getId, type IncomingFilter, type OutgoingFilter } from './types'
21
+ import { type ElementsIterator, ElementModel } from './ElementModel'
22
+ import { type RelationshipsIterator, RelationshipModel } from './RelationModel'
23
+ import { type AnyAux, type Aux, type IncomingFilter, type OutgoingFilter, getId } from './types'
22
24
  import { EdgeModel } from './view/EdgeModel'
23
25
  import { LikeC4ViewModel } from './view/LikeC4ViewModel'
24
26
  import { NodeModel } from './view/NodeModel'
25
27
 
26
28
  export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
27
29
  /**
28
- * Only available in compile time
30
+ * Only available in compile tim
29
31
  */
30
32
  readonly Aux!: M
31
33
 
@@ -48,7 +50,7 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
48
50
  // Relationships inside the element, among descendants
49
51
  readonly #internal = new DefaultMap<M['Element'], Set<RelationshipModel<M>>>(() => new Set())
50
52
 
51
- readonly #views = new Map<M['ViewIdLiteral'], LikeC4ViewModel<M>>()
53
+ readonly #views = new Map<M['ViewId'], LikeC4ViewModel<M>>()
52
54
 
53
55
  readonly #allTags = new DefaultMap<C4Tag, Set<ElementModel<M> | RelationshipModel<M> | LikeC4ViewModel<M>>>(() =>
54
56
  new Set()
@@ -56,21 +58,62 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
56
58
 
57
59
  public readonly deployment: LikeC4DeploymentModel<M>
58
60
 
59
- static create<const M extends AnyLikeC4Model>(model: M) {
60
- type T = Aux.FromModel<M>
61
- return new LikeC4Model<T>(model as any)
61
+ /**
62
+ * Computes views from the parsed model
63
+ * Creates a new LikeC4Model instance from a parsed model.
64
+ *
65
+ * May throw an error if the model is invalid.
66
+ *
67
+ * @typeParam M - The type of the parsed LikeC4 model, must extend ParsedLikeC4Model
68
+ * @param parsed - The parsed LikeC4 model to compute from
69
+ * @returns A new LikeC4Model instance with computed relationships and structure
70
+ */
71
+ static compute<const M extends AnyParsedLikeC4Model>(parsed: M): LikeC4Model<Aux.FromParsed<M>> {
72
+ let { views, ...rest } = parsed as Omit<M, '__'>
73
+ const model = new LikeC4Model({ ...rest, views: {} })
74
+ return new LikeC4Model({
75
+ ...rest,
76
+ views: mapValues(views, view => unsafeComputeView(view as LikeC4View, model)),
77
+ } as any)
78
+ }
79
+
80
+ /**
81
+ * Creates a function that computes a view using the data from the model.
82
+ *
83
+ * @example
84
+ * const compute = LikeC4Model.makeCompute(parsedModel);
85
+ * const result = compute(viewSource);
86
+ */
87
+ static makeCompute<M extends AnyParsedLikeC4Model>(parsed: M): (viewsource: LikeC4View) => ComputeViewResult {
88
+ let { views, ...rest } = parsed as Omit<M, '__'>
89
+ const model = new LikeC4Model(structuredClone({ ...rest, views: {} }))
90
+ return (viewsource) => computeView(viewsource, model)
62
91
  }
63
92
 
64
93
  /**
65
- * This method is supposed to be used only in dumps
94
+ * Creates a new LikeC4Model instance from the provided model data.
95
+ *
96
+ * @typeParam M - Type parameter constrained to AnyLikeC4Model
97
+ * @param model - The model data to create a LikeC4Model from
98
+ * @returns A new LikeC4Model instance with the type derived from the input model
66
99
  */
67
- static fromDump<const M extends ParsedLikeC4ModelDump>(dump: M) {
68
- type T = Aux.FromDump<M>
69
- return new LikeC4Model<T>(dump as any)
100
+ static create<const M extends GenericLikeC4Model>(model: M): LikeC4Model<Aux.FromModel<M>> {
101
+ return new LikeC4Model(model as any)
102
+ }
103
+
104
+ /**
105
+ * Creates a new LikeC4Model instance from a model dump.
106
+ *
107
+ * @typeParam M - A constant type parameter extending LikeC4ModelDump
108
+ * @param dump - The model dump to create the instance from
109
+ * @returns A new LikeC4Model instance with types inferred from the dump
110
+ */
111
+ static fromDump<const M extends LikeC4ModelDump>(dump: M): LikeC4Model<Aux.FromDump<M>> {
112
+ return new LikeC4Model(dump as any)
70
113
  }
71
114
 
72
115
  private constructor(
73
- public readonly $model: M['Model']
116
+ public readonly $model: M['Model'],
74
117
  ) {
75
118
  for (const element of values($model.elements)) {
76
119
  const el = this.addElement(element)
@@ -87,10 +130,10 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
87
130
  this.deployment = new LikeC4DeploymentModel(this, $model.deployments)
88
131
  const views = pipe(
89
132
  values($model.views),
90
- sort((a, b) => compareNatural(a.title ?? 'untitled', b.title ?? 'untitled'))
133
+ sort((a, b) => compareNatural(a.title ?? 'untitled', b.title ?? 'untitled')),
91
134
  )
92
135
  for (const view of views) {
93
- const vm = new LikeC4ViewModel(this, view as M['ViewType'])
136
+ const vm = new LikeC4ViewModel(this, Object.freeze(view as M['ViewType']))
94
137
  this.#views.set(view.id, vm)
95
138
  for (const tag of vm.tags) {
96
139
  this.#allTags.get(tag).add(vm)
@@ -110,7 +153,6 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
110
153
  return nonNullable(this.findElement(id), `Element ${getId(el)} not found`)
111
154
  }
112
155
  public findElement(el: LiteralUnion<M['Element'], string>): ElementModel<M> | null {
113
- // const id = getId(el) as C4Fqn
114
156
  return this.#elements.get(el) ?? null
115
157
  }
116
158
 
@@ -144,11 +186,11 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
144
186
  public relationship(id: M['RelationId'], type: 'deployment'): DeploymentRelationModel<M>
145
187
  public relationship(
146
188
  id: M['RelationId'],
147
- type?: 'model' | 'deployment'
189
+ type?: 'model' | 'deployment',
148
190
  ): RelationshipModel<M> | DeploymentRelationModel<M>
149
191
  public relationship(
150
192
  id: M['RelationId'],
151
- type: 'model' | 'deployment' | undefined
193
+ type: 'model' | 'deployment' | undefined,
152
194
  ): RelationshipModel<M> | DeploymentRelationModel<M> {
153
195
  if (type === 'deployment') {
154
196
  return this.deployment.relationship(id)
@@ -164,15 +206,15 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
164
206
  public findRelationship(id: LiteralUnion<M['RelationId'], string>, type: 'model'): RelationshipModel<M> | null
165
207
  public findRelationship(
166
208
  id: LiteralUnion<M['RelationId'], string>,
167
- type: 'deployment'
209
+ type: 'deployment',
168
210
  ): DeploymentRelationModel<M> | null
169
211
  public findRelationship(
170
212
  id: LiteralUnion<M['RelationId'], string>,
171
- type?: 'model' | 'deployment'
213
+ type?: 'model' | 'deployment',
172
214
  ): RelationshipModel<M> | DeploymentRelationModel<M> | null
173
215
  public findRelationship(
174
216
  id: LiteralUnion<M['RelationId'], string>,
175
- type: 'model' | 'deployment' | undefined
217
+ type: 'model' | 'deployment' | undefined,
176
218
  ): RelationshipModel<M> | DeploymentRelationModel<M> | null {
177
219
  if (type === 'deployment') {
178
220
  return this.deployment.findRelationship(id)
@@ -187,18 +229,18 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
187
229
  /**
188
230
  * Returns all views in the model.
189
231
  */
190
- public views() {
232
+ public views(): IteratorLike<LikeC4ViewModel<M>> {
191
233
  return this.#views.values()
192
234
  }
193
235
 
194
236
  /**
195
237
  * Returns a specific view by its ID.
196
238
  */
197
- public view(viewId: M['ViewIdLiteral']): LikeC4ViewModel<M> {
198
- return nonNullable(this.#views.get(viewId), `View ${viewId} not found`)
239
+ public view(viewId: M['View']): LikeC4ViewModel<M> {
240
+ return nonNullable(this.#views.get(viewId as any), `View ${viewId} not found`)
199
241
  }
200
- public findView(viewId: LiteralUnion<M['ViewId'], string>): LikeC4ViewModel<M> {
201
- return nonNullable(this.#views.get(viewId), `View ${viewId} not found`)
242
+ public findView(viewId: M['View']): LikeC4ViewModel<M> {
243
+ return nonNullable(this.#views.get(viewId as M['ViewId']), `View ${viewId} not found`)
202
244
  }
203
245
 
204
246
  /**
@@ -265,7 +307,7 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
265
307
  */
266
308
  public *incoming(
267
309
  element: M['ElementOrFqn'],
268
- filter: IncomingFilter = 'all'
310
+ filter: IncomingFilter = 'all',
269
311
  ): RelationshipsIterator<M> {
270
312
  const id = getId(element)
271
313
  for (const rel of this.#incoming.get(id)) {
@@ -286,7 +328,7 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
286
328
  */
287
329
  public *outgoing(
288
330
  element: M['ElementOrFqn'],
289
- filter: OutgoingFilter = 'all'
331
+ filter: OutgoingFilter = 'all',
290
332
  ): RelationshipsIterator<M> {
291
333
  const id = getId(element)
292
334
  for (const rel of this.#outgoing.get(id)) {
@@ -301,11 +343,25 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
301
343
  return
302
344
  }
303
345
 
346
+ public globals(): ModelGlobals {
347
+ return {
348
+ predicates: {
349
+ ...this.$model.globals?.predicates,
350
+ },
351
+ dynamicPredicates: {
352
+ ...this.$model.globals?.dynamicPredicates,
353
+ },
354
+ styles: {
355
+ ...this.$model.globals?.styles,
356
+ },
357
+ }
358
+ }
359
+
304
360
  private addElement(element: Element) {
305
361
  if (this.#elements.has(element.id)) {
306
362
  throw new Error(`Element ${element.id} already exists`)
307
363
  }
308
- const el = new ElementModel(this, element)
364
+ const el = new ElementModel(this, Object.freeze(structuredClone(element)))
309
365
  this.#elements.set(el.id, el)
310
366
  const parentId = parentFqn(el.id)
311
367
  if (parentId) {
@@ -318,13 +374,13 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
318
374
  return el
319
375
  }
320
376
 
321
- private addRelation(relation: Relation) {
377
+ private addRelation(relation: ModelRelation) {
322
378
  if (this.#relations.has(relation.id)) {
323
379
  throw new Error(`Relation ${relation.id} already exists`)
324
380
  }
325
381
  const rel = new RelationshipModel(
326
382
  this,
327
- relation
383
+ Object.freeze(structuredClone(relation)),
328
384
  )
329
385
  const { source, target } = rel
330
386
  this.#relations.set(rel.id, rel)
@@ -374,17 +430,17 @@ export namespace LikeC4Model {
374
430
 
375
431
  export type View<
376
432
  M extends AnyAux = Any,
377
- V extends ComputedView | DiagramView = M['ViewType']
433
+ V extends ComputedView | DiagramView = M['ViewType'],
378
434
  > = LikeC4ViewModel<M, V>
379
435
 
380
436
  export type Node<
381
437
  M extends AnyAux = Any,
382
- V extends ComputedView | DiagramView = M['ViewType']
438
+ V extends ComputedView | DiagramView = M['ViewType'],
383
439
  > = NodeModel<M, V>
384
440
 
385
441
  export type Edge<
386
442
  M extends AnyAux = Any,
387
- V extends ComputedView | DiagramView = M['ViewType']
443
+ V extends ComputedView | DiagramView = M['ViewType'],
388
444
  > = EdgeModel<M, V>
389
445
 
390
446
  export type DeploymentModel<M extends AnyAux = AnyAux> = LikeC4DeploymentModel<M>
@@ -397,18 +453,18 @@ export namespace LikeC4Model {
397
453
  Elements extends string = string,
398
454
  Deployments extends string = string,
399
455
  Views extends string = string,
400
- ViewType = DiagramView<Views> | ComputedView<Views>
456
+ ViewType = DiagramView<Views> | ComputedView<Views>,
401
457
  > = Aux<Elements, Deployments, Views, ViewType>
402
458
 
403
459
  export type Computed<
404
460
  Elements extends string = string,
405
461
  Deployments extends string = string,
406
- Views extends string = string
462
+ Views extends string = string,
407
463
  > = LikeC4Model<Typed<Elements, Deployments, Views, ComputedView<Views>>>
408
464
 
409
465
  export type Layouted<
410
466
  Elements extends string = string,
411
467
  Deployments extends string = string,
412
- Views extends string = string
468
+ Views extends string = string,
413
469
  > = LikeC4Model<Typed<Elements, Deployments, Views, DiagramView<Views>>>
414
470
  }
@@ -1,15 +1,16 @@
1
1
  import { isEmpty } from 'remeda'
2
+ import type { IteratorLike } from '../types'
2
3
  import type { Link, Tag } from '../types/element'
3
- import type { Relation } from '../types/relation'
4
+ import type { ModelRelation } from '../types/relation'
4
5
  import { commonAncestor } from '../utils/fqn'
5
6
  import type { ElementModel } from './ElementModel'
6
7
  import type { LikeC4Model } from './LikeC4Model'
7
- import type { AnyAux, IteratorLike } from './types'
8
+ import type { AnyAux } from './types'
8
9
  import type { LikeC4ViewModel, ViewsIterator } from './view/LikeC4ViewModel'
9
10
 
10
11
  export type RelationshipsIterator<M extends AnyAux> = IteratorLike<RelationshipModel<M>>
11
12
 
12
- export class RelationshipModel<M extends AnyAux> {
13
+ export class RelationshipModel<M extends AnyAux = AnyAux> {
13
14
  public readonly source: ElementModel<M>
14
15
  public readonly target: ElementModel<M>
15
16
 
@@ -21,7 +22,7 @@ export class RelationshipModel<M extends AnyAux> {
21
22
 
22
23
  constructor(
23
24
  public readonly model: LikeC4Model<M>,
24
- public readonly $relationship: Relation
25
+ public readonly $relationship: ModelRelation,
25
26
  ) {
26
27
  this.source = model.element($relationship.source)
27
28
  this.target = model.element($relationship.target)
@@ -26,9 +26,7 @@ const {
26
26
  viewOf,
27
27
  deploymentView,
28
28
  $rules,
29
- $include,
30
- $exclude,
31
- $style
29
+ $include
32
30
  }
33
31
  } = Builder.forSpecification({
34
32
  elements: {
@@ -102,6 +100,7 @@ const local = b
102
100
  $m.rel('cloud.backend.api', 'cloud.media', 'uploads media'),
103
101
  $m.rel('cloud.backend.api', 'aws.rds', 'reads/writes'),
104
102
  $m.rel('cloud.backend.api', 'email', 'sends emails'),
103
+ $m.rel('cloud', 'email', 'uses'),
105
104
  $m.rel('cloud.media', 'aws.s3', 'uploads'),
106
105
  $m.rel('email', 'customer', 'sends emails')
107
106
  ),
@@ -138,12 +137,9 @@ const local = b
138
137
  $include('cloud.frontend.dashboard')
139
138
  )
140
139
  ),
141
- deploymentView(
142
- 'prod',
143
- $rules(
144
- $include('customer.instance'),
145
- $include('prod.eu.zone1.ui')
146
- )
140
+ deploymentView('prod').with(
141
+ $include('customer.instance'),
142
+ $include('prod.eu.zone1.ui')
147
143
  )
148
144
  )
149
145
  )
@@ -0,0 +1,72 @@
1
+ import type { Fqn } from '../../types'
2
+ import { isAncestor } from '../../utils'
3
+ import type { DeploymentElementModel } from '../DeploymentElementModel'
4
+ import type { ElementModel } from '../ElementModel'
5
+
6
+ export interface Connection<Elem = ElementModel | DeploymentElementModel, Id = string> {
7
+ readonly id: Id
8
+ readonly source: Elem
9
+ readonly target: Elem
10
+
11
+ /**
12
+ * Common ancestor of the source and target elements.
13
+ * Represents the boundary of the connection.
14
+ */
15
+ readonly boundary: Elem | null
16
+
17
+ /**
18
+ * Human readable expression of the connection
19
+ * Mostly used for testing and debugging
20
+ */
21
+ readonly expression: string
22
+
23
+ mergeWith(this: Connection<Elem, Id>, other: typeof this): typeof this
24
+
25
+ nonEmpty(): boolean
26
+
27
+ difference(this: Connection<Elem, Id>, other: typeof this): typeof this
28
+
29
+ intersect(this: Connection<Elem, Id>, other: typeof this): typeof this
30
+
31
+ equals(other: Connection): boolean
32
+ }
33
+
34
+ export namespace Connection {
35
+ type ConnectionPredicate = <C extends Connection<{ id: string }, any>>(connection: C) => boolean
36
+
37
+ type ElementId = Fqn | string
38
+
39
+ export const isInside = (fqn: ElementId): ConnectionPredicate => {
40
+ return (connection) => isAncestor(fqn, connection.source.id) && isAncestor(fqn, connection.target.id)
41
+ }
42
+
43
+ export const isDirectedBetween = (source: ElementId, target: ElementId): ConnectionPredicate => {
44
+ return (connection) =>
45
+ (connection.source.id === source || isAncestor(source, connection.source.id))
46
+ && (connection.target.id === target || isAncestor(target, connection.target.id))
47
+ }
48
+
49
+ export const isAnyBetween = (source: ElementId, target: ElementId): ConnectionPredicate => {
50
+ const forward = isDirectedBetween(source, target),
51
+ backward = isDirectedBetween(target, source)
52
+ return (connection) => forward(connection) || backward(connection)
53
+ }
54
+
55
+ export const isIncoming = (target: ElementId): ConnectionPredicate => {
56
+ return (connection) =>
57
+ (connection.target.id === target || isAncestor(target, connection.target.id))
58
+ && !isAncestor(target, connection.source.id)
59
+ }
60
+
61
+ export const isOutgoing = (source: ElementId): ConnectionPredicate => {
62
+ return (connection) =>
63
+ (connection.source.id === source || isAncestor(source, connection.source.id))
64
+ && !isAncestor(source, connection.target.id)
65
+ }
66
+
67
+ export const isAnyInOut = (source: ElementId): ConnectionPredicate => {
68
+ const isIn = isIncoming(source),
69
+ isOut = isOutgoing(source)
70
+ return (connection) => isIn(connection) || isOut(connection)
71
+ }
72
+ }