@likec4/core 1.17.0 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/compute-view/index.d.mts +91 -0
- package/dist/compute-view/index.d.ts +91 -0
- package/dist/compute-view/index.mjs +79 -0
- package/dist/index.d.mts +248 -435
- package/dist/index.d.ts +248 -435
- package/dist/index.mjs +418 -1322
- package/dist/model/index.d.mts +642 -0
- package/dist/model/index.d.ts +642 -0
- package/dist/model/index.mjs +2 -0
- package/dist/shared/core.BBgiAAv1.mjs +144 -0
- package/dist/shared/core.BPyJvuMU.mjs +2420 -0
- package/dist/shared/core.BQ4AbfvB.mjs +9228 -0
- package/dist/shared/{core.CoLQkcYL.d.ts → core.DMYY7C8s.d.mts} +398 -249
- package/dist/shared/{core.CoLQkcYL.d.cts → core.DMYY7C8s.d.ts} +398 -249
- package/dist/shared/core.k12s_keD.mjs +203 -0
- package/dist/types/index.d.mts +69 -1
- package/dist/types/index.d.ts +69 -1
- package/dist/types/index.mjs +21 -1
- package/package.json +40 -42
- package/src/builder/Builder.deployment.ts +219 -0
- package/src/builder/Builder.deploymentModel.ts +360 -0
- package/src/builder/Builder.element.ts +17 -19
- package/src/builder/Builder.model.ts +13 -2
- package/src/builder/Builder.test-d.ts +59 -4
- package/src/builder/Builder.ts +238 -53
- package/src/builder/Builder.view.ts +238 -53
- package/src/builder/_types.ts +87 -15
- package/src/compute-view/LikeC4ModelGraph.ts +330 -0
- package/src/compute-view/compute-view.ts +92 -0
- package/src/compute-view/deployment-view/Memory.ts +108 -0
- package/src/compute-view/deployment-view/Stage.ts +145 -0
- package/src/compute-view/deployment-view/__test__/fixture.ts +210 -0
- package/src/compute-view/deployment-view/_types.ts +29 -0
- package/src/compute-view/deployment-view/clean-connections.ts +229 -0
- package/src/compute-view/deployment-view/compute.ts +111 -0
- package/src/compute-view/deployment-view/predicates/deployment-ref.ts +146 -0
- package/src/compute-view/deployment-view/predicates/relation-direct.ts +130 -0
- package/src/compute-view/deployment-view/predicates/relation-in-out.ts +43 -0
- package/src/compute-view/deployment-view/predicates/relation-incoming.ts +46 -0
- package/src/compute-view/deployment-view/predicates/relation-outgoing.ts +49 -0
- package/src/compute-view/deployment-view/predicates/wildcard.ts +29 -0
- package/src/compute-view/deployment-view/utils.ts +212 -0
- package/src/compute-view/dynamic-view/__test__/fixture.ts +58 -0
- package/src/compute-view/dynamic-view/compute.ts +310 -0
- package/src/compute-view/element-view/__test__/__snapshots__/legacy.spec.ts.snap +456 -0
- package/src/compute-view/element-view/__test__/fixture.ts +645 -0
- package/src/compute-view/element-view/compute.ts +734 -0
- package/src/compute-view/element-view/predicates.ts +517 -0
- package/src/compute-view/index.ts +5 -0
- package/src/compute-view/utils/__snapshots__/sortNodes.spec.ts.snap +113 -0
- package/src/compute-view/utils/ancestorsOfNode.ts +19 -0
- package/src/compute-view/utils/applyCustomElementProperties.ts +66 -0
- package/src/compute-view/utils/applyCustomRelationProperties.ts +41 -0
- package/src/compute-view/utils/applyViewRuleStyles.ts +53 -0
- package/src/compute-view/utils/buildComputedNodes.ts +148 -0
- package/src/compute-view/utils/buildElementNotations.ts +63 -0
- package/src/compute-view/utils/derive-edge-props-from-relationships.ts +97 -0
- package/src/compute-view/utils/elementExpressionToPredicate.ts +52 -0
- package/src/compute-view/utils/link-nodes-with-edges.ts +58 -0
- package/src/compute-view/utils/relationExpressionToPredicates.ts +55 -0
- package/src/compute-view/utils/resolve-extended-views.ts +65 -0
- package/src/compute-view/utils/resolve-global-rules.ts +83 -0
- package/src/compute-view/utils/sortNodes.ts +101 -0
- package/src/compute-view/utils/topological-sort.ts +160 -0
- package/src/compute-view/utils/uniqueTags.test.ts +42 -0
- package/src/compute-view/utils/uniqueTags.ts +21 -0
- package/src/compute-view/utils/view-hash.ts +27 -0
- package/src/compute-view/utils/with-readable-edges.ts +31 -0
- package/src/index.ts +5 -1
- package/src/model/DeploymentElementModel.ts +534 -0
- package/src/model/DeploymentModel.ts +364 -0
- package/src/model/ElementModel.ts +195 -0
- package/src/model/LikeC4Model.test-d.ts +151 -0
- package/src/model/LikeC4Model.ts +280 -457
- package/src/model/RelationModel.ts +84 -0
- package/src/model/__test__/fixture.ts +153 -338
- package/src/model/connection/ConnectionModel.ts +94 -0
- package/src/model/connection/DeploymentConnectionModel.ts +99 -0
- package/src/model/connection/deployment.ts +124 -0
- package/src/model/connection/model.ts +123 -0
- package/src/model/index.ts +4 -2
- package/src/model/types.ts +95 -15
- package/src/model/view/EdgeModel.ts +103 -0
- package/src/model/view/LikeC4ViewModel.ts +220 -0
- package/src/model/view/NodeModel.ts +228 -0
- package/src/types/_common.ts +2 -0
- package/src/types/deployments.ts +237 -0
- package/src/types/element.ts +7 -3
- package/src/types/expression.ts +8 -6
- package/src/types/index.ts +1 -0
- package/src/types/model.ts +68 -9
- package/src/types/overview-graph.ts +2 -2
- package/src/types/relation.ts +4 -4
- package/src/types/view-notation.ts +2 -2
- package/src/types/view.ts +104 -30
- package/src/utils/{commonHead.ts → common-head.ts} +2 -0
- package/src/utils/fqn.ts +83 -27
- package/src/utils/getOrCreate.ts +42 -0
- package/src/utils/graphlib.ts +10 -0
- package/src/utils/index.ts +4 -1
- package/src/utils/object-hash.ts +3 -0
- package/src/utils/set.ts +76 -0
- package/src/utils/string-hash.ts +16 -0
- package/dist/index.cjs +0 -5452
- package/dist/index.d.cts +0 -1052
- package/dist/shared/core.CoLQkcYL.d.mts +0 -734
- package/dist/shared/core.v5DxGcX6.mjs +0 -293
- package/dist/shared/core.zN6ZscFQ.cjs +0 -353
- package/dist/types/index.cjs +0 -70
- package/dist/types/index.d.cts +0 -2
- package/src/model/LikeC4DiagramModel.ts +0 -338
- package/src/model/LikeC4ViewModel.ts +0 -320
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { hasAtLeast } from 'remeda'
|
|
2
|
+
import { invariant } from '../../../errors'
|
|
3
|
+
import { findConnectionsBetween, findConnectionsWithin } from '../../../model/connection/deployment'
|
|
4
|
+
import type { DeploymentConnectionModel } from '../../../model/connection/deployment'
|
|
5
|
+
import type { DeployedInstanceModel, DeploymentNodeModel } from '../../../model/DeploymentElementModel'
|
|
6
|
+
import type { DeploymentElementExpression } from '../../../types'
|
|
7
|
+
import type { PredicateCtx, PredicateExecutor } from '../_types'
|
|
8
|
+
import { deploymentExpressionToPredicate } from '../utils'
|
|
9
|
+
|
|
10
|
+
export const DeploymentRefPredicate: PredicateExecutor<DeploymentElementExpression.Ref> = {
|
|
11
|
+
include: (expr, ctx) => {
|
|
12
|
+
const el = ctx.model.element(expr.ref.id)
|
|
13
|
+
|
|
14
|
+
if (el.isInstance()) {
|
|
15
|
+
includeDeployedInstance(el, ctx)
|
|
16
|
+
return ctx.stage.patch()
|
|
17
|
+
}
|
|
18
|
+
invariant(el.isDeploymentNode(), 'Type inference failed')
|
|
19
|
+
switch (true) {
|
|
20
|
+
case expr.selector === 'expanded':
|
|
21
|
+
includeDeployedNodeWithExpanded(el, ctx)
|
|
22
|
+
break
|
|
23
|
+
case expr.selector === 'children':
|
|
24
|
+
includeDeployedNodeChildren(el, ctx)
|
|
25
|
+
break
|
|
26
|
+
case expr.selector === 'descendants':
|
|
27
|
+
includeDeployedNodeDescendants(el, ctx)
|
|
28
|
+
break
|
|
29
|
+
default:
|
|
30
|
+
includeDeployedNode(el, ctx)
|
|
31
|
+
break
|
|
32
|
+
}
|
|
33
|
+
return ctx.stage.patch()
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
exclude: (expr, { stage, memory }) => {
|
|
37
|
+
const exprPredicate = deploymentExpressionToPredicate(expr)
|
|
38
|
+
stage.exclude([...memory.elements].filter(exprPredicate))
|
|
39
|
+
return stage.patch()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function includeDeployedInstance(
|
|
44
|
+
instance: DeployedInstanceModel,
|
|
45
|
+
{ memory, stage }: PredicateCtx
|
|
46
|
+
) {
|
|
47
|
+
stage.addExplicit(instance)
|
|
48
|
+
stage.addConnections(findConnectionsBetween(instance, memory.elements))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* include node
|
|
53
|
+
*/
|
|
54
|
+
function includeDeployedNode(
|
|
55
|
+
node: DeploymentNodeModel,
|
|
56
|
+
{ memory, stage }: PredicateCtx
|
|
57
|
+
) {
|
|
58
|
+
stage.addExplicit(node)
|
|
59
|
+
stage.addConnections(findConnectionsBetween(node, memory.elements))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* include node.*
|
|
64
|
+
*/
|
|
65
|
+
function includeDeployedNodeChildren(
|
|
66
|
+
node: DeploymentNodeModel,
|
|
67
|
+
{ memory, stage }: PredicateCtx
|
|
68
|
+
) {
|
|
69
|
+
const children = [...node.children()]
|
|
70
|
+
if (children.length === 0) {
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
stage.addExplicit(children)
|
|
74
|
+
|
|
75
|
+
if (hasAtLeast(children, 2)) {
|
|
76
|
+
stage.addConnections(findConnectionsWithin(children))
|
|
77
|
+
}
|
|
78
|
+
for (const child of children) {
|
|
79
|
+
stage.addConnections(findConnectionsBetween(child, memory.elements))
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* include node._
|
|
85
|
+
*/
|
|
86
|
+
function includeDeployedNodeWithExpanded(
|
|
87
|
+
node: DeploymentNodeModel,
|
|
88
|
+
{ memory, stage }: PredicateCtx
|
|
89
|
+
) {
|
|
90
|
+
const children = [...node.children()]
|
|
91
|
+
stage.addImplicit(node)
|
|
92
|
+
|
|
93
|
+
const connections = [] as DeploymentConnectionModel[]
|
|
94
|
+
|
|
95
|
+
for (const child of children) {
|
|
96
|
+
const found = findConnectionsBetween(child, memory.elements)
|
|
97
|
+
if (found.length > 0) {
|
|
98
|
+
// on first connection, add node as explicit
|
|
99
|
+
if (connections.length === 0) {
|
|
100
|
+
stage.addExplicit(node)
|
|
101
|
+
}
|
|
102
|
+
stage.addExplicit(child)
|
|
103
|
+
connections.push(...found)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (connections.length > 0) {
|
|
108
|
+
// First connections inside
|
|
109
|
+
stage.addConnections([
|
|
110
|
+
...findConnectionsWithin(children),
|
|
111
|
+
...connections
|
|
112
|
+
])
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
stage.addConnections(
|
|
116
|
+
findConnectionsBetween(node, memory.elements)
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* include node.**
|
|
122
|
+
*/
|
|
123
|
+
function includeDeployedNodeDescendants(
|
|
124
|
+
node: DeploymentNodeModel,
|
|
125
|
+
{ memory, stage }: PredicateCtx
|
|
126
|
+
) {
|
|
127
|
+
const descendants = [...node.descendants()]
|
|
128
|
+
if (descendants.length === 0) {
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
for (const child of descendants) {
|
|
132
|
+
if (child.isInstance()) {
|
|
133
|
+
stage.addExplicit(child)
|
|
134
|
+
} else {
|
|
135
|
+
stage.addImplicit(child)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (hasAtLeast(descendants, 2)) {
|
|
140
|
+
stage.addConnections(findConnectionsWithin(descendants))
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
for (const child of descendants) {
|
|
144
|
+
stage.addConnections(findConnectionsBetween(child, memory.elements))
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { identity, isNonNullish } from 'remeda'
|
|
2
|
+
import { invariant } from '../../../errors'
|
|
3
|
+
import type { LikeC4DeploymentModel } from '../../../model'
|
|
4
|
+
import { findConnectionsBetween } from '../../../model/connection/deployment'
|
|
5
|
+
import type { DeploymentConnectionModel } from '../../../model/connection/deployment'
|
|
6
|
+
import { DeploymentElementModel } from '../../../model/DeploymentElementModel'
|
|
7
|
+
import { DeploymentElementExpression, type DeploymentRelationExpression } from '../../../types/deployments'
|
|
8
|
+
import { union } from '../../../utils/set'
|
|
9
|
+
import type { PredicateExecutor } from '../_types'
|
|
10
|
+
import { deploymentExpressionToPredicate, resolveElements } from '../utils'
|
|
11
|
+
|
|
12
|
+
const isRef = DeploymentElementExpression.isRef
|
|
13
|
+
|
|
14
|
+
export const resolveAscendingSiblings = (element: DeploymentElementModel) => {
|
|
15
|
+
const siblings = new Set<DeploymentElementModel>()
|
|
16
|
+
for (let sibling of element.ascendingSiblings()) {
|
|
17
|
+
if (element.isInstance() && sibling.isDeploymentNode()) {
|
|
18
|
+
// we flatten nodes that contain only one instance
|
|
19
|
+
sibling = sibling.onlyOneInstance() ?? sibling
|
|
20
|
+
}
|
|
21
|
+
siblings.add(sibling)
|
|
22
|
+
}
|
|
23
|
+
return siblings
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const resolveIfWildcard = (model: LikeC4DeploymentModel, nonWildcard: DeploymentElementExpression.Ref) => {
|
|
27
|
+
const sources = resolveElements(model, nonWildcard)
|
|
28
|
+
const [head, ...rest] = sources.map(s => resolveAscendingSiblings(s))
|
|
29
|
+
if (head) {
|
|
30
|
+
let targets = rest.length > 0 ? union(head, ...rest) : head
|
|
31
|
+
return [sources, [...targets]] as const
|
|
32
|
+
}
|
|
33
|
+
return [sources, []] as const
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const DirectRelationPredicate: PredicateExecutor<DeploymentRelationExpression.Direct> = {
|
|
37
|
+
include: (expr, { model, stage }) => {
|
|
38
|
+
const sourceIsWildcard = DeploymentElementExpression.isWildcard(expr.source)
|
|
39
|
+
const targetIsWildcard = DeploymentElementExpression.isWildcard(expr.target)
|
|
40
|
+
|
|
41
|
+
let sources, targets
|
|
42
|
+
|
|
43
|
+
switch (true) {
|
|
44
|
+
case sourceIsWildcard && targetIsWildcard: {
|
|
45
|
+
sources = [...model.instances()]
|
|
46
|
+
targets = sources.slice()
|
|
47
|
+
break
|
|
48
|
+
}
|
|
49
|
+
case targetIsWildcard && isRef(expr.source): {
|
|
50
|
+
const [
|
|
51
|
+
resolvedSources,
|
|
52
|
+
resolvedTargets
|
|
53
|
+
] = resolveIfWildcard(model, expr.source)
|
|
54
|
+
sources = resolvedSources
|
|
55
|
+
targets = resolvedTargets
|
|
56
|
+
break
|
|
57
|
+
}
|
|
58
|
+
case sourceIsWildcard && isRef(expr.target): {
|
|
59
|
+
const [
|
|
60
|
+
resolvedSources,
|
|
61
|
+
resolvedTargets
|
|
62
|
+
] = resolveIfWildcard(model, expr.target)
|
|
63
|
+
// Swap sources and targets
|
|
64
|
+
sources = resolvedTargets
|
|
65
|
+
targets = resolvedSources
|
|
66
|
+
break
|
|
67
|
+
}
|
|
68
|
+
default: {
|
|
69
|
+
invariant(isRef(expr.source), 'Type inference failed')
|
|
70
|
+
invariant(isRef(expr.target), 'Type inference failed')
|
|
71
|
+
sources = resolveElements(model, expr.source)
|
|
72
|
+
targets = resolveElements(model, expr.target)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (sources.length === 0 || targets.length === 0) {
|
|
77
|
+
return identity()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const dir = expr.isBidirectional ? 'both' : 'directed'
|
|
81
|
+
for (const source of sources) {
|
|
82
|
+
stage.addConnections(findConnectionsBetween(source, targets, dir))
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (stage.connections.length === 0) {
|
|
86
|
+
return identity()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const c of stage.connections) {
|
|
90
|
+
if (c.source.isInstance() && c.target.isInstance() && c.boundary) {
|
|
91
|
+
stage.addImplicit(c.boundary)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (isRef(expr.source) && isNonNullish(expr.source.selector)) {
|
|
95
|
+
stage.addImplicit(model.element(expr.source.ref))
|
|
96
|
+
}
|
|
97
|
+
if (isRef(expr.target) && isNonNullish(expr.target.selector)) {
|
|
98
|
+
stage.addImplicit(model.element(expr.target.ref))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return stage.patch()
|
|
102
|
+
},
|
|
103
|
+
exclude: (expr, { memory, stage }) => {
|
|
104
|
+
// * -> *
|
|
105
|
+
// Exclude all connections
|
|
106
|
+
if (
|
|
107
|
+
DeploymentElementExpression.isWildcard(expr.source)
|
|
108
|
+
&& DeploymentElementExpression.isWildcard(expr.target)
|
|
109
|
+
) {
|
|
110
|
+
stage.excludeConnections(memory.connections)
|
|
111
|
+
return stage.patch()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const isSource = deploymentExpressionToPredicate(expr.source)
|
|
115
|
+
const isTarget = deploymentExpressionToPredicate(expr.target)
|
|
116
|
+
|
|
117
|
+
const satisfies = (connection: DeploymentConnectionModel) => {
|
|
118
|
+
return isSource(connection.source) && isTarget(connection.target)
|
|
119
|
+
|| (expr.isBidirectional && isSource(connection.target) && isTarget(connection.source))
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const toExclude = memory.connections.filter(satisfies)
|
|
123
|
+
if (toExclude.length === 0) {
|
|
124
|
+
return identity()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
stage.excludeConnections(toExclude)
|
|
128
|
+
return stage.patch()
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { identity } from 'remeda'
|
|
2
|
+
import type { DeploymentConnectionModel } from '../../../model/connection/deployment'
|
|
3
|
+
import { findConnectionsBetween } from '../../../model/connection/deployment'
|
|
4
|
+
import { DeploymentElementExpression, type DeploymentRelationExpression } from '../../../types/deployments'
|
|
5
|
+
import type { PredicateExecutor } from '../_types'
|
|
6
|
+
import { deploymentExpressionToPredicate, resolveElements } from '../utils'
|
|
7
|
+
import { resolveAscendingSiblings } from './relation-direct'
|
|
8
|
+
|
|
9
|
+
//
|
|
10
|
+
export const InOutRelationPredicate: PredicateExecutor<DeploymentRelationExpression.InOut> = {
|
|
11
|
+
include: (expr, { model, memory, stage }) => {
|
|
12
|
+
const sources = [...memory.elements]
|
|
13
|
+
if (DeploymentElementExpression.isWildcard(expr.inout)) {
|
|
14
|
+
for (const source of sources) {
|
|
15
|
+
const targets = [...resolveAscendingSiblings(source)]
|
|
16
|
+
stage.addConnections(findConnectionsBetween(source, targets, 'both'))
|
|
17
|
+
}
|
|
18
|
+
return stage.patch()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const targets = resolveElements(model, expr.inout)
|
|
22
|
+
for (const source of sources) {
|
|
23
|
+
stage.addConnections(findConnectionsBetween(source, targets, 'both'))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return stage.patch()
|
|
27
|
+
},
|
|
28
|
+
exclude: (expr, { memory, stage }) => {
|
|
29
|
+
const isSourceOrTarget = deploymentExpressionToPredicate(expr.inout)
|
|
30
|
+
|
|
31
|
+
const satisfies = (connection: DeploymentConnectionModel) => {
|
|
32
|
+
return isSourceOrTarget(connection.source) || isSourceOrTarget(connection.target)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const toExclude = memory.connections.filter(satisfies)
|
|
36
|
+
if (toExclude.length === 0) {
|
|
37
|
+
return identity()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
stage.excludeConnections(toExclude)
|
|
41
|
+
return stage.patch()
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { identity } from 'remeda'
|
|
2
|
+
import { findConnection, findConnectionsBetween } from '../../../model/connection/deployment'
|
|
3
|
+
import type { DeploymentConnectionModel } from '../../../model/connection/deployment'
|
|
4
|
+
import { DeploymentElementExpression, type DeploymentRelationExpression } from '../../../types/deployments'
|
|
5
|
+
import type { PredicateExecutor } from '../_types'
|
|
6
|
+
import { deploymentExpressionToPredicate, resolveElements } from '../utils'
|
|
7
|
+
import { resolveAscendingSiblings } from './relation-direct'
|
|
8
|
+
|
|
9
|
+
// from visible element incoming to this
|
|
10
|
+
export const IncomingRelationPredicate: PredicateExecutor<DeploymentRelationExpression.Incoming> = {
|
|
11
|
+
include: (expr, { model, memory, stage }) => {
|
|
12
|
+
const sources = [...memory.elements]
|
|
13
|
+
if (DeploymentElementExpression.isWildcard(expr.incoming)) {
|
|
14
|
+
for (const source of sources) {
|
|
15
|
+
if (source.allOutgoing.isEmpty) {
|
|
16
|
+
continue
|
|
17
|
+
}
|
|
18
|
+
const targets = [...resolveAscendingSiblings(source)]
|
|
19
|
+
stage.addConnections(findConnectionsBetween(source, targets, 'directed'))
|
|
20
|
+
}
|
|
21
|
+
return stage.patch()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const targets = resolveElements(model, expr.incoming)
|
|
25
|
+
for (const source of sources) {
|
|
26
|
+
stage.addConnections(findConnectionsBetween(source, targets, 'directed'))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return stage.patch()
|
|
30
|
+
},
|
|
31
|
+
exclude: (expr, { memory, stage }) => {
|
|
32
|
+
const isTarget = deploymentExpressionToPredicate(expr.incoming)
|
|
33
|
+
|
|
34
|
+
const satisfies = (connection: DeploymentConnectionModel) => {
|
|
35
|
+
return isTarget(connection.target)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const toExclude = memory.connections.filter(satisfies)
|
|
39
|
+
if (toExclude.length === 0) {
|
|
40
|
+
return identity()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
stage.excludeConnections(toExclude)
|
|
44
|
+
return stage.patch()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { identity } from 'remeda'
|
|
2
|
+
import { findConnection, findConnectionsBetween } from '../../../model/connection/deployment'
|
|
3
|
+
import type { DeploymentConnectionModel } from '../../../model/connection/deployment'
|
|
4
|
+
import { DeploymentElementExpression, type DeploymentRelationExpression } from '../../../types/deployments'
|
|
5
|
+
import type { PredicateExecutor } from '../_types'
|
|
6
|
+
import { deploymentExpressionToPredicate, resolveElements } from '../utils'
|
|
7
|
+
import { resolveAscendingSiblings } from './relation-direct'
|
|
8
|
+
|
|
9
|
+
export const OutgoingRelationPredicate: PredicateExecutor<DeploymentRelationExpression.Outgoing> = {
|
|
10
|
+
include: (expr, { model, memory, stage }) => {
|
|
11
|
+
const targets = [...memory.elements]
|
|
12
|
+
|
|
13
|
+
// * -> (to visible elements)
|
|
14
|
+
// outgoing from wildcard to visible element
|
|
15
|
+
if (DeploymentElementExpression.isWildcard(expr.outgoing)) {
|
|
16
|
+
for (const target of targets) {
|
|
17
|
+
if (target.allIncoming.isEmpty) {
|
|
18
|
+
continue
|
|
19
|
+
}
|
|
20
|
+
for (const source of resolveAscendingSiblings(target)) {
|
|
21
|
+
stage.addConnections(findConnection(source, target, 'directed'))
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return stage.patch()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const sources = resolveElements(model, expr.outgoing)
|
|
28
|
+
for (const source of sources) {
|
|
29
|
+
stage.addConnections(findConnectionsBetween(source, targets, 'directed'))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return stage.patch()
|
|
33
|
+
},
|
|
34
|
+
exclude: (expr, { memory, stage }) => {
|
|
35
|
+
const isSource = deploymentExpressionToPredicate(expr.outgoing)
|
|
36
|
+
|
|
37
|
+
const satisfies = (connection: DeploymentConnectionModel) => {
|
|
38
|
+
return isSource(connection.source)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const toExclude = memory.connections.filter(satisfies)
|
|
42
|
+
if (toExclude.length === 0) {
|
|
43
|
+
return identity()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
stage.excludeConnections(toExclude)
|
|
47
|
+
return stage.patch()
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { findConnectionsWithin } from '../../../model/connection/deployment'
|
|
2
|
+
import type { DeploymentElementExpression } from '../../../types/deployments'
|
|
3
|
+
import type { Elem, PredicateExecutor } from '../_types'
|
|
4
|
+
import { MutableMemory } from '../Memory'
|
|
5
|
+
|
|
6
|
+
export const WildcardPredicate: PredicateExecutor<DeploymentElementExpression.Wildcard> = {
|
|
7
|
+
include: (_, { model, stage }) => {
|
|
8
|
+
const children = [] as Elem[]
|
|
9
|
+
|
|
10
|
+
const rootElements = [...model.roots()].map(root => {
|
|
11
|
+
const onlyOneInstance = root.onlyOneInstance()
|
|
12
|
+
if (onlyOneInstance) {
|
|
13
|
+
children.push(onlyOneInstance)
|
|
14
|
+
return onlyOneInstance
|
|
15
|
+
}
|
|
16
|
+
children.push(...root.children())
|
|
17
|
+
return root
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
stage.addExplicit(rootElements)
|
|
21
|
+
if (children.length > 1) {
|
|
22
|
+
stage.addConnections(findConnectionsWithin(children))
|
|
23
|
+
}
|
|
24
|
+
return stage.patch()
|
|
25
|
+
},
|
|
26
|
+
exclude: () => {
|
|
27
|
+
return () => MutableMemory.empty()
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { hasAtLeast, isEmpty } 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 { AnyAux } from '../../model/types'
|
|
6
|
+
import {
|
|
7
|
+
type ComputedEdge,
|
|
8
|
+
type ComputedNode,
|
|
9
|
+
DefaultArrowType,
|
|
10
|
+
DeploymentElementExpression,
|
|
11
|
+
type DeploymentNodeKind,
|
|
12
|
+
type DeploymentViewRule,
|
|
13
|
+
type Fqn,
|
|
14
|
+
isViewRuleStyle,
|
|
15
|
+
type NonEmptyArray,
|
|
16
|
+
type Tag
|
|
17
|
+
} from '../../types'
|
|
18
|
+
import { nameFromFqn, parentFqn } from '../../utils'
|
|
19
|
+
import { applyViewRuleStyle } from '../utils/applyViewRuleStyles'
|
|
20
|
+
import { buildComputedNodes, type ComputedNodeSource } from '../utils/buildComputedNodes'
|
|
21
|
+
import { deriveEdgePropsFromRelationships } from '../utils/derive-edge-props-from-relationships'
|
|
22
|
+
import { uniqueTags } from '../utils/uniqueTags'
|
|
23
|
+
import type { Elem } from './_types'
|
|
24
|
+
import { type Memory, MutableMemory } from './Memory'
|
|
25
|
+
|
|
26
|
+
type Predicate<T> = (x: T) => boolean
|
|
27
|
+
|
|
28
|
+
export function resolveElements(
|
|
29
|
+
model: LikeC4DeploymentModel,
|
|
30
|
+
expr: DeploymentElementExpression.Ref
|
|
31
|
+
) {
|
|
32
|
+
const ref = model.element(expr.ref)
|
|
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 deploymentExpressionToPredicate<T extends { id: string }>(
|
|
48
|
+
target: DeploymentElementExpression
|
|
49
|
+
): Predicate<T> {
|
|
50
|
+
if (DeploymentElementExpression.isWildcard(target)) {
|
|
51
|
+
return () => true
|
|
52
|
+
}
|
|
53
|
+
if (DeploymentElementExpression.isRef(target)) {
|
|
54
|
+
const fqn = target.ref.id
|
|
55
|
+
if (target.selector === 'expanded') {
|
|
56
|
+
const fqnWithDot = fqn + '.'
|
|
57
|
+
return n => n.id === fqn || n.id.startsWith(fqnWithDot)
|
|
58
|
+
}
|
|
59
|
+
if (target.selector === 'descendants') {
|
|
60
|
+
const fqnWithDot = fqn + '.'
|
|
61
|
+
return n => n.id.startsWith(fqnWithDot)
|
|
62
|
+
}
|
|
63
|
+
if (target.selector === 'children') {
|
|
64
|
+
return n => parentFqn(n.id) === fqn
|
|
65
|
+
}
|
|
66
|
+
return n => n.id === fqn
|
|
67
|
+
}
|
|
68
|
+
nonexhaustive(target)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function toNodeSource(el: Elem): ComputedNodeSource {
|
|
72
|
+
if (el.isDeploymentNode()) {
|
|
73
|
+
const onlyOneInstance = el.onlyOneInstance()
|
|
74
|
+
let { title, kind, id, ...$node } = el.$node
|
|
75
|
+
const { icon, color, shape, ...style } = el.$node.style ?? {}
|
|
76
|
+
|
|
77
|
+
// If there is only one instance and title was not overriden
|
|
78
|
+
if (onlyOneInstance && title === nameFromFqn(el.id)) {
|
|
79
|
+
title = onlyOneInstance.title
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
...(onlyOneInstance && {
|
|
84
|
+
...toNodeSource(onlyOneInstance),
|
|
85
|
+
modelRef: onlyOneInstance.$element.id
|
|
86
|
+
}),
|
|
87
|
+
title,
|
|
88
|
+
...$node,
|
|
89
|
+
...(icon && { icon }),
|
|
90
|
+
...(color && { color }),
|
|
91
|
+
...(shape && { shape }),
|
|
92
|
+
style: {
|
|
93
|
+
...style
|
|
94
|
+
},
|
|
95
|
+
deploymentRef: 1,
|
|
96
|
+
kind,
|
|
97
|
+
id
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
invariant(el.isInstance(), 'Expected Instance')
|
|
101
|
+
const instance = el.$instance
|
|
102
|
+
const element = el.$element.$element
|
|
103
|
+
|
|
104
|
+
const icon = instance.style?.icon ?? element.icon
|
|
105
|
+
const color = instance.style?.color ?? element.color
|
|
106
|
+
const shape = instance.style?.shape ?? element.shape
|
|
107
|
+
|
|
108
|
+
const links = [
|
|
109
|
+
...(element.links ?? []),
|
|
110
|
+
...(instance.links ?? [])
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
const metadata = {
|
|
114
|
+
...element.metadata,
|
|
115
|
+
...instance.metadata
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const notation = instance.notation ?? element.notation
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
id: el.id,
|
|
122
|
+
kind: 'instance' as DeploymentNodeKind,
|
|
123
|
+
title: instance.title ?? element.title,
|
|
124
|
+
description: instance.description ?? element.description,
|
|
125
|
+
technology: instance.technology ?? element.technology,
|
|
126
|
+
tags: uniqueTags([element, instance]) as NonEmptyArray<Tag>,
|
|
127
|
+
links: hasAtLeast(links, 1) ? links : null,
|
|
128
|
+
...icon && { icon },
|
|
129
|
+
...color && { color },
|
|
130
|
+
...shape && { shape },
|
|
131
|
+
style: {
|
|
132
|
+
...element.style,
|
|
133
|
+
...instance.style
|
|
134
|
+
},
|
|
135
|
+
deploymentRef: el.id === instance.id ? 1 : instance.id,
|
|
136
|
+
modelRef: el.id === element.id ? 1 : element.id,
|
|
137
|
+
...notation && { notation },
|
|
138
|
+
...!isEmpty(metadata) && ({ metadata })
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function toComputedEdges<M extends AnyAux>(
|
|
143
|
+
connections: ReadonlyArray<DeploymentConnectionModel<M>>
|
|
144
|
+
): ComputedEdge[] {
|
|
145
|
+
return connections.reduce((acc, e) => {
|
|
146
|
+
// const modelRelations = []
|
|
147
|
+
// const deploymentRelations = []
|
|
148
|
+
const relations = [
|
|
149
|
+
...e.relations.model,
|
|
150
|
+
...e.relations.deployment
|
|
151
|
+
]
|
|
152
|
+
invariant(hasAtLeast(relations, 1), 'Edge must have at least one relation')
|
|
153
|
+
|
|
154
|
+
const source = e.source.id
|
|
155
|
+
const target = e.target.id
|
|
156
|
+
|
|
157
|
+
const {
|
|
158
|
+
title,
|
|
159
|
+
...props
|
|
160
|
+
} = deriveEdgePropsFromRelationships(relations.map(r => r.$relationship)) // || relations.find(r => r.source === source && r.target === target)
|
|
161
|
+
|
|
162
|
+
const edge: ComputedEdge = {
|
|
163
|
+
id: e.id,
|
|
164
|
+
parent: e.boundary?.id ?? null,
|
|
165
|
+
source,
|
|
166
|
+
target,
|
|
167
|
+
label: title ?? null,
|
|
168
|
+
relations: relations.map((r) => r.id as M['RelationId']),
|
|
169
|
+
...props
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// If exists same edge but in opposite direction
|
|
173
|
+
const existing = acc.find(e => e.source === target && e.target === source)
|
|
174
|
+
if (existing && edge.label === existing.label) {
|
|
175
|
+
existing.dir = 'both'
|
|
176
|
+
const head = existing.head ?? edge.head ?? DefaultArrowType
|
|
177
|
+
existing.head ??= head
|
|
178
|
+
existing.tail ??= head
|
|
179
|
+
|
|
180
|
+
if (edge.color) {
|
|
181
|
+
existing.color ??= edge.color
|
|
182
|
+
}
|
|
183
|
+
if (edge.line) {
|
|
184
|
+
existing.line ??= edge.line
|
|
185
|
+
}
|
|
186
|
+
return acc
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
acc.push(edge)
|
|
190
|
+
return acc
|
|
191
|
+
}, [] as ComputedEdge[])
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function buildNodes(memory: Memory): ReadonlyMap<Fqn, ComputedNode> {
|
|
195
|
+
// typecast to MutableMemory
|
|
196
|
+
invariant(memory instanceof MutableMemory, 'Expected MutableMemory')
|
|
197
|
+
return buildComputedNodes([...memory.finalElements].map(toNodeSource))
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function applyDeploymentViewRuleStyles(
|
|
201
|
+
rules: DeploymentViewRule[],
|
|
202
|
+
nodes: ComputedNode[]
|
|
203
|
+
) {
|
|
204
|
+
for (const rule of rules) {
|
|
205
|
+
if (!isViewRuleStyle(rule) || rule.targets.length === 0) {
|
|
206
|
+
continue
|
|
207
|
+
}
|
|
208
|
+
const predicates = rule.targets.map(deploymentExpressionToPredicate)
|
|
209
|
+
applyViewRuleStyle(rule, predicates, nodes)
|
|
210
|
+
}
|
|
211
|
+
return nodes
|
|
212
|
+
}
|