@likec4/core 1.17.1 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/dist/builder/index.d.mts +502 -0
- package/dist/builder/index.d.ts +502 -0
- package/dist/builder/index.js +871 -0
- package/dist/compute-view/index.d.mts +22 -0
- package/dist/compute-view/index.d.ts +22 -0
- package/dist/compute-view/index.js +78 -0
- package/dist/index.d.mts +10 -919
- package/dist/index.d.ts +10 -919
- package/dist/{index.mjs → index.js} +7 -1688
- package/dist/model/index.d.mts +254 -0
- package/dist/model/index.d.ts +254 -0
- package/dist/model/index.js +1 -0
- package/dist/shared/chunk-RAAYCPUM.M-JWF7SS.js +21 -0
- package/dist/shared/core.BIfgabEw.d.ts +719 -0
- package/dist/shared/core.BZ9Msq7w.d.mts +719 -0
- package/dist/shared/{core.CoLQkcYL.d.cts → core.C05RDLhi.d.ts} +460 -362
- package/dist/shared/{core.CoLQkcYL.d.mts → core.CmYMqgha.d.mts} +460 -362
- package/dist/shared/core.D6-fWbM3.d.ts +127 -0
- package/dist/shared/core.D74xkKkG.d.mts +148 -0
- package/dist/shared/core.D74xkKkG.d.ts +148 -0
- package/dist/shared/core.sLaWHBjR.d.mts +127 -0
- package/dist/shared/index.-BdzdI2C.js +7443 -0
- package/dist/shared/object_hash.CE_oF3I3.js +1222 -0
- package/dist/shared/{core.BXTs-8n7.mjs → view.CyZrG8BJ.js} +108 -47
- package/dist/types/index.d.mts +71 -1
- package/dist/types/index.d.ts +71 -1
- package/dist/types/index.js +20 -0
- package/dist/utils/index.d.mts +404 -0
- package/dist/utils/index.d.ts +404 -0
- package/dist/utils/index.js +330 -0
- package/package.json +59 -47
- package/src/builder/Builder-style1.test-d.ts +190 -0
- package/src/builder/Builder-style2.test-d.ts +175 -0
- package/src/builder/Builder.deployment.ts +224 -0
- package/src/builder/Builder.deploymentModel.ts +385 -0
- package/src/builder/Builder.element.ts +19 -19
- package/src/builder/Builder.model.ts +67 -42
- package/src/builder/Builder.ts +533 -367
- package/src/builder/Builder.view-common.ts +234 -0
- package/src/builder/Builder.view-deployment.ts +135 -0
- package/src/builder/Builder.view-element.ts +189 -0
- package/src/builder/Builder.views.ts +109 -33
- package/src/builder/Builder.with.ts +475 -0
- package/src/builder/__snapshots__/Builder-style1.spec.ts.snap +374 -0
- package/src/builder/__snapshots__/Builder-style2.spec.ts.snap +183 -0
- package/src/builder/__snapshots__/Builder.spec.ts.snap +374 -0
- package/src/builder/_types.ts +113 -18
- package/src/builder/index.ts +10 -4
- package/src/colors/index.ts +2 -2
- package/src/compute-view/compute-view.ts +83 -0
- package/src/compute-view/deployment-view/__test__/TestHelper.ts +236 -0
- package/src/compute-view/deployment-view/__test__/fixture.ts +198 -0
- package/src/compute-view/deployment-view/_types.ts +37 -0
- package/src/compute-view/deployment-view/clean-connections.ts +238 -0
- package/src/compute-view/deployment-view/compute.ts +125 -0
- package/src/compute-view/deployment-view/memory/index.ts +9 -0
- package/src/compute-view/deployment-view/memory/memory.ts +97 -0
- package/src/compute-view/deployment-view/predicates/elements.ts +144 -0
- package/src/compute-view/deployment-view/predicates/relation-direct.ts +237 -0
- package/src/compute-view/deployment-view/predicates/relation-in-out.ts +61 -0
- package/src/compute-view/deployment-view/predicates/relation-incoming.ts +95 -0
- package/src/compute-view/deployment-view/predicates/relation-outgoing.ts +88 -0
- package/src/compute-view/deployment-view/predicates/wildcard.ts +29 -0
- package/src/compute-view/deployment-view/stages/stage-exclude.ts +5 -0
- package/src/compute-view/deployment-view/stages/stage-final.ts +245 -0
- package/src/compute-view/deployment-view/stages/stage-include.ts +95 -0
- package/src/compute-view/deployment-view/utils.ts +276 -0
- package/src/compute-view/dynamic-view/__test__/fixture.ts +58 -0
- package/src/compute-view/dynamic-view/compute.ts +324 -0
- package/src/compute-view/element-view/__test__/TestHelper.ts +258 -0
- package/src/compute-view/element-view/__test__/__snapshots__/legacy.spec.ts.snap +458 -0
- package/src/compute-view/element-view/__test__/fixture.ts +675 -0
- package/src/compute-view/element-view/_types.ts +48 -0
- package/src/compute-view/element-view/clean-connections.ts +113 -0
- package/src/compute-view/element-view/compute.ts +315 -0
- package/src/compute-view/element-view/memory/NodeGroup.ts +31 -0
- package/src/compute-view/element-view/memory/index.ts +10 -0
- package/src/compute-view/element-view/memory/memory.ts +177 -0
- package/src/compute-view/element-view/memory/stage-exclude.ts +59 -0
- package/src/compute-view/element-view/memory/stage-final.ts +129 -0
- package/src/compute-view/element-view/memory/stage-include.ts +128 -0
- package/src/compute-view/element-view/predicates/_utils.ts +86 -0
- package/src/compute-view/element-view/predicates/element-expand.ts +33 -0
- package/src/compute-view/element-view/predicates/element-kind-tag.ts +26 -0
- package/src/compute-view/element-view/predicates/element-ref.ts +37 -0
- package/src/compute-view/element-view/predicates/relation-direct.ts +309 -0
- package/src/compute-view/element-view/predicates/relation-in-out.ts +66 -0
- package/src/compute-view/element-view/predicates/relation-in.ts +118 -0
- package/src/compute-view/element-view/predicates/relation-out.ts +116 -0
- package/src/compute-view/element-view/predicates/wildcard.ts +88 -0
- package/src/compute-view/element-view/utils.ts +59 -0
- package/src/compute-view/index.ts +4 -0
- package/src/compute-view/memory/AbstractMemory.ts +45 -0
- package/src/compute-view/memory/AbstractStageExclude.ts +216 -0
- package/src/compute-view/memory/AbstractStageInclude.ts +200 -0
- package/src/compute-view/memory/_types.ts +147 -0
- package/src/compute-view/memory/index.ts +5 -0
- package/src/compute-view/memory/ops.ts +43 -0
- package/src/compute-view/utils/__snapshots__/sortNodes.spec.ts.snap +113 -0
- package/src/compute-view/utils/ancestorsOfNode.ts +19 -0
- package/src/compute-view/utils/applyCustomElementProperties.ts +66 -0
- package/src/compute-view/utils/applyCustomRelationProperties.ts +41 -0
- package/src/compute-view/utils/applyViewRuleStyles.ts +53 -0
- package/src/compute-view/utils/buildComputedNodes.ts +148 -0
- package/src/compute-view/utils/buildElementNotations.ts +63 -0
- package/src/compute-view/utils/elementExpressionToPredicate.ts +50 -0
- package/src/compute-view/utils/link-nodes-with-edges.ts +54 -0
- package/src/compute-view/utils/merge-props-from-relationships.ts +128 -0
- package/src/compute-view/utils/relationExpressionToPredicates.ts +55 -0
- package/src/compute-view/utils/resolve-extended-views.ts +69 -0
- package/src/compute-view/utils/resolve-global-rules.ts +83 -0
- package/src/compute-view/utils/topological-sort.ts +256 -0
- package/src/compute-view/utils/uniqueTags.test.ts +42 -0
- package/src/compute-view/utils/uniqueTags.ts +21 -0
- package/src/compute-view/utils/view-hash.ts +27 -0
- package/src/compute-view/utils/with-readable-edges.ts +36 -0
- package/src/index.ts +22 -12
- package/src/model/DeploymentElementModel.ts +599 -0
- package/src/model/DeploymentModel.ts +370 -0
- package/src/model/ElementModel.ts +221 -0
- package/src/model/LikeC4Model.test-d.ts +152 -0
- package/src/model/LikeC4Model.ts +336 -457
- package/src/model/RelationModel.ts +85 -0
- package/src/model/__test__/fixture.ts +149 -338
- package/src/model/connection/Connection.ts +72 -0
- package/src/model/connection/deployment/DeploymentConnectionModel.ts +178 -0
- package/src/model/connection/deployment/find.ts +120 -0
- package/src/model/connection/deployment/index.ts +8 -0
- package/src/model/connection/index.ts +22 -0
- package/src/model/connection/model/ConnectionModel.ts +157 -0
- package/src/model/connection/model/find.ts +117 -0
- package/src/model/connection/model/index.ts +8 -0
- package/src/model/connection/ops.ts +274 -0
- package/src/model/guards.ts +9 -0
- package/src/model/index.ts +46 -2
- package/src/model/types.ts +112 -15
- package/src/model/view/EdgeModel.ts +103 -0
- package/src/model/view/LikeC4ViewModel.ts +226 -0
- package/src/model/view/NodeModel.ts +229 -0
- package/src/types/_common.ts +55 -1
- package/src/types/deployments.ts +169 -0
- package/src/types/element.ts +9 -5
- package/src/types/expression-v2.ts +172 -0
- package/src/types/expression.ts +17 -11
- package/src/types/global.ts +3 -3
- package/src/types/index.ts +220 -12
- package/src/types/model.ts +75 -12
- package/src/types/operators.ts +1 -1
- package/src/types/overview-graph.ts +2 -2
- package/src/types/relation.ts +18 -14
- package/src/types/theme.ts +0 -1
- package/src/types/view-notation.ts +2 -2
- package/src/types/view.ts +117 -41
- package/src/utils/{commonHead.ts → common-head.ts} +2 -0
- package/src/utils/const.ts +1 -0
- package/src/utils/fqn.ts +203 -56
- package/src/utils/getOrCreate.ts +42 -0
- package/src/utils/graphlib.ts +10 -0
- package/src/utils/index.ts +74 -6
- package/src/utils/iterable/_types.ts +5 -0
- package/src/utils/iterable/filter.ts +55 -0
- package/src/utils/iterable/flat.ts +17 -0
- package/src/utils/iterable/index.ts +12 -0
- package/src/utils/iterable/map.ts +40 -0
- package/src/utils/iterable/reduce.ts +24 -0
- package/src/utils/iterable/some.ts +36 -0
- package/src/utils/iterable/to.ts +19 -0
- package/src/utils/iterable/unique.ts +29 -0
- package/src/utils/object-hash.ts +3 -0
- package/src/utils/relations.ts +3 -64
- package/src/utils/set.ts +78 -0
- package/src/utils/string-hash.ts +16 -0
- package/dist/index.cjs +0 -5452
- package/dist/index.d.cts +0 -1053
- package/dist/shared/core.CoLQkcYL.d.ts +0 -734
- package/dist/shared/core.DIYZvpVg.cjs +0 -353
- package/dist/types/index.cjs +0 -70
- package/dist/types/index.d.cts +0 -2
- package/dist/types/index.mjs +0 -1
- package/src/builder/Builder.test-d.ts +0 -69
- package/src/builder/Builder.view.ts +0 -358
- package/src/model/LikeC4DiagramModel.ts +0 -338
- package/src/model/LikeC4ViewModel.ts +0 -320
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import DefaultMap from 'mnemonist/default-map'
|
|
2
|
+
import {
|
|
3
|
+
dropWhile,
|
|
4
|
+
filter,
|
|
5
|
+
flatMap,
|
|
6
|
+
forEach,
|
|
7
|
+
groupBy,
|
|
8
|
+
hasAtLeast,
|
|
9
|
+
map,
|
|
10
|
+
pipe,
|
|
11
|
+
piped,
|
|
12
|
+
prop,
|
|
13
|
+
reduce,
|
|
14
|
+
values,
|
|
15
|
+
} from 'remeda'
|
|
16
|
+
import { invariant } from '../../errors'
|
|
17
|
+
import {
|
|
18
|
+
DeploymentConnectionModel,
|
|
19
|
+
differenceConnections,
|
|
20
|
+
isNestedConnection,
|
|
21
|
+
mergeConnections,
|
|
22
|
+
sortConnectionsByBoundaryHierarchy,
|
|
23
|
+
} from '../../model/connection'
|
|
24
|
+
import { findConnection } from '../../model/connection/deployment'
|
|
25
|
+
import { RelationshipsAccum } from '../../model/DeploymentElementModel'
|
|
26
|
+
import type { RelationshipModel } from '../../model/RelationModel'
|
|
27
|
+
import { imap, toArray } from '../../utils/iterable'
|
|
28
|
+
import { intersection, union } from '../../utils/set'
|
|
29
|
+
import type { Connection, Connections } from './_types'
|
|
30
|
+
|
|
31
|
+
const filterEmptyConnection = filter((c: DeploymentConnectionModel<any>) => c.nonEmpty())
|
|
32
|
+
|
|
33
|
+
type MapOfExcludesFromConnection = DefaultMap<DeploymentConnectionModel, Set<RelationshipModel>>
|
|
34
|
+
function findCrossBoundarySameSourceOrTarget(connections: Connections): MapOfExcludesFromConnection {
|
|
35
|
+
// Also find connections based on same relation
|
|
36
|
+
const groupedByRelation = new DefaultMap<RelationshipModel, Array<DeploymentConnectionModel>>(() => [])
|
|
37
|
+
for (const conn of connections) {
|
|
38
|
+
for (const relation of conn.relations.model) {
|
|
39
|
+
groupedByRelation.get(relation).push(conn)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Aggreate excluded relations for each connection
|
|
44
|
+
const excludedRelations = new DefaultMap<
|
|
45
|
+
DeploymentConnectionModel,
|
|
46
|
+
Set<RelationshipModel>
|
|
47
|
+
>(() => new Set())
|
|
48
|
+
|
|
49
|
+
// In each group, find connected to same leaf
|
|
50
|
+
for (const [relation, sameRelationGroup] of groupedByRelation) {
|
|
51
|
+
if (!hasAtLeast(sameRelationGroup, 2)) {
|
|
52
|
+
continue
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Outgoing from same source
|
|
56
|
+
pipe(
|
|
57
|
+
sameRelationGroup,
|
|
58
|
+
flatMap(conn => [
|
|
59
|
+
{ group: `$source-${conn.source.id}`, conn },
|
|
60
|
+
{ group: `$target-${conn.target.id}`, conn },
|
|
61
|
+
]),
|
|
62
|
+
groupBy(prop('group')),
|
|
63
|
+
values(),
|
|
64
|
+
filter(hasAtLeast(2)),
|
|
65
|
+
forEach(
|
|
66
|
+
// In each group, sort by hierarchy, first are deepest
|
|
67
|
+
piped(
|
|
68
|
+
map(prop('conn')),
|
|
69
|
+
sortConnectionsByBoundaryHierarchy('desc'),
|
|
70
|
+
// Drop first, as it is the deepest
|
|
71
|
+
// Drop if boundary is same as previous
|
|
72
|
+
dropWhile((conn, i, all) => i === 0 || conn.boundary === all[i - 1]!.boundary),
|
|
73
|
+
// Drop relations from boundaries above
|
|
74
|
+
forEach((conn) => {
|
|
75
|
+
excludedRelations.get(conn).add(relation)
|
|
76
|
+
}),
|
|
77
|
+
),
|
|
78
|
+
),
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return excludedRelations
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Identifies and processes cross-boundary connections in a deployment diagram.
|
|
87
|
+
* This function analyzes connections between nodes and removes redundant relations
|
|
88
|
+
* when multiple connections exist between the same elements across different boundaries.
|
|
89
|
+
*
|
|
90
|
+
* The function performs the following:
|
|
91
|
+
* 1. Groups connections by their relationship type
|
|
92
|
+
* 2. For each relationship group, identifies connections that:
|
|
93
|
+
* - Share the same source
|
|
94
|
+
* - Share the same target
|
|
95
|
+
* 3. When multiple connections are found in the same group, keeps only the most specific
|
|
96
|
+
* (deepest in hierarchy) connection and removes the relationship from others
|
|
97
|
+
*
|
|
98
|
+
* @param connections - The input collection of deployment connections to analyze
|
|
99
|
+
* @returns connections with redundant cross-boundary relationships
|
|
100
|
+
*/
|
|
101
|
+
export function findCrossBoundaryConnections(connections: Connections): Array<Connection> {
|
|
102
|
+
// Ensure connections are merged
|
|
103
|
+
connections = mergeConnections(connections)
|
|
104
|
+
|
|
105
|
+
const excludedRelations = findCrossBoundarySameSourceOrTarget(connections)
|
|
106
|
+
|
|
107
|
+
for (const c of connections) {
|
|
108
|
+
const { source, target } = c
|
|
109
|
+
const connectionModelRelations = c.relations.model
|
|
110
|
+
// Connections between node and instance
|
|
111
|
+
// Exclude internal relations of the node (otherwise they are cross-boundary)
|
|
112
|
+
if (source.isDeploymentNode() !== target.isDeploymentNode()) {
|
|
113
|
+
const node = source.isDeploymentNode() ? source : c.target
|
|
114
|
+
invariant(node.isDeploymentNode())
|
|
115
|
+
const nodeInternals = node.internalModelRelationships()
|
|
116
|
+
const toExclude = intersection(
|
|
117
|
+
connectionModelRelations,
|
|
118
|
+
nodeInternals,
|
|
119
|
+
)
|
|
120
|
+
for (const relation of toExclude) {
|
|
121
|
+
excludedRelations.get(c).add(relation)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Connection between nodes
|
|
128
|
+
if (source.isDeploymentNode() && target.isDeploymentNode()) {
|
|
129
|
+
const toExclude = union(
|
|
130
|
+
// Exclude node internals (otherwise thay are cross-boundary)
|
|
131
|
+
intersection(
|
|
132
|
+
c.relations.model,
|
|
133
|
+
source.internalModelRelationships(),
|
|
134
|
+
),
|
|
135
|
+
intersection(
|
|
136
|
+
c.relations.model,
|
|
137
|
+
target.internalModelRelationships(),
|
|
138
|
+
),
|
|
139
|
+
)
|
|
140
|
+
for (const relation of toExclude) {
|
|
141
|
+
excludedRelations.get(c).add(relation)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return pipe(
|
|
147
|
+
excludedRelations.entries(),
|
|
148
|
+
imap(([c, excluded]) =>
|
|
149
|
+
c.update({
|
|
150
|
+
model: excluded,
|
|
151
|
+
deployment: null,
|
|
152
|
+
})
|
|
153
|
+
),
|
|
154
|
+
toArray(),
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Cleans connections that cross boundaries by removing overlapping parts.
|
|
160
|
+
* @example
|
|
161
|
+
* ```ts
|
|
162
|
+
* const connections = getConnections();
|
|
163
|
+
* const cleanedConnections = cleanCrossBoundary(connections);
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
export function cleanCrossBoundary(connections: Connections): Array<Connection> {
|
|
167
|
+
return differenceConnections(
|
|
168
|
+
connections,
|
|
169
|
+
findCrossBoundaryConnections(connections),
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Identifies and returns redundant connections in a deployment view.
|
|
175
|
+
* A connection is considered redundant if its relationships are already implied
|
|
176
|
+
* through other connections
|
|
177
|
+
*
|
|
178
|
+
* @returns Array of redundant connections that can be safely removed
|
|
179
|
+
*/
|
|
180
|
+
export function findRedundantConnections(connections: Connections): Array<Connection> {
|
|
181
|
+
return pipe(
|
|
182
|
+
connections,
|
|
183
|
+
mergeConnections,
|
|
184
|
+
reduce((redundants, connection) => {
|
|
185
|
+
const { source, target, relations } = connection
|
|
186
|
+
|
|
187
|
+
// Connection between instances is always explicit
|
|
188
|
+
if (source.isInstance() && target.isInstance()) {
|
|
189
|
+
return redundants
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
let redundantAccum = RelationshipsAccum.empty()
|
|
193
|
+
|
|
194
|
+
if (source.isDeploymentNode() && target.isDeploymentNode()) {
|
|
195
|
+
const [reversed] = findConnection(target, source, 'directed')
|
|
196
|
+
if (reversed) {
|
|
197
|
+
redundantAccum = relations.intersect(reversed.relations)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
for (const c of connections) {
|
|
202
|
+
if (isNestedConnection(c, connection)) {
|
|
203
|
+
redundantAccum = redundantAccum.union(
|
|
204
|
+
relations.intersect(c.relations),
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (redundantAccum.nonEmpty) {
|
|
210
|
+
redundants.push(
|
|
211
|
+
new DeploymentConnectionModel(
|
|
212
|
+
source,
|
|
213
|
+
target,
|
|
214
|
+
redundantAccum,
|
|
215
|
+
),
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return redundants
|
|
220
|
+
}, [] as DeploymentConnectionModel[]),
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Remove relationships from connection model, that are already included in the connections between descendants.
|
|
226
|
+
* In other words - if there is same connection down the hierarchy.
|
|
227
|
+
*
|
|
228
|
+
* @returns New connection without redundant relationships
|
|
229
|
+
* Connection may be empty if all relationships are redundant, in this case it should be removed
|
|
230
|
+
*/
|
|
231
|
+
export function cleanRedundantRelationships(
|
|
232
|
+
connections: Connections,
|
|
233
|
+
): Array<Connection> {
|
|
234
|
+
return differenceConnections(
|
|
235
|
+
connections,
|
|
236
|
+
findRedundantConnections(connections),
|
|
237
|
+
)
|
|
238
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { findLast, map } from 'remeda'
|
|
2
|
+
import { nonexhaustive } from '../../errors'
|
|
3
|
+
import { type LikeC4DeploymentModel, LikeC4Model } from '../../model'
|
|
4
|
+
import type { AnyAux } from '../../model/types'
|
|
5
|
+
import type { DeploymentViewRule } from '../../types'
|
|
6
|
+
import {
|
|
7
|
+
type ComputedDeploymentView,
|
|
8
|
+
type DeploymentView,
|
|
9
|
+
FqnExpr,
|
|
10
|
+
isViewRuleAutoLayout,
|
|
11
|
+
isViewRulePredicate,
|
|
12
|
+
} from '../../types'
|
|
13
|
+
import { RelationExpr } from '../../types/expression-v2'
|
|
14
|
+
import { buildElementNotations } from '../utils/buildElementNotations'
|
|
15
|
+
import { linkNodesWithEdges } from '../utils/link-nodes-with-edges'
|
|
16
|
+
import { topologicalSort } from '../utils/topological-sort'
|
|
17
|
+
import { calcViewLayoutHash } from '../utils/view-hash'
|
|
18
|
+
import { Memory } from './memory'
|
|
19
|
+
import { DeploymentRefPredicate } from './predicates/elements'
|
|
20
|
+
import { DirectRelationPredicate } from './predicates/relation-direct'
|
|
21
|
+
import { InOutRelationPredicate } from './predicates/relation-in-out'
|
|
22
|
+
import { IncomingRelationPredicate } from './predicates/relation-incoming'
|
|
23
|
+
import { OutgoingRelationPredicate } from './predicates/relation-outgoing'
|
|
24
|
+
import { WildcardPredicate } from './predicates/wildcard'
|
|
25
|
+
import { StageFinal } from './stages/stage-final'
|
|
26
|
+
import { applyDeploymentViewRuleStyles, buildNodes, toComputedEdges } from './utils'
|
|
27
|
+
|
|
28
|
+
export function processPredicates<M extends AnyAux>(
|
|
29
|
+
model: LikeC4DeploymentModel<M>,
|
|
30
|
+
rules: DeploymentViewRule[],
|
|
31
|
+
) {
|
|
32
|
+
let memory = Memory.empty()
|
|
33
|
+
|
|
34
|
+
for (const rule of rules) {
|
|
35
|
+
if (isViewRulePredicate(rule)) {
|
|
36
|
+
const op = 'include' in rule ? 'include' : 'exclude'
|
|
37
|
+
const exprs = rule.include ?? rule.exclude
|
|
38
|
+
for (const expr of exprs) {
|
|
39
|
+
let stage = op === 'include' ? memory.stageInclude(expr) : memory.stageExclude(expr)
|
|
40
|
+
const ctx = { model, stage, memory }
|
|
41
|
+
switch (true) {
|
|
42
|
+
case FqnExpr.isModelRef(expr):
|
|
43
|
+
// Ignore model refs in deployment view
|
|
44
|
+
break
|
|
45
|
+
case FqnExpr.isDeploymentRef(expr):
|
|
46
|
+
stage = DeploymentRefPredicate[op]({ ...ctx, expr } as any) ?? stage
|
|
47
|
+
break
|
|
48
|
+
case FqnExpr.isWildcard(expr):
|
|
49
|
+
stage = WildcardPredicate[op]({ ...ctx, expr } as any) ?? stage
|
|
50
|
+
break
|
|
51
|
+
case RelationExpr.isDirect(expr):
|
|
52
|
+
stage = DirectRelationPredicate[op]({ ...ctx, expr } as any) ?? stage
|
|
53
|
+
break
|
|
54
|
+
case RelationExpr.isInOut(expr):
|
|
55
|
+
stage = InOutRelationPredicate[op]({ ...ctx, expr } as any) ?? stage
|
|
56
|
+
break
|
|
57
|
+
case RelationExpr.isOutgoing(expr):
|
|
58
|
+
stage = OutgoingRelationPredicate[op]({ ...ctx, expr } as any) ?? stage
|
|
59
|
+
break
|
|
60
|
+
case RelationExpr.isIncoming(expr):
|
|
61
|
+
stage = IncomingRelationPredicate[op]({ ...ctx, expr } as any) ?? stage
|
|
62
|
+
break
|
|
63
|
+
default:
|
|
64
|
+
nonexhaustive(expr)
|
|
65
|
+
}
|
|
66
|
+
memory = stage.commit()
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return StageFinal.for(memory).commit()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function computeDeploymentView<M extends AnyAux>(
|
|
74
|
+
likec4model: LikeC4Model<M>,
|
|
75
|
+
{
|
|
76
|
+
docUri: _docUri, // exclude docUri
|
|
77
|
+
rules, // exclude rules
|
|
78
|
+
...view
|
|
79
|
+
}: DeploymentView,
|
|
80
|
+
): ComputedDeploymentView<M['ViewId']> {
|
|
81
|
+
let memory = processPredicates<M>(likec4model.deployment, rules)
|
|
82
|
+
|
|
83
|
+
const nodesMap = buildNodes(memory)
|
|
84
|
+
|
|
85
|
+
const computedEdges = toComputedEdges(memory.connections)
|
|
86
|
+
|
|
87
|
+
linkNodesWithEdges(nodesMap, computedEdges)
|
|
88
|
+
|
|
89
|
+
const sorted = topologicalSort({
|
|
90
|
+
nodes: nodesMap,
|
|
91
|
+
edges: computedEdges,
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const nodes = applyDeploymentViewRuleStyles(
|
|
95
|
+
rules,
|
|
96
|
+
sorted.nodes,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
const autoLayoutRule = findLast(rules, isViewRuleAutoLayout)
|
|
100
|
+
|
|
101
|
+
const elementNotations = buildElementNotations(nodes)
|
|
102
|
+
|
|
103
|
+
return calcViewLayoutHash({
|
|
104
|
+
...view,
|
|
105
|
+
autoLayout: {
|
|
106
|
+
direction: autoLayoutRule?.direction ?? 'TB',
|
|
107
|
+
...(autoLayoutRule?.nodeSep && { nodeSep: autoLayoutRule.nodeSep }),
|
|
108
|
+
...(autoLayoutRule?.rankSep && { rankSep: autoLayoutRule.rankSep }),
|
|
109
|
+
},
|
|
110
|
+
edges: sorted.edges,
|
|
111
|
+
nodes: map(nodes, n => {
|
|
112
|
+
// omit notation
|
|
113
|
+
delete n.notation
|
|
114
|
+
if (n.icon === 'none') {
|
|
115
|
+
delete n.icon
|
|
116
|
+
}
|
|
117
|
+
return n
|
|
118
|
+
}),
|
|
119
|
+
...(elementNotations.length > 0 && {
|
|
120
|
+
notation: {
|
|
121
|
+
elements: elementNotations,
|
|
122
|
+
},
|
|
123
|
+
}),
|
|
124
|
+
})
|
|
125
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { StageExclude } from '../stages/stage-exclude'
|
|
2
|
+
import type { StageInclude } from '../stages/stage-include'
|
|
3
|
+
|
|
4
|
+
export { StageExclude } from '../stages/stage-exclude'
|
|
5
|
+
export { StageInclude } from '../stages/stage-include'
|
|
6
|
+
export { Memory } from './memory'
|
|
7
|
+
export type { Ctx } from './memory'
|
|
8
|
+
|
|
9
|
+
export type Stage = StageInclude | StageExclude
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { isDeepEqual } from 'remeda'
|
|
2
|
+
import { type DeploymentConnectionModel, differenceConnections } from '../../../model/connection'
|
|
3
|
+
import type { DeploymentElementModel } from '../../../model/DeploymentElementModel'
|
|
4
|
+
import type { AnyAux } from '../../../model/types'
|
|
5
|
+
import type { ExpressionV2 } from '../../../types'
|
|
6
|
+
import { difference as differenceSet } from '../../../utils'
|
|
7
|
+
import { customInspectSymbol } from '../../../utils/const'
|
|
8
|
+
import { toArray } from '../../../utils/iterable'
|
|
9
|
+
import { AbstractMemory, type ComputeCtx, type StageExpression } from '../../memory'
|
|
10
|
+
import { StageExclude } from '../stages/stage-exclude'
|
|
11
|
+
import { StageInclude } from '../stages/stage-include'
|
|
12
|
+
|
|
13
|
+
export type Ctx = ComputeCtx<
|
|
14
|
+
DeploymentElementModel<AnyAux>,
|
|
15
|
+
DeploymentConnectionModel<AnyAux>,
|
|
16
|
+
Memory,
|
|
17
|
+
StageInclude,
|
|
18
|
+
StageExclude,
|
|
19
|
+
ExpressionV2
|
|
20
|
+
>
|
|
21
|
+
|
|
22
|
+
export class Memory extends AbstractMemory<Ctx> {
|
|
23
|
+
static empty(): Memory {
|
|
24
|
+
return new Memory({
|
|
25
|
+
elements: new Set(),
|
|
26
|
+
explicits: new Set(),
|
|
27
|
+
final: new Set(),
|
|
28
|
+
connections: [],
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
override stageInclude(expr: StageExpression<Ctx>): StageInclude {
|
|
33
|
+
return new StageInclude(this, expr)
|
|
34
|
+
}
|
|
35
|
+
override stageExclude(expr: StageExpression<Ctx>): StageExclude {
|
|
36
|
+
return new StageExclude(this, expr)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
override mutableState(): Ctx['MutableState'] {
|
|
40
|
+
return ({
|
|
41
|
+
elements: new Set(this.state.elements),
|
|
42
|
+
explicits: new Set(this.state.explicits),
|
|
43
|
+
final: new Set(this.state.final),
|
|
44
|
+
connections: [...this.state.connections],
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
override update(newstate: Partial<Ctx['MutableState']>): Memory {
|
|
49
|
+
return new Memory({
|
|
50
|
+
...this.state,
|
|
51
|
+
...newstate,
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
equals(other: unknown): boolean {
|
|
56
|
+
return other instanceof Memory && isDeepEqual(this.state, other.state)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
diff(state: Memory) {
|
|
60
|
+
const current = this
|
|
61
|
+
return {
|
|
62
|
+
added: {
|
|
63
|
+
elements: toArray(differenceSet(state.elements, current.elements)),
|
|
64
|
+
explicits: toArray(differenceSet(state.explicits, current.explicits)),
|
|
65
|
+
final: toArray(differenceSet(state.final, current.final)),
|
|
66
|
+
connections: toArray(differenceConnections(state.connections, current.connections)),
|
|
67
|
+
},
|
|
68
|
+
removed: {
|
|
69
|
+
elements: toArray(differenceSet(current.elements, state.elements)),
|
|
70
|
+
explicits: toArray(differenceSet(current.explicits, state.explicits)),
|
|
71
|
+
final: toArray(differenceSet(current.final, state.final)),
|
|
72
|
+
connections: differenceConnections(current.connections, state.connections),
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public override toString(): string {
|
|
78
|
+
return [
|
|
79
|
+
'final:',
|
|
80
|
+
...[...this.final].map(e => ' ' + e.id),
|
|
81
|
+
'connections:',
|
|
82
|
+
...this.connections.map(c => ' ' + c.expression),
|
|
83
|
+
].join('\n')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
[customInspectSymbol](_depth: unknown, _inspectOptions: unknown, _inspect: unknown) {
|
|
87
|
+
const asString = this.toString()
|
|
88
|
+
|
|
89
|
+
// // Trick so that node displays the name of the constructor
|
|
90
|
+
// Object.defineProperty(asString, 'constructor', {
|
|
91
|
+
// value: this.constructor,
|
|
92
|
+
// enumerable: false,
|
|
93
|
+
// })
|
|
94
|
+
|
|
95
|
+
return asString
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { pipe, reduce } from 'remeda'
|
|
2
|
+
import { isDeployedInstance } from '../../../model'
|
|
3
|
+
import { findConnectionsBetween, findConnectionsWithin } from '../../../model/connection/deployment'
|
|
4
|
+
import type { DeploymentElementModel, DeploymentNodeModel } from '../../../model/DeploymentElementModel'
|
|
5
|
+
import type { FqnExpr } from '../../../types'
|
|
6
|
+
import type { Elem, IncludePredicateCtx, PredicateExecutor } from '../_types'
|
|
7
|
+
import { cleanCrossBoundary, cleanRedundantRelationships } from '../clean-connections'
|
|
8
|
+
import type { StageInclude } from '../memory'
|
|
9
|
+
import { deploymentExpressionToPredicate } from '../utils'
|
|
10
|
+
|
|
11
|
+
export const DeploymentRefPredicate: PredicateExecutor<FqnExpr.DeploymentRef> = {
|
|
12
|
+
include: (ctx) => {
|
|
13
|
+
const { expr } = ctx
|
|
14
|
+
const el = ctx.model.element(expr.ref.deployment)
|
|
15
|
+
|
|
16
|
+
if (isDeployedInstance(el)) {
|
|
17
|
+
ctx.stage.addExplicit(el)
|
|
18
|
+
ctx.stage.connectWithExisting(el)
|
|
19
|
+
return ctx.stage
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
switch (true) {
|
|
23
|
+
case expr.selector === 'expanded':
|
|
24
|
+
includeDeployedNodeWithExpanded(el, ctx)
|
|
25
|
+
break
|
|
26
|
+
case expr.selector === 'children':
|
|
27
|
+
includeDeployedNodeChildren(el, ctx)
|
|
28
|
+
break
|
|
29
|
+
case expr.selector === 'descendants':
|
|
30
|
+
includeDeployedNodeDescendants(el, ctx)
|
|
31
|
+
break
|
|
32
|
+
default: {
|
|
33
|
+
ctx.stage.addExplicit(el)
|
|
34
|
+
ctx.stage.connectWithExisting(el)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return ctx.stage
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
exclude: ({ expr, stage, memory }) => {
|
|
41
|
+
const exprPredicate = deploymentExpressionToPredicate(expr)
|
|
42
|
+
stage.exclude([...memory.elements].filter(exprPredicate))
|
|
43
|
+
return stage
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* include node.*
|
|
49
|
+
*/
|
|
50
|
+
function includeDeployedNodeChildren(
|
|
51
|
+
node: DeploymentNodeModel,
|
|
52
|
+
{ stage }: IncludePredicateCtx,
|
|
53
|
+
) {
|
|
54
|
+
const children = [...node.children()]
|
|
55
|
+
if (children.length === 0) {
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
stage.addImplicit(node)
|
|
59
|
+
|
|
60
|
+
stage.addConnections(findConnectionsWithin(children))
|
|
61
|
+
stage.connectWithExisting(children)
|
|
62
|
+
|
|
63
|
+
stage.addExplicit(children)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* include node._
|
|
68
|
+
*/
|
|
69
|
+
function includeDeployedNodeWithExpanded(
|
|
70
|
+
node: DeploymentNodeModel,
|
|
71
|
+
{ memory, stage }: IncludePredicateCtx,
|
|
72
|
+
) {
|
|
73
|
+
const children = [...node.children()]
|
|
74
|
+
stage.addImplicit(node)
|
|
75
|
+
stage.connectWithExisting(node)
|
|
76
|
+
|
|
77
|
+
let hasConnectionsWithVisible = false
|
|
78
|
+
for (const child of children) {
|
|
79
|
+
if (findConnectionsBetween(child, memory.elements).length > 0) {
|
|
80
|
+
hasConnectionsWithVisible = true
|
|
81
|
+
break
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (hasConnectionsWithVisible) {
|
|
86
|
+
stage.connectWithExisting(children, 'in')
|
|
87
|
+
stage.addConnections(findConnectionsWithin(children))
|
|
88
|
+
stage.connectWithExisting(children, 'out')
|
|
89
|
+
}
|
|
90
|
+
stage.addImplicit(children)
|
|
91
|
+
|
|
92
|
+
if (stage.connections.length > 0) {
|
|
93
|
+
stage.addExplicit(node)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* include node.**
|
|
99
|
+
*/
|
|
100
|
+
function includeDeployedNodeDescendants(
|
|
101
|
+
node: DeploymentNodeModel,
|
|
102
|
+
{ stage }: IncludePredicateCtx,
|
|
103
|
+
) {
|
|
104
|
+
const dfs = (node: DeploymentNodeModel): DeploymentElementModel[] => {
|
|
105
|
+
const children = [] as DeploymentElementModel[]
|
|
106
|
+
for (const child of node.children()) {
|
|
107
|
+
if (child.isDeploymentNode()) {
|
|
108
|
+
children.push(...dfs(child))
|
|
109
|
+
}
|
|
110
|
+
children.push(child)
|
|
111
|
+
}
|
|
112
|
+
stage.connectWithExisting(children, 'in')
|
|
113
|
+
stage.addConnections(findConnectionsWithin(children))
|
|
114
|
+
stage.addImplicit(children)
|
|
115
|
+
return children
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const descendants = dfs(node)
|
|
119
|
+
if (descendants.length === 0) {
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
stage.connectWithExisting(descendants, 'out')
|
|
123
|
+
// stage.addImplicit(descendants)
|
|
124
|
+
|
|
125
|
+
const allconnected = findConnectedElements(stage)
|
|
126
|
+
descendants.forEach((desc) => {
|
|
127
|
+
if (allconnected.has(desc)) {
|
|
128
|
+
stage.addExplicit(desc)
|
|
129
|
+
}
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function findConnectedElements(stage: StageInclude) {
|
|
134
|
+
return pipe(
|
|
135
|
+
stage.mergedConnections(),
|
|
136
|
+
cleanCrossBoundary,
|
|
137
|
+
cleanRedundantRelationships,
|
|
138
|
+
reduce((acc, c) => {
|
|
139
|
+
acc.add(c.source)
|
|
140
|
+
acc.add(c.target)
|
|
141
|
+
return acc
|
|
142
|
+
}, new Set<Elem>()),
|
|
143
|
+
)
|
|
144
|
+
}
|