@likec4/core 1.30.0 → 1.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +407 -5
- package/dist/builder/index.d.mts +221 -42
- package/dist/builder/index.mjs +118 -65
- package/dist/compute-view/index.d.mts +23 -7
- package/dist/compute-view/index.mjs +11 -6
- package/dist/compute-view/relationships-view/index.d.mts +9 -7
- package/dist/compute-view/relationships-view/index.mjs +3 -2
- package/dist/index.d.mts +10 -14
- package/dist/index.mjs +10 -6
- package/dist/model/index.d.mts +173 -19
- package/dist/model/index.mjs +6 -1
- package/dist/shared/core.BaYE_r29.d.mts +258 -0
- package/dist/shared/core.Bk7wEZFG.mjs +276 -0
- package/dist/shared/core.BrCnZFTn.mjs +32 -0
- package/dist/shared/core.BuO4ncfY.d.mts +146 -0
- package/dist/shared/{core.CAcZ6_cP.mjs → core.CBpKAlhj.mjs} +1742 -4128
- package/dist/shared/core.CCWXGc6G.d.mts +920 -0
- package/dist/shared/core.CUYDgv2-.mjs +214 -0
- package/dist/shared/{core.Cu-UdkSl.mjs → core.CpXlRyE5.mjs} +7 -494
- package/dist/shared/{core.C-YXI-43.mjs → core.Cy9smucx.mjs} +3 -1
- package/dist/shared/core.CywrQs86.d.mts +31 -0
- package/dist/shared/core.DoK86JEe.mjs +518 -0
- package/dist/shared/core.Dpq3P3YJ.d.mts +1265 -0
- package/dist/shared/{core.D2830qgg.d.mts → core.Pf5I9o0e.d.mts} +1 -1
- package/dist/shared/core.cbA9VZ02.mjs +2681 -0
- package/dist/shared/{core.CQXU9DF7.mjs → core.rmvsy0n3.mjs} +23 -32
- package/dist/types/aux.d.mts +3 -0
- package/dist/types/aux.mjs +1 -0
- package/dist/types/index.d.mts +7 -45
- package/dist/types/index.mjs +130 -3
- package/dist/utils/index.d.mts +221 -53
- package/dist/utils/index.mjs +14 -10
- package/package.json +19 -10
- package/src/builder/Builder.deploymentModel.ts +82 -18
- package/src/builder/Builder.element.ts +40 -20
- package/src/builder/Builder.model.ts +66 -17
- package/src/builder/Builder.ts +168 -76
- package/src/builder/Builder.view-common.ts +35 -17
- package/src/builder/Builder.view-deployment.ts +25 -25
- package/src/builder/Builder.view-element.ts +13 -12
- package/src/builder/Builder.views.ts +12 -7
- package/src/builder/__snapshots__/Builder-style1.spec.ts.snap +62 -34
- package/src/builder/__snapshots__/Builder-style2.compute-model.json5 +319 -0
- package/src/builder/__snapshots__/Builder-style2.spec.ts.snap +45 -24
- package/src/builder/_types.ts +34 -27
- package/src/compute-view/compute-view.ts +31 -34
- package/src/compute-view/deployment-view/__test__/TestHelper.ts +27 -14
- package/src/compute-view/deployment-view/__test__/fixture.ts +15 -13
- package/src/compute-view/deployment-view/_types.ts +10 -10
- package/src/compute-view/deployment-view/clean-connections.ts +5 -7
- package/src/compute-view/deployment-view/compute.ts +16 -13
- package/src/compute-view/deployment-view/memory/memory.ts +5 -6
- package/src/compute-view/deployment-view/predicates/deploymentRefs.ts +4 -5
- package/src/compute-view/deployment-view/predicates/relation-direct.ts +25 -50
- package/src/compute-view/deployment-view/predicates/relation-in-out.ts +5 -7
- package/src/compute-view/deployment-view/predicates/relation-incoming.ts +7 -11
- package/src/compute-view/deployment-view/predicates/relation-outgoing.ts +7 -10
- package/src/compute-view/deployment-view/predicates/utils.ts +37 -31
- package/src/compute-view/deployment-view/predicates/wildcard.ts +3 -2
- package/src/compute-view/deployment-view/stages/stage-final.ts +2 -2
- package/src/compute-view/deployment-view/stages/stage-include.ts +1 -1
- package/src/compute-view/deployment-view/utils.ts +79 -72
- package/src/compute-view/dynamic-view/__test__/fixture.ts +20 -11
- package/src/compute-view/dynamic-view/compute.ts +60 -53
- package/src/compute-view/element-view/__test__/TestHelper.ts +53 -29
- package/src/compute-view/element-view/__test__/__snapshots__/legacy.spec.ts.snap +26 -26
- package/src/compute-view/element-view/__test__/fixture.ts +134 -83
- package/src/compute-view/element-view/_types.ts +14 -19
- package/src/compute-view/element-view/clean-connections.ts +12 -8
- package/src/compute-view/element-view/compute.ts +61 -51
- package/src/compute-view/element-view/memory/NodeGroup.ts +8 -9
- package/src/compute-view/element-view/memory/memory.ts +22 -22
- package/src/compute-view/element-view/memory/stage-include.ts +5 -3
- package/src/compute-view/element-view/predicates/_utils.ts +18 -14
- package/src/compute-view/element-view/predicates/element-expand.ts +4 -5
- package/src/compute-view/element-view/predicates/element-kind-tag.ts +3 -4
- package/src/compute-view/element-view/predicates/element-ref.ts +3 -4
- package/src/compute-view/element-view/predicates/relation-direct.ts +16 -12
- package/src/compute-view/element-view/predicates/relation-in-out.ts +7 -8
- package/src/compute-view/element-view/predicates/relation-in.ts +19 -26
- package/src/compute-view/element-view/predicates/relation-out.ts +20 -21
- package/src/compute-view/element-view/predicates/wildcard.ts +3 -3
- package/src/compute-view/element-view/utils.ts +25 -23
- package/src/compute-view/index.ts +1 -1
- package/src/compute-view/memory/AbstractStageExclude.ts +3 -4
- package/src/compute-view/memory/AbstractStageInclude.ts +1 -1
- package/src/compute-view/memory/_types.ts +3 -2
- package/src/compute-view/relationships-view/_types.ts +3 -4
- package/src/compute-view/relationships-view/compute.ts +8 -11
- package/src/compute-view/relationships-view/layout.ts +6 -6
- package/src/compute-view/relationships-view/utils.ts +4 -4
- package/src/compute-view/utils/ancestorsOfNode.ts +7 -7
- package/src/compute-view/utils/applyCustomElementProperties.ts +16 -8
- package/src/compute-view/utils/applyCustomRelationProperties.ts +19 -9
- package/src/compute-view/utils/applyViewRuleStyles.ts +18 -10
- package/src/compute-view/utils/buildComputedNodes.ts +40 -40
- package/src/compute-view/utils/buildElementNotations.ts +14 -14
- package/src/compute-view/utils/elementExpressionToPredicate.ts +14 -15
- package/src/compute-view/utils/link-nodes-with-edges.ts +6 -4
- package/src/compute-view/utils/merge-props-from-relationships.ts +52 -20
- package/src/compute-view/utils/relationExpressionToPredicates.ts +19 -19
- package/src/compute-view/utils/resolve-extended-views.ts +8 -7
- package/src/compute-view/utils/resolve-global-rules.ts +31 -26
- package/src/compute-view/utils/topological-sort.ts +14 -14
- package/src/compute-view/utils/view-hash.ts +2 -2
- package/src/compute-view/utils/with-readable-edges.ts +9 -5
- package/src/index.ts +0 -32
- package/src/model/DeploymentElementModel.ts +182 -137
- package/src/model/DeploymentModel.ts +88 -65
- package/src/model/ElementModel.ts +92 -72
- package/src/model/LikeC4Model.ts +430 -241
- package/src/model/RelationModel.ts +69 -26
- package/src/model/__test__/fixture.ts +50 -36
- package/src/model/connection/Connection.ts +2 -2
- package/src/model/connection/deployment/DeploymentConnectionModel.ts +21 -24
- package/src/model/connection/deployment/find.ts +1 -1
- package/src/model/connection/model/ConnectionModel.ts +21 -20
- package/src/model/connection/model/find.ts +7 -7
- package/src/model/connection/ops.ts +6 -6
- package/src/model/guards.ts +66 -14
- package/src/model/index.ts +20 -7
- package/src/model/types.ts +41 -97
- package/src/model/view/EdgeModel.ts +47 -41
- package/src/model/view/LikeC4ViewModel.ts +130 -80
- package/src/model/view/NodeModel.ts +102 -96
- package/src/theme/element.ts +1 -1
- package/src/theme/index.ts +9 -9
- package/src/theme/relationships.ts +1 -1
- package/src/types/_common.ts +21 -15
- package/src/types/aux.ts +285 -0
- package/src/types/const.ts +34 -0
- package/src/types/expression-model.ts +292 -0
- package/src/types/expression.ts +311 -0
- package/src/types/fqnRef.ts +88 -0
- package/src/types/geometry.ts +131 -0
- package/src/types/global.ts +10 -8
- package/src/types/index.ts +39 -201
- package/src/types/model-data.ts +52 -84
- package/src/types/model-deployment.ts +87 -0
- package/src/types/model-dump.ts +72 -0
- package/src/types/model-logical.ts +111 -0
- package/src/types/model-spec.ts +81 -0
- package/src/types/operators.ts +51 -52
- package/src/types/scalar.ts +114 -0
- package/src/types/styles.ts +123 -0
- package/src/types/view-changes.ts +4 -5
- package/src/types/view-common.ts +157 -0
- package/src/types/view-computed.ts +113 -0
- package/src/types/view-layouted.ts +82 -0
- package/src/types/view-parsed.deployment.ts +40 -0
- package/src/types/view-parsed.dynamic.ts +78 -0
- package/src/types/view-parsed.element.ts +65 -0
- package/src/types/view.ts +122 -542
- package/src/utils/compare-natural.ts +43 -1
- package/src/utils/getOrCreate.ts +0 -34
- package/src/utils/index.ts +7 -12
- package/src/utils/iterable/filter.ts +1 -1
- package/src/utils/iterable/find.ts +3 -1
- package/src/utils/iterable/head.ts +22 -0
- package/src/utils/iterable/index.ts +1 -0
- package/src/utils/iterable/map.ts +1 -1
- package/src/utils/iterable/reduce.ts +1 -1
- package/src/utils/iterable/some.ts +1 -1
- package/src/utils/memoize-prop.ts +30 -0
- package/src/utils/object-hash.ts +1 -1
- package/src/utils/relations.ts +14 -11
- package/src/utils/string-hash.ts +1 -1
- package/dist/shared/core.CZR9J1Qx.d.mts +0 -146
- package/dist/shared/core.CiFKryGW.d.mts +0 -745
- package/dist/shared/core.DJvmfnEi.d.mts +0 -1153
- package/dist/shared/core.DgfwjBtu.mjs +0 -495
- package/dist/shared/core.SjLhMA7a.d.mts +0 -62
- package/src/builder/__snapshots__/Builder.spec.ts.snap +0 -374
- package/src/compute-view/element-view/predicates.ts +0 -520
- package/src/compute-view/utils/uniqueTags.ts +0 -21
- package/src/types/deployments.ts +0 -170
- package/src/types/element.ts +0 -109
- package/src/types/expression-v2-model.ts +0 -314
- package/src/types/expression-v2.ts +0 -392
- package/src/types/overview-graph.ts +0 -43
- package/src/types/relation.ts +0 -61
- package/src/types/scalars.ts +0 -39
- package/src/types/theme.ts +0 -82
- package/src/types/view-notation.ts +0 -9
- package/src/utils/graphlib.ts +0 -10
- /package/src/{errors/index.ts → utils/invariant.ts} +0 -0
|
@@ -0,0 +1,2681 @@
|
|
|
1
|
+
import { C, f as s$1, u as hierarchyLevel, a as isAncestor, k as commonAncestor, v as sortNaturalByFqn, s as sortParentsFirst, D as DefaultMap, p as parentFqn, l as ancestorsFqn, b as ifilter, m as compareNatural, i as isDescendantOf, w as isString, d as isSameHierarchy } from './core.rmvsy0n3.mjs';
|
|
2
|
+
import { E as splitGlobalFqn, D as DefaultElementShape, a as DefaultThemeColor, l as DefaultShapeSize, c as n$1, g as DefaultLineStyle, F as FqnRef, k as isDeploymentNode, h as DefaultRelationshipColor, I as extractStep, d as isStepEdgeId, e as isGroupElementKind, _ as _type, b as _stage, j as isOnStage, w as whereOperatorAsPredicate, C as isGlobalFqn, B as GlobalFqn, o as o$2 } from './core.CUYDgv2-.mjs';
|
|
3
|
+
import { f as memoizeProp, e as ihead, a as intersection, d as difference, u as union, g as getOrCreate, c as ifind, m as m$2, k as stringHash, j as equals, b as isome } from './core.DoK86JEe.mjs';
|
|
4
|
+
import { u as u$1 } from './core.DbRvwARP.mjs';
|
|
5
|
+
import { n as nonNullable, i as invariant } from './core.Cy9smucx.mjs';
|
|
6
|
+
|
|
7
|
+
function y(t,i){let a=i.length-t.length;if(a===1){let[n,...r]=i;return C(n,{lazy:t,lazyArgs:r})}if(a===0){let n={lazy:t,lazyArgs:i};return Object.assign(e=>C(e,n),n)}throw new Error("Wrong number of arguments")}
|
|
8
|
+
|
|
9
|
+
function i(...e){return y(a$1,e)}function a$1(){let e=new Set;return t=>e.has(t)?s$1:(e.add(t),{done:false,hasNext:true,next:t})}
|
|
10
|
+
|
|
11
|
+
function r(...t){return u$1(Object.values,t)}
|
|
12
|
+
|
|
13
|
+
var T={asc:(r,n)=>r>n,desc:(r,n)=>r<n};function s(r,n){let[e,...o]=n;if(!m$1(e)){let t=u(...o);return r(e,t)}let a=u(e,...o);return t=>r(t,a)}function u(r,n,...e){let o=typeof r=="function"?r:r[0],a=typeof r=="function"?"asc":r[1],{[a]:t}=T,i=n===void 0?void 0:u(n,...e);return (y,c)=>{let p=o(y),l=o(c);return t(p,l)?1:t(l,p)?-1:i?.(y,c)??0}}function m$1(r){if(d(r))return true;if(typeof r!="object"||!Array.isArray(r))return false;let[n,e,...o]=r;return d(n)&&typeof e=="string"&&e in T&&o.length===0}var d=r=>typeof r=="function"&&r.length===1;
|
|
14
|
+
|
|
15
|
+
function m(...r){return u$1(o$1,r)}function o$1(r,t){let e=[...r];return e.sort(t),e}
|
|
16
|
+
|
|
17
|
+
function a(...r){return s(n,r)}var n=(r,t)=>[...r].sort(t);
|
|
18
|
+
|
|
19
|
+
function p(...e){return u$1(t$2,e)}var t$2=(e,o)=>e[o];
|
|
20
|
+
|
|
21
|
+
function t$1(...n){return u$1(o,n)}var o=n=>n.length===1?n[0]:void 0;
|
|
22
|
+
|
|
23
|
+
function t(...r){return u$1(Object.entries,r)}
|
|
24
|
+
|
|
25
|
+
class ElementModel {
|
|
26
|
+
constructor($model, $element) {
|
|
27
|
+
this.$model = $model;
|
|
28
|
+
this.$element = $element;
|
|
29
|
+
this.id = this.$element.id;
|
|
30
|
+
this._literalId = this.$element.id;
|
|
31
|
+
const [projectId, fqn] = splitGlobalFqn(this.id);
|
|
32
|
+
if (projectId) {
|
|
33
|
+
this.imported = {
|
|
34
|
+
from: projectId,
|
|
35
|
+
fqn
|
|
36
|
+
};
|
|
37
|
+
this.hierarchyLevel = hierarchyLevel(fqn);
|
|
38
|
+
} else {
|
|
39
|
+
this.imported = null;
|
|
40
|
+
this.hierarchyLevel = hierarchyLevel(this.id);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
id;
|
|
44
|
+
_literalId;
|
|
45
|
+
hierarchyLevel;
|
|
46
|
+
imported;
|
|
47
|
+
get parent() {
|
|
48
|
+
return this.$model.parent(this);
|
|
49
|
+
}
|
|
50
|
+
get kind() {
|
|
51
|
+
return this.$element.kind;
|
|
52
|
+
}
|
|
53
|
+
get shape() {
|
|
54
|
+
return this.$element.shape ?? DefaultElementShape;
|
|
55
|
+
}
|
|
56
|
+
get color() {
|
|
57
|
+
return this.$element.color ?? DefaultThemeColor;
|
|
58
|
+
}
|
|
59
|
+
get icon() {
|
|
60
|
+
return this.$element.icon ?? null;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Returns all tags of the element.
|
|
64
|
+
* It includes tags from the element and its kind.
|
|
65
|
+
*/
|
|
66
|
+
get tags() {
|
|
67
|
+
return memoizeProp(this, Symbol.for("tags"), () => {
|
|
68
|
+
return i([
|
|
69
|
+
...this.$element.tags ?? [],
|
|
70
|
+
...this.$model.specification.elements[this.$element.kind]?.tags ?? []
|
|
71
|
+
]);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
get title() {
|
|
75
|
+
return this.$element.title;
|
|
76
|
+
}
|
|
77
|
+
get description() {
|
|
78
|
+
return this.$element.description ?? null;
|
|
79
|
+
}
|
|
80
|
+
get technology() {
|
|
81
|
+
return this.$element.technology ?? null;
|
|
82
|
+
}
|
|
83
|
+
get links() {
|
|
84
|
+
return this.$element.links ?? [];
|
|
85
|
+
}
|
|
86
|
+
get defaultView() {
|
|
87
|
+
return memoizeProp(this, Symbol.for("defaultView"), () => ihead(this.scopedViews()) ?? null);
|
|
88
|
+
}
|
|
89
|
+
get isRoot() {
|
|
90
|
+
return this.parent === null;
|
|
91
|
+
}
|
|
92
|
+
get style() {
|
|
93
|
+
return {
|
|
94
|
+
size: DefaultShapeSize,
|
|
95
|
+
...this.$element.style
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
isAncestorOf(another) {
|
|
99
|
+
return isAncestor(this, another);
|
|
100
|
+
}
|
|
101
|
+
isDescendantOf(another) {
|
|
102
|
+
return isAncestor(another, this);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get all ancestor elements (i.e. parent, parent’s parent, etc.)
|
|
106
|
+
* (from closest to root)
|
|
107
|
+
*/
|
|
108
|
+
ancestors() {
|
|
109
|
+
return this.$model.ancestors(this);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Returns the common ancestor of this element and another element.
|
|
113
|
+
*/
|
|
114
|
+
commonAncestor(another) {
|
|
115
|
+
const common = commonAncestor(this.id, another.id);
|
|
116
|
+
return common ? this.$model.element(common) : null;
|
|
117
|
+
}
|
|
118
|
+
children() {
|
|
119
|
+
return this.$model.children(this);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Get all descendant elements (i.e. children, children’s children, etc.)
|
|
123
|
+
*/
|
|
124
|
+
descendants(sort) {
|
|
125
|
+
if (sort) {
|
|
126
|
+
const sorted = sortNaturalByFqn([...this.$model.descendants(this)], sort);
|
|
127
|
+
return sorted[Symbol.iterator]();
|
|
128
|
+
}
|
|
129
|
+
return this.$model.descendants(this);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get all sibling (i.e. same parent)
|
|
133
|
+
*/
|
|
134
|
+
siblings() {
|
|
135
|
+
return this.$model.siblings(this);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Resolve siblings of the element and its ancestors
|
|
139
|
+
* (from closest parent to root)
|
|
140
|
+
*/
|
|
141
|
+
*ascendingSiblings() {
|
|
142
|
+
yield* this.siblings();
|
|
143
|
+
for (const ancestor of this.ancestors()) {
|
|
144
|
+
yield* ancestor.siblings();
|
|
145
|
+
}
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Resolve siblings of the element and its ancestors
|
|
150
|
+
* (from root to closest)
|
|
151
|
+
*/
|
|
152
|
+
*descendingSiblings() {
|
|
153
|
+
for (const ancestor of [...this.ancestors()].reverse()) {
|
|
154
|
+
yield* ancestor.siblings();
|
|
155
|
+
}
|
|
156
|
+
yield* this.siblings();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
incoming(filter = "all") {
|
|
160
|
+
return this.$model.incoming(this, filter);
|
|
161
|
+
}
|
|
162
|
+
*incomers(filter = "all") {
|
|
163
|
+
const unique2 = /* @__PURE__ */ new Set();
|
|
164
|
+
for (const r of this.incoming(filter)) {
|
|
165
|
+
if (unique2.has(r.source.id)) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
unique2.add(r.source.id);
|
|
169
|
+
yield r.source;
|
|
170
|
+
}
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
outgoing(filter = "all") {
|
|
174
|
+
return this.$model.outgoing(this, filter);
|
|
175
|
+
}
|
|
176
|
+
*outgoers(filter = "all") {
|
|
177
|
+
const unique2 = /* @__PURE__ */ new Set();
|
|
178
|
+
for (const r of this.outgoing(filter)) {
|
|
179
|
+
if (unique2.has(r.target.id)) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
unique2.add(r.target.id);
|
|
183
|
+
yield r.target;
|
|
184
|
+
}
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
get allOutgoing() {
|
|
188
|
+
return memoizeProp(this, Symbol.for("allOutgoing"), () => new Set(this.outgoing()));
|
|
189
|
+
}
|
|
190
|
+
get allIncoming() {
|
|
191
|
+
return memoizeProp(this, Symbol.for("allIncoming"), () => new Set(this.incoming()));
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Iterate over all views that include this element.
|
|
195
|
+
*/
|
|
196
|
+
views() {
|
|
197
|
+
return memoizeProp(this, Symbol.for("views"), () => {
|
|
198
|
+
const views = /* @__PURE__ */ new Set();
|
|
199
|
+
for (const view of this.$model.views()) {
|
|
200
|
+
if (view.includesElement(this.id)) {
|
|
201
|
+
views.add(view);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return views;
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Iterate over all views that scope this element.
|
|
209
|
+
* It is possible that element is not included in the view.
|
|
210
|
+
*/
|
|
211
|
+
scopedViews() {
|
|
212
|
+
return memoizeProp(this, Symbol.for("scopedViews"), () => {
|
|
213
|
+
const views = /* @__PURE__ */ new Set();
|
|
214
|
+
for (const vm of this.$model.views()) {
|
|
215
|
+
if (vm.isScopedElementView() && vm.viewOf.id === this.id) {
|
|
216
|
+
views.add(vm);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return views;
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* @returns true if the element is deployed
|
|
224
|
+
*/
|
|
225
|
+
isDeployed() {
|
|
226
|
+
return n$1(ihead(this.deployments()));
|
|
227
|
+
}
|
|
228
|
+
deployments() {
|
|
229
|
+
return this.$model.deployment.instancesOf(this);
|
|
230
|
+
}
|
|
231
|
+
getMetadata(field) {
|
|
232
|
+
if (field) {
|
|
233
|
+
return this.$element.metadata?.[field];
|
|
234
|
+
}
|
|
235
|
+
return this.$element.metadata ?? {};
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Checks if the element has the given tag.
|
|
239
|
+
*/
|
|
240
|
+
isTagged(tag) {
|
|
241
|
+
return this.tags.includes(tag);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
class AbstractDeploymentElementModel {
|
|
246
|
+
get style() {
|
|
247
|
+
return {
|
|
248
|
+
shape: DefaultElementShape,
|
|
249
|
+
color: DefaultThemeColor,
|
|
250
|
+
size: DefaultShapeSize,
|
|
251
|
+
...this.$node.style
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
get shape() {
|
|
255
|
+
return this.$node.style?.shape ?? DefaultElementShape;
|
|
256
|
+
}
|
|
257
|
+
get color() {
|
|
258
|
+
return this.$node.style?.color ?? DefaultThemeColor;
|
|
259
|
+
}
|
|
260
|
+
get description() {
|
|
261
|
+
return this.$node.description ?? null;
|
|
262
|
+
}
|
|
263
|
+
get technology() {
|
|
264
|
+
return this.$node.technology ?? null;
|
|
265
|
+
}
|
|
266
|
+
get links() {
|
|
267
|
+
return this.$node.links ?? [];
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Get all ancestor elements (i.e. parent, parent’s parent, etc.)
|
|
271
|
+
* (from closest to root)
|
|
272
|
+
*/
|
|
273
|
+
ancestors() {
|
|
274
|
+
return this.$model.ancestors(this);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Returns the common ancestor of this element and another element.
|
|
278
|
+
*/
|
|
279
|
+
commonAncestor(another) {
|
|
280
|
+
const common = commonAncestor(this.id, another.id);
|
|
281
|
+
return common ? this.$model.node(common) : null;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Get all sibling (i.e. same parent)
|
|
285
|
+
*/
|
|
286
|
+
siblings() {
|
|
287
|
+
return this.$model.siblings(this);
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Check if the element is a sibling of another element
|
|
291
|
+
*/
|
|
292
|
+
isSibling(other) {
|
|
293
|
+
return this.parent === other.parent;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Resolve siblings of the element and its ancestors
|
|
297
|
+
* (from closest to root)
|
|
298
|
+
*/
|
|
299
|
+
*ascendingSiblings() {
|
|
300
|
+
yield* this.siblings();
|
|
301
|
+
for (const ancestor of this.ancestors()) {
|
|
302
|
+
yield* ancestor.siblings();
|
|
303
|
+
}
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Resolve siblings of the element and its ancestors
|
|
308
|
+
* (from root to closest)
|
|
309
|
+
*/
|
|
310
|
+
*descendingSiblings() {
|
|
311
|
+
for (const ancestor of [...this.ancestors()].reverse()) {
|
|
312
|
+
yield* ancestor.siblings();
|
|
313
|
+
}
|
|
314
|
+
yield* this.siblings();
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
incoming(filter = "all") {
|
|
318
|
+
return this.$model.incoming(this, filter);
|
|
319
|
+
}
|
|
320
|
+
outgoing(filter = "all") {
|
|
321
|
+
return this.$model.outgoing(this, filter);
|
|
322
|
+
}
|
|
323
|
+
*incomers(filter = "all") {
|
|
324
|
+
const unique2 = /* @__PURE__ */ new Set();
|
|
325
|
+
for (const r of this.incoming(filter)) {
|
|
326
|
+
if (unique2.has(r.source.id)) {
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
unique2.add(r.source.id);
|
|
330
|
+
yield r.source;
|
|
331
|
+
}
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
*outgoers(filter = "all") {
|
|
335
|
+
const unique2 = /* @__PURE__ */ new Set();
|
|
336
|
+
for (const r of this.outgoing(filter)) {
|
|
337
|
+
if (unique2.has(r.target.id)) {
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
unique2.add(r.target.id);
|
|
341
|
+
yield r.target;
|
|
342
|
+
}
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Iterate over all views that include this deployment element.
|
|
347
|
+
*/
|
|
348
|
+
*views() {
|
|
349
|
+
for (const view of this.$model.views()) {
|
|
350
|
+
if (view._type !== "deployment") {
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (view.includesDeployment(this.id)) {
|
|
354
|
+
yield view;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
// type guard
|
|
359
|
+
isDeploymentNode() {
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
// type guard
|
|
363
|
+
isInstance() {
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
get allOutgoing() {
|
|
367
|
+
return memoizeProp(this, Symbol.for("allOutgoing"), () => RelationshipsAccum.from(
|
|
368
|
+
new Set(this.outgoingModelRelationships()),
|
|
369
|
+
new Set(this.outgoing())
|
|
370
|
+
));
|
|
371
|
+
}
|
|
372
|
+
get allIncoming() {
|
|
373
|
+
return memoizeProp(this, Symbol.for("allIncoming"), () => RelationshipsAccum.from(
|
|
374
|
+
new Set(this.incomingModelRelationships()),
|
|
375
|
+
new Set(this.incoming())
|
|
376
|
+
));
|
|
377
|
+
}
|
|
378
|
+
getMetadata(field) {
|
|
379
|
+
if (field) {
|
|
380
|
+
return this.$node.metadata?.[field];
|
|
381
|
+
}
|
|
382
|
+
return this.$node.metadata ?? {};
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Checks if the deployment element has the given tag.
|
|
386
|
+
*/
|
|
387
|
+
isTagged(tag) {
|
|
388
|
+
return this.tags.includes(tag);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
class DeploymentNodeModel extends AbstractDeploymentElementModel {
|
|
392
|
+
constructor($model, $node) {
|
|
393
|
+
super();
|
|
394
|
+
this.$model = $model;
|
|
395
|
+
this.$node = $node;
|
|
396
|
+
this.id = $node.id;
|
|
397
|
+
this._literalId = $node.id;
|
|
398
|
+
this.title = $node.title;
|
|
399
|
+
this.hierarchyLevel = hierarchyLevel($node.id);
|
|
400
|
+
}
|
|
401
|
+
id;
|
|
402
|
+
_literalId;
|
|
403
|
+
title;
|
|
404
|
+
hierarchyLevel;
|
|
405
|
+
get parent() {
|
|
406
|
+
return this.$model.parent(this);
|
|
407
|
+
}
|
|
408
|
+
get kind() {
|
|
409
|
+
return this.$node.kind;
|
|
410
|
+
}
|
|
411
|
+
get tags() {
|
|
412
|
+
return memoizeProp(this, Symbol.for("tags"), () => {
|
|
413
|
+
return i([
|
|
414
|
+
...this.$node.tags ?? [],
|
|
415
|
+
...this.$model.$model.specification.deployments[this.kind]?.tags ?? []
|
|
416
|
+
]);
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
children() {
|
|
420
|
+
return this.$model.children(this);
|
|
421
|
+
}
|
|
422
|
+
descendants(sort = "desc") {
|
|
423
|
+
return this.$model.descendants(this, sort);
|
|
424
|
+
}
|
|
425
|
+
isDeploymentNode() {
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Iterate over all instances nested in this deployment node.
|
|
430
|
+
*/
|
|
431
|
+
*instances() {
|
|
432
|
+
for (const nested of this.descendants("desc")) {
|
|
433
|
+
if (nested.isInstance()) {
|
|
434
|
+
yield nested;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Returns deployed instance inside this deployment node
|
|
441
|
+
* if only there are no more instances
|
|
442
|
+
*/
|
|
443
|
+
onlyOneInstance() {
|
|
444
|
+
const children = this.children();
|
|
445
|
+
if (children.size !== 1) {
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
const child = t$1([...children]);
|
|
449
|
+
return child?.isInstance() ? child : null;
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Cached result of relationships from instances
|
|
453
|
+
*/
|
|
454
|
+
_relationshipsFromInstances = null;
|
|
455
|
+
relationshipsFromInstances() {
|
|
456
|
+
if (this._relationshipsFromInstances) {
|
|
457
|
+
return this._relationshipsFromInstances;
|
|
458
|
+
}
|
|
459
|
+
const {
|
|
460
|
+
outgoing,
|
|
461
|
+
incoming
|
|
462
|
+
} = this._relationshipsFromInstances = {
|
|
463
|
+
outgoing: /* @__PURE__ */ new Set(),
|
|
464
|
+
incoming: /* @__PURE__ */ new Set()
|
|
465
|
+
};
|
|
466
|
+
for (const instance of this.instances()) {
|
|
467
|
+
for (const r of instance.element.outgoing()) {
|
|
468
|
+
outgoing.add(r);
|
|
469
|
+
}
|
|
470
|
+
for (const r of instance.element.incoming()) {
|
|
471
|
+
incoming.add(r);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return this._relationshipsFromInstances;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* We return only relationships that are not already present in nested instances
|
|
478
|
+
*/
|
|
479
|
+
outgoingModelRelationships() {
|
|
480
|
+
return this.relationshipsFromInstances().outgoing.values();
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* We return only relationships that are not already present in nested instances
|
|
484
|
+
*/
|
|
485
|
+
incomingModelRelationships() {
|
|
486
|
+
return this.relationshipsFromInstances().incoming.values();
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Returns an iterator of relationships between nested instances
|
|
490
|
+
*/
|
|
491
|
+
internalModelRelationships() {
|
|
492
|
+
const {
|
|
493
|
+
outgoing,
|
|
494
|
+
incoming
|
|
495
|
+
} = this.relationshipsFromInstances();
|
|
496
|
+
return intersection(incoming, outgoing);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
class DeployedInstanceModel extends AbstractDeploymentElementModel {
|
|
500
|
+
constructor($model, $instance, element) {
|
|
501
|
+
super();
|
|
502
|
+
this.$model = $model;
|
|
503
|
+
this.$instance = $instance;
|
|
504
|
+
this.element = element;
|
|
505
|
+
this.id = $instance.id;
|
|
506
|
+
this._literalId = $instance.id;
|
|
507
|
+
this.title = $instance.title ?? element.title;
|
|
508
|
+
this.hierarchyLevel = hierarchyLevel($instance.id);
|
|
509
|
+
}
|
|
510
|
+
id;
|
|
511
|
+
_literalId;
|
|
512
|
+
title;
|
|
513
|
+
hierarchyLevel;
|
|
514
|
+
get $node() {
|
|
515
|
+
return this.$instance;
|
|
516
|
+
}
|
|
517
|
+
get parent() {
|
|
518
|
+
return nonNullable(this.$model.parent(this), `Parent of ${this.id} not found`);
|
|
519
|
+
}
|
|
520
|
+
get style() {
|
|
521
|
+
const { icon, style } = this.element.$element;
|
|
522
|
+
return {
|
|
523
|
+
shape: this.element.shape,
|
|
524
|
+
color: this.element.color,
|
|
525
|
+
size: DefaultShapeSize,
|
|
526
|
+
...icon && { icon },
|
|
527
|
+
...style,
|
|
528
|
+
...this.$instance.style
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
get shape() {
|
|
532
|
+
return this.$instance.style?.shape ?? this.element.shape;
|
|
533
|
+
}
|
|
534
|
+
get color() {
|
|
535
|
+
return this.$instance.style?.color ?? this.element.color;
|
|
536
|
+
}
|
|
537
|
+
get tags() {
|
|
538
|
+
return memoizeProp(this, Symbol.for("tags"), () => {
|
|
539
|
+
return i([
|
|
540
|
+
...this.$instance.tags ?? [],
|
|
541
|
+
...this.element.tags
|
|
542
|
+
]);
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
get kind() {
|
|
546
|
+
return this.element.kind;
|
|
547
|
+
}
|
|
548
|
+
get description() {
|
|
549
|
+
return this.$instance.description ?? this.element.description;
|
|
550
|
+
}
|
|
551
|
+
get technology() {
|
|
552
|
+
return this.$instance.technology ?? this.element.technology;
|
|
553
|
+
}
|
|
554
|
+
get links() {
|
|
555
|
+
return this.$instance.links ?? this.element.links;
|
|
556
|
+
}
|
|
557
|
+
isInstance() {
|
|
558
|
+
return true;
|
|
559
|
+
}
|
|
560
|
+
outgoingModelRelationships() {
|
|
561
|
+
return this.element.outgoing();
|
|
562
|
+
}
|
|
563
|
+
incomingModelRelationships() {
|
|
564
|
+
return this.element.incoming();
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Iterate over all views that include this instance.
|
|
568
|
+
* (Some views may include the parent deployment node instead of the instance.)
|
|
569
|
+
*/
|
|
570
|
+
*views() {
|
|
571
|
+
for (const view of this.$model.views()) {
|
|
572
|
+
if (view._type !== "deployment") {
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
if (view.includesDeployment(this.id)) {
|
|
576
|
+
yield view;
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
if (view.includesDeployment(this.parent.id) && this.parent.onlyOneInstance()) {
|
|
580
|
+
yield view;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
class NestedElementOfDeployedInstanceModel {
|
|
586
|
+
constructor(instance, element) {
|
|
587
|
+
this.instance = instance;
|
|
588
|
+
this.element = element;
|
|
589
|
+
}
|
|
590
|
+
get id() {
|
|
591
|
+
return this.instance.id;
|
|
592
|
+
}
|
|
593
|
+
get _literalId() {
|
|
594
|
+
return this.instance.id;
|
|
595
|
+
}
|
|
596
|
+
get style() {
|
|
597
|
+
const { icon, style } = this.element.$element;
|
|
598
|
+
return {
|
|
599
|
+
shape: this.element.shape,
|
|
600
|
+
color: this.element.color,
|
|
601
|
+
...icon && { icon },
|
|
602
|
+
...style
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
get shape() {
|
|
606
|
+
return this.element.shape;
|
|
607
|
+
}
|
|
608
|
+
get color() {
|
|
609
|
+
return this.element.color;
|
|
610
|
+
}
|
|
611
|
+
get title() {
|
|
612
|
+
return this.element.title;
|
|
613
|
+
}
|
|
614
|
+
get description() {
|
|
615
|
+
return this.element.description;
|
|
616
|
+
}
|
|
617
|
+
get technology() {
|
|
618
|
+
return this.element.technology;
|
|
619
|
+
}
|
|
620
|
+
isDeploymentNode() {
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
isInstance() {
|
|
624
|
+
return false;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
class DeploymentRelationModel {
|
|
628
|
+
constructor($model, $relationship) {
|
|
629
|
+
this.$model = $model;
|
|
630
|
+
this.$relationship = $relationship;
|
|
631
|
+
this.source = $model.deploymentRef($relationship.source);
|
|
632
|
+
this.target = $model.deploymentRef($relationship.target);
|
|
633
|
+
const parent = commonAncestor(this.source.id, this.target.id);
|
|
634
|
+
this.boundary = parent ? this.$model.node(parent) : null;
|
|
635
|
+
}
|
|
636
|
+
boundary;
|
|
637
|
+
source;
|
|
638
|
+
target;
|
|
639
|
+
get id() {
|
|
640
|
+
return this.$relationship.id;
|
|
641
|
+
}
|
|
642
|
+
get expression() {
|
|
643
|
+
return `${this.source.id} -> ${this.target.id}`;
|
|
644
|
+
}
|
|
645
|
+
get title() {
|
|
646
|
+
if (!n$1(this.$relationship.title)) {
|
|
647
|
+
return null;
|
|
648
|
+
}
|
|
649
|
+
return this.$relationship.title;
|
|
650
|
+
}
|
|
651
|
+
get technology() {
|
|
652
|
+
if (!n$1(this.$relationship.technology)) {
|
|
653
|
+
return null;
|
|
654
|
+
}
|
|
655
|
+
return this.$relationship.technology;
|
|
656
|
+
}
|
|
657
|
+
get description() {
|
|
658
|
+
if (!n$1(this.$relationship.description)) {
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
return this.$relationship.description;
|
|
662
|
+
}
|
|
663
|
+
get tags() {
|
|
664
|
+
return this.$relationship.tags ?? [];
|
|
665
|
+
}
|
|
666
|
+
get kind() {
|
|
667
|
+
return this.$relationship.kind ?? null;
|
|
668
|
+
}
|
|
669
|
+
get navigateTo() {
|
|
670
|
+
return this.$relationship.navigateTo ? this.$model.$model.view(this.$relationship.navigateTo) : null;
|
|
671
|
+
}
|
|
672
|
+
get links() {
|
|
673
|
+
return this.$relationship.links ?? [];
|
|
674
|
+
}
|
|
675
|
+
get color() {
|
|
676
|
+
return this.$relationship.color ?? DefaultThemeColor;
|
|
677
|
+
}
|
|
678
|
+
get line() {
|
|
679
|
+
return this.$relationship.line ?? DefaultLineStyle;
|
|
680
|
+
}
|
|
681
|
+
*views() {
|
|
682
|
+
for (const view of this.$model.views()) {
|
|
683
|
+
if (view.includesRelation(this.id)) {
|
|
684
|
+
yield view;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
isDeploymentRelation() {
|
|
690
|
+
return true;
|
|
691
|
+
}
|
|
692
|
+
isModelRelation() {
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
getMetadata(field) {
|
|
696
|
+
if (field) {
|
|
697
|
+
return this.$relationship.metadata?.[field];
|
|
698
|
+
}
|
|
699
|
+
return this.$relationship.metadata ?? {};
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Checks if the relationship has the given tag.
|
|
703
|
+
*/
|
|
704
|
+
isTagged(tag) {
|
|
705
|
+
return this.tags.includes(tag);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
class RelationshipsAccum {
|
|
709
|
+
/**
|
|
710
|
+
* @param model relationships from logical model
|
|
711
|
+
* @param deployment relationships from deployment model
|
|
712
|
+
*/
|
|
713
|
+
constructor(model = /* @__PURE__ */ new Set(), deployment = /* @__PURE__ */ new Set()) {
|
|
714
|
+
this.model = model;
|
|
715
|
+
this.deployment = deployment;
|
|
716
|
+
}
|
|
717
|
+
static empty() {
|
|
718
|
+
return new RelationshipsAccum();
|
|
719
|
+
}
|
|
720
|
+
static from(model, deployment) {
|
|
721
|
+
return new RelationshipsAccum(
|
|
722
|
+
new Set(model),
|
|
723
|
+
new Set(deployment)
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
get isEmpty() {
|
|
727
|
+
return this.model.size === 0 && this.deployment.size === 0;
|
|
728
|
+
}
|
|
729
|
+
get nonEmpty() {
|
|
730
|
+
return this.model.size > 0 || this.deployment.size > 0;
|
|
731
|
+
}
|
|
732
|
+
get size() {
|
|
733
|
+
return this.model.size + this.deployment.size;
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Returns new Accum containing all the elements which are both in this and otherAccum
|
|
737
|
+
*/
|
|
738
|
+
intersect(otherAccum) {
|
|
739
|
+
return RelationshipsAccum.from(
|
|
740
|
+
intersection(this.model, otherAccum.model),
|
|
741
|
+
intersection(this.deployment, otherAccum.deployment)
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Returns new Accum containing all the elements which are both in this and otherAccum
|
|
746
|
+
*/
|
|
747
|
+
difference(otherAccum) {
|
|
748
|
+
return RelationshipsAccum.from(
|
|
749
|
+
difference(this.model, otherAccum.model),
|
|
750
|
+
difference(this.deployment, otherAccum.deployment)
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Returns new Accum containing all the elements from both
|
|
755
|
+
*/
|
|
756
|
+
union(otherAccum) {
|
|
757
|
+
return RelationshipsAccum.from(
|
|
758
|
+
union(this.model, otherAccum.model),
|
|
759
|
+
union(this.deployment, otherAccum.deployment)
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function getId(element) {
|
|
765
|
+
return typeof element === "string" ? element : element.id;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
class LikeC4DeploymentModel {
|
|
769
|
+
constructor($model) {
|
|
770
|
+
this.$model = $model;
|
|
771
|
+
const $deployments = this.$deployments = $model.$data.deployments;
|
|
772
|
+
const elements = r($deployments.elements);
|
|
773
|
+
for (const element of sortParentsFirst(elements)) {
|
|
774
|
+
const el = this.addElement(element);
|
|
775
|
+
for (const tag of el.tags) {
|
|
776
|
+
this.#allTags.get(tag).add(el);
|
|
777
|
+
}
|
|
778
|
+
if (el.isInstance()) {
|
|
779
|
+
this.#instancesOf.get(el.element.id).add(el);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
for (const relation of r($deployments.relations)) {
|
|
783
|
+
const el = this.addRelation(relation);
|
|
784
|
+
for (const tag of el.tags) {
|
|
785
|
+
this.#allTags.get(tag).add(el);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
#elements = /* @__PURE__ */ new Map();
|
|
790
|
+
// Parent element for given FQN
|
|
791
|
+
#parents = /* @__PURE__ */ new Map();
|
|
792
|
+
// Children elements for given FQN
|
|
793
|
+
#children = new DefaultMap(() => /* @__PURE__ */ new Set());
|
|
794
|
+
// Keep track of instances of the logical element
|
|
795
|
+
#instancesOf = new DefaultMap(() => /* @__PURE__ */ new Set());
|
|
796
|
+
#rootElements = /* @__PURE__ */ new Set();
|
|
797
|
+
#relations = /* @__PURE__ */ new Map();
|
|
798
|
+
// Incoming to an element or its descendants
|
|
799
|
+
#incoming = new DefaultMap(() => /* @__PURE__ */ new Set());
|
|
800
|
+
// Outgoing from an element or its descendants
|
|
801
|
+
#outgoing = new DefaultMap(() => /* @__PURE__ */ new Set());
|
|
802
|
+
// Relationships inside the element, among descendants
|
|
803
|
+
#internal = new DefaultMap(() => /* @__PURE__ */ new Set());
|
|
804
|
+
// readonly #views = new Map<ViewID, LikeC4ViewModel<A>>()
|
|
805
|
+
#allTags = new DefaultMap(
|
|
806
|
+
() => /* @__PURE__ */ new Set()
|
|
807
|
+
);
|
|
808
|
+
#nestedElementsOfDeployment = /* @__PURE__ */ new Map();
|
|
809
|
+
$deployments;
|
|
810
|
+
element(el) {
|
|
811
|
+
if (el instanceof DeploymentNodeModel || el instanceof DeployedInstanceModel) {
|
|
812
|
+
return el;
|
|
813
|
+
}
|
|
814
|
+
const id = getId(el);
|
|
815
|
+
return nonNullable(this.#elements.get(id), `Element ${id} not found`);
|
|
816
|
+
}
|
|
817
|
+
findElement(el) {
|
|
818
|
+
return this.#elements.get(el) ?? null;
|
|
819
|
+
}
|
|
820
|
+
node(el) {
|
|
821
|
+
const element = this.element(el);
|
|
822
|
+
invariant(element.isDeploymentNode(), `Element ${element.id} is not a deployment node`);
|
|
823
|
+
return element;
|
|
824
|
+
}
|
|
825
|
+
findNode(el) {
|
|
826
|
+
const element = this.findElement(el);
|
|
827
|
+
if (!element) {
|
|
828
|
+
return null;
|
|
829
|
+
}
|
|
830
|
+
invariant(element.isDeploymentNode(), `Element ${element?.id} is not a deployment node`);
|
|
831
|
+
return element;
|
|
832
|
+
}
|
|
833
|
+
instance(el) {
|
|
834
|
+
const element = this.element(el);
|
|
835
|
+
invariant(element.isInstance(), `Element ${element.id} is not a deployed instance`);
|
|
836
|
+
return element;
|
|
837
|
+
}
|
|
838
|
+
findInstance(el) {
|
|
839
|
+
const element = this.findElement(el);
|
|
840
|
+
if (!element) {
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
invariant(element.isInstance(), `Element ${element?.id} is not a deployed instance`);
|
|
844
|
+
return element;
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Returns the root elements of the model.
|
|
848
|
+
*/
|
|
849
|
+
roots() {
|
|
850
|
+
return this.#rootElements.values();
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Returns all elements in the model.
|
|
854
|
+
*/
|
|
855
|
+
elements() {
|
|
856
|
+
return this.#elements.values();
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Returns all elements in the model.
|
|
860
|
+
*/
|
|
861
|
+
*nodes() {
|
|
862
|
+
for (const element of this.#elements.values()) {
|
|
863
|
+
if (element.isDeploymentNode()) {
|
|
864
|
+
yield element;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
*nodesOfKind(kind) {
|
|
870
|
+
for (const node of this.#elements.values()) {
|
|
871
|
+
if (node.isDeploymentNode() && node.kind === kind) {
|
|
872
|
+
yield node;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
*instances() {
|
|
878
|
+
for (const element of this.#elements.values()) {
|
|
879
|
+
if (element.isInstance()) {
|
|
880
|
+
yield element;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Iterate over all instances of the given logical element.
|
|
887
|
+
*/
|
|
888
|
+
*instancesOf(element) {
|
|
889
|
+
const id = getId(element);
|
|
890
|
+
const instances = this.#instancesOf.get(id);
|
|
891
|
+
if (instances) {
|
|
892
|
+
yield* instances;
|
|
893
|
+
}
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
deploymentRef(ref) {
|
|
897
|
+
if (FqnRef.isInsideInstanceRef(ref)) {
|
|
898
|
+
const { deployment, element } = ref;
|
|
899
|
+
return getOrCreate(this.#nestedElementsOfDeployment, `${deployment}@${element}`, () => {
|
|
900
|
+
return new NestedElementOfDeployedInstanceModel(this.instance(deployment), this.$model.element(element));
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
return this.element(ref.deployment);
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Returns all relationships in the model.
|
|
907
|
+
*/
|
|
908
|
+
relationships() {
|
|
909
|
+
return this.#relations.values();
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Returns a specific relationship by its ID.
|
|
913
|
+
*/
|
|
914
|
+
relationship(id) {
|
|
915
|
+
return nonNullable(this.#relations.get(getId(id)), `DeploymentRelationModel ${id} not found`);
|
|
916
|
+
}
|
|
917
|
+
findRelationship(id) {
|
|
918
|
+
return this.#relations.get(id) ?? null;
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Returns all deployment views in the model.
|
|
922
|
+
*/
|
|
923
|
+
*views() {
|
|
924
|
+
for (const view of this.$model.views()) {
|
|
925
|
+
if (view.isDeploymentView()) {
|
|
926
|
+
yield view;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Returns the parent element of given element.
|
|
933
|
+
* @see ancestors
|
|
934
|
+
*/
|
|
935
|
+
parent(element) {
|
|
936
|
+
const id = getId(element);
|
|
937
|
+
return this.#parents.get(id) || null;
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Get all children of the element (only direct children),
|
|
941
|
+
* @see descendants
|
|
942
|
+
*/
|
|
943
|
+
children(element) {
|
|
944
|
+
const id = getId(element);
|
|
945
|
+
return this.#children.get(id);
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Get all sibling (i.e. same parent)
|
|
949
|
+
*/
|
|
950
|
+
*siblings(element) {
|
|
951
|
+
const id = getId(element);
|
|
952
|
+
const siblings = this.parent(element)?.children() ?? this.roots();
|
|
953
|
+
for (const sibling of siblings) {
|
|
954
|
+
if (sibling.id !== id) {
|
|
955
|
+
yield sibling;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Get all ancestor elements (i.e. parent, parent’s parent, etc.)
|
|
962
|
+
* (from closest to root)
|
|
963
|
+
*/
|
|
964
|
+
*ancestors(element) {
|
|
965
|
+
let id = getId(element);
|
|
966
|
+
let parent;
|
|
967
|
+
while (parent = this.#parents.get(id)) {
|
|
968
|
+
yield parent;
|
|
969
|
+
id = parent.id;
|
|
970
|
+
}
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Get all descendant elements (i.e. children, children’s children, etc.)
|
|
975
|
+
*/
|
|
976
|
+
*descendants(element, sort = "desc") {
|
|
977
|
+
for (const child of this.children(element)) {
|
|
978
|
+
if (sort === "asc") {
|
|
979
|
+
yield child;
|
|
980
|
+
yield* this.descendants(child.id);
|
|
981
|
+
} else {
|
|
982
|
+
yield* this.descendants(child.id);
|
|
983
|
+
yield child;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Incoming relationships to the element and its descendants
|
|
990
|
+
* @see incomers
|
|
991
|
+
*/
|
|
992
|
+
*incoming(element, filter = "all") {
|
|
993
|
+
const id = getId(element);
|
|
994
|
+
for (const rel of this.#incoming.get(id)) {
|
|
995
|
+
switch (true) {
|
|
996
|
+
case filter === "all":
|
|
997
|
+
case (filter === "direct" && rel.target.id === id):
|
|
998
|
+
case (filter === "to-descendants" && rel.target.id !== id):
|
|
999
|
+
yield rel;
|
|
1000
|
+
break;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Outgoing relationships from the element and its descendants
|
|
1007
|
+
* @see outgoers
|
|
1008
|
+
*/
|
|
1009
|
+
*outgoing(element, filter = "all") {
|
|
1010
|
+
const id = getId(element);
|
|
1011
|
+
for (const rel of this.#outgoing.get(id)) {
|
|
1012
|
+
switch (true) {
|
|
1013
|
+
case filter === "all":
|
|
1014
|
+
case (filter === "direct" && rel.source.id === id):
|
|
1015
|
+
case (filter === "from-descendants" && rel.source.id !== id):
|
|
1016
|
+
yield rel;
|
|
1017
|
+
break;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
addElement(element) {
|
|
1023
|
+
if (this.#elements.has(element.id)) {
|
|
1024
|
+
throw new Error(`Element ${element.id} already exists`);
|
|
1025
|
+
}
|
|
1026
|
+
const el = isDeploymentNode(element) ? new DeploymentNodeModel(this, Object.freeze(element)) : new DeployedInstanceModel(this, Object.freeze(element), this.$model.element(element.element));
|
|
1027
|
+
this.#elements.set(el.id, el);
|
|
1028
|
+
const parentId = parentFqn(el.id);
|
|
1029
|
+
if (parentId) {
|
|
1030
|
+
invariant(this.#elements.has(parentId), `Parent ${parentId} of ${el.id} not found`);
|
|
1031
|
+
this.#parents.set(el.id, this.node(parentId));
|
|
1032
|
+
this.#children.get(parentId).add(el);
|
|
1033
|
+
} else {
|
|
1034
|
+
invariant(el.isDeploymentNode(), `Root element ${el.id} is not a deployment node`);
|
|
1035
|
+
this.#rootElements.add(el);
|
|
1036
|
+
}
|
|
1037
|
+
return el;
|
|
1038
|
+
}
|
|
1039
|
+
addRelation(relation) {
|
|
1040
|
+
if (this.#relations.has(relation.id)) {
|
|
1041
|
+
throw new Error(`Relation ${relation.id} already exists`);
|
|
1042
|
+
}
|
|
1043
|
+
const rel = new DeploymentRelationModel(
|
|
1044
|
+
this,
|
|
1045
|
+
Object.freeze(relation)
|
|
1046
|
+
);
|
|
1047
|
+
this.#relations.set(rel.id, rel);
|
|
1048
|
+
this.#incoming.get(rel.target.id).add(rel);
|
|
1049
|
+
this.#outgoing.get(rel.source.id).add(rel);
|
|
1050
|
+
const relParent = rel.boundary?.id ?? null;
|
|
1051
|
+
if (relParent) {
|
|
1052
|
+
for (const ancestor of [relParent, ...ancestorsFqn(relParent)]) {
|
|
1053
|
+
this.#internal.get(ancestor).add(rel);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
for (const sourceAncestor of ancestorsFqn(rel.source.id)) {
|
|
1057
|
+
if (sourceAncestor === relParent) {
|
|
1058
|
+
break;
|
|
1059
|
+
}
|
|
1060
|
+
this.#outgoing.get(sourceAncestor).add(rel);
|
|
1061
|
+
}
|
|
1062
|
+
for (const targetAncestor of ancestorsFqn(rel.target.id)) {
|
|
1063
|
+
if (targetAncestor === relParent) {
|
|
1064
|
+
break;
|
|
1065
|
+
}
|
|
1066
|
+
this.#incoming.get(targetAncestor).add(rel);
|
|
1067
|
+
}
|
|
1068
|
+
return rel;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
class RelationshipModel {
|
|
1073
|
+
constructor(model, $relationship) {
|
|
1074
|
+
this.model = model;
|
|
1075
|
+
this.$relationship = $relationship;
|
|
1076
|
+
this.source = model.element(FqnRef.flatten($relationship.source));
|
|
1077
|
+
this.target = model.element(FqnRef.flatten($relationship.target));
|
|
1078
|
+
const parent = commonAncestor(this.source.id, this.target.id);
|
|
1079
|
+
this.boundary = parent ? this.model.element(parent) : null;
|
|
1080
|
+
}
|
|
1081
|
+
source;
|
|
1082
|
+
target;
|
|
1083
|
+
/**
|
|
1084
|
+
* Common ancestor of the source and target elements.
|
|
1085
|
+
* Represents the boundary of the Relation.
|
|
1086
|
+
*/
|
|
1087
|
+
boundary;
|
|
1088
|
+
get id() {
|
|
1089
|
+
return this.$relationship.id;
|
|
1090
|
+
}
|
|
1091
|
+
get expression() {
|
|
1092
|
+
return `${this.source.id} -> ${this.target.id}`;
|
|
1093
|
+
}
|
|
1094
|
+
get title() {
|
|
1095
|
+
if (!n$1(this.$relationship.title)) {
|
|
1096
|
+
return null;
|
|
1097
|
+
}
|
|
1098
|
+
return this.$relationship.title;
|
|
1099
|
+
}
|
|
1100
|
+
get technology() {
|
|
1101
|
+
if (!n$1(this.$relationship.technology)) {
|
|
1102
|
+
return null;
|
|
1103
|
+
}
|
|
1104
|
+
return this.$relationship.technology;
|
|
1105
|
+
}
|
|
1106
|
+
get description() {
|
|
1107
|
+
if (!n$1(this.$relationship.description)) {
|
|
1108
|
+
return null;
|
|
1109
|
+
}
|
|
1110
|
+
return this.$relationship.description;
|
|
1111
|
+
}
|
|
1112
|
+
get navigateTo() {
|
|
1113
|
+
return this.$relationship.navigateTo ? this.model.view(this.$relationship.navigateTo) : null;
|
|
1114
|
+
}
|
|
1115
|
+
get tags() {
|
|
1116
|
+
return this.$relationship.tags ?? [];
|
|
1117
|
+
}
|
|
1118
|
+
get kind() {
|
|
1119
|
+
return this.$relationship.kind ?? null;
|
|
1120
|
+
}
|
|
1121
|
+
get links() {
|
|
1122
|
+
return this.$relationship.links ?? [];
|
|
1123
|
+
}
|
|
1124
|
+
get color() {
|
|
1125
|
+
return this.$relationship.color ?? DefaultRelationshipColor;
|
|
1126
|
+
}
|
|
1127
|
+
get line() {
|
|
1128
|
+
return this.$relationship.line ?? DefaultLineStyle;
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Iterate over all views that include this relationship.
|
|
1132
|
+
*/
|
|
1133
|
+
*views() {
|
|
1134
|
+
for (const view of this.model.views()) {
|
|
1135
|
+
if (view.includesRelation(this.id)) {
|
|
1136
|
+
yield view;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
isDeploymentRelation() {
|
|
1142
|
+
return false;
|
|
1143
|
+
}
|
|
1144
|
+
isModelRelation() {
|
|
1145
|
+
return true;
|
|
1146
|
+
}
|
|
1147
|
+
getMetadata(field) {
|
|
1148
|
+
if (field) {
|
|
1149
|
+
return this.$relationship.metadata?.[field];
|
|
1150
|
+
}
|
|
1151
|
+
return this.$relationship.metadata ?? {};
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* Checks if the relationship has the given tag.
|
|
1155
|
+
*/
|
|
1156
|
+
isTagged(tag) {
|
|
1157
|
+
return this.tags.includes(tag);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
class EdgeModel {
|
|
1162
|
+
constructor(view, $edge, source, target) {
|
|
1163
|
+
this.view = view;
|
|
1164
|
+
this.$edge = $edge;
|
|
1165
|
+
this.source = source;
|
|
1166
|
+
this.target = target;
|
|
1167
|
+
this.#edge = $edge;
|
|
1168
|
+
}
|
|
1169
|
+
#edge;
|
|
1170
|
+
get id() {
|
|
1171
|
+
return this.#edge.id;
|
|
1172
|
+
}
|
|
1173
|
+
get parent() {
|
|
1174
|
+
return this.#edge.parent ? this.view.node(this.#edge.parent) : null;
|
|
1175
|
+
}
|
|
1176
|
+
get label() {
|
|
1177
|
+
return this.#edge.label;
|
|
1178
|
+
}
|
|
1179
|
+
get description() {
|
|
1180
|
+
return this.#edge.description ?? null;
|
|
1181
|
+
}
|
|
1182
|
+
get technology() {
|
|
1183
|
+
return this.#edge.technology ?? null;
|
|
1184
|
+
}
|
|
1185
|
+
hasParent() {
|
|
1186
|
+
return this.#edge.parent !== null;
|
|
1187
|
+
}
|
|
1188
|
+
get tags() {
|
|
1189
|
+
return this.#edge.tags ?? [];
|
|
1190
|
+
}
|
|
1191
|
+
get stepNumber() {
|
|
1192
|
+
return this.isStep() ? extractStep(this.id) : null;
|
|
1193
|
+
}
|
|
1194
|
+
get navigateTo() {
|
|
1195
|
+
return this.#edge.navigateTo ? this.view.$model.view(this.#edge.navigateTo) : null;
|
|
1196
|
+
}
|
|
1197
|
+
get color() {
|
|
1198
|
+
return this.#edge.color ?? "gray";
|
|
1199
|
+
}
|
|
1200
|
+
get line() {
|
|
1201
|
+
return this.#edge.line ?? "dashed";
|
|
1202
|
+
}
|
|
1203
|
+
isStep() {
|
|
1204
|
+
return isStepEdgeId(this.id);
|
|
1205
|
+
}
|
|
1206
|
+
*relationships(type) {
|
|
1207
|
+
for (const id of this.#edge.relations) {
|
|
1208
|
+
if (type) {
|
|
1209
|
+
const rel = this.view.$model.findRelationship(id, type);
|
|
1210
|
+
if (rel) {
|
|
1211
|
+
yield rel;
|
|
1212
|
+
}
|
|
1213
|
+
} else {
|
|
1214
|
+
yield this.view.$model.relationship(id);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
includesRelation(rel) {
|
|
1220
|
+
const id = typeof rel === "string" ? rel : rel.id;
|
|
1221
|
+
return this.#edge.relations.includes(id);
|
|
1222
|
+
}
|
|
1223
|
+
isTagged(tag) {
|
|
1224
|
+
return this.tags.includes(tag);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
class NodeModel {
|
|
1229
|
+
constructor($view, $node) {
|
|
1230
|
+
this.$view = $view;
|
|
1231
|
+
this.$node = $node;
|
|
1232
|
+
this.#node = $node;
|
|
1233
|
+
}
|
|
1234
|
+
#node;
|
|
1235
|
+
get id() {
|
|
1236
|
+
return this.#node.id;
|
|
1237
|
+
}
|
|
1238
|
+
get title() {
|
|
1239
|
+
return this.#node.title;
|
|
1240
|
+
}
|
|
1241
|
+
get kind() {
|
|
1242
|
+
return this.#node.kind;
|
|
1243
|
+
}
|
|
1244
|
+
get description() {
|
|
1245
|
+
return this.#node.description ?? null;
|
|
1246
|
+
}
|
|
1247
|
+
get technology() {
|
|
1248
|
+
return this.#node.technology ?? null;
|
|
1249
|
+
}
|
|
1250
|
+
get parent() {
|
|
1251
|
+
return this.#node.parent ? this.$view.node(this.#node.parent) : null;
|
|
1252
|
+
}
|
|
1253
|
+
get element() {
|
|
1254
|
+
const modelRef = this.#node.modelRef;
|
|
1255
|
+
return modelRef ? this.$view.$model.element(modelRef) : null;
|
|
1256
|
+
}
|
|
1257
|
+
get deployment() {
|
|
1258
|
+
const modelRef = this.#node.deploymentRef;
|
|
1259
|
+
return modelRef ? this.$view.$model.deployment.element(modelRef) : null;
|
|
1260
|
+
}
|
|
1261
|
+
get shape() {
|
|
1262
|
+
return this.#node.shape;
|
|
1263
|
+
}
|
|
1264
|
+
get color() {
|
|
1265
|
+
return this.#node.color;
|
|
1266
|
+
}
|
|
1267
|
+
get icon() {
|
|
1268
|
+
return this.#node.icon ?? null;
|
|
1269
|
+
}
|
|
1270
|
+
get tags() {
|
|
1271
|
+
return this.#node.tags;
|
|
1272
|
+
}
|
|
1273
|
+
get links() {
|
|
1274
|
+
return this.#node.links ?? [];
|
|
1275
|
+
}
|
|
1276
|
+
get navigateTo() {
|
|
1277
|
+
return this.#node.navigateTo ? this.$view.$model.view(this.#node.navigateTo) : null;
|
|
1278
|
+
}
|
|
1279
|
+
get style() {
|
|
1280
|
+
return this.#node.style;
|
|
1281
|
+
}
|
|
1282
|
+
children() {
|
|
1283
|
+
return memoizeProp(this, "children", () => new Set(this.#node.children.map((child) => this.$view.node(child))));
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Get all ancestor elements (i.e. parent, parent’s parent, etc.)
|
|
1287
|
+
* (from closest to root)
|
|
1288
|
+
*/
|
|
1289
|
+
*ancestors() {
|
|
1290
|
+
let parent = this.parent;
|
|
1291
|
+
while (parent) {
|
|
1292
|
+
yield parent;
|
|
1293
|
+
parent = parent.parent;
|
|
1294
|
+
}
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
*siblings() {
|
|
1298
|
+
const siblings = this.parent?.children() ?? this.$view.roots();
|
|
1299
|
+
for (const sibling of siblings) {
|
|
1300
|
+
if (sibling.id !== this.id) {
|
|
1301
|
+
yield sibling;
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
*incoming(filter = "all") {
|
|
1307
|
+
for (const edgeId of this.#node.inEdges) {
|
|
1308
|
+
const edge = this.$view.edge(edgeId);
|
|
1309
|
+
switch (true) {
|
|
1310
|
+
case filter === "all":
|
|
1311
|
+
case (filter === "direct" && edge.target.id === this.id):
|
|
1312
|
+
case (filter === "to-descendants" && edge.target.id !== this.id):
|
|
1313
|
+
yield edge;
|
|
1314
|
+
break;
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
*incomers(filter = "all") {
|
|
1320
|
+
const unique = /* @__PURE__ */ new Set();
|
|
1321
|
+
for (const r of this.incoming(filter)) {
|
|
1322
|
+
if (unique.has(r.source.id)) {
|
|
1323
|
+
continue;
|
|
1324
|
+
}
|
|
1325
|
+
unique.add(r.source.id);
|
|
1326
|
+
yield r.source;
|
|
1327
|
+
}
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
*outgoing(filter = "all") {
|
|
1331
|
+
for (const edgeId of this.#node.outEdges) {
|
|
1332
|
+
const edge = this.$view.edge(edgeId);
|
|
1333
|
+
switch (true) {
|
|
1334
|
+
case filter === "all":
|
|
1335
|
+
case (filter === "direct" && edge.source.id === this.id):
|
|
1336
|
+
case (filter === "from-descendants" && edge.source.id !== this.id):
|
|
1337
|
+
yield edge;
|
|
1338
|
+
break;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
*outgoers(filter = "all") {
|
|
1344
|
+
const unique = /* @__PURE__ */ new Set();
|
|
1345
|
+
for (const r of this.outgoing(filter)) {
|
|
1346
|
+
if (unique.has(r.target.id)) {
|
|
1347
|
+
continue;
|
|
1348
|
+
}
|
|
1349
|
+
unique.add(r.target.id);
|
|
1350
|
+
yield r.target;
|
|
1351
|
+
}
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
isDiagramNode() {
|
|
1355
|
+
return "width" in this.#node && "height" in this.#node;
|
|
1356
|
+
}
|
|
1357
|
+
hasChildren() {
|
|
1358
|
+
return this.#node.children.length > 0;
|
|
1359
|
+
}
|
|
1360
|
+
hasParent() {
|
|
1361
|
+
return this.#node.parent !== null;
|
|
1362
|
+
}
|
|
1363
|
+
/**
|
|
1364
|
+
* Check if this node references to logical model element.
|
|
1365
|
+
*/
|
|
1366
|
+
hasElement() {
|
|
1367
|
+
return n$1(this.#node.modelRef);
|
|
1368
|
+
}
|
|
1369
|
+
/**
|
|
1370
|
+
* Check if this node references to deployment element (Node or Instance).
|
|
1371
|
+
*/
|
|
1372
|
+
hasDeployment() {
|
|
1373
|
+
return n$1(this.#node.deploymentRef);
|
|
1374
|
+
}
|
|
1375
|
+
/**
|
|
1376
|
+
* Check if this node references to deployed instance
|
|
1377
|
+
* Deployed instance always references to element and deployment element.
|
|
1378
|
+
*/
|
|
1379
|
+
hasDeployedInstance() {
|
|
1380
|
+
return this.hasElement() && this.hasDeployment();
|
|
1381
|
+
}
|
|
1382
|
+
isGroup() {
|
|
1383
|
+
return isGroupElementKind(this.#node);
|
|
1384
|
+
}
|
|
1385
|
+
/**
|
|
1386
|
+
* Checks if the node has the given tag.
|
|
1387
|
+
*/
|
|
1388
|
+
isTagged(tag) {
|
|
1389
|
+
return this.tags.includes(tag);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
class LikeC4ViewModel {
|
|
1394
|
+
/**
|
|
1395
|
+
* Don't use in runtime, only for type inference
|
|
1396
|
+
*/
|
|
1397
|
+
Aux;
|
|
1398
|
+
#rootnodes = /* @__PURE__ */ new Set();
|
|
1399
|
+
#nodes = /* @__PURE__ */ new Map();
|
|
1400
|
+
#edges = /* @__PURE__ */ new Map();
|
|
1401
|
+
#includeElements = /* @__PURE__ */ new Set();
|
|
1402
|
+
#includeDeployments = /* @__PURE__ */ new Set();
|
|
1403
|
+
#includeRelations = /* @__PURE__ */ new Set();
|
|
1404
|
+
#allTags = new DefaultMap((_key) => /* @__PURE__ */ new Set());
|
|
1405
|
+
$view;
|
|
1406
|
+
$model;
|
|
1407
|
+
constructor(model, view) {
|
|
1408
|
+
this.$model = model;
|
|
1409
|
+
this.$view = view;
|
|
1410
|
+
for (const node of view.nodes) {
|
|
1411
|
+
const el = new NodeModel(this, Object.freeze(node));
|
|
1412
|
+
this.#nodes.set(node.id, el);
|
|
1413
|
+
if (!node.parent) {
|
|
1414
|
+
this.#rootnodes.add(el);
|
|
1415
|
+
}
|
|
1416
|
+
if (el.hasDeployment()) {
|
|
1417
|
+
this.#includeDeployments.add(el.deployment.id);
|
|
1418
|
+
}
|
|
1419
|
+
if (el.hasElement()) {
|
|
1420
|
+
this.#includeElements.add(el.element.id);
|
|
1421
|
+
}
|
|
1422
|
+
for (const tag of el.tags) {
|
|
1423
|
+
this.#allTags.get(tag).add(el);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
for (const edge of view.edges) {
|
|
1427
|
+
const edgeModel = new EdgeModel(
|
|
1428
|
+
this,
|
|
1429
|
+
Object.freeze(edge),
|
|
1430
|
+
this.node(edge.source),
|
|
1431
|
+
this.node(edge.target)
|
|
1432
|
+
);
|
|
1433
|
+
for (const tag of edgeModel.tags) {
|
|
1434
|
+
this.#allTags.get(tag).add(edgeModel);
|
|
1435
|
+
}
|
|
1436
|
+
for (const rel of edge.relations) {
|
|
1437
|
+
this.#includeRelations.add(rel);
|
|
1438
|
+
}
|
|
1439
|
+
this.#edges.set(edge.id, edgeModel);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
get _type() {
|
|
1443
|
+
return this.$view[_type];
|
|
1444
|
+
}
|
|
1445
|
+
get id() {
|
|
1446
|
+
return this.$view.id;
|
|
1447
|
+
}
|
|
1448
|
+
get title() {
|
|
1449
|
+
return this.$view.title;
|
|
1450
|
+
}
|
|
1451
|
+
get tags() {
|
|
1452
|
+
return this.$view.tags ?? [];
|
|
1453
|
+
}
|
|
1454
|
+
get links() {
|
|
1455
|
+
return this.$view.links ?? [];
|
|
1456
|
+
}
|
|
1457
|
+
get viewOf() {
|
|
1458
|
+
if (this.isElementView()) {
|
|
1459
|
+
const viewOf = this.$view.viewOf;
|
|
1460
|
+
return viewOf ? this.$model.element(viewOf) : null;
|
|
1461
|
+
}
|
|
1462
|
+
return null;
|
|
1463
|
+
}
|
|
1464
|
+
/**
|
|
1465
|
+
* All tags from nodes and edges.
|
|
1466
|
+
*/
|
|
1467
|
+
get includedTags() {
|
|
1468
|
+
return [...this.#allTags.keys()];
|
|
1469
|
+
}
|
|
1470
|
+
roots() {
|
|
1471
|
+
return this.#rootnodes.values();
|
|
1472
|
+
}
|
|
1473
|
+
/**
|
|
1474
|
+
* Iterate over all nodes that have children.
|
|
1475
|
+
*/
|
|
1476
|
+
*compounds() {
|
|
1477
|
+
for (const node of this.#nodes.values()) {
|
|
1478
|
+
if (node.hasChildren()) {
|
|
1479
|
+
yield node;
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
/**
|
|
1485
|
+
* Get node by id.
|
|
1486
|
+
* @throws Error if node is not found.
|
|
1487
|
+
*/
|
|
1488
|
+
node(node) {
|
|
1489
|
+
const nodeId = getId(node);
|
|
1490
|
+
return nonNullable(this.#nodes.get(nodeId), `Node ${nodeId} not found in view ${this.$view.id}`);
|
|
1491
|
+
}
|
|
1492
|
+
/**
|
|
1493
|
+
* Find node by id.
|
|
1494
|
+
*/
|
|
1495
|
+
findNode(node) {
|
|
1496
|
+
return this.#nodes.get(getId(node)) ?? null;
|
|
1497
|
+
}
|
|
1498
|
+
findNodeWithElement(element) {
|
|
1499
|
+
const id = getId(element);
|
|
1500
|
+
return ifind(
|
|
1501
|
+
this.#nodes.values(),
|
|
1502
|
+
(node) => node.hasElement() && node.element.id === id
|
|
1503
|
+
) ?? null;
|
|
1504
|
+
}
|
|
1505
|
+
/**
|
|
1506
|
+
* Iterate over all nodes.
|
|
1507
|
+
*/
|
|
1508
|
+
nodes() {
|
|
1509
|
+
return this.#nodes.values();
|
|
1510
|
+
}
|
|
1511
|
+
/**
|
|
1512
|
+
* Find edge by id.
|
|
1513
|
+
* @param edge Edge or id
|
|
1514
|
+
* @returns EdgeModel
|
|
1515
|
+
*/
|
|
1516
|
+
edge(edge) {
|
|
1517
|
+
const edgeId = getId(edge);
|
|
1518
|
+
return nonNullable(this.#edges.get(edgeId), `Edge ${edgeId} not found in view ${this.$view.id}`);
|
|
1519
|
+
}
|
|
1520
|
+
findEdge(edge) {
|
|
1521
|
+
return this.#edges.get(getId(edge)) ?? null;
|
|
1522
|
+
}
|
|
1523
|
+
/**
|
|
1524
|
+
* Iterate over all edges.
|
|
1525
|
+
*/
|
|
1526
|
+
edges() {
|
|
1527
|
+
return this.#edges.values();
|
|
1528
|
+
}
|
|
1529
|
+
/**
|
|
1530
|
+
* Iterate over all edges.
|
|
1531
|
+
*/
|
|
1532
|
+
*edgesWithRelation(relation) {
|
|
1533
|
+
for (const edge of this.#edges.values()) {
|
|
1534
|
+
if (edge.includesRelation(relation)) {
|
|
1535
|
+
yield edge;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
/**
|
|
1541
|
+
* Nodes that have references to elements from logical model.
|
|
1542
|
+
*/
|
|
1543
|
+
*elements() {
|
|
1544
|
+
for (const node of this.#nodes.values()) {
|
|
1545
|
+
if (node.hasElement()) {
|
|
1546
|
+
yield node;
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
/**
|
|
1552
|
+
* Checks if the view has the given tag.
|
|
1553
|
+
*/
|
|
1554
|
+
isTagged(tag) {
|
|
1555
|
+
return this.tags.includes(tag);
|
|
1556
|
+
}
|
|
1557
|
+
includesElement(element) {
|
|
1558
|
+
return this.#includeElements.has(getId(element));
|
|
1559
|
+
}
|
|
1560
|
+
includesDeployment(deployment) {
|
|
1561
|
+
return this.#includeDeployments.has(getId(deployment));
|
|
1562
|
+
}
|
|
1563
|
+
includesRelation(relation) {
|
|
1564
|
+
return this.#includeRelations.has(getId(relation));
|
|
1565
|
+
}
|
|
1566
|
+
/**
|
|
1567
|
+
* Below are type guards.
|
|
1568
|
+
*/
|
|
1569
|
+
isComputed() {
|
|
1570
|
+
return this.$view[_stage] === "computed";
|
|
1571
|
+
}
|
|
1572
|
+
isDiagram() {
|
|
1573
|
+
return this.$view[_stage] === "layouted";
|
|
1574
|
+
}
|
|
1575
|
+
isElementView() {
|
|
1576
|
+
return this.$view[_type] === "element";
|
|
1577
|
+
}
|
|
1578
|
+
isScopedElementView() {
|
|
1579
|
+
return this.$view[_type] === "element" && n$1(this.$view.viewOf);
|
|
1580
|
+
}
|
|
1581
|
+
isDeploymentView() {
|
|
1582
|
+
return this.$view[_type] === "deployment";
|
|
1583
|
+
}
|
|
1584
|
+
isDynamicView() {
|
|
1585
|
+
return this.$view[_type] === "dynamic";
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
class LikeC4Model {
|
|
1590
|
+
/**
|
|
1591
|
+
* Don't use in runtime, only for type inference
|
|
1592
|
+
*/
|
|
1593
|
+
Aux;
|
|
1594
|
+
_elements = /* @__PURE__ */ new Map();
|
|
1595
|
+
// Parent element for given FQN
|
|
1596
|
+
_parents = /* @__PURE__ */ new Map();
|
|
1597
|
+
// Children elements for given FQN
|
|
1598
|
+
_children = new DefaultMap(() => /* @__PURE__ */ new Set());
|
|
1599
|
+
_rootElements = /* @__PURE__ */ new Set();
|
|
1600
|
+
_relations = /* @__PURE__ */ new Map();
|
|
1601
|
+
// Incoming to an element or its descendants
|
|
1602
|
+
_incoming = new DefaultMap(() => /* @__PURE__ */ new Set());
|
|
1603
|
+
// Outgoing from an element or its descendants
|
|
1604
|
+
_outgoing = new DefaultMap(() => /* @__PURE__ */ new Set());
|
|
1605
|
+
// Relationships inside the element, among descendants
|
|
1606
|
+
_internal = new DefaultMap(() => /* @__PURE__ */ new Set());
|
|
1607
|
+
_views = /* @__PURE__ */ new Map();
|
|
1608
|
+
_allTags = new DefaultMap(
|
|
1609
|
+
() => /* @__PURE__ */ new Set()
|
|
1610
|
+
);
|
|
1611
|
+
static fromParsed(model) {
|
|
1612
|
+
return new LikeC4Model(model).asParsed;
|
|
1613
|
+
}
|
|
1614
|
+
static create(model) {
|
|
1615
|
+
return new LikeC4Model(model);
|
|
1616
|
+
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Creates a new LikeC4Model instance and infers types from a model dump.\
|
|
1619
|
+
* Model dump expected to be computed or layouted.
|
|
1620
|
+
*
|
|
1621
|
+
* @typeParam D - A constant type parameter extending LikeC4ModelDump
|
|
1622
|
+
* @param dump - The model dump to create the instance from
|
|
1623
|
+
* @returns A new LikeC4Model instance with types inferred from the dump
|
|
1624
|
+
*/
|
|
1625
|
+
static fromDump(dump) {
|
|
1626
|
+
const {
|
|
1627
|
+
_stage: stage = "layouted",
|
|
1628
|
+
projectId = "unknown",
|
|
1629
|
+
globals,
|
|
1630
|
+
imports,
|
|
1631
|
+
deployments,
|
|
1632
|
+
views,
|
|
1633
|
+
relations,
|
|
1634
|
+
elements,
|
|
1635
|
+
specification
|
|
1636
|
+
} = dump;
|
|
1637
|
+
return new LikeC4Model({
|
|
1638
|
+
[_stage]: stage,
|
|
1639
|
+
projectId,
|
|
1640
|
+
globals: {
|
|
1641
|
+
predicates: globals?.predicates ?? {},
|
|
1642
|
+
dynamicPredicates: globals?.dynamicPredicates ?? {},
|
|
1643
|
+
styles: globals?.styles ?? {}
|
|
1644
|
+
},
|
|
1645
|
+
imports: imports ?? {},
|
|
1646
|
+
deployments: {
|
|
1647
|
+
elements: deployments?.elements ?? {},
|
|
1648
|
+
relations: deployments?.relations ?? {}
|
|
1649
|
+
},
|
|
1650
|
+
views: views ?? {},
|
|
1651
|
+
relations: relations ?? {},
|
|
1652
|
+
elements: elements ?? {},
|
|
1653
|
+
specification
|
|
1654
|
+
});
|
|
1655
|
+
}
|
|
1656
|
+
deployment;
|
|
1657
|
+
$data;
|
|
1658
|
+
constructor($data) {
|
|
1659
|
+
this.$data = $data;
|
|
1660
|
+
for (const [, element] of t($data.elements)) {
|
|
1661
|
+
const el = this.addElement(element);
|
|
1662
|
+
for (const tag of el.tags) {
|
|
1663
|
+
this._allTags.get(tag).add(el);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
for (const [projectId, elements] of t($data.imports ?? {})) {
|
|
1667
|
+
for (const element of sortParentsFirst(elements)) {
|
|
1668
|
+
const el = this.addImportedElement(projectId, element);
|
|
1669
|
+
for (const tag of el.tags) {
|
|
1670
|
+
this._allTags.get(tag).add(el);
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
for (const relation of r($data.relations)) {
|
|
1675
|
+
const el = this.addRelation(relation);
|
|
1676
|
+
for (const tag of el.tags) {
|
|
1677
|
+
this._allTags.get(tag).add(el);
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
this.deployment = new LikeC4DeploymentModel(this);
|
|
1681
|
+
if (isOnStage($data, "computed") || isOnStage($data, "layouted")) {
|
|
1682
|
+
const views = C(
|
|
1683
|
+
r($data.views),
|
|
1684
|
+
m((a, b) => compareNatural(a.title ?? "untitled", b.title ?? "untitled"))
|
|
1685
|
+
);
|
|
1686
|
+
for (const view of views) {
|
|
1687
|
+
const vm = new LikeC4ViewModel(this, view);
|
|
1688
|
+
this._views.set(view.id, vm);
|
|
1689
|
+
for (const tag of vm.tags) {
|
|
1690
|
+
this._allTags.get(tag).add(vm);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
/**
|
|
1696
|
+
* Type narrows the model to the parsed stage.
|
|
1697
|
+
* This is useful for tests
|
|
1698
|
+
*/
|
|
1699
|
+
get asParsed() {
|
|
1700
|
+
return this;
|
|
1701
|
+
}
|
|
1702
|
+
/**
|
|
1703
|
+
* Type narrows the model to the layouted stage.
|
|
1704
|
+
* This is useful for tests
|
|
1705
|
+
*/
|
|
1706
|
+
get asComputed() {
|
|
1707
|
+
return this;
|
|
1708
|
+
}
|
|
1709
|
+
/**
|
|
1710
|
+
* Type narrows the model to the layouted stage.
|
|
1711
|
+
* This is useful for tests
|
|
1712
|
+
*/
|
|
1713
|
+
get asLayouted() {
|
|
1714
|
+
return this;
|
|
1715
|
+
}
|
|
1716
|
+
/**
|
|
1717
|
+
* Type guard the model to the parsed stage.
|
|
1718
|
+
*/
|
|
1719
|
+
isParsed() {
|
|
1720
|
+
return this.stage === "parsed";
|
|
1721
|
+
}
|
|
1722
|
+
/**
|
|
1723
|
+
* Type guard the model to the layouted stage.
|
|
1724
|
+
*/
|
|
1725
|
+
isLayouted() {
|
|
1726
|
+
return this.stage === "layouted";
|
|
1727
|
+
}
|
|
1728
|
+
/**
|
|
1729
|
+
* Type guard the model to the computed stage.
|
|
1730
|
+
*/
|
|
1731
|
+
isComputed() {
|
|
1732
|
+
return this.stage === "computed";
|
|
1733
|
+
}
|
|
1734
|
+
/**
|
|
1735
|
+
* Keeping here for backward compatibility
|
|
1736
|
+
* @deprecated use {@link $data}
|
|
1737
|
+
*/
|
|
1738
|
+
get $model() {
|
|
1739
|
+
return this.$data;
|
|
1740
|
+
}
|
|
1741
|
+
get stage() {
|
|
1742
|
+
return this.$data[_stage];
|
|
1743
|
+
}
|
|
1744
|
+
get projectId() {
|
|
1745
|
+
return this.$data.projectId ?? "unknown";
|
|
1746
|
+
}
|
|
1747
|
+
get specification() {
|
|
1748
|
+
return this.$data.specification;
|
|
1749
|
+
}
|
|
1750
|
+
get globals() {
|
|
1751
|
+
return memoizeProp(this, Symbol.for("globals"), () => ({
|
|
1752
|
+
predicates: {
|
|
1753
|
+
...this.$data.globals?.predicates
|
|
1754
|
+
},
|
|
1755
|
+
dynamicPredicates: {
|
|
1756
|
+
...this.$data.globals?.dynamicPredicates
|
|
1757
|
+
},
|
|
1758
|
+
styles: {
|
|
1759
|
+
...this.$data.globals?.styles
|
|
1760
|
+
}
|
|
1761
|
+
}));
|
|
1762
|
+
}
|
|
1763
|
+
element(el) {
|
|
1764
|
+
if (el instanceof ElementModel) {
|
|
1765
|
+
return el;
|
|
1766
|
+
}
|
|
1767
|
+
const id = getId(el);
|
|
1768
|
+
return nonNullable(this._elements.get(id), `Element ${id} not found`);
|
|
1769
|
+
}
|
|
1770
|
+
findElement(el) {
|
|
1771
|
+
return this._elements.get(getId(el)) ?? null;
|
|
1772
|
+
}
|
|
1773
|
+
/**
|
|
1774
|
+
* Returns the root elements of the model.
|
|
1775
|
+
*/
|
|
1776
|
+
roots() {
|
|
1777
|
+
return this._rootElements.values();
|
|
1778
|
+
}
|
|
1779
|
+
/**
|
|
1780
|
+
* Returns all elements in the model.
|
|
1781
|
+
*/
|
|
1782
|
+
elements() {
|
|
1783
|
+
return this._elements.values();
|
|
1784
|
+
}
|
|
1785
|
+
/**
|
|
1786
|
+
* Returns all relationships in the model.
|
|
1787
|
+
*/
|
|
1788
|
+
relationships() {
|
|
1789
|
+
return this._relations.values();
|
|
1790
|
+
}
|
|
1791
|
+
relationship(rel, type) {
|
|
1792
|
+
if (type === "deployment") {
|
|
1793
|
+
return this.deployment.relationship(rel);
|
|
1794
|
+
}
|
|
1795
|
+
const id = getId(rel);
|
|
1796
|
+
let model = this._relations.get(id) ?? null;
|
|
1797
|
+
if (model || type === "model") {
|
|
1798
|
+
return nonNullable(model, `Model relation ${id} not found`);
|
|
1799
|
+
}
|
|
1800
|
+
return nonNullable(this.deployment.findRelationship(id), `No model/deployment relation ${id} not found`);
|
|
1801
|
+
}
|
|
1802
|
+
findRelationship(id, type) {
|
|
1803
|
+
if (type === "deployment") {
|
|
1804
|
+
return this.deployment.findRelationship(id);
|
|
1805
|
+
}
|
|
1806
|
+
let model = this._relations.get(getId(id)) ?? null;
|
|
1807
|
+
if (model || type === "model") {
|
|
1808
|
+
return model;
|
|
1809
|
+
}
|
|
1810
|
+
return this.deployment.findRelationship(id);
|
|
1811
|
+
}
|
|
1812
|
+
/**
|
|
1813
|
+
* Returns all views in the model.
|
|
1814
|
+
*/
|
|
1815
|
+
views() {
|
|
1816
|
+
return this._views.values();
|
|
1817
|
+
}
|
|
1818
|
+
/**
|
|
1819
|
+
* Returns a specific view by its ID.
|
|
1820
|
+
*/
|
|
1821
|
+
view(viewId) {
|
|
1822
|
+
const id = getId(viewId);
|
|
1823
|
+
return nonNullable(this._views.get(id), `View ${id} not found`);
|
|
1824
|
+
}
|
|
1825
|
+
findView(viewId) {
|
|
1826
|
+
return this._views.get(viewId) ?? null;
|
|
1827
|
+
}
|
|
1828
|
+
/**
|
|
1829
|
+
* Returns the parent element of given element.
|
|
1830
|
+
* @see ancestors
|
|
1831
|
+
*/
|
|
1832
|
+
parent(element) {
|
|
1833
|
+
const id = getId(element);
|
|
1834
|
+
return this._parents.get(id) || null;
|
|
1835
|
+
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Get all children of the element (only direct children),
|
|
1838
|
+
* @see descendants
|
|
1839
|
+
*/
|
|
1840
|
+
children(element) {
|
|
1841
|
+
const id = getId(element);
|
|
1842
|
+
return this._children.get(id);
|
|
1843
|
+
}
|
|
1844
|
+
/**
|
|
1845
|
+
* Get all sibling (i.e. same parent)
|
|
1846
|
+
*/
|
|
1847
|
+
*siblings(element) {
|
|
1848
|
+
const id = getId(element);
|
|
1849
|
+
const parent = this._parents.get(id);
|
|
1850
|
+
const siblings = parent ? this._children.get(parent.id).values() : this.roots();
|
|
1851
|
+
for (const sibling of siblings) {
|
|
1852
|
+
if (sibling.id !== id) {
|
|
1853
|
+
yield sibling;
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
/**
|
|
1859
|
+
* Get all ancestor elements (i.e. parent, parent’s parent, etc.)
|
|
1860
|
+
* (from closest to root)
|
|
1861
|
+
*/
|
|
1862
|
+
*ancestors(element) {
|
|
1863
|
+
let id = getId(element);
|
|
1864
|
+
let parent;
|
|
1865
|
+
while (parent = this._parents.get(id)) {
|
|
1866
|
+
yield parent;
|
|
1867
|
+
id = parent.id;
|
|
1868
|
+
}
|
|
1869
|
+
return;
|
|
1870
|
+
}
|
|
1871
|
+
/**
|
|
1872
|
+
* Get all descendant elements (i.e. children, children’s children, etc.)
|
|
1873
|
+
*/
|
|
1874
|
+
*descendants(element) {
|
|
1875
|
+
for (const child of this.children(element)) {
|
|
1876
|
+
yield child;
|
|
1877
|
+
yield* this.descendants(child.id);
|
|
1878
|
+
}
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
/**
|
|
1882
|
+
* Incoming relationships to the element and its descendants
|
|
1883
|
+
* @see incomers
|
|
1884
|
+
*/
|
|
1885
|
+
*incoming(element, filter = "all") {
|
|
1886
|
+
const id = getId(element);
|
|
1887
|
+
for (const rel of this._incoming.get(id)) {
|
|
1888
|
+
switch (true) {
|
|
1889
|
+
case filter === "all":
|
|
1890
|
+
case (filter === "direct" && rel.target.id === id):
|
|
1891
|
+
case (filter === "to-descendants" && rel.target.id !== id):
|
|
1892
|
+
yield rel;
|
|
1893
|
+
break;
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
return;
|
|
1897
|
+
}
|
|
1898
|
+
/**
|
|
1899
|
+
* Outgoing relationships from the element and its descendants
|
|
1900
|
+
* @see outgoers
|
|
1901
|
+
*/
|
|
1902
|
+
*outgoing(element, filter = "all") {
|
|
1903
|
+
const id = getId(element);
|
|
1904
|
+
for (const rel of this._outgoing.get(id)) {
|
|
1905
|
+
switch (true) {
|
|
1906
|
+
case filter === "all":
|
|
1907
|
+
case (filter === "direct" && rel.source.id === id):
|
|
1908
|
+
case (filter === "from-descendants" && rel.source.id !== id):
|
|
1909
|
+
yield rel;
|
|
1910
|
+
break;
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
return;
|
|
1914
|
+
}
|
|
1915
|
+
/**
|
|
1916
|
+
* Returns all tags used in the model, sorted alphabetically.
|
|
1917
|
+
*/
|
|
1918
|
+
get tags() {
|
|
1919
|
+
return memoizeProp(this, "tags", () => m([...this._allTags.keys()], compareNatural));
|
|
1920
|
+
}
|
|
1921
|
+
/**
|
|
1922
|
+
* Returns all tags used in the model, sorted by usage count (descending).
|
|
1923
|
+
*/
|
|
1924
|
+
get tagsSortedByUsage() {
|
|
1925
|
+
return memoizeProp(this, "tagsSortedByUsage", () => C(
|
|
1926
|
+
[...this._allTags.entries()],
|
|
1927
|
+
m$2(([tag, tagged]) => ({
|
|
1928
|
+
tag,
|
|
1929
|
+
count: tagged.size,
|
|
1930
|
+
tagged
|
|
1931
|
+
})),
|
|
1932
|
+
m((a, b) => compareNatural(a.tag, b.tag)),
|
|
1933
|
+
a(
|
|
1934
|
+
[p("count"), "desc"]
|
|
1935
|
+
)
|
|
1936
|
+
));
|
|
1937
|
+
}
|
|
1938
|
+
findByTag(tag, type) {
|
|
1939
|
+
return ifilter(this._allTags.get(tag), (el) => {
|
|
1940
|
+
if (type === "elements") {
|
|
1941
|
+
return el instanceof ElementModel;
|
|
1942
|
+
}
|
|
1943
|
+
if (type === "views") {
|
|
1944
|
+
return el instanceof LikeC4ViewModel;
|
|
1945
|
+
}
|
|
1946
|
+
if (type === "relationships") {
|
|
1947
|
+
return el instanceof RelationshipModel;
|
|
1948
|
+
}
|
|
1949
|
+
return true;
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1952
|
+
/**
|
|
1953
|
+
* Returns all elements of the given kind.
|
|
1954
|
+
*/
|
|
1955
|
+
*elementsOfKind(kind) {
|
|
1956
|
+
for (const el of this._elements.values()) {
|
|
1957
|
+
if (el.kind === kind) {
|
|
1958
|
+
yield el;
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
return;
|
|
1962
|
+
}
|
|
1963
|
+
/**
|
|
1964
|
+
* Returns all elements that match the given where operator.
|
|
1965
|
+
*
|
|
1966
|
+
* @example
|
|
1967
|
+
* ```ts
|
|
1968
|
+
* model.where({
|
|
1969
|
+
* and: [
|
|
1970
|
+
* { kind: 'component' },
|
|
1971
|
+
* {
|
|
1972
|
+
* or: [
|
|
1973
|
+
* { tag: 'old' },
|
|
1974
|
+
* { tag: { neq: 'new' } },
|
|
1975
|
+
* ],
|
|
1976
|
+
* },
|
|
1977
|
+
* ],
|
|
1978
|
+
* })
|
|
1979
|
+
* ```
|
|
1980
|
+
*/
|
|
1981
|
+
*elementsWhere(where) {
|
|
1982
|
+
const predicate = whereOperatorAsPredicate(where);
|
|
1983
|
+
for (const el of this._elements.values()) {
|
|
1984
|
+
if (predicate(el)) {
|
|
1985
|
+
yield el;
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
return;
|
|
1989
|
+
}
|
|
1990
|
+
/**
|
|
1991
|
+
* Returns all **model** relationships that match the given where operator.
|
|
1992
|
+
*
|
|
1993
|
+
* @example
|
|
1994
|
+
* ```ts
|
|
1995
|
+
* model.relationshipsWhere({
|
|
1996
|
+
* and: [
|
|
1997
|
+
* { kind: 'uses' },
|
|
1998
|
+
* {
|
|
1999
|
+
* or: [
|
|
2000
|
+
* { tag: 'old' },
|
|
2001
|
+
* { tag: { neq: 'new' } },
|
|
2002
|
+
* ],
|
|
2003
|
+
* },
|
|
2004
|
+
* ],
|
|
2005
|
+
* })
|
|
2006
|
+
* ```
|
|
2007
|
+
*/
|
|
2008
|
+
*relationshipsWhere(where) {
|
|
2009
|
+
const predicate = whereOperatorAsPredicate(where);
|
|
2010
|
+
for (const rel of this._relations.values()) {
|
|
2011
|
+
if (predicate(rel)) {
|
|
2012
|
+
yield rel;
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
2017
|
+
addElement(element) {
|
|
2018
|
+
if (this._elements.has(element.id)) {
|
|
2019
|
+
throw new Error(`Element ${element.id} already exists`);
|
|
2020
|
+
}
|
|
2021
|
+
const el = new ElementModel(this, Object.freeze(element));
|
|
2022
|
+
this._elements.set(el.id, el);
|
|
2023
|
+
const parentId = parentFqn(el.id);
|
|
2024
|
+
if (parentId) {
|
|
2025
|
+
invariant(this._elements.has(parentId), `Parent ${parentId} of ${el.id} not found`);
|
|
2026
|
+
this._parents.set(el.id, this.element(parentId));
|
|
2027
|
+
this._children.get(parentId).add(el);
|
|
2028
|
+
} else {
|
|
2029
|
+
this._rootElements.add(el);
|
|
2030
|
+
}
|
|
2031
|
+
return el;
|
|
2032
|
+
}
|
|
2033
|
+
addImportedElement(projectId, element) {
|
|
2034
|
+
invariant(!isGlobalFqn(element.id), `Imported element already has global FQN`);
|
|
2035
|
+
const id = GlobalFqn(projectId, element.id);
|
|
2036
|
+
if (this._elements.has(id)) {
|
|
2037
|
+
throw new Error(`Element ${id} already exists`);
|
|
2038
|
+
}
|
|
2039
|
+
const el = new ElementModel(
|
|
2040
|
+
this,
|
|
2041
|
+
Object.freeze({
|
|
2042
|
+
...element,
|
|
2043
|
+
id
|
|
2044
|
+
})
|
|
2045
|
+
);
|
|
2046
|
+
this._elements.set(el.id, el);
|
|
2047
|
+
let parentId = parentFqn(el.id);
|
|
2048
|
+
while (parentId) {
|
|
2049
|
+
if (parentId.includes(".") && this._elements.has(parentId)) {
|
|
2050
|
+
this._parents.set(el.id, this.element(parentId));
|
|
2051
|
+
this._children.get(parentId).add(el);
|
|
2052
|
+
return el;
|
|
2053
|
+
}
|
|
2054
|
+
parentId = parentFqn(parentId);
|
|
2055
|
+
}
|
|
2056
|
+
this._rootElements.add(el);
|
|
2057
|
+
return el;
|
|
2058
|
+
}
|
|
2059
|
+
addRelation(relation) {
|
|
2060
|
+
if (this._relations.has(relation.id)) {
|
|
2061
|
+
throw new Error(`Relation ${relation.id} already exists`);
|
|
2062
|
+
}
|
|
2063
|
+
const rel = new RelationshipModel(
|
|
2064
|
+
this,
|
|
2065
|
+
Object.freeze(relation)
|
|
2066
|
+
);
|
|
2067
|
+
const { source, target } = rel;
|
|
2068
|
+
this._relations.set(rel.id, rel);
|
|
2069
|
+
this._incoming.get(target.id).add(rel);
|
|
2070
|
+
this._outgoing.get(source.id).add(rel);
|
|
2071
|
+
const relParent = commonAncestor(source.id, target.id);
|
|
2072
|
+
if (relParent) {
|
|
2073
|
+
for (const ancestor of [relParent, ...ancestorsFqn(relParent)]) {
|
|
2074
|
+
this._internal.get(ancestor).add(rel);
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
for (const sourceAncestor of ancestorsFqn(source.id)) {
|
|
2078
|
+
if (sourceAncestor === relParent) {
|
|
2079
|
+
break;
|
|
2080
|
+
}
|
|
2081
|
+
this._outgoing.get(sourceAncestor).add(rel);
|
|
2082
|
+
}
|
|
2083
|
+
for (const targetAncestor of ancestorsFqn(target.id)) {
|
|
2084
|
+
if (targetAncestor === relParent) {
|
|
2085
|
+
break;
|
|
2086
|
+
}
|
|
2087
|
+
this._incoming.get(targetAncestor).add(rel);
|
|
2088
|
+
}
|
|
2089
|
+
return rel;
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
((LikeC4Model2) => {
|
|
2093
|
+
LikeC4Model2.EMPTY = LikeC4Model2.create({
|
|
2094
|
+
_stage: "computed",
|
|
2095
|
+
projectId: "default",
|
|
2096
|
+
specification: {
|
|
2097
|
+
elements: {},
|
|
2098
|
+
relationships: {},
|
|
2099
|
+
deployments: {},
|
|
2100
|
+
tags: {}
|
|
2101
|
+
},
|
|
2102
|
+
globals: {
|
|
2103
|
+
predicates: {},
|
|
2104
|
+
dynamicPredicates: {},
|
|
2105
|
+
styles: {}
|
|
2106
|
+
},
|
|
2107
|
+
deployments: {
|
|
2108
|
+
elements: {},
|
|
2109
|
+
relations: {}
|
|
2110
|
+
},
|
|
2111
|
+
elements: {},
|
|
2112
|
+
relations: {},
|
|
2113
|
+
views: {},
|
|
2114
|
+
imports: {}
|
|
2115
|
+
});
|
|
2116
|
+
})(LikeC4Model || (LikeC4Model = {}));
|
|
2117
|
+
|
|
2118
|
+
var Connection;
|
|
2119
|
+
((Connection2) => {
|
|
2120
|
+
Connection2.isInside = (fqn) => {
|
|
2121
|
+
return (connection) => isAncestor(fqn, connection.source.id) && isAncestor(fqn, connection.target.id);
|
|
2122
|
+
};
|
|
2123
|
+
Connection2.isDirectedBetween = (source, target) => {
|
|
2124
|
+
return (connection) => (connection.source.id === source || isAncestor(source, connection.source.id)) && (connection.target.id === target || isAncestor(target, connection.target.id));
|
|
2125
|
+
};
|
|
2126
|
+
Connection2.isAnyBetween = (source, target) => {
|
|
2127
|
+
const forward = (0, Connection2.isDirectedBetween)(source, target), backward = (0, Connection2.isDirectedBetween)(target, source);
|
|
2128
|
+
return (connection) => forward(connection) || backward(connection);
|
|
2129
|
+
};
|
|
2130
|
+
Connection2.isIncoming = (target) => {
|
|
2131
|
+
return (connection) => (connection.target.id === target || isAncestor(target, connection.target.id)) && !isAncestor(target, connection.source.id);
|
|
2132
|
+
};
|
|
2133
|
+
Connection2.isOutgoing = (source) => {
|
|
2134
|
+
return (connection) => (connection.source.id === source || isAncestor(source, connection.source.id)) && !isAncestor(source, connection.target.id);
|
|
2135
|
+
};
|
|
2136
|
+
Connection2.isAnyInOut = (source) => {
|
|
2137
|
+
const isIn = (0, Connection2.isIncoming)(source), isOut = (0, Connection2.isOutgoing)(source);
|
|
2138
|
+
return (connection) => isIn(connection) || isOut(connection);
|
|
2139
|
+
};
|
|
2140
|
+
})(Connection || (Connection = {}));
|
|
2141
|
+
|
|
2142
|
+
const customInspectSymbol = Symbol.for("nodejs.util.inspect.custom");
|
|
2143
|
+
|
|
2144
|
+
class DeploymentConnectionModel {
|
|
2145
|
+
constructor(source, target, relations) {
|
|
2146
|
+
this.source = source;
|
|
2147
|
+
this.target = target;
|
|
2148
|
+
this.relations = relations;
|
|
2149
|
+
this.id = stringHash(`deployment:${source.id}:${target.id}`);
|
|
2150
|
+
}
|
|
2151
|
+
id;
|
|
2152
|
+
/**
|
|
2153
|
+
* Human readable expression of the connection
|
|
2154
|
+
* Mostly used for testing and debugging
|
|
2155
|
+
*/
|
|
2156
|
+
get expression() {
|
|
2157
|
+
return `${this.source.id} -> ${this.target.id}`;
|
|
2158
|
+
}
|
|
2159
|
+
_boundary;
|
|
2160
|
+
/**
|
|
2161
|
+
* Common ancestor of the source and target elements.
|
|
2162
|
+
* Represents the boundary of the connection.
|
|
2163
|
+
*/
|
|
2164
|
+
get boundary() {
|
|
2165
|
+
this._boundary ??= this.source.commonAncestor(this.target);
|
|
2166
|
+
return this._boundary;
|
|
2167
|
+
}
|
|
2168
|
+
nonEmpty() {
|
|
2169
|
+
return this.relations.nonEmpty;
|
|
2170
|
+
}
|
|
2171
|
+
[customInspectSymbol](depth, inspectOptions, inspect) {
|
|
2172
|
+
const asString = this.toString();
|
|
2173
|
+
Object.defineProperty(asString, "constructor", {
|
|
2174
|
+
value: DeploymentConnectionModel,
|
|
2175
|
+
enumerable: false
|
|
2176
|
+
});
|
|
2177
|
+
return asString;
|
|
2178
|
+
}
|
|
2179
|
+
toString() {
|
|
2180
|
+
const model = [...this.relations.model].map((c) => " " + c.expression);
|
|
2181
|
+
if (model.length) {
|
|
2182
|
+
model.unshift(" model:");
|
|
2183
|
+
} else {
|
|
2184
|
+
model.unshift(" model: []");
|
|
2185
|
+
}
|
|
2186
|
+
const deployment = [...this.relations.deployment].map((c) => " " + c.expression);
|
|
2187
|
+
if (deployment.length) {
|
|
2188
|
+
deployment.unshift(" deployment:");
|
|
2189
|
+
} else {
|
|
2190
|
+
deployment.unshift(" deployment: []");
|
|
2191
|
+
}
|
|
2192
|
+
return [
|
|
2193
|
+
this.expression,
|
|
2194
|
+
...model,
|
|
2195
|
+
...deployment
|
|
2196
|
+
].join("\n");
|
|
2197
|
+
}
|
|
2198
|
+
/**
|
|
2199
|
+
* Check if connection contains deployment relation,
|
|
2200
|
+
* that is directly connected to source or target.
|
|
2201
|
+
*/
|
|
2202
|
+
hasDirectDeploymentRelation() {
|
|
2203
|
+
for (const relation of this.relations.deployment) {
|
|
2204
|
+
if (relation.source.id === this.source.id || relation.target.id === this.target.id) {
|
|
2205
|
+
return true;
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
return false;
|
|
2209
|
+
}
|
|
2210
|
+
*values() {
|
|
2211
|
+
yield* this.relations.model;
|
|
2212
|
+
yield* this.relations.deployment;
|
|
2213
|
+
}
|
|
2214
|
+
mergeWith(other) {
|
|
2215
|
+
if (Array.isArray(other)) {
|
|
2216
|
+
return other.reduce((acc, o) => acc.mergeWith(o), this);
|
|
2217
|
+
}
|
|
2218
|
+
invariant(this.source.id === other.source.id, "Cannot merge connections with different sources");
|
|
2219
|
+
invariant(this.target.id === other.target.id, "Cannot merge connections with different targets");
|
|
2220
|
+
return new DeploymentConnectionModel(
|
|
2221
|
+
this.source,
|
|
2222
|
+
this.target,
|
|
2223
|
+
this.relations.union(other.relations)
|
|
2224
|
+
);
|
|
2225
|
+
}
|
|
2226
|
+
difference(other) {
|
|
2227
|
+
return new DeploymentConnectionModel(
|
|
2228
|
+
this.source,
|
|
2229
|
+
this.target,
|
|
2230
|
+
this.relations.difference(other.relations)
|
|
2231
|
+
);
|
|
2232
|
+
}
|
|
2233
|
+
intersect(other) {
|
|
2234
|
+
return new DeploymentConnectionModel(
|
|
2235
|
+
this.source,
|
|
2236
|
+
this.target,
|
|
2237
|
+
this.relations.intersect(other.relations)
|
|
2238
|
+
);
|
|
2239
|
+
}
|
|
2240
|
+
equals(other) {
|
|
2241
|
+
invariant(other instanceof DeploymentConnectionModel, "Other should ne DeploymentConnectionModel");
|
|
2242
|
+
return this.id === other.id && this.source.id === other.source.id && this.target.id === other.target.id && equals(this.relations.model, other.relations.model) && equals(this.relations.deployment, other.relations.deployment);
|
|
2243
|
+
}
|
|
2244
|
+
/**
|
|
2245
|
+
* Creates a clone of the current `DeploymentConnectionModel` instance with optional overrides.
|
|
2246
|
+
* if `null` is provided in overrides, the corresponding relation set will be empty.
|
|
2247
|
+
*/
|
|
2248
|
+
update(overrides) {
|
|
2249
|
+
if (overrides) {
|
|
2250
|
+
overrides = {
|
|
2251
|
+
model: this.relations.model,
|
|
2252
|
+
deployment: this.relations.deployment,
|
|
2253
|
+
...overrides
|
|
2254
|
+
};
|
|
2255
|
+
}
|
|
2256
|
+
return new DeploymentConnectionModel(
|
|
2257
|
+
this.source,
|
|
2258
|
+
this.target,
|
|
2259
|
+
overrides ? new RelationshipsAccum(
|
|
2260
|
+
overrides.model ?? /* @__PURE__ */ new Set(),
|
|
2261
|
+
overrides.deployment ?? /* @__PURE__ */ new Set()
|
|
2262
|
+
) : this.relations
|
|
2263
|
+
);
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
function isNestedConnection(nested, parent) {
|
|
2268
|
+
if (!parent) {
|
|
2269
|
+
const p = nested;
|
|
2270
|
+
return (n) => isNestedConnection(n, p);
|
|
2271
|
+
}
|
|
2272
|
+
const isSameSource = nested.source === parent.source;
|
|
2273
|
+
const isSameTarget = nested.target === parent.target;
|
|
2274
|
+
if (isSameSource && isSameTarget) {
|
|
2275
|
+
return false;
|
|
2276
|
+
}
|
|
2277
|
+
const isSourceNested = isAncestor(parent.source.id, nested.source.id);
|
|
2278
|
+
const isTargetNested = isAncestor(parent.target.id, nested.target.id);
|
|
2279
|
+
return isSourceNested && isTargetNested || isSameSource && isTargetNested || isSameTarget && isSourceNested;
|
|
2280
|
+
}
|
|
2281
|
+
function findDeepestNestedConnection(connections, connection) {
|
|
2282
|
+
let deepest = connection;
|
|
2283
|
+
for (const c of connections) {
|
|
2284
|
+
if (isNestedConnection(c, deepest)) {
|
|
2285
|
+
deepest = c;
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
return deepest !== connection ? deepest : null;
|
|
2289
|
+
}
|
|
2290
|
+
function sortDeepestFirst(connections) {
|
|
2291
|
+
const sorted = [];
|
|
2292
|
+
const unsorted = connections.slice();
|
|
2293
|
+
let next;
|
|
2294
|
+
while (next = unsorted.shift()) {
|
|
2295
|
+
let deepest;
|
|
2296
|
+
while (deepest = findDeepestNestedConnection(unsorted, next)) {
|
|
2297
|
+
const index = unsorted.indexOf(deepest);
|
|
2298
|
+
sorted.push(unsorted.splice(index, 1)[0]);
|
|
2299
|
+
}
|
|
2300
|
+
sorted.push(next);
|
|
2301
|
+
}
|
|
2302
|
+
return sorted;
|
|
2303
|
+
}
|
|
2304
|
+
const boundaryHierarchy = (conn) => conn.boundary?.id ? `.${conn.boundary.id}` : "";
|
|
2305
|
+
function sortConnectionsByBoundaryHierarchy(connections, sort) {
|
|
2306
|
+
if (!connections || isString(connections)) {
|
|
2307
|
+
const dir = connections ?? "asc";
|
|
2308
|
+
return (arr) => _sortByBoundary(arr, dir);
|
|
2309
|
+
}
|
|
2310
|
+
return _sortByBoundary(connections, sort ?? "asc");
|
|
2311
|
+
}
|
|
2312
|
+
function _sortByBoundary(connections, order) {
|
|
2313
|
+
return C(
|
|
2314
|
+
connections,
|
|
2315
|
+
m$2((conn) => ({
|
|
2316
|
+
id: boundaryHierarchy(conn),
|
|
2317
|
+
conn
|
|
2318
|
+
})),
|
|
2319
|
+
sortNaturalByFqn(order),
|
|
2320
|
+
m$2(p("conn"))
|
|
2321
|
+
);
|
|
2322
|
+
}
|
|
2323
|
+
function findAscendingConnections(connections, connection) {
|
|
2324
|
+
return connections.filter((c) => isNestedConnection(connection, c));
|
|
2325
|
+
}
|
|
2326
|
+
function findDescendantConnections(connections, connection) {
|
|
2327
|
+
return connections.filter(isNestedConnection(connection));
|
|
2328
|
+
}
|
|
2329
|
+
function mergeConnections(connections) {
|
|
2330
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
2331
|
+
for (const conn of connections) {
|
|
2332
|
+
const existing = map2.get(conn.id);
|
|
2333
|
+
if (existing) {
|
|
2334
|
+
map2.set(conn.id, conn.mergeWith(existing));
|
|
2335
|
+
} else {
|
|
2336
|
+
map2.set(conn.id, conn);
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
return [...map2.values()];
|
|
2340
|
+
}
|
|
2341
|
+
function differenceConnections(source, exclude) {
|
|
2342
|
+
const minus = new Map([...exclude].map((c) => [c.id, c]));
|
|
2343
|
+
return [...source].reduce((acc, c) => {
|
|
2344
|
+
const other = minus.get(c.id);
|
|
2345
|
+
if (!other) {
|
|
2346
|
+
acc.push(c);
|
|
2347
|
+
return acc;
|
|
2348
|
+
}
|
|
2349
|
+
const updated = c.difference(other);
|
|
2350
|
+
if (updated.nonEmpty()) {
|
|
2351
|
+
acc.push(updated);
|
|
2352
|
+
}
|
|
2353
|
+
return acc;
|
|
2354
|
+
}, []);
|
|
2355
|
+
}
|
|
2356
|
+
function hasSameSourceTarget(a, b) {
|
|
2357
|
+
if (b) {
|
|
2358
|
+
return a.source === b.source && a.target === b.target;
|
|
2359
|
+
}
|
|
2360
|
+
return (b2) => a.source === b2.source && a.target === b2.target;
|
|
2361
|
+
}
|
|
2362
|
+
function hasSameSource(a, b) {
|
|
2363
|
+
if (b) {
|
|
2364
|
+
return a.source === b.source;
|
|
2365
|
+
}
|
|
2366
|
+
return (b2) => a.source === b2.source;
|
|
2367
|
+
}
|
|
2368
|
+
function hasSameTarget(a, b) {
|
|
2369
|
+
if (b) {
|
|
2370
|
+
return a.target === b.target;
|
|
2371
|
+
}
|
|
2372
|
+
return (b2) => a.target === b2.target;
|
|
2373
|
+
}
|
|
2374
|
+
function isOutgoing(a, source) {
|
|
2375
|
+
if (!source) {
|
|
2376
|
+
const _source = a;
|
|
2377
|
+
return (b) => isOutgoing(b, _source);
|
|
2378
|
+
}
|
|
2379
|
+
const at = a;
|
|
2380
|
+
return isDescendantOf(at.source, source) && !isDescendantOf(at.target, source);
|
|
2381
|
+
}
|
|
2382
|
+
function isIncoming(a, target) {
|
|
2383
|
+
if (!target) {
|
|
2384
|
+
const _target = a;
|
|
2385
|
+
return (b) => isIncoming(b, _target);
|
|
2386
|
+
}
|
|
2387
|
+
const at = a;
|
|
2388
|
+
return isDescendantOf(at.target, target) && !isDescendantOf(at.source, target);
|
|
2389
|
+
}
|
|
2390
|
+
function isAnyInOut(a, source) {
|
|
2391
|
+
if (!source) {
|
|
2392
|
+
const _source = a;
|
|
2393
|
+
return (b) => isAnyInOut(b, _source);
|
|
2394
|
+
}
|
|
2395
|
+
const at = a;
|
|
2396
|
+
return isDescendantOf(at.source, source) !== isDescendantOf(at.target, source);
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
function findConnection$1(source, target, direction = "directed") {
|
|
2400
|
+
if (source === target) {
|
|
2401
|
+
return [];
|
|
2402
|
+
}
|
|
2403
|
+
if (isSameHierarchy(source, target)) {
|
|
2404
|
+
return [];
|
|
2405
|
+
}
|
|
2406
|
+
const directedIntersection = intersection(source.allOutgoing, target.allIncoming);
|
|
2407
|
+
const directed = directedIntersection.size > 0 ? new ConnectionModel(
|
|
2408
|
+
source,
|
|
2409
|
+
target,
|
|
2410
|
+
directedIntersection
|
|
2411
|
+
) : null;
|
|
2412
|
+
if (direction === "directed") {
|
|
2413
|
+
return directed ? [directed] : [];
|
|
2414
|
+
}
|
|
2415
|
+
const reverseIntersection = intersection(source.allIncoming, target.allOutgoing);
|
|
2416
|
+
const reverse = reverseIntersection.size > 0 ? new ConnectionModel(
|
|
2417
|
+
target,
|
|
2418
|
+
source,
|
|
2419
|
+
reverseIntersection
|
|
2420
|
+
) : null;
|
|
2421
|
+
if (directed && reverse) {
|
|
2422
|
+
return [directed, reverse];
|
|
2423
|
+
}
|
|
2424
|
+
if (directed) {
|
|
2425
|
+
return [directed];
|
|
2426
|
+
}
|
|
2427
|
+
if (reverse) {
|
|
2428
|
+
return [reverse];
|
|
2429
|
+
}
|
|
2430
|
+
return [];
|
|
2431
|
+
}
|
|
2432
|
+
function findConnectionsBetween$1(element, others, direction = "both") {
|
|
2433
|
+
if (element.allIncoming.size === 0 && element.allOutgoing.size === 0) {
|
|
2434
|
+
return [];
|
|
2435
|
+
}
|
|
2436
|
+
const outgoing = [];
|
|
2437
|
+
const incoming = [];
|
|
2438
|
+
for (const _other of others) {
|
|
2439
|
+
if (element === _other) {
|
|
2440
|
+
continue;
|
|
2441
|
+
}
|
|
2442
|
+
for (const found of findConnection$1(element, _other, direction)) {
|
|
2443
|
+
if (found.source === element) {
|
|
2444
|
+
outgoing.push(found);
|
|
2445
|
+
} else {
|
|
2446
|
+
incoming.push(found);
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
return [
|
|
2451
|
+
...outgoing,
|
|
2452
|
+
...incoming
|
|
2453
|
+
];
|
|
2454
|
+
}
|
|
2455
|
+
function findConnectionsWithin$1(elements) {
|
|
2456
|
+
return [...elements].reduce((acc, el, index, array) => {
|
|
2457
|
+
if (index === array.length - 1) {
|
|
2458
|
+
return acc;
|
|
2459
|
+
}
|
|
2460
|
+
acc.push(
|
|
2461
|
+
...findConnectionsBetween$1(el, array.slice(index + 1), "both")
|
|
2462
|
+
);
|
|
2463
|
+
return acc;
|
|
2464
|
+
}, []);
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
const modelConnection = {
|
|
2468
|
+
__proto__: null,
|
|
2469
|
+
findConnection: findConnection$1,
|
|
2470
|
+
findConnectionsBetween: findConnectionsBetween$1,
|
|
2471
|
+
findConnectionsWithin: findConnectionsWithin$1
|
|
2472
|
+
};
|
|
2473
|
+
|
|
2474
|
+
class ConnectionModel {
|
|
2475
|
+
constructor(source, target, relations = /* @__PURE__ */ new Set()) {
|
|
2476
|
+
this.source = source;
|
|
2477
|
+
this.target = target;
|
|
2478
|
+
this.relations = relations;
|
|
2479
|
+
this.id = stringHash(`model:${source.id}:${target.id}`);
|
|
2480
|
+
}
|
|
2481
|
+
id;
|
|
2482
|
+
_boundary;
|
|
2483
|
+
/**
|
|
2484
|
+
* Common ancestor of the source and target elements.
|
|
2485
|
+
* Represents the boundary of the connection.
|
|
2486
|
+
*/
|
|
2487
|
+
get boundary() {
|
|
2488
|
+
return this._boundary ??= this.source.commonAncestor(this.target);
|
|
2489
|
+
}
|
|
2490
|
+
/**
|
|
2491
|
+
* Human readable expression of the connection
|
|
2492
|
+
* Mostly used for testing and debugging
|
|
2493
|
+
*/
|
|
2494
|
+
get expression() {
|
|
2495
|
+
return `${this.source.id} -> ${this.target.id}`;
|
|
2496
|
+
}
|
|
2497
|
+
/**
|
|
2498
|
+
* Returns true if only includes relations between the source and target elements.
|
|
2499
|
+
*/
|
|
2500
|
+
get isDirect() {
|
|
2501
|
+
return this.nonEmpty() && !this.isImplicit;
|
|
2502
|
+
}
|
|
2503
|
+
/**
|
|
2504
|
+
* Returns true if includes relations between nested elements of the source and target elements.
|
|
2505
|
+
*/
|
|
2506
|
+
get isImplicit() {
|
|
2507
|
+
return this.nonEmpty() && isome(this.relations, o$2(hasSameSourceTarget(this)));
|
|
2508
|
+
}
|
|
2509
|
+
get directRelations() {
|
|
2510
|
+
return new Set(ifilter(this.relations, hasSameSourceTarget(this)));
|
|
2511
|
+
}
|
|
2512
|
+
nonEmpty() {
|
|
2513
|
+
return this.relations.size > 0;
|
|
2514
|
+
}
|
|
2515
|
+
mergeWith(other) {
|
|
2516
|
+
invariant(this.source.id === other.source.id, "Cannot merge connections with different sources");
|
|
2517
|
+
invariant(this.target.id === other.target.id, "Cannot merge connections with different targets");
|
|
2518
|
+
return new ConnectionModel(
|
|
2519
|
+
this.source,
|
|
2520
|
+
this.target,
|
|
2521
|
+
union(this.relations, other.relations)
|
|
2522
|
+
);
|
|
2523
|
+
}
|
|
2524
|
+
difference(other) {
|
|
2525
|
+
return new ConnectionModel(
|
|
2526
|
+
this.source,
|
|
2527
|
+
this.target,
|
|
2528
|
+
difference(this.relations, other.relations)
|
|
2529
|
+
);
|
|
2530
|
+
}
|
|
2531
|
+
intersect(other) {
|
|
2532
|
+
invariant(other instanceof ConnectionModel, "Cannot intersect connection with different type");
|
|
2533
|
+
return new ConnectionModel(
|
|
2534
|
+
this.source,
|
|
2535
|
+
this.target,
|
|
2536
|
+
intersection(this.relations, other.relations)
|
|
2537
|
+
);
|
|
2538
|
+
}
|
|
2539
|
+
equals(other) {
|
|
2540
|
+
invariant(other instanceof ConnectionModel, "Cannot merge connection with different type");
|
|
2541
|
+
const _other = other;
|
|
2542
|
+
return this.id === _other.id && this.source.id === _other.source.id && this.target.id === _other.target.id && equals(this.relations, _other.relations);
|
|
2543
|
+
}
|
|
2544
|
+
/**
|
|
2545
|
+
* Returns a new instance with the updated relations.
|
|
2546
|
+
*
|
|
2547
|
+
* @param relations - A readonly set of `RelationshipModel` instances representing the new relations.
|
|
2548
|
+
* @returns A new `ConnectionModel` instance with the updated relations.
|
|
2549
|
+
*/
|
|
2550
|
+
update(relations) {
|
|
2551
|
+
return new ConnectionModel(
|
|
2552
|
+
this.source,
|
|
2553
|
+
this.target,
|
|
2554
|
+
relations
|
|
2555
|
+
);
|
|
2556
|
+
}
|
|
2557
|
+
[customInspectSymbol](depth, inspectOptions, inspect) {
|
|
2558
|
+
const asString = this.toString();
|
|
2559
|
+
Object.defineProperty(asString, "constructor", {
|
|
2560
|
+
value: ConnectionModel,
|
|
2561
|
+
enumerable: false
|
|
2562
|
+
});
|
|
2563
|
+
return asString;
|
|
2564
|
+
}
|
|
2565
|
+
toString() {
|
|
2566
|
+
return [
|
|
2567
|
+
this.expression,
|
|
2568
|
+
this.relations.size ? " relations:" : " relations: [ ]",
|
|
2569
|
+
...[...this.relations].map((c) => " " + c.expression)
|
|
2570
|
+
].join("\n");
|
|
2571
|
+
}
|
|
2572
|
+
/**
|
|
2573
|
+
* Creates a new connection with reversed direction (target becomes source and vice versa)
|
|
2574
|
+
* @param search - When true, attempts to find an existing connection between the reversed nodes
|
|
2575
|
+
*/
|
|
2576
|
+
reversed(search = false) {
|
|
2577
|
+
if (!search) {
|
|
2578
|
+
return new ConnectionModel(this.target, this.source);
|
|
2579
|
+
}
|
|
2580
|
+
const [found] = findConnection$1(this.target, this.source, "directed");
|
|
2581
|
+
return found ?? new ConnectionModel(this.target, this.source, /* @__PURE__ */ new Set());
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2585
|
+
function findConnection(source, target, direction = "directed") {
|
|
2586
|
+
if (source === target) {
|
|
2587
|
+
return [];
|
|
2588
|
+
}
|
|
2589
|
+
if (isSameHierarchy(source, target)) {
|
|
2590
|
+
return [];
|
|
2591
|
+
}
|
|
2592
|
+
const directedIntersection = source.allOutgoing.intersect(target.allIncoming);
|
|
2593
|
+
const directed = directedIntersection.nonEmpty ? [
|
|
2594
|
+
new DeploymentConnectionModel(
|
|
2595
|
+
source,
|
|
2596
|
+
target,
|
|
2597
|
+
directedIntersection
|
|
2598
|
+
)
|
|
2599
|
+
] : [];
|
|
2600
|
+
if (direction === "directed") {
|
|
2601
|
+
return directed;
|
|
2602
|
+
}
|
|
2603
|
+
return [
|
|
2604
|
+
...directed,
|
|
2605
|
+
...findConnection(target, source, "directed")
|
|
2606
|
+
];
|
|
2607
|
+
}
|
|
2608
|
+
function findConnectionsBetween(element, others, direction = "both") {
|
|
2609
|
+
if (element.allIncoming.isEmpty && element.allOutgoing.isEmpty) {
|
|
2610
|
+
return [];
|
|
2611
|
+
}
|
|
2612
|
+
const outgoing = [];
|
|
2613
|
+
const incoming = [];
|
|
2614
|
+
for (const _other of others) {
|
|
2615
|
+
if (element === _other) {
|
|
2616
|
+
continue;
|
|
2617
|
+
}
|
|
2618
|
+
for (const found of findConnection(element, _other, direction)) {
|
|
2619
|
+
if (found.source === element) {
|
|
2620
|
+
outgoing.push(found);
|
|
2621
|
+
} else {
|
|
2622
|
+
incoming.push(found);
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
return [
|
|
2627
|
+
...outgoing,
|
|
2628
|
+
...incoming
|
|
2629
|
+
];
|
|
2630
|
+
}
|
|
2631
|
+
function findConnectionsWithin(elements) {
|
|
2632
|
+
return [...elements].reduce((acc, el, index, array) => {
|
|
2633
|
+
if (index === array.length - 1) {
|
|
2634
|
+
return acc;
|
|
2635
|
+
}
|
|
2636
|
+
acc.push(
|
|
2637
|
+
...findConnectionsBetween(el, array.slice(index + 1), "both")
|
|
2638
|
+
);
|
|
2639
|
+
return acc;
|
|
2640
|
+
}, []);
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
const deploymentConnection = {
|
|
2644
|
+
__proto__: null,
|
|
2645
|
+
findConnection: findConnection,
|
|
2646
|
+
findConnectionsBetween: findConnectionsBetween,
|
|
2647
|
+
findConnectionsWithin: findConnectionsWithin
|
|
2648
|
+
};
|
|
2649
|
+
|
|
2650
|
+
function isDeploymentNodeModel(model) {
|
|
2651
|
+
return model instanceof DeploymentNodeModel;
|
|
2652
|
+
}
|
|
2653
|
+
function isDeployedInstanceModel(model) {
|
|
2654
|
+
return model instanceof DeployedInstanceModel;
|
|
2655
|
+
}
|
|
2656
|
+
function isDeploymentElementModel(model) {
|
|
2657
|
+
return isDeploymentNodeModel(model) || isDeployedInstanceModel(model);
|
|
2658
|
+
}
|
|
2659
|
+
function isNestedElementOfDeployedInstanceModel(model) {
|
|
2660
|
+
return model instanceof NestedElementOfDeployedInstanceModel;
|
|
2661
|
+
}
|
|
2662
|
+
function isDeploymentRelationModel(x) {
|
|
2663
|
+
return x instanceof DeploymentRelationModel;
|
|
2664
|
+
}
|
|
2665
|
+
function isRelationModel(x) {
|
|
2666
|
+
return x instanceof RelationshipModel;
|
|
2667
|
+
}
|
|
2668
|
+
function isElementModel(element) {
|
|
2669
|
+
return element instanceof ElementModel;
|
|
2670
|
+
}
|
|
2671
|
+
function isLikeC4ViewModel(view) {
|
|
2672
|
+
return view instanceof LikeC4ViewModel;
|
|
2673
|
+
}
|
|
2674
|
+
function isNodeModel(node) {
|
|
2675
|
+
return node instanceof NodeModel;
|
|
2676
|
+
}
|
|
2677
|
+
function isEdgeModel(edge) {
|
|
2678
|
+
return edge instanceof EdgeModel;
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
export { RelationshipModel as A, hasSameSource as B, Connection as C, DeploymentConnectionModel as D, ElementModel as E, hasSameSourceTarget as F, hasSameTarget as G, sortDeepestFirst as H, DeployedInstanceModel as I, DeploymentNodeModel as J, DeploymentRelationModel as K, LikeC4Model as L, LikeC4DeploymentModel as M, EdgeModel as N, LikeC4ViewModel as O, NodeModel as P, isDeploymentRelationModel as Q, RelationshipsAccum as R, isRelationModel as S, isLikeC4ViewModel as T, isNodeModel as U, isEdgeModel as V, a, t$1 as b, differenceConnections as c, deploymentConnection as d, isNestedConnection as e, findAscendingConnections as f, customInspectSymbol as g, findConnectionsBetween$1 as h, i, isElementModel as j, isNestedElementOfDeployedInstanceModel as k, isDeployedInstanceModel as l, mergeConnections as m, isDeploymentNodeModel as n, isDeploymentElementModel as o, p, modelConnection as q, r, sortConnectionsByBoundaryHierarchy as s, t, findDescendantConnections as u, findDeepestNestedConnection as v, isAnyInOut as w, isOutgoing as x, isIncoming as y, ConnectionModel as z };
|