@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,245 @@
|
|
|
1
|
+
import DefaultMap from 'mnemonist/default-map'
|
|
2
|
+
import { forEach, only, pipe } from 'remeda'
|
|
3
|
+
import { differenceConnections } from '../../../model/connection'
|
|
4
|
+
import type { RelationshipModel } from '../../../model/RelationModel'
|
|
5
|
+
import { isAncestor, sortByFqnHierarchically } from '../../../utils/fqn'
|
|
6
|
+
import { ifilter, isome, toArray } from '../../../utils/iterable'
|
|
7
|
+
import { difference, union } from '../../../utils/set'
|
|
8
|
+
import { treeFromMemoryState } from '../../memory'
|
|
9
|
+
import { cleanCrossBoundary, cleanRedundantRelationships } from '../clean-connections'
|
|
10
|
+
import type { Ctx, Memory } from '../memory'
|
|
11
|
+
|
|
12
|
+
type Elem = Ctx['Element']
|
|
13
|
+
/**
|
|
14
|
+
* This patch:
|
|
15
|
+
* 1. Keeps connections between leafs or having direct deployment relations
|
|
16
|
+
* 2. Removes cross-boundary model relations, that already exist inside boundaries
|
|
17
|
+
* (e.g. prefer relations inside same deployment node over relations between nodes)
|
|
18
|
+
* 3. Removes implicit connections between elements, if their descendants have same connection
|
|
19
|
+
*/
|
|
20
|
+
export class StageFinal {
|
|
21
|
+
static for(memory: Memory) {
|
|
22
|
+
return new StageFinal(memory)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private constructor(
|
|
26
|
+
protected readonly memory: Memory,
|
|
27
|
+
) {
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public step1CleanConnections(memory: Memory): Memory {
|
|
31
|
+
if (memory.connections.length < 2) {
|
|
32
|
+
return memory
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// const leafs = new Set<Elem>()
|
|
36
|
+
|
|
37
|
+
// for (const element of memory.final) {
|
|
38
|
+
// // Instance is always leaf
|
|
39
|
+
// if (element.isInstance()) {
|
|
40
|
+
// leafs.add(element)
|
|
41
|
+
// continue
|
|
42
|
+
// }
|
|
43
|
+
// // Check if element is ancestor of any element in elements
|
|
44
|
+
// let isLeaf = true
|
|
45
|
+
// for (const el of memory.final) {
|
|
46
|
+
// if (isAncestor(element.id, el.id)) {
|
|
47
|
+
// isLeaf = false
|
|
48
|
+
// break
|
|
49
|
+
// }
|
|
50
|
+
// }
|
|
51
|
+
// if (isLeaf) {
|
|
52
|
+
// leafs.add(element)
|
|
53
|
+
// }
|
|
54
|
+
// }
|
|
55
|
+
|
|
56
|
+
const connections = pipe(
|
|
57
|
+
memory.connections,
|
|
58
|
+
// Keep connections
|
|
59
|
+
// - between leafs
|
|
60
|
+
// - has direct deployment relation
|
|
61
|
+
// filter(c => {
|
|
62
|
+
// return (leafs.has(c.source) && leafs.has(c.target)) || c.hasDirectDeploymentRelation()
|
|
63
|
+
// }),
|
|
64
|
+
cleanCrossBoundary,
|
|
65
|
+
cleanRedundantRelationships,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
const connectionsToExclude = differenceConnections(
|
|
69
|
+
memory.connections,
|
|
70
|
+
connections,
|
|
71
|
+
)
|
|
72
|
+
if (connectionsToExclude.length === 0) {
|
|
73
|
+
return memory
|
|
74
|
+
}
|
|
75
|
+
const stage = memory.stageExclude({} as any)
|
|
76
|
+
stage.excludeConnections(connectionsToExclude, true)
|
|
77
|
+
return stage.commit()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public step2ProcessImplicits(memory: Memory): Memory {
|
|
81
|
+
const implicits = union(this.memory.elements, memory.elements)
|
|
82
|
+
const final = union(memory.final, this.memory.explicits)
|
|
83
|
+
|
|
84
|
+
// Find connections based on same relation but different boundaries
|
|
85
|
+
const groupedByRelation = new DefaultMap<RelationshipModel, Set<Elem>>(() => new Set())
|
|
86
|
+
for (const conn of memory.connections) {
|
|
87
|
+
if (conn.boundary) {
|
|
88
|
+
for (const relation of conn.relations.model) {
|
|
89
|
+
groupedByRelation.get(relation).add(conn.boundary)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
for (const [_, boundaries] of groupedByRelation) {
|
|
94
|
+
if (boundaries.size < 2) {
|
|
95
|
+
continue
|
|
96
|
+
}
|
|
97
|
+
for (const boundary of boundaries) {
|
|
98
|
+
if (implicits.delete(boundary)) {
|
|
99
|
+
final.add(boundary)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const snapshot = new Set(final)
|
|
105
|
+
const isFinalOrHasIncludedDescendant = (el: Elem) => {
|
|
106
|
+
for (const final of snapshot) {
|
|
107
|
+
if (el === final || isAncestor(el, final)) {
|
|
108
|
+
return true
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return false
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Pick from implicit elements
|
|
115
|
+
pipe(
|
|
116
|
+
difference(implicits, snapshot),
|
|
117
|
+
ifilter(e => e.isDeploymentNode()),
|
|
118
|
+
toArray(),
|
|
119
|
+
sortByFqnHierarchically,
|
|
120
|
+
forEach((el) => {
|
|
121
|
+
// If element has more 2 or more children included
|
|
122
|
+
// It can "box" around
|
|
123
|
+
const childrensToWrap = [...el.children()].filter(isFinalOrHasIncludedDescendant).length
|
|
124
|
+
if (childrensToWrap >= 2) {
|
|
125
|
+
final.add(el)
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
// If Element has only one child included
|
|
129
|
+
// and has "similar" sibling with included descendant
|
|
130
|
+
if (childrensToWrap === 1 && isome(el.siblings(), isFinalOrHasIncludedDescendant)) {
|
|
131
|
+
final.add(el)
|
|
132
|
+
}
|
|
133
|
+
}),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
return memory.update({ final })
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
public step3ProcessBoundaries(memory: Memory): Memory {
|
|
140
|
+
const boundaries = new Set<Elem>()
|
|
141
|
+
for (const conn of memory.connections) {
|
|
142
|
+
if (conn.boundary) {
|
|
143
|
+
boundaries.add(conn.boundary)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const tree = treeFromMemoryState<Ctx>(memory, 'final')
|
|
147
|
+
const stage = memory.stageExclude({} as any)
|
|
148
|
+
|
|
149
|
+
const isRemovable = (el: Elem) =>
|
|
150
|
+
!(
|
|
151
|
+
boundaries.has(el)
|
|
152
|
+
|| memory.explicits.has(el)
|
|
153
|
+
|| tree.hasInOut(el)
|
|
154
|
+
|| tree.root.has(el)
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
const singleRoot = only([...tree.root])
|
|
158
|
+
if (singleRoot && !memory.explicits.has(singleRoot)) {
|
|
159
|
+
stage.exclude(singleRoot)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
for (const el of memory.final) {
|
|
163
|
+
const singleChild = only(tree.children(el))
|
|
164
|
+
if (singleChild && !tree.hasInOut(singleChild) && isRemovable(el)) {
|
|
165
|
+
stage.exclude(el)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (stage.isDirty()) {
|
|
170
|
+
return stage.commit()
|
|
171
|
+
}
|
|
172
|
+
return memory
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// TODO: Lot of corner cases to cover, skip for now
|
|
176
|
+
// public step3FlatNodes(memory: Memory): Memory {
|
|
177
|
+
// // final implicits
|
|
178
|
+
// const explicits = new Set<Elem>([
|
|
179
|
+
// ...memory.explicits,
|
|
180
|
+
// ...memory.connections.flatMap(c => [c.source, c.target]),
|
|
181
|
+
// ])
|
|
182
|
+
// const sorted = sortParentsFirst(toArray(memory.final))
|
|
183
|
+
|
|
184
|
+
// const toplevel = new Set<Elem>(sorted)
|
|
185
|
+
// const children = sorted.reduce((acc, el, index, all) => {
|
|
186
|
+
// acc.set(
|
|
187
|
+
// el,
|
|
188
|
+
// new Set(
|
|
189
|
+
// all
|
|
190
|
+
// .slice(index + 1)
|
|
191
|
+
// .filter(e => isAncestor(el, e))
|
|
192
|
+
// .reduce((acc, el) => {
|
|
193
|
+
// if (!acc.some(e => isAncestor(e, el))) {
|
|
194
|
+
// toplevel.delete(el)
|
|
195
|
+
// acc.push(el)
|
|
196
|
+
// }
|
|
197
|
+
// return acc
|
|
198
|
+
// }, [] as Elem[]),
|
|
199
|
+
// ),
|
|
200
|
+
// )
|
|
201
|
+
// return acc
|
|
202
|
+
// }, new DefaultMap<Elem, Set<Elem>>(() => new Set()))
|
|
203
|
+
|
|
204
|
+
// const state = memory.mutableState()
|
|
205
|
+
|
|
206
|
+
// function flattenNode(node: Elem) {
|
|
207
|
+
// const _children = [...children.get(node)]
|
|
208
|
+
// if (_children.length > 1) {
|
|
209
|
+
// for (const child of _children) {
|
|
210
|
+
// flattenNode(child)
|
|
211
|
+
// }
|
|
212
|
+
// return !explicits.has(node)
|
|
213
|
+
// }
|
|
214
|
+
// if (hasAtLeast(_children, 1)) {
|
|
215
|
+
// if (flattenNode(_children[0])) {
|
|
216
|
+
// state.final.delete(_children[0])
|
|
217
|
+
// }
|
|
218
|
+
// }
|
|
219
|
+
// return !explicits.has(node)
|
|
220
|
+
// }
|
|
221
|
+
|
|
222
|
+
// const root = [...toplevel]
|
|
223
|
+
// for (const node of root) {
|
|
224
|
+
// flattenNode(node)
|
|
225
|
+
// if (!explicits.has(node) && children.get(node).size === 1) {
|
|
226
|
+
// state.final.delete(node)
|
|
227
|
+
// }
|
|
228
|
+
// }
|
|
229
|
+
// if (root.length === 1 && !explicits.has(root[0]!)) {
|
|
230
|
+
// state.final.delete(root[0]!)
|
|
231
|
+
// }
|
|
232
|
+
|
|
233
|
+
// return memory.update(state)
|
|
234
|
+
// }
|
|
235
|
+
|
|
236
|
+
public commit(): Memory {
|
|
237
|
+
// return []
|
|
238
|
+
const step1 = this.step1CleanConnections(this.memory)
|
|
239
|
+
const step2 = this.step2ProcessImplicits(step1)
|
|
240
|
+
return this.step3ProcessBoundaries(step2)
|
|
241
|
+
// return step2memory
|
|
242
|
+
// const step3m?emory = this.step3FlatNodes(step2memory)
|
|
243
|
+
// return step3memory
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { dropWhile, forEach, pipe, take, zip } from 'remeda'
|
|
2
|
+
import { findConnection, findConnectionsBetween } from '../../../model/connection/deployment'
|
|
3
|
+
import { isIterable } from '../../../utils'
|
|
4
|
+
import { toArray } from '../../../utils/iterable'
|
|
5
|
+
import { type CtxConnection, type CtxElement, AbstractStageInclude } from '../../memory'
|
|
6
|
+
import { cleanCrossBoundary, cleanRedundantRelationships } from '../clean-connections'
|
|
7
|
+
import { type Ctx } from '../memory/memory'
|
|
8
|
+
|
|
9
|
+
type Elem = CtxElement<Ctx>
|
|
10
|
+
type Connection = CtxConnection<Ctx>
|
|
11
|
+
|
|
12
|
+
export class StageInclude extends AbstractStageInclude<Ctx> {
|
|
13
|
+
/**
|
|
14
|
+
* Connects elements with existing ones in the memory
|
|
15
|
+
*/
|
|
16
|
+
public override connectWithExisting(
|
|
17
|
+
elements: Elem | Iterable<Elem>,
|
|
18
|
+
direction: 'in' | 'out' | 'both' = 'both',
|
|
19
|
+
): boolean {
|
|
20
|
+
const before = this._connections.length
|
|
21
|
+
const hasChanged = () => this._connections.length > before
|
|
22
|
+
if (!isIterable(elements)) {
|
|
23
|
+
if (direction === 'in' || direction === 'both') {
|
|
24
|
+
for (const el of this.memory.elements) {
|
|
25
|
+
this.addConnections(
|
|
26
|
+
findConnection(el, elements, 'directed'),
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (direction === 'out' || direction === 'both') {
|
|
31
|
+
this.addConnections(
|
|
32
|
+
findConnectionsBetween(elements, this.memory.elements, 'directed'),
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
return hasChanged()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const targets = [...elements]
|
|
39
|
+
if (direction === 'in' || direction === 'both') {
|
|
40
|
+
for (const el of this.memory.elements) {
|
|
41
|
+
this.addConnections(
|
|
42
|
+
findConnectionsBetween(el, targets, 'directed'),
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (direction === 'out' || direction === 'both') {
|
|
47
|
+
for (const el of targets) {
|
|
48
|
+
this.addConnections(
|
|
49
|
+
findConnectionsBetween(el, this.memory.elements, 'directed'),
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return hasChanged()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
protected override processConnections(connections: Connection[]) {
|
|
57
|
+
const clean = pipe(
|
|
58
|
+
connections,
|
|
59
|
+
cleanCrossBoundary,
|
|
60
|
+
cleanRedundantRelationships,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
pipe(
|
|
64
|
+
clean,
|
|
65
|
+
// Process only connection from this stage
|
|
66
|
+
// filter(c => this._connections.some(c2 => c2.id === c.id)),
|
|
67
|
+
forEach(({ source, target, boundary }) => {
|
|
68
|
+
pipe(
|
|
69
|
+
zip(
|
|
70
|
+
[...toArray(source.ancestors()).reverse(), source],
|
|
71
|
+
[...toArray(target.ancestors()).reverse(), target],
|
|
72
|
+
),
|
|
73
|
+
// Filter out common ancestors
|
|
74
|
+
dropWhile(([sourceAncestor, targetAncestor]) => sourceAncestor === targetAncestor),
|
|
75
|
+
take(1),
|
|
76
|
+
forEach(([sourceAncestor, targetAncestor]) => {
|
|
77
|
+
if (source === sourceAncestor && target === targetAncestor) {
|
|
78
|
+
this.addImplicit(boundary)
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
if (sourceAncestor !== source && sourceAncestor.isDeploymentNode() && !sourceAncestor.onlyOneInstance()) {
|
|
82
|
+
// state.final.add(source)
|
|
83
|
+
this.addImplicit(sourceAncestor)
|
|
84
|
+
}
|
|
85
|
+
if (targetAncestor !== target && targetAncestor.isDeploymentNode() && !targetAncestor.onlyOneInstance()) {
|
|
86
|
+
// state.final.add(source)
|
|
87
|
+
this.addImplicit(targetAncestor)
|
|
88
|
+
}
|
|
89
|
+
}),
|
|
90
|
+
)
|
|
91
|
+
}),
|
|
92
|
+
)
|
|
93
|
+
return clean
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { hasAtLeast, isEmpty, isNumber, isString } from 'remeda'
|
|
2
|
+
import { invariant, nonexhaustive } from '../../errors'
|
|
3
|
+
import type { LikeC4DeploymentModel } from '../../model'
|
|
4
|
+
import type { DeploymentConnectionModel } from '../../model/connection/deployment'
|
|
5
|
+
import type { ElementModel } from '../../model/ElementModel'
|
|
6
|
+
import type { AnyAux } from '../../model/types'
|
|
7
|
+
import {
|
|
8
|
+
type ComputedEdge,
|
|
9
|
+
type ComputedNode,
|
|
10
|
+
DefaultArrowType,
|
|
11
|
+
type DeploymentNodeKind,
|
|
12
|
+
type DeploymentViewRule,
|
|
13
|
+
type Fqn,
|
|
14
|
+
FqnExpr,
|
|
15
|
+
isViewRuleStyle,
|
|
16
|
+
type NonEmptyArray,
|
|
17
|
+
type Tag,
|
|
18
|
+
} from '../../types'
|
|
19
|
+
import { nameFromFqn, parentFqn } from '../../utils'
|
|
20
|
+
import { applyViewRuleStyle } from '../utils/applyViewRuleStyles'
|
|
21
|
+
import { buildComputedNodes, type ComputedNodeSource } from '../utils/buildComputedNodes'
|
|
22
|
+
import { mergePropsFromRelationships } from '../utils/merge-props-from-relationships'
|
|
23
|
+
import { uniqueTags } from '../utils/uniqueTags'
|
|
24
|
+
import type { Elem, Memory } from './_types'
|
|
25
|
+
|
|
26
|
+
type Predicate<T> = (x: T) => boolean
|
|
27
|
+
|
|
28
|
+
export function resolveElements(
|
|
29
|
+
model: LikeC4DeploymentModel,
|
|
30
|
+
expr: FqnExpr.DeploymentRef,
|
|
31
|
+
): Elem[] {
|
|
32
|
+
const ref = model.element(expr.ref.deployment)
|
|
33
|
+
if (ref.isDeploymentNode()) {
|
|
34
|
+
if (expr.selector === 'children') {
|
|
35
|
+
return [...ref.children()]
|
|
36
|
+
}
|
|
37
|
+
if (expr.selector === 'expanded') {
|
|
38
|
+
return [ref, ...ref.children()]
|
|
39
|
+
}
|
|
40
|
+
if (expr.selector === 'descendants') {
|
|
41
|
+
return [...ref.descendants()]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return [ref]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function resolveModelElements(
|
|
48
|
+
model: LikeC4DeploymentModel,
|
|
49
|
+
expr: FqnExpr.ModelRef,
|
|
50
|
+
): ElementModel[] {
|
|
51
|
+
const ref = model.$model.element(expr.ref.model)
|
|
52
|
+
if (expr.selector === 'children') {
|
|
53
|
+
return [...ref.children()]
|
|
54
|
+
}
|
|
55
|
+
if (expr.selector === 'expanded') {
|
|
56
|
+
return [ref, ...ref.children()]
|
|
57
|
+
}
|
|
58
|
+
if (expr.selector === 'descendants') {
|
|
59
|
+
return [...ref.descendants()]
|
|
60
|
+
}
|
|
61
|
+
return [ref]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function deploymentExpressionToPredicate<T extends { id: string; modelRef?: number | string }>(
|
|
65
|
+
target: FqnExpr,
|
|
66
|
+
): Predicate<T> {
|
|
67
|
+
if (FqnExpr.isWildcard(target)) {
|
|
68
|
+
return () => true
|
|
69
|
+
}
|
|
70
|
+
if (FqnExpr.isDeploymentRef(target)) {
|
|
71
|
+
const fqn = target.ref.deployment
|
|
72
|
+
if (target.selector === 'expanded') {
|
|
73
|
+
const fqnWithDot = fqn + '.'
|
|
74
|
+
return n => n.id === fqn || n.id.startsWith(fqnWithDot)
|
|
75
|
+
}
|
|
76
|
+
if (target.selector === 'descendants') {
|
|
77
|
+
const fqnWithDot = fqn + '.'
|
|
78
|
+
return n => n.id.startsWith(fqnWithDot)
|
|
79
|
+
}
|
|
80
|
+
if (target.selector === 'children') {
|
|
81
|
+
return n => parentFqn(n.id) === fqn
|
|
82
|
+
}
|
|
83
|
+
return n => n.id === fqn
|
|
84
|
+
}
|
|
85
|
+
if (FqnExpr.isModelRef(target)) {
|
|
86
|
+
const modelFqn = (node: T) => {
|
|
87
|
+
if (isString(node.modelRef)) {
|
|
88
|
+
return node.modelRef
|
|
89
|
+
}
|
|
90
|
+
if (isNumber(node.modelRef)) {
|
|
91
|
+
return node.id
|
|
92
|
+
}
|
|
93
|
+
return null
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const fqn = target.ref.model
|
|
97
|
+
if (target.selector === 'expanded') {
|
|
98
|
+
const fqnWithDot = fqn + '.'
|
|
99
|
+
return (n) => {
|
|
100
|
+
const m = modelFqn(n)
|
|
101
|
+
if (!m) {
|
|
102
|
+
return true
|
|
103
|
+
}
|
|
104
|
+
return m === fqn || m.startsWith(fqnWithDot)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (target.selector === 'descendants') {
|
|
108
|
+
const fqnWithDot = fqn + '.'
|
|
109
|
+
return (n) => {
|
|
110
|
+
const m = modelFqn(n)
|
|
111
|
+
if (!m) {
|
|
112
|
+
return true
|
|
113
|
+
}
|
|
114
|
+
return m.startsWith(fqnWithDot)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (target.selector === 'children') {
|
|
118
|
+
return (n) => {
|
|
119
|
+
const m = modelFqn(n)
|
|
120
|
+
if (!m) {
|
|
121
|
+
return true
|
|
122
|
+
}
|
|
123
|
+
return parentFqn(m) === fqn
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return (n) => {
|
|
127
|
+
const m = modelFqn(n)
|
|
128
|
+
if (!m) {
|
|
129
|
+
return true
|
|
130
|
+
}
|
|
131
|
+
return m === fqn
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
nonexhaustive(target)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function toNodeSource(el: Elem): ComputedNodeSource {
|
|
138
|
+
if (el.isDeploymentNode()) {
|
|
139
|
+
const onlyOneInstance = el.onlyOneInstance()
|
|
140
|
+
let { title, kind, id, ...$node } = el.$node
|
|
141
|
+
const { icon, color, shape, ...style } = el.$node.style ?? {}
|
|
142
|
+
|
|
143
|
+
// If there is only one instance and title was not overriden
|
|
144
|
+
if (onlyOneInstance && title === nameFromFqn(el.id)) {
|
|
145
|
+
title = onlyOneInstance.title
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
...(onlyOneInstance && {
|
|
150
|
+
...toNodeSource(onlyOneInstance),
|
|
151
|
+
modelRef: onlyOneInstance.element.id,
|
|
152
|
+
}),
|
|
153
|
+
title,
|
|
154
|
+
...$node,
|
|
155
|
+
...(icon && { icon }),
|
|
156
|
+
...(color && { color }),
|
|
157
|
+
...(shape && { shape }),
|
|
158
|
+
style: {
|
|
159
|
+
...style,
|
|
160
|
+
},
|
|
161
|
+
deploymentRef: 1,
|
|
162
|
+
kind,
|
|
163
|
+
id,
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
invariant(el.isInstance(), 'Expected Instance')
|
|
167
|
+
const instance = el.$instance
|
|
168
|
+
const element = el.element.$element
|
|
169
|
+
|
|
170
|
+
const icon = instance.style?.icon ?? element.icon
|
|
171
|
+
const color = instance.style?.color ?? element.color
|
|
172
|
+
const shape = instance.style?.shape ?? element.shape
|
|
173
|
+
|
|
174
|
+
const links = [
|
|
175
|
+
...(element.links ?? []),
|
|
176
|
+
...(instance.links ?? []),
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
const metadata = {
|
|
180
|
+
...element.metadata,
|
|
181
|
+
...instance.metadata,
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const notation = instance.notation ?? element.notation
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
id: el.id,
|
|
188
|
+
kind: 'instance' as DeploymentNodeKind,
|
|
189
|
+
title: instance.title ?? element.title,
|
|
190
|
+
description: instance.description ?? element.description,
|
|
191
|
+
technology: instance.technology ?? element.technology,
|
|
192
|
+
tags: uniqueTags([element, instance]) as NonEmptyArray<Tag>,
|
|
193
|
+
links: hasAtLeast(links, 1) ? links : null,
|
|
194
|
+
...icon && { icon },
|
|
195
|
+
...color && { color },
|
|
196
|
+
...shape && { shape },
|
|
197
|
+
style: {
|
|
198
|
+
...element.style,
|
|
199
|
+
...instance.style,
|
|
200
|
+
},
|
|
201
|
+
deploymentRef: el.id === instance.id ? 1 : instance.id,
|
|
202
|
+
modelRef: el.id === element.id ? 1 : element.id,
|
|
203
|
+
...notation && { notation },
|
|
204
|
+
...!isEmpty(metadata) && ({ metadata }),
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function toComputedEdges<M extends AnyAux>(
|
|
209
|
+
connections: ReadonlyArray<DeploymentConnectionModel<M>>,
|
|
210
|
+
): ComputedEdge[] {
|
|
211
|
+
return connections.reduce((acc, e) => {
|
|
212
|
+
// const modelRelations = []
|
|
213
|
+
// const deploymentRelations = []
|
|
214
|
+
const relations = [
|
|
215
|
+
...e.relations.model,
|
|
216
|
+
...e.relations.deployment,
|
|
217
|
+
]
|
|
218
|
+
invariant(hasAtLeast(relations, 1), 'Edge must have at least one relation')
|
|
219
|
+
|
|
220
|
+
const source = e.source.id
|
|
221
|
+
const target = e.target.id
|
|
222
|
+
|
|
223
|
+
const {
|
|
224
|
+
title,
|
|
225
|
+
...props
|
|
226
|
+
} = mergePropsFromRelationships(relations.map(r => r.$relationship)) // || relations.find(r => r.source === source && r.target === target)
|
|
227
|
+
|
|
228
|
+
const edge: ComputedEdge = {
|
|
229
|
+
id: e.id,
|
|
230
|
+
parent: e.boundary?.id ?? null,
|
|
231
|
+
source,
|
|
232
|
+
target,
|
|
233
|
+
label: title ?? null,
|
|
234
|
+
relations: relations.map((r) => r.id as M['RelationId']),
|
|
235
|
+
...props,
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// If exists same edge but in opposite direction
|
|
239
|
+
const existing = acc.find(e => e.source === target && e.target === source)
|
|
240
|
+
if (existing && edge.label === existing.label) {
|
|
241
|
+
existing.dir = 'both'
|
|
242
|
+
const head = existing.head ?? edge.head ?? DefaultArrowType
|
|
243
|
+
existing.head ??= head
|
|
244
|
+
existing.tail ??= head
|
|
245
|
+
|
|
246
|
+
if (edge.color) {
|
|
247
|
+
existing.color ??= edge.color
|
|
248
|
+
}
|
|
249
|
+
if (edge.line) {
|
|
250
|
+
existing.line ??= edge.line
|
|
251
|
+
}
|
|
252
|
+
return acc
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
acc.push(edge)
|
|
256
|
+
return acc
|
|
257
|
+
}, [] as ComputedEdge[])
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function buildNodes(memory: Memory): ReadonlyMap<Fqn, ComputedNode> {
|
|
261
|
+
return buildComputedNodes([...memory.final].map(toNodeSource))
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function applyDeploymentViewRuleStyles(
|
|
265
|
+
rules: DeploymentViewRule[],
|
|
266
|
+
nodes: ComputedNode[],
|
|
267
|
+
): ComputedNode[] {
|
|
268
|
+
for (const rule of rules) {
|
|
269
|
+
if (!isViewRuleStyle(rule) || rule.targets.length === 0) {
|
|
270
|
+
continue
|
|
271
|
+
}
|
|
272
|
+
const predicates = rule.targets.map(deploymentExpressionToPredicate)
|
|
273
|
+
applyViewRuleStyle(rule, predicates, nodes)
|
|
274
|
+
}
|
|
275
|
+
return nodes
|
|
276
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { partition } from 'remeda'
|
|
2
|
+
import type { DynamicViewRule, DynamicViewStep, Fqn, ViewId, ViewRulePredicate } from '../../../types'
|
|
3
|
+
import { type FakeElementIds, fakeModel } from '../../element-view/__test__/fixture'
|
|
4
|
+
import { computeDynamicView } from '../compute'
|
|
5
|
+
|
|
6
|
+
const emptyView = {
|
|
7
|
+
__: 'dynamic' as const,
|
|
8
|
+
id: 'index' as ViewId,
|
|
9
|
+
title: null,
|
|
10
|
+
description: null,
|
|
11
|
+
tags: null,
|
|
12
|
+
links: null,
|
|
13
|
+
customColorDefinitions: {},
|
|
14
|
+
rules: []
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type StepExpr = `${FakeElementIds} ${'->' | '<-'} ${FakeElementIds}`
|
|
18
|
+
type StepProps = Omit<DynamicViewStep, 'source' | 'target' | 'isBackward'>
|
|
19
|
+
|
|
20
|
+
export function $step(expr: StepExpr, props?: string | Partial<StepProps>): DynamicViewStep {
|
|
21
|
+
const title = typeof props === 'string' ? props : props?.title
|
|
22
|
+
if (expr.includes(' -> ')) {
|
|
23
|
+
const [source, target] = expr.split(' -> ')
|
|
24
|
+
return {
|
|
25
|
+
source: source as Fqn,
|
|
26
|
+
target: target as Fqn,
|
|
27
|
+
...(typeof props === 'object' ? props : {}),
|
|
28
|
+
title: title ?? null
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (expr.includes(' <- ')) {
|
|
32
|
+
const [target, source] = expr.split(' <- ')
|
|
33
|
+
return {
|
|
34
|
+
source: source as Fqn,
|
|
35
|
+
target: target as Fqn,
|
|
36
|
+
...(typeof props === 'object' ? props : {}),
|
|
37
|
+
title: title ?? null,
|
|
38
|
+
isBackward: true
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
throw new Error(`Invalid step expression: ${expr}`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function compute(stepsAndRules: (DynamicViewStep | ViewRulePredicate)[]) {
|
|
45
|
+
const [steps, rules] = partition(stepsAndRules, (s): s is DynamicViewStep => 'source' in s)
|
|
46
|
+
let view = computeDynamicView(
|
|
47
|
+
fakeModel,
|
|
48
|
+
{
|
|
49
|
+
...emptyView,
|
|
50
|
+
steps,
|
|
51
|
+
rules: rules as DynamicViewRule[]
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
return Object.assign(view, {
|
|
55
|
+
nodeIds: view.nodes.map((node) => node.id) as string[],
|
|
56
|
+
edgeIds: view.edges.map((edge) => edge.id) as string[]
|
|
57
|
+
})
|
|
58
|
+
}
|