@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
@@ -1,305 +1,341 @@
1
- import { isString, isTruthy, map, pipe, sort, values } from 'remeda'
2
- import type { Class } from 'type-fest'
3
- import { nonNullable } from '../errors'
1
+ import DefaultMap from 'mnemonist/default-map'
2
+ import { pipe, sort, values } from 'remeda'
3
+ import type { LiteralUnion } from 'type-fest'
4
+ import { invariant, nonNullable } from '../errors'
5
+ import type { ComputedView, DiagramView } from '../types'
6
+ import type { Element } from '../types/element'
7
+ 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 { compareNatural } from '../utils'
11
+ import { ancestorsFqn, commonAncestor, parentFqn } from '../utils/fqn'
4
12
  import type {
5
- Element as C4Element,
6
- ElementKind as C4ElementKind,
7
- ElementShape as C4ElementShape,
8
- Tag as C4Tag
9
- } from '../types/element'
10
- import { DefaultElementShape, DefaultThemeColor } from '../types/element'
11
- import type {
12
- ComputedLikeC4Model as C4ComputedLikeC4Model,
13
- LayoutedLikeC4Model as C4LayoutedLikeC4Model
14
- } from '../types/model'
15
- import type { Relation as C4Relation } from '../types/relation'
16
- import type { Color as C4Color } from '../types/theme'
17
- import { compareNatural } from '../utils/compare-natural'
18
- import { ancestorsFqn, commonAncestor, parentFqn, sortNaturalByFqn } from '../utils/fqn'
19
- import { LikeC4DiagramModel } from './LikeC4DiagramModel'
20
- import { LikeC4ViewModel } from './LikeC4ViewModel'
21
- import type { ElementOrFqn, Fqn, IncomingFilter, OutgoingFilter, RelationID, ViewID } from './types'
22
- import { getId } from './types'
23
-
24
- type PickBySource<Source> = Source extends LikeC4Model.Layouted.SourceModel ? LikeC4Model.Layouted
25
- : Source extends LikeC4Model.Computed.SourceModel ? LikeC4Model.Computed
26
- : never
27
-
28
- export class LikeC4Model<M extends LikeC4Model.ViewModel = LikeC4Model.ViewModel> {
29
- private _elements = new Map<Fqn, LikeC4Model.ElementModel<M>>()
13
+ DeployedInstanceModel,
14
+ DeploymentElementModel,
15
+ DeploymentNodeModel,
16
+ DeploymentRelationModel
17
+ } from './DeploymentElementModel'
18
+ 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'
22
+ import { EdgeModel } from './view/EdgeModel'
23
+ import { LikeC4ViewModel } from './view/LikeC4ViewModel'
24
+ import { NodeModel } from './view/NodeModel'
25
+
26
+ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
27
+ /**
28
+ * Only available in compile time
29
+ */
30
+ readonly Aux!: M
30
31
 
32
+ readonly #elements = new Map<M['Element'], ElementModel<M>>()
31
33
  // Parent element for given FQN
32
- private _parents = new Map<Fqn, LikeC4Model.ElementModel<M>>()
33
-
34
+ readonly #parents = new Map<M['Element'], ElementModel<M>>()
34
35
  // Children elements for given FQN
35
- private _children = new Map<Fqn, LikeC4Model.ElementModel<M>[]>()
36
+ readonly #children = new DefaultMap<M['Element'], Set<ElementModel<M>>>(() => new Set())
36
37
 
37
- private _rootElements = new Set<LikeC4Model.ElementModel<M>>()
38
+ readonly #rootElements = new Set<ElementModel<M>>()
38
39
 
39
- private _relations = new Map<RelationID, LikeC4Model.Relationship<M>>()
40
+ readonly #relations = new Map<M['RelationId'], RelationshipModel<M>>()
40
41
 
41
42
  // Incoming to an element or its descendants
42
- private _incoming = new Map<Fqn, Set<LikeC4Model.Relationship<M>>>()
43
+ readonly #incoming = new DefaultMap<M['Element'], Set<RelationshipModel<M>>>(() => new Set())
43
44
 
44
45
  // Outgoing from an element or its descendants
45
- private _outgoing = new Map<Fqn, Set<LikeC4Model.Relationship<M>>>()
46
+ readonly #outgoing = new DefaultMap<M['Element'], Set<RelationshipModel<M>>>(() => new Set())
46
47
 
47
48
  // Relationships inside the element, among descendants
48
- private _internal = new Map<Fqn, Set<LikeC4Model.Relationship<M>>>()
49
+ readonly #internal = new DefaultMap<M['Element'], Set<RelationshipModel<M>>>(() => new Set())
50
+
51
+ readonly #views = new Map<M['ViewIdLiteral'], LikeC4ViewModel<M>>()
52
+
53
+ readonly #allTags = new DefaultMap<C4Tag, Set<ElementModel<M> | RelationshipModel<M> | LikeC4ViewModel<M>>>(() =>
54
+ new Set()
55
+ )
49
56
 
50
- private _cacheAscendingSiblings = new Map<Fqn, LikeC4Model.ElementModel<M>[]>()
57
+ public readonly deployment: LikeC4DeploymentModel<M>
51
58
 
52
- private _views: Map<Fqn, M>
59
+ static create<const M extends AnyLikeC4Model>(model: M) {
60
+ type T = Aux.FromModel<M>
61
+ return new LikeC4Model<T>(model as any)
62
+ }
63
+
64
+ /**
65
+ * This method is supposed to be used only in dumps
66
+ */
67
+ static fromDump<const M extends ParsedLikeC4ModelDump>(dump: M) {
68
+ type T = Aux.FromDump<M>
69
+ return new LikeC4Model<T>(dump as any)
70
+ }
53
71
 
54
- constructor(
55
- public readonly type: LikeC4Model.ModelType<M>,
56
- public readonly sourcemodel: LikeC4Model.SourceModel<M>,
57
- elements: C4Element[],
58
- relations: C4Relation[],
59
- views: LikeC4Model.SourceModel<M>['views'],
60
- ViewModelClass: Class<M>
72
+ private constructor(
73
+ public readonly $model: M['Model']
61
74
  ) {
62
- for (const el of elements) {
63
- this.addElement(el)
75
+ for (const element of values($model.elements)) {
76
+ const el = this.addElement(element)
77
+ for (const tag of el.tags) {
78
+ this.#allTags.get(tag).add(el)
79
+ }
64
80
  }
65
- for (const rel of relations) {
66
- this.addRelation(rel)
81
+ for (const relation of values($model.relations)) {
82
+ const el = this.addRelation(relation)
83
+ for (const tag of el.tags) {
84
+ this.#allTags.get(tag).add(el)
85
+ }
67
86
  }
68
- this._views = new Map(
69
- pipe(
70
- values(views),
71
- sort((a, b) => compareNatural(a.title ?? 'untitled', b.title ?? 'untitled')),
72
- map(view => [view.id, new ViewModelClass(view, this)] as [Fqn, M])
73
- )
87
+ this.deployment = new LikeC4DeploymentModel(this, $model.deployments)
88
+ const views = pipe(
89
+ values($model.views),
90
+ sort((a, b) => compareNatural(a.title ?? 'untitled', b.title ?? 'untitled'))
74
91
  )
92
+ for (const view of views) {
93
+ const vm = new LikeC4ViewModel(this, view as M['ViewType'])
94
+ this.#views.set(view.id, vm)
95
+ for (const tag of vm.tags) {
96
+ this.#allTags.get(tag).add(vm)
97
+ }
98
+ }
75
99
  }
76
100
 
77
- /**
78
- * Returns the root elements of the model.
79
- */
80
- public roots(): ReadonlyArray<LikeC4Model.ElementModel<M>> {
81
- return [...this._rootElements]
101
+ get type(): 'computed' | 'layouted' {
102
+ return this.$model.__ ?? 'computed'
103
+ }
104
+
105
+ public element(el: M['ElementOrFqn']): ElementModel<M> {
106
+ if (el instanceof ElementModel) {
107
+ return el
108
+ }
109
+ const id = getId(el)
110
+ return nonNullable(this.findElement(id), `Element ${getId(el)} not found`)
111
+ }
112
+ public findElement(el: LiteralUnion<M['Element'], string>): ElementModel<M> | null {
113
+ // const id = getId(el) as C4Fqn
114
+ return this.#elements.get(el) ?? null
82
115
  }
83
116
 
84
117
  /**
85
- * Returns all elements in the model.
118
+ * Returns the root elements of the model.
86
119
  */
87
- public elements(): ReadonlyArray<LikeC4Model.ElementModel<M>> {
88
- return [...this._elements.values()]
120
+ public roots(): ElementsIterator<M> {
121
+ return this.#rootElements.values()
89
122
  }
90
123
 
91
124
  /**
92
- * Returns a specific element by its FQN.
125
+ * Returns all elements in the model.
93
126
  */
94
- public element(id: Fqn): LikeC4Model.ElementModel<M> {
95
- return nonNullable(this._elements.get(id), `Element ${id} not found`)
127
+ public elements(): ElementsIterator<M> {
128
+ return this.#elements.values()
96
129
  }
97
130
 
98
131
  /**
99
132
  * Returns all relationships in the model.
100
133
  */
101
- public relationships(): ReadonlyArray<LikeC4Model.Relationship<M>> {
102
- return [...this._relations.values()]
134
+ public relationships(): RelationshipsIterator<M> {
135
+ return this.#relations.values()
103
136
  }
104
137
 
105
138
  /**
106
139
  * Returns a specific relationship by its ID.
140
+ * If the relationship is not found in the model, it will be searched in the deployment model.
141
+ * Search can be limited to the model or deployment model only.
107
142
  */
108
- public relationship(id: RelationID) {
109
- return nonNullable(this._relations.get(id), `Relation ${id} not found`)
143
+ public relationship(id: M['RelationId'], type: 'model'): RelationshipModel<M>
144
+ public relationship(id: M['RelationId'], type: 'deployment'): DeploymentRelationModel<M>
145
+ public relationship(
146
+ id: M['RelationId'],
147
+ type?: 'model' | 'deployment'
148
+ ): RelationshipModel<M> | DeploymentRelationModel<M>
149
+ public relationship(
150
+ id: M['RelationId'],
151
+ type: 'model' | 'deployment' | undefined
152
+ ): RelationshipModel<M> | DeploymentRelationModel<M> {
153
+ if (type === 'deployment') {
154
+ return this.deployment.relationship(id)
155
+ }
156
+ let model = this.#relations.get(id) ?? null
157
+ if (model || type === 'model') {
158
+ return nonNullable(model, `Model relation ${id} not found`)
159
+ }
160
+ // We did not find the relation in the model, let's search in the deployment
161
+ return nonNullable(this.deployment.findRelationship(id), `No model/deployment relation ${id} not found`)
162
+ }
163
+
164
+ public findRelationship(id: LiteralUnion<M['RelationId'], string>, type: 'model'): RelationshipModel<M> | null
165
+ public findRelationship(
166
+ id: LiteralUnion<M['RelationId'], string>,
167
+ type: 'deployment'
168
+ ): DeploymentRelationModel<M> | null
169
+ public findRelationship(
170
+ id: LiteralUnion<M['RelationId'], string>,
171
+ type?: 'model' | 'deployment'
172
+ ): RelationshipModel<M> | DeploymentRelationModel<M> | null
173
+ public findRelationship(
174
+ id: LiteralUnion<M['RelationId'], string>,
175
+ type: 'model' | 'deployment' | undefined
176
+ ): RelationshipModel<M> | DeploymentRelationModel<M> | null {
177
+ if (type === 'deployment') {
178
+ return this.deployment.findRelationship(id)
179
+ }
180
+ let model = this.#relations.get(id as M['RelationId']) ?? null
181
+ if (model || type === 'model') {
182
+ return model
183
+ }
184
+ return this.deployment.findRelationship(id)
110
185
  }
111
186
 
112
187
  /**
113
188
  * Returns all views in the model.
114
189
  */
115
- public views(): ReadonlyArray<M> {
116
- return [...this._views.values()]
190
+ public views() {
191
+ return this.#views.values()
117
192
  }
118
193
 
119
194
  /**
120
195
  * Returns a specific view by its ID.
121
196
  */
122
- public view(viewId: ViewID): M {
123
- return nonNullable(this._views.get(viewId), `View ${viewId} not found`)
197
+ public view(viewId: M['ViewIdLiteral']): LikeC4ViewModel<M> {
198
+ return nonNullable(this.#views.get(viewId), `View ${viewId} not found`)
199
+ }
200
+ public findView(viewId: LiteralUnion<M['ViewId'], string>): LikeC4ViewModel<M> {
201
+ return nonNullable(this.#views.get(viewId), `View ${viewId} not found`)
124
202
  }
125
203
 
126
204
  /**
127
205
  * Returns the parent element of given element.
128
206
  * @see ancestors
129
207
  */
130
- public parent(element: ElementOrFqn): LikeC4Model.ElementModel<M> | null {
208
+ public parent(element: M['ElementOrFqn']): ElementModel<M> | null {
131
209
  const id = getId(element)
132
- return this._parents.get(id) || null
210
+ return this.#parents.get(id) || null
133
211
  }
134
212
 
135
213
  /**
136
214
  * Get all children of the element (only direct children),
137
215
  * @see descendants
138
216
  */
139
- public children(element: ElementOrFqn): ReadonlyArray<LikeC4Model.ElementModel<M>> {
140
- const id = getId(element)
141
- return this._childrenOf(id)
217
+ public children(element: M['ElementOrFqn']): ReadonlySet<ElementModel<M>> {
218
+ const id = getId(element) as M['Fqn']
219
+ return this.#children.get(id)
142
220
  }
143
221
 
144
222
  /**
145
223
  * Get all sibling (i.e. same parent)
146
224
  */
147
- public siblings(element: ElementOrFqn): ReadonlyArray<LikeC4Model.ElementModel<M>> {
148
- const id = getId(element)
149
- const parent = this._parents.get(id)
150
- const siblings = parent ? this._childrenOf(parent.id) : this.roots()
151
- return siblings.filter(e => e.id !== id)
225
+ public *siblings(element: M['ElementOrFqn']): ElementsIterator<M> {
226
+ const id = getId(element) as M['Fqn']
227
+ const parent = this.#parents.get(id)
228
+ const siblings = parent ? this.#children.get(parent.id).values() : this.roots()
229
+ for (const sibling of siblings) {
230
+ if (sibling.id !== id) {
231
+ yield sibling
232
+ }
233
+ }
234
+ return
152
235
  }
153
236
 
154
237
  /**
155
238
  * Get all ancestor elements (i.e. parent, parent’s parent, etc.)
156
239
  * (from closest to root)
157
240
  */
158
- public ancestors(element: ElementOrFqn): ReadonlyArray<LikeC4Model.ElementModel<M>> {
159
- let id = isString(element) ? element : element.id
160
- const result = [] as LikeC4Model.ElementModel<M>[]
241
+ public *ancestors(element: M['ElementOrFqn']): ElementsIterator<M> {
242
+ let id = getId(element) as M['Fqn']
161
243
  let parent
162
- while (parent = this._parents.get(id)) {
163
- result.push(parent)
244
+ while (parent = this.#parents.get(id)) {
245
+ yield parent
164
246
  id = parent.id
165
247
  }
166
- return result
248
+ return
167
249
  }
168
250
 
169
251
  /**
170
252
  * Get all descendant elements (i.e. children, children’s children, etc.)
171
253
  */
172
- public descendants(element: ElementOrFqn): ReadonlyArray<LikeC4Model.ElementModel<M>> {
173
- const id = getId(element)
174
- const children = this._childrenOf(id)
175
- return children.flatMap(c => [c, ...this.descendants(c.id)])
254
+ public *descendants(element: M['ElementOrFqn']): ElementsIterator<M> {
255
+ for (const child of this.children(element)) {
256
+ yield child
257
+ yield* this.descendants(child.id)
258
+ }
259
+ return
176
260
  }
177
261
 
178
262
  /**
179
263
  * Incoming relationships to the element and its descendants
180
264
  * @see incomers
181
265
  */
182
- public incoming(
183
- element: ElementOrFqn,
266
+ public *incoming(
267
+ element: M['ElementOrFqn'],
184
268
  filter: IncomingFilter = 'all'
185
- ): ReadonlyArray<LikeC4Model.Relationship<M>> {
269
+ ): RelationshipsIterator<M> {
186
270
  const id = getId(element)
187
- const incoming = Array.from(this._incomingTo(id))
188
- if (filter === 'all' || incoming.length === 0) {
189
- return incoming
190
- }
191
-
192
- return incoming.filter(rel => {
193
- if (filter === 'direct') {
194
- return rel.target.id === id
271
+ for (const rel of this.#incoming.get(id)) {
272
+ switch (true) {
273
+ case filter === 'all':
274
+ case filter === 'direct' && rel.target.id === id:
275
+ case filter === 'to-descendants' && rel.target.id !== id:
276
+ yield rel
277
+ break
195
278
  }
196
- return rel.target.id !== id
197
- })
198
- }
199
-
200
- /**
201
- * Source elements of incoming relationships
202
- */
203
- public incomers(
204
- element: ElementOrFqn,
205
- filter: IncomingFilter = 'all'
206
- ): ReadonlyArray<LikeC4Model.ElementModel<M>> {
207
- return [...new Set(this.incoming(element, filter).map(r => r.source))]
279
+ }
280
+ return
208
281
  }
209
282
 
210
283
  /**
211
284
  * Outgoing relationships from the element and its descendants
212
285
  * @see outgoers
213
286
  */
214
- public outgoing(
215
- element: ElementOrFqn,
287
+ public *outgoing(
288
+ element: M['ElementOrFqn'],
216
289
  filter: OutgoingFilter = 'all'
217
- ): ReadonlyArray<LikeC4Model.Relationship<M>> {
290
+ ): RelationshipsIterator<M> {
218
291
  const id = getId(element)
219
- const outgoing = Array.from(this._outgoingFrom(id))
220
- if (filter === 'all' || outgoing.length === 0) {
221
- return outgoing
222
- }
223
- return outgoing.filter(rel => {
224
- if (filter === 'direct') {
225
- return rel.source.id === id
292
+ for (const rel of this.#outgoing.get(id)) {
293
+ switch (true) {
294
+ case filter === 'all':
295
+ case filter === 'direct' && rel.source.id === id:
296
+ case filter === 'from-descendants' && rel.source.id !== id:
297
+ yield rel
298
+ break
226
299
  }
227
- return rel.source.id !== id
228
- })
229
- }
230
-
231
- /**
232
- * Target elements of outgoing relationships
233
- */
234
- public outgoers(
235
- element: ElementOrFqn,
236
- filter: OutgoingFilter = 'all'
237
- ): ReadonlyArray<LikeC4Model.ElementModel<M>> {
238
- return [...new Set(this.outgoing(element, filter).map(r => r.target))]
239
- }
240
-
241
- /**
242
- * Relationships inside the element, among descendants
243
- */
244
- public internal(element: ElementOrFqn): ReadonlyArray<LikeC4Model.Relationship<M>> {
245
- const id = getId(element)
246
- return Array.from(this._internalOf(id))
247
- }
248
-
249
- /**
250
- * Resolve siblings of the element and siblings of ancestors
251
- * (from closest to root)
252
- */
253
- public ascendingSiblings(element: ElementOrFqn): ReadonlyArray<LikeC4Model.ElementModel<M>> {
254
- const id = getId(element)
255
- let siblings = this._cacheAscendingSiblings.get(id)
256
- if (!siblings) {
257
- siblings = [
258
- ...this.siblings(id),
259
- ...this.ancestors(id).flatMap(a => this.siblings(a.id))
260
- ]
261
- this._cacheAscendingSiblings.set(id, siblings)
262
300
  }
263
- return siblings.slice()
264
- }
265
-
266
- /**
267
- * Resolve all views that contain the element
268
- */
269
- public viewsWithElement(element: ElementOrFqn): ReadonlyArray<M> {
270
- const id = getId(element)
271
- return [...this._views.values()].filter(v => v.hasElement(id))
301
+ return
272
302
  }
273
303
 
274
- private addElement(parsed: C4Element) {
275
- if (this._elements.has(parsed.id)) {
276
- throw new Error(`Element ${parsed.id} already exists`)
304
+ private addElement(element: Element) {
305
+ if (this.#elements.has(element.id)) {
306
+ throw new Error(`Element ${element.id} already exists`)
277
307
  }
278
- const el = new LikeC4Model.ElementModel(parsed, this)
279
- this._elements.set(el.id, el)
308
+ const el = new ElementModel(this, element)
309
+ this.#elements.set(el.id, el)
280
310
  const parentId = parentFqn(el.id)
281
311
  if (parentId) {
282
- this._parents.set(el.id, this.element(parentId))
283
- this._childrenOf(parentId).push(el)
312
+ invariant(this.#elements.has(parentId), `Parent ${parentId} of ${el.id} not found`)
313
+ this.#parents.set(el.id, this.element(parentId))
314
+ this.#children.get(parentId).add(el)
284
315
  } else {
285
- this._rootElements.add(el)
316
+ this.#rootElements.add(el)
286
317
  }
318
+ return el
287
319
  }
288
320
 
289
- private addRelation(relation: C4Relation) {
290
- if (this._relations.has(relation.id)) {
321
+ private addRelation(relation: Relation) {
322
+ if (this.#relations.has(relation.id)) {
291
323
  throw new Error(`Relation ${relation.id} already exists`)
292
324
  }
293
- const rel = new LikeC4Model.Relationship(relation, this)
294
- this._relations.set(rel.id, rel)
295
- this._incomingTo(relation.target).add(rel)
296
- this._outgoingFrom(relation.source).add(rel)
325
+ const rel = new RelationshipModel(
326
+ this,
327
+ relation
328
+ )
329
+ const { source, target } = rel
330
+ this.#relations.set(rel.id, rel)
331
+ this.#incoming.get(target.id).add(rel)
332
+ this.#outgoing.get(source.id).add(rel)
297
333
 
298
- const relParent = commonAncestor(relation.source, relation.target)
334
+ const relParent = commonAncestor(source.id, target.id)
299
335
  // Process internal relationships
300
336
  if (relParent) {
301
337
  for (const ancestor of [relParent, ...ancestorsFqn(relParent)]) {
302
- this._internalOf(ancestor).add(rel)
338
+ this.#internal.get(ancestor).add(rel)
303
339
  }
304
340
  }
305
341
  // Process source hierarchy
@@ -307,285 +343,72 @@ export class LikeC4Model<M extends LikeC4Model.ViewModel = LikeC4Model.ViewModel
307
343
  if (sourceAncestor === relParent) {
308
344
  break
309
345
  }
310
- this._outgoingFrom(sourceAncestor).add(rel)
346
+ this.#outgoing.get(sourceAncestor).add(rel)
311
347
  }
312
348
  // Process target hierarchy
313
349
  for (const targetAncestor of ancestorsFqn(relation.target)) {
314
350
  if (targetAncestor === relParent) {
315
351
  break
316
352
  }
317
- this._incomingTo(targetAncestor).add(rel)
318
- }
319
- }
320
-
321
- private _childrenOf(id: Fqn) {
322
- let children = this._children.get(id)
323
- if (!children) {
324
- children = []
325
- this._children.set(id, children)
326
- }
327
- return children
328
- }
329
-
330
- private _incomingTo(id: Fqn) {
331
- let incoming = this._incoming.get(id)
332
- if (!incoming) {
333
- incoming = new Set()
334
- this._incoming.set(id, incoming)
335
- }
336
- return incoming
337
- }
338
-
339
- private _outgoingFrom(id: Fqn) {
340
- let outgoing = this._outgoing.get(id)
341
- if (!outgoing) {
342
- outgoing = new Set()
343
- this._outgoing.set(id, outgoing)
344
- }
345
- return outgoing
346
- }
347
-
348
- private _internalOf(id: Fqn) {
349
- let internal = this._internal.get(id)
350
- if (!internal) {
351
- internal = new Set()
352
- this._internal.set(id, internal)
353
+ this.#incoming.get(targetAncestor).add(rel)
353
354
  }
354
- return internal
355
+ return rel
355
356
  }
356
357
  }
357
358
 
358
359
  export namespace LikeC4Model {
359
- export function create(source: LikeC4Model.SourceModel): PickBySource<typeof source> {
360
- // static create<MM extends LikeC4Model>(source: MM['s']): PickBySource<typeof source> {
361
- if (source.__ === 'layouted') {
362
- return LikeC4Model.layouted(source)
363
- }
364
- return LikeC4Model.computed(source)
365
- }
366
-
367
- export function computed(source: C4ComputedLikeC4Model): LikeC4Model<LikeC4ViewModel> {
368
- return new LikeC4Model(
369
- 'computed',
370
- source,
371
- values(source.elements),
372
- values(source.relations),
373
- source.views,
374
- LikeC4ViewModel
375
- )
376
- }
377
-
378
- export function layouted(source: C4LayoutedLikeC4Model): LikeC4Model<LikeC4DiagramModel> {
379
- return new LikeC4Model(
380
- 'layouted',
381
- source,
382
- values(source.elements),
383
- values(source.relations),
384
- source.views,
385
- LikeC4DiagramModel
386
- )
387
- }
388
-
389
- export function isModel(model: any): model is LikeC4Model {
390
- return model instanceof LikeC4Model
391
- }
392
-
393
- export type SourceModel<M extends ViewModel = ViewModel> = M extends LikeC4DiagramModel ? C4LayoutedLikeC4Model
394
- : C4ComputedLikeC4Model
395
-
396
- export type ViewModel = LikeC4ViewModel | LikeC4DiagramModel
397
- export namespace ViewModel {
398
- export type ElementModel = LikeC4ViewModel.Element | LikeC4DiagramModel.Element
399
- export type Relationship = LikeC4Model.Relationship<ViewModel>
400
-
401
- export function isLayouted(model: ViewModel): model is LikeC4DiagramModel {
402
- return model instanceof LikeC4DiagramModel
403
- }
404
- }
405
-
406
- export type Computed = LikeC4Model<LikeC4ViewModel>
407
- export namespace Computed {
408
- export type ViewModel = LikeC4ViewModel
409
- export type SourceModel = LikeC4Model.SourceModel<ViewModel>
410
- }
411
-
412
- export type Layouted = LikeC4Model<LikeC4DiagramModel>
413
- export namespace Layouted {
414
- export type ViewModel = LikeC4DiagramModel
415
- export type SourceModel = LikeC4Model.SourceModel<ViewModel>
416
- }
417
-
418
- export type ModelType<M extends ViewModel = ViewModel> = M extends LikeC4DiagramModel ? 'layouted'
419
- : M extends LikeC4ViewModel ? 'computed'
420
- : never
421
-
422
- export function isLayouted(model: LikeC4Model): model is Layouted {
423
- return model.type === 'layouted'
424
- }
425
-
426
- export class Relationship<M extends ViewModel = ViewModel> {
427
- constructor(
428
- public readonly relationship: C4Relation,
429
- private readonly model: LikeC4Model<M>
430
- ) {
431
- }
432
-
433
- get id() {
434
- return this.relationship.id
435
- }
436
-
437
- get title() {
438
- return this.relationship.title
439
- }
440
-
441
- get kind() {
442
- return this.relationship.kind ?? null
443
- }
444
-
445
- get tags(): C4Tag[] {
446
- return this.relationship.tags ?? []
447
- }
448
-
449
- get source() {
450
- return this.model.element(this.relationship.source)
451
- }
452
-
453
- get target() {
454
- return this.model.element(this.relationship.target)
455
- }
456
-
457
- public metadata(key: string): string | undefined
458
- public metadata(key: string, defaultValue: string): string
459
- public metadata(key: string, defaultValue?: string): string | undefined {
460
- return this.relationship.metadata?.[key] ?? defaultValue
461
- }
462
-
463
- public hasMetadata(key: string): boolean {
464
- return isTruthy(this.relationship.metadata?.[key])
465
- }
466
- }
467
-
468
- // Class renamed to ElementModel, otherwise generated DTS will be incorrect
469
- export class ElementModel<M extends ViewModel = ViewModel> {
470
- constructor(
471
- public readonly element: C4Element,
472
- private readonly model: LikeC4Model<M>
473
- ) {
474
- }
475
-
476
- get id() {
477
- return this.element.id
478
- }
479
-
480
- get title() {
481
- return this.element.title
482
- }
483
-
484
- get kind(): C4ElementKind {
485
- return this.element.kind
486
- }
487
-
488
- get isRoot(): boolean {
489
- return parentFqn(this.element.id) === null
490
- }
491
-
492
- get hasNested(): boolean {
493
- return this.model.children(this).length > 0
494
- }
495
-
496
- get shape(): C4ElementShape {
497
- return this.element.shape ?? DefaultElementShape
498
- }
499
-
500
- get color(): C4Color {
501
- return this.element.color ?? DefaultThemeColor
502
- }
503
-
504
- get tags(): C4Tag[] {
505
- return this.element.tags ?? []
506
- }
507
-
508
- public parent(): ElementModel<M> | null {
509
- return this.model.parent(this)
510
- }
511
-
512
- public metadata(key: string): string | undefined
513
- public metadata(key: string, defaultValue: string): string
514
- public metadata(key: string, defaultValue?: string): string | undefined {
515
- return this.element.metadata?.[key] ?? defaultValue
516
- }
517
-
518
- public hasMetadata(key: string): boolean {
519
- return isTruthy(this.element.metadata?.[key])
520
- }
521
-
522
- public ancestors() {
523
- return this.model.ancestors(this)
524
- }
525
-
526
- public siblings() {
527
- return this.model.siblings(this)
528
- }
529
-
530
- public descendants() {
531
- return this.model.descendants(this)
532
- }
533
-
534
- public children() {
535
- return this.model.children(this)
536
- }
537
-
538
- /**
539
- * First 'view of' current element
540
- */
541
- public viewOf() {
542
- return this.model.views().find(v => v.viewOf?.id === this.id) ?? null
543
- }
544
-
545
- /**
546
- * All views 'view of' current element
547
- */
548
- public viewsOf() {
549
- return this.model.views().filter(v => v.viewOf?.id === this.id)
550
- }
551
-
552
- /**
553
- * Views that contain this element
554
- */
555
- public views() {
556
- return this.model.viewsWithElement(this)
557
- }
558
-
559
- public incoming(filter?: IncomingFilter) {
560
- return this.model.incoming(this, filter)
561
- }
562
-
563
- public incomers(filter?: IncomingFilter) {
564
- return this.model.incomers(this, filter)
565
- }
566
-
567
- public outgoing(filter?: OutgoingFilter) {
568
- return this.model.outgoing(this, filter)
569
- }
570
-
571
- public outgoers(filter?: OutgoingFilter) {
572
- return this.model.outgoers(this, filter)
573
- }
574
-
575
- public internal() {
576
- return this.model.internal(this)
577
- }
578
-
579
- /**
580
- * Resolve siblings of the element and siblings of ancestors
581
- * (from closest to root)
582
- */
583
- public ascendingSiblings() {
584
- return this.model.ascendingSiblings(this)
585
- }
586
-
587
- // public *descendants(): IterableIterator<LikeC4Element> {
588
- // return
589
- // }
590
- }
360
+ export type Any = Aux<
361
+ string,
362
+ string,
363
+ string,
364
+ ComputedView | DiagramView
365
+ >
366
+
367
+ export type Element<M extends AnyAux = Any> = ElementModel<M>
368
+
369
+ // Relationship in logical model
370
+ export type Relation<M extends AnyAux = Any> = RelationshipModel<M>
371
+
372
+ // Relationship in logical or deployment model
373
+ export type AnyRelation<M extends AnyAux = Any> = RelationshipModel<M> | DeploymentRelationModel<M>
374
+
375
+ export type View<
376
+ M extends AnyAux = Any,
377
+ V extends ComputedView | DiagramView = M['ViewType']
378
+ > = LikeC4ViewModel<M, V>
379
+
380
+ export type Node<
381
+ M extends AnyAux = Any,
382
+ V extends ComputedView | DiagramView = M['ViewType']
383
+ > = NodeModel<M, V>
384
+
385
+ export type Edge<
386
+ M extends AnyAux = Any,
387
+ V extends ComputedView | DiagramView = M['ViewType']
388
+ > = EdgeModel<M, V>
389
+
390
+ export type DeploymentModel<M extends AnyAux = AnyAux> = LikeC4DeploymentModel<M>
391
+
392
+ export type Deployment<M extends AnyAux = AnyAux> = DeploymentElementModel<M>
393
+ export type DeploymentNode<M extends AnyAux = AnyAux> = DeploymentNodeModel<M>
394
+ export type DeployedInstance<M extends AnyAux = AnyAux> = DeployedInstanceModel<M>
395
+
396
+ export type Typed<
397
+ Elements extends string = string,
398
+ Deployments extends string = string,
399
+ Views extends string = string,
400
+ ViewType = DiagramView<Views> | ComputedView<Views>
401
+ > = Aux<Elements, Deployments, Views, ViewType>
402
+
403
+ export type Computed<
404
+ Elements extends string = string,
405
+ Deployments extends string = string,
406
+ Views extends string = string
407
+ > = LikeC4Model<Typed<Elements, Deployments, Views, ComputedView<Views>>>
408
+
409
+ export type Layouted<
410
+ Elements extends string = string,
411
+ Deployments extends string = string,
412
+ Views extends string = string
413
+ > = LikeC4Model<Typed<Elements, Deployments, Views, DiagramView<Views>>>
591
414
  }