@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.
- package/dist/compute-view/index.d.mts +91 -0
- package/dist/compute-view/index.d.ts +91 -0
- package/dist/compute-view/index.mjs +79 -0
- package/dist/index.d.mts +246 -434
- package/dist/index.d.ts +246 -434
- package/dist/index.mjs +418 -1322
- package/dist/model/index.d.mts +642 -0
- package/dist/model/index.d.ts +642 -0
- package/dist/model/index.mjs +2 -0
- package/dist/shared/core.BBgiAAv1.mjs +144 -0
- package/dist/shared/core.BPyJvuMU.mjs +2420 -0
- package/dist/shared/core.BQ4AbfvB.mjs +9228 -0
- package/dist/shared/{core.CoLQkcYL.d.mts → core.DMYY7C8s.d.mts} +398 -249
- package/dist/shared/{core.CoLQkcYL.d.ts → core.DMYY7C8s.d.ts} +398 -249
- package/dist/shared/core.k12s_keD.mjs +203 -0
- package/dist/types/index.d.mts +69 -1
- package/dist/types/index.d.ts +69 -1
- package/dist/types/index.mjs +21 -1
- package/package.json +38 -40
- package/src/builder/Builder.deployment.ts +219 -0
- package/src/builder/Builder.deploymentModel.ts +360 -0
- package/src/builder/Builder.element.ts +17 -19
- package/src/builder/Builder.model.ts +13 -2
- package/src/builder/Builder.test-d.ts +59 -4
- package/src/builder/Builder.ts +238 -53
- package/src/builder/Builder.view.ts +238 -53
- package/src/builder/_types.ts +87 -15
- package/src/compute-view/LikeC4ModelGraph.ts +330 -0
- package/src/compute-view/compute-view.ts +92 -0
- package/src/compute-view/deployment-view/Memory.ts +108 -0
- package/src/compute-view/deployment-view/Stage.ts +145 -0
- package/src/compute-view/deployment-view/__test__/fixture.ts +210 -0
- package/src/compute-view/deployment-view/_types.ts +29 -0
- package/src/compute-view/deployment-view/clean-connections.ts +229 -0
- package/src/compute-view/deployment-view/compute.ts +111 -0
- package/src/compute-view/deployment-view/predicates/deployment-ref.ts +146 -0
- package/src/compute-view/deployment-view/predicates/relation-direct.ts +130 -0
- package/src/compute-view/deployment-view/predicates/relation-in-out.ts +43 -0
- package/src/compute-view/deployment-view/predicates/relation-incoming.ts +46 -0
- package/src/compute-view/deployment-view/predicates/relation-outgoing.ts +49 -0
- package/src/compute-view/deployment-view/predicates/wildcard.ts +29 -0
- package/src/compute-view/deployment-view/utils.ts +212 -0
- package/src/compute-view/dynamic-view/__test__/fixture.ts +58 -0
- package/src/compute-view/dynamic-view/compute.ts +310 -0
- package/src/compute-view/element-view/__test__/__snapshots__/legacy.spec.ts.snap +456 -0
- package/src/compute-view/element-view/__test__/fixture.ts +645 -0
- package/src/compute-view/element-view/compute.ts +734 -0
- package/src/compute-view/element-view/predicates.ts +517 -0
- package/src/compute-view/index.ts +5 -0
- package/src/compute-view/utils/__snapshots__/sortNodes.spec.ts.snap +113 -0
- package/src/compute-view/utils/ancestorsOfNode.ts +19 -0
- package/src/compute-view/utils/applyCustomElementProperties.ts +66 -0
- package/src/compute-view/utils/applyCustomRelationProperties.ts +41 -0
- package/src/compute-view/utils/applyViewRuleStyles.ts +53 -0
- package/src/compute-view/utils/buildComputedNodes.ts +148 -0
- package/src/compute-view/utils/buildElementNotations.ts +63 -0
- package/src/compute-view/utils/derive-edge-props-from-relationships.ts +97 -0
- package/src/compute-view/utils/elementExpressionToPredicate.ts +52 -0
- package/src/compute-view/utils/link-nodes-with-edges.ts +58 -0
- package/src/compute-view/utils/relationExpressionToPredicates.ts +55 -0
- package/src/compute-view/utils/resolve-extended-views.ts +65 -0
- package/src/compute-view/utils/resolve-global-rules.ts +83 -0
- package/src/compute-view/utils/sortNodes.ts +101 -0
- package/src/compute-view/utils/topological-sort.ts +160 -0
- package/src/compute-view/utils/uniqueTags.test.ts +42 -0
- package/src/compute-view/utils/uniqueTags.ts +21 -0
- package/src/compute-view/utils/view-hash.ts +27 -0
- package/src/compute-view/utils/with-readable-edges.ts +31 -0
- package/src/index.ts +5 -1
- package/src/model/DeploymentElementModel.ts +534 -0
- package/src/model/DeploymentModel.ts +364 -0
- package/src/model/ElementModel.ts +195 -0
- package/src/model/LikeC4Model.test-d.ts +151 -0
- package/src/model/LikeC4Model.ts +280 -457
- package/src/model/RelationModel.ts +84 -0
- package/src/model/__test__/fixture.ts +153 -338
- package/src/model/connection/ConnectionModel.ts +94 -0
- package/src/model/connection/DeploymentConnectionModel.ts +99 -0
- package/src/model/connection/deployment.ts +124 -0
- package/src/model/connection/model.ts +123 -0
- package/src/model/index.ts +4 -2
- package/src/model/types.ts +95 -15
- package/src/model/view/EdgeModel.ts +103 -0
- package/src/model/view/LikeC4ViewModel.ts +220 -0
- package/src/model/view/NodeModel.ts +228 -0
- package/src/types/_common.ts +2 -0
- package/src/types/deployments.ts +237 -0
- package/src/types/element.ts +7 -3
- package/src/types/expression.ts +8 -6
- package/src/types/index.ts +1 -0
- package/src/types/model.ts +68 -9
- package/src/types/overview-graph.ts +2 -2
- package/src/types/relation.ts +4 -4
- package/src/types/view-notation.ts +2 -2
- package/src/types/view.ts +104 -30
- package/src/utils/{commonHead.ts → common-head.ts} +2 -0
- package/src/utils/fqn.ts +83 -27
- package/src/utils/getOrCreate.ts +42 -0
- package/src/utils/graphlib.ts +10 -0
- package/src/utils/index.ts +4 -1
- package/src/utils/object-hash.ts +3 -0
- package/src/utils/set.ts +76 -0
- package/src/utils/string-hash.ts +16 -0
- package/dist/index.cjs +0 -5452
- package/dist/index.d.cts +0 -1053
- package/dist/shared/core.BXTs-8n7.mjs +0 -293
- package/dist/shared/core.CoLQkcYL.d.cts +0 -734
- package/dist/shared/core.DIYZvpVg.cjs +0 -353
- package/dist/types/index.cjs +0 -70
- package/dist/types/index.d.cts +0 -2
- package/src/model/LikeC4DiagramModel.ts +0 -338
- package/src/model/LikeC4ViewModel.ts +0 -320
package/src/model/LikeC4Model.ts
CHANGED
|
@@ -1,305 +1,341 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from '
|
|
10
|
-
import {
|
|
11
|
-
import type
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} from '
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
+
readonly #parents = new Map<M['Element'], ElementModel<M>>()
|
|
34
35
|
// Children elements for given FQN
|
|
35
|
-
|
|
36
|
+
readonly #children = new DefaultMap<M['Element'], Set<ElementModel<M>>>(() => new Set())
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
readonly #rootElements = new Set<ElementModel<M>>()
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
readonly #relations = new Map<M['RelationId'], RelationshipModel<M>>()
|
|
40
41
|
|
|
41
42
|
// Incoming to an element or its descendants
|
|
42
|
-
|
|
43
|
+
readonly #incoming = new DefaultMap<M['Element'], Set<RelationshipModel<M>>>(() => new Set())
|
|
43
44
|
|
|
44
45
|
// Outgoing from an element or its descendants
|
|
45
|
-
|
|
46
|
+
readonly #outgoing = new DefaultMap<M['Element'], Set<RelationshipModel<M>>>(() => new Set())
|
|
46
47
|
|
|
47
48
|
// Relationships inside the element, among descendants
|
|
48
|
-
|
|
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
|
-
|
|
57
|
+
public readonly deployment: LikeC4DeploymentModel<M>
|
|
51
58
|
|
|
52
|
-
|
|
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
|
|
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
|
|
63
|
-
this.addElement(
|
|
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
|
|
66
|
-
this.addRelation(
|
|
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.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
118
|
+
* Returns the root elements of the model.
|
|
86
119
|
*/
|
|
87
|
-
public
|
|
88
|
-
return
|
|
120
|
+
public roots(): ElementsIterator<M> {
|
|
121
|
+
return this.#rootElements.values()
|
|
89
122
|
}
|
|
90
123
|
|
|
91
124
|
/**
|
|
92
|
-
* Returns
|
|
125
|
+
* Returns all elements in the model.
|
|
93
126
|
*/
|
|
94
|
-
public
|
|
95
|
-
return
|
|
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():
|
|
102
|
-
return
|
|
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:
|
|
109
|
-
|
|
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()
|
|
116
|
-
return
|
|
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:
|
|
123
|
-
return nonNullable(this.
|
|
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):
|
|
208
|
+
public parent(element: M['ElementOrFqn']): ElementModel<M> | null {
|
|
131
209
|
const id = getId(element)
|
|
132
|
-
return this.
|
|
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):
|
|
140
|
-
const id = getId(element)
|
|
141
|
-
return this.
|
|
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):
|
|
148
|
-
const id = getId(element)
|
|
149
|
-
const parent = this.
|
|
150
|
-
const siblings = parent ? this.
|
|
151
|
-
|
|
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):
|
|
159
|
-
let 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.
|
|
163
|
-
|
|
244
|
+
while (parent = this.#parents.get(id)) {
|
|
245
|
+
yield parent
|
|
164
246
|
id = parent.id
|
|
165
247
|
}
|
|
166
|
-
return
|
|
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):
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
):
|
|
269
|
+
): RelationshipsIterator<M> {
|
|
186
270
|
const id = getId(element)
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
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
|
-
):
|
|
290
|
+
): RelationshipsIterator<M> {
|
|
218
291
|
const id = getId(element)
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
|
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(
|
|
275
|
-
if (this.
|
|
276
|
-
throw new Error(`Element ${
|
|
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
|
|
279
|
-
this.
|
|
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.
|
|
283
|
-
this.
|
|
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.
|
|
316
|
+
this.#rootElements.add(el)
|
|
286
317
|
}
|
|
318
|
+
return el
|
|
287
319
|
}
|
|
288
320
|
|
|
289
|
-
private addRelation(relation:
|
|
290
|
-
if (this.
|
|
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
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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(
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
355
|
+
return rel
|
|
355
356
|
}
|
|
356
357
|
}
|
|
357
358
|
|
|
358
359
|
export namespace LikeC4Model {
|
|
359
|
-
export
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
export
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
export type
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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
|
}
|