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