@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,43 @@
|
|
|
1
|
+
import DefaultMap from 'mnemonist/default-map'
|
|
2
|
+
import { Connection } from '../../model'
|
|
3
|
+
import { isAncestor, sortParentsFirst } from '../../utils'
|
|
4
|
+
import { toArray } from '../../utils/iterable'
|
|
5
|
+
import type { AnyCtx, ComputeMemory, CtxElement } from './_types'
|
|
6
|
+
|
|
7
|
+
export function treeFromMemoryState<T extends AnyCtx, M extends ComputeMemory<T> = ComputeMemory<T>>(
|
|
8
|
+
memory: M,
|
|
9
|
+
filter: 'all' | 'final' = 'final',
|
|
10
|
+
) {
|
|
11
|
+
const sorted = sortParentsFirst(toArray(filter === 'final' ? memory.final : memory.elements))
|
|
12
|
+
const connected = new Set(memory.connections.flatMap(c => [c.source, c.target]))
|
|
13
|
+
const root = new Set(sorted)
|
|
14
|
+
const parents = new DefaultMap<CtxElement<T>, CtxElement<T> | null>(() => null)
|
|
15
|
+
const children = sorted.reduce((acc, parent, index, all) => {
|
|
16
|
+
acc.set(
|
|
17
|
+
parent,
|
|
18
|
+
all
|
|
19
|
+
.slice(index + 1)
|
|
20
|
+
.filter(e => isAncestor(parent, e))
|
|
21
|
+
.map(e => {
|
|
22
|
+
root.delete(e)
|
|
23
|
+
return e
|
|
24
|
+
})
|
|
25
|
+
.reduce((acc, el) => {
|
|
26
|
+
if (!acc.some(e => isAncestor(e, el))) {
|
|
27
|
+
acc.push(el)
|
|
28
|
+
parents.set(el, parent)
|
|
29
|
+
}
|
|
30
|
+
return acc
|
|
31
|
+
}, [] as CtxElement<T>[]),
|
|
32
|
+
)
|
|
33
|
+
return acc
|
|
34
|
+
}, new DefaultMap<CtxElement<T>, ReadonlyArray<CtxElement<T>>>(() => []))
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
root: root as ReadonlySet<CtxElement<T>>,
|
|
38
|
+
connected: connected as ReadonlySet<CtxElement<T>>,
|
|
39
|
+
hasInOut: (el: CtxElement<T>) => memory.connections.some(Connection.isAnyInOut(el.id)),
|
|
40
|
+
parents: (el: CtxElement<T>) => children.get(el),
|
|
41
|
+
children: (el: CtxElement<T>) => children.get(el),
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`sortNodes > should generate valid testnodes 1`] = `
|
|
4
|
+
{
|
|
5
|
+
"edges": [
|
|
6
|
+
{
|
|
7
|
+
"id": "cloud.frontend.ui:cloud.backend",
|
|
8
|
+
"parent": "cloud",
|
|
9
|
+
"source": "cloud.frontend.ui",
|
|
10
|
+
"target": "cloud.backend",
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "cloud.backend:cloud.db",
|
|
14
|
+
"parent": "cloud",
|
|
15
|
+
"source": "cloud.backend",
|
|
16
|
+
"target": "cloud.db",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "cloud.db:amazon",
|
|
20
|
+
"parent": null,
|
|
21
|
+
"source": "cloud.db",
|
|
22
|
+
"target": "amazon",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "customer:cloud.frontend.ui",
|
|
26
|
+
"parent": null,
|
|
27
|
+
"source": "customer",
|
|
28
|
+
"target": "cloud.frontend.ui",
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
"nodes": [
|
|
32
|
+
{
|
|
33
|
+
"children": [],
|
|
34
|
+
"id": "customer",
|
|
35
|
+
"inEdges": [],
|
|
36
|
+
"outEdges": [
|
|
37
|
+
"customer:cloud.frontend.ui",
|
|
38
|
+
],
|
|
39
|
+
"parent": null,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"children": [],
|
|
43
|
+
"id": "amazon",
|
|
44
|
+
"inEdges": [
|
|
45
|
+
"cloud.db:amazon",
|
|
46
|
+
],
|
|
47
|
+
"outEdges": [],
|
|
48
|
+
"parent": null,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"children": [
|
|
52
|
+
"cloud.frontend.ui",
|
|
53
|
+
],
|
|
54
|
+
"id": "cloud.frontend",
|
|
55
|
+
"inEdges": [
|
|
56
|
+
"customer:cloud.frontend.ui",
|
|
57
|
+
],
|
|
58
|
+
"outEdges": [
|
|
59
|
+
"cloud.frontend.ui:cloud.backend",
|
|
60
|
+
],
|
|
61
|
+
"parent": "cloud",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"children": [],
|
|
65
|
+
"id": "cloud.frontend.ui",
|
|
66
|
+
"inEdges": [
|
|
67
|
+
"customer:cloud.frontend.ui",
|
|
68
|
+
],
|
|
69
|
+
"outEdges": [
|
|
70
|
+
"cloud.frontend.ui:cloud.backend",
|
|
71
|
+
],
|
|
72
|
+
"parent": "cloud.frontend",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"children": [
|
|
76
|
+
"cloud.frontend",
|
|
77
|
+
"cloud.db",
|
|
78
|
+
"cloud.backend",
|
|
79
|
+
],
|
|
80
|
+
"id": "cloud",
|
|
81
|
+
"inEdges": [
|
|
82
|
+
"customer:cloud.frontend.ui",
|
|
83
|
+
],
|
|
84
|
+
"outEdges": [
|
|
85
|
+
"cloud.db:amazon",
|
|
86
|
+
],
|
|
87
|
+
"parent": null,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"children": [],
|
|
91
|
+
"id": "cloud.db",
|
|
92
|
+
"inEdges": [
|
|
93
|
+
"cloud.backend:cloud.db",
|
|
94
|
+
],
|
|
95
|
+
"outEdges": [
|
|
96
|
+
"cloud.db:amazon",
|
|
97
|
+
],
|
|
98
|
+
"parent": "cloud",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"children": [],
|
|
102
|
+
"id": "cloud.backend",
|
|
103
|
+
"inEdges": [
|
|
104
|
+
"cloud.frontend.ui:cloud.backend",
|
|
105
|
+
],
|
|
106
|
+
"outEdges": [
|
|
107
|
+
"cloud.backend:cloud.db",
|
|
108
|
+
],
|
|
109
|
+
"parent": "cloud",
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
}
|
|
113
|
+
`;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { nonNullable } from '../../errors'
|
|
2
|
+
import type { ComputedNode, Fqn } from '../../types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns the ancestors of given computed node, starting with the direct parent and ending with the root node.
|
|
6
|
+
*/
|
|
7
|
+
export function ancestorsOfNode(
|
|
8
|
+
node: ComputedNode,
|
|
9
|
+
nodes: ReadonlyMap<Fqn, ComputedNode>
|
|
10
|
+
): ReadonlyArray<ComputedNode> {
|
|
11
|
+
const ancestors = [] as ComputedNode[]
|
|
12
|
+
let parentId = node.parent
|
|
13
|
+
while (parentId) {
|
|
14
|
+
const parentNode = nonNullable(nodes.get(parentId), `Parent node ${parentId} not found`)
|
|
15
|
+
ancestors.push(parentNode)
|
|
16
|
+
parentId = parentNode.parent
|
|
17
|
+
}
|
|
18
|
+
return ancestors
|
|
19
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { isEmpty, isNullish, omitBy } from 'remeda'
|
|
2
|
+
import { ComputedNode, isCustomElement, isViewRuleGroup, isViewRulePredicate, type ViewRule } from '../../types'
|
|
3
|
+
import type { Expression } from '../../types/expression'
|
|
4
|
+
import { elementExprToPredicate } from './elementExpressionToPredicate'
|
|
5
|
+
|
|
6
|
+
export function flattenGroupRules<T extends Expression>(guard: (expr: Expression) => expr is T) {
|
|
7
|
+
return (rule: ViewRule): Array<T> => {
|
|
8
|
+
if (isViewRuleGroup(rule)) {
|
|
9
|
+
return rule.groupRules.flatMap(flattenGroupRules(guard))
|
|
10
|
+
}
|
|
11
|
+
if (isViewRulePredicate(rule)) {
|
|
12
|
+
return 'include' in rule ? rule.include.filter(guard) : []
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return []
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function applyCustomElementProperties(_rules: ViewRule[], _nodes: ComputedNode[]) {
|
|
20
|
+
const rules = _rules.flatMap(flattenGroupRules(isCustomElement))
|
|
21
|
+
if (rules.length === 0) {
|
|
22
|
+
return _nodes
|
|
23
|
+
}
|
|
24
|
+
const nodes = [..._nodes]
|
|
25
|
+
for (
|
|
26
|
+
const {
|
|
27
|
+
custom: { expr, ...props }
|
|
28
|
+
} of rules
|
|
29
|
+
) {
|
|
30
|
+
const { border, opacity, ...rest } = omitBy(props, isNullish)
|
|
31
|
+
const notEmpty = !isEmpty(rest)
|
|
32
|
+
const satisfies = elementExprToPredicate(expr)
|
|
33
|
+
nodes.forEach((node, i) => {
|
|
34
|
+
if (ComputedNode.isNodesGroup(node) || !satisfies(node)) {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
if (notEmpty) {
|
|
38
|
+
node = {
|
|
39
|
+
...node,
|
|
40
|
+
isCustomized: true,
|
|
41
|
+
...rest
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let styleOverride: ComputedNode['style'] | undefined
|
|
46
|
+
if (border !== undefined) {
|
|
47
|
+
styleOverride = { border }
|
|
48
|
+
}
|
|
49
|
+
if (opacity !== undefined) {
|
|
50
|
+
styleOverride = { ...styleOverride, opacity }
|
|
51
|
+
}
|
|
52
|
+
if (styleOverride) {
|
|
53
|
+
node = {
|
|
54
|
+
...node,
|
|
55
|
+
isCustomized: true,
|
|
56
|
+
style: {
|
|
57
|
+
...node.style,
|
|
58
|
+
...styleOverride
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
nodes[i] = node
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
return nodes
|
|
66
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { isNullish, omitBy, pick } from 'remeda'
|
|
2
|
+
import type { ComputedEdge, ComputedNode, ViewRule } from '../../types'
|
|
3
|
+
import * as Expr from '../../types/expression'
|
|
4
|
+
import { flattenGroupRules } from './applyCustomElementProperties'
|
|
5
|
+
import { relationExpressionToPredicates } from './relationExpressionToPredicates'
|
|
6
|
+
|
|
7
|
+
export function applyCustomRelationProperties(
|
|
8
|
+
_rules: ViewRule[],
|
|
9
|
+
nodes: ComputedNode[],
|
|
10
|
+
_edges: Iterable<ComputedEdge>
|
|
11
|
+
): ComputedEdge[] {
|
|
12
|
+
const rules = _rules.flatMap(flattenGroupRules(Expr.isCustomRelationExpr))
|
|
13
|
+
const edges = Array.from(_edges)
|
|
14
|
+
if (rules.length === 0 || edges.length === 0) {
|
|
15
|
+
return edges
|
|
16
|
+
}
|
|
17
|
+
for (
|
|
18
|
+
const {
|
|
19
|
+
customRelation: { relation, title, ...customprops }
|
|
20
|
+
} of rules
|
|
21
|
+
) {
|
|
22
|
+
const props = omitBy(customprops, isNullish)
|
|
23
|
+
const satisfies = relationExpressionToPredicates(relation)
|
|
24
|
+
edges.forEach((edge, i) => {
|
|
25
|
+
const source = nodes.find(n => n.id === edge.source)
|
|
26
|
+
const target = nodes.find(n => n.id === edge.target)
|
|
27
|
+
if (!source || !target) {
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
if (satisfies({ source, target, ...pick(edge, ['kind', 'tags']) })) {
|
|
31
|
+
edges[i] = {
|
|
32
|
+
...edge,
|
|
33
|
+
...props,
|
|
34
|
+
label: title ?? edge.label,
|
|
35
|
+
isCustomized: true
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
return edges
|
|
41
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { anyPass, filter, forEach, isDefined, isNot, pipe } from 'remeda'
|
|
2
|
+
import { ComputedNode, isViewRuleStyle, type ViewRule, type ViewRuleStyle } from '../../types'
|
|
3
|
+
import { elementExprToPredicate } from './elementExpressionToPredicate'
|
|
4
|
+
|
|
5
|
+
type Predicate<T> = (x: T) => boolean
|
|
6
|
+
|
|
7
|
+
type CommonViewRuleStyle = Pick<ViewRuleStyle, 'style' | 'notation'>
|
|
8
|
+
|
|
9
|
+
export function applyViewRuleStyle<Rule extends CommonViewRuleStyle>(
|
|
10
|
+
rule: Rule,
|
|
11
|
+
predicates: Predicate<ComputedNode>[],
|
|
12
|
+
nodes: ComputedNode[]
|
|
13
|
+
) {
|
|
14
|
+
pipe(
|
|
15
|
+
nodes,
|
|
16
|
+
filter(isNot(ComputedNode.isNodesGroup)),
|
|
17
|
+
filter(anyPass(predicates)),
|
|
18
|
+
forEach(n => {
|
|
19
|
+
n.shape = rule.style.shape ?? n.shape
|
|
20
|
+
n.color = rule.style.color ?? n.color
|
|
21
|
+
if (isDefined(rule.style.icon)) {
|
|
22
|
+
n.icon = rule.style.icon
|
|
23
|
+
}
|
|
24
|
+
if (isDefined(rule.notation)) {
|
|
25
|
+
n.notation = rule.notation
|
|
26
|
+
}
|
|
27
|
+
let styleOverride: ComputedNode['style'] | undefined
|
|
28
|
+
if (isDefined(rule.style.border)) {
|
|
29
|
+
styleOverride = { border: rule.style.border }
|
|
30
|
+
}
|
|
31
|
+
if (isDefined(rule.style.opacity)) {
|
|
32
|
+
styleOverride = { ...styleOverride, opacity: rule.style.opacity }
|
|
33
|
+
}
|
|
34
|
+
if (styleOverride) {
|
|
35
|
+
n.style = {
|
|
36
|
+
...n.style,
|
|
37
|
+
...styleOverride
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function applyViewRuleStyles(rules: ViewRule[], nodes: ComputedNode[]) {
|
|
45
|
+
for (const rule of rules) {
|
|
46
|
+
if (!isViewRuleStyle(rule) || rule.targets.length === 0) {
|
|
47
|
+
continue
|
|
48
|
+
}
|
|
49
|
+
const predicates = rule.targets.map(elementExprToPredicate)
|
|
50
|
+
applyViewRuleStyle(rule, predicates, nodes)
|
|
51
|
+
}
|
|
52
|
+
return nodes
|
|
53
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { Simplify } from 'type-fest'
|
|
2
|
+
import { nonNullable } from '../../errors'
|
|
3
|
+
import {
|
|
4
|
+
type ComputedNode,
|
|
5
|
+
DefaultElementShape,
|
|
6
|
+
DefaultThemeColor,
|
|
7
|
+
type Element,
|
|
8
|
+
ElementKind,
|
|
9
|
+
type Fqn,
|
|
10
|
+
type NodeId
|
|
11
|
+
} from '../../types'
|
|
12
|
+
import { compareByFqnHierarchically, parentFqn } from '../../utils/fqn'
|
|
13
|
+
import { NodesGroup } from '../element-view/memory'
|
|
14
|
+
|
|
15
|
+
function updateDepthOfAncestors(node: ComputedNode, nodes: ReadonlyMap<Fqn, ComputedNode>) {
|
|
16
|
+
let parentNd
|
|
17
|
+
while (!!node.parent && (parentNd = nodes.get(node.parent))) {
|
|
18
|
+
const depth = parentNd.depth ?? 1
|
|
19
|
+
parentNd.depth = Math.max(depth, (node.depth ?? 0) + 1)
|
|
20
|
+
if (parentNd.depth === depth) {
|
|
21
|
+
// stop if we didn't change depth
|
|
22
|
+
break
|
|
23
|
+
}
|
|
24
|
+
node = parentNd
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const modelElementAsNodeSource = (element: Element): ComputedNodeSource => {
|
|
29
|
+
return {
|
|
30
|
+
...element,
|
|
31
|
+
modelRef: 1
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// type ComputedNodeSource = Simplify<SetRequired<Partial<Omit<ComputedNode, 'parent' | 'children' | 'inEdges' | 'outEdges' | 'level' | 'depth'>>, 'id' | 'title' | 'kind'>>
|
|
36
|
+
export type ComputedNodeSource = Simplify<
|
|
37
|
+
& Pick<ComputedNode, 'id' | 'title' | 'kind' | 'deploymentRef' | 'modelRef'>
|
|
38
|
+
& Partial<Omit<Element, 'id' | 'title' | 'kind'>>
|
|
39
|
+
>
|
|
40
|
+
|
|
41
|
+
export function buildComputedNodesFromElements(elements: ReadonlyArray<Element>, groups?: NodesGroup[]) {
|
|
42
|
+
return buildComputedNodes(elements.map(modelElementAsNodeSource), groups)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function buildComputedNodes(
|
|
46
|
+
elements: ReadonlyArray<ComputedNodeSource>,
|
|
47
|
+
groups?: ReadonlyArray<NodesGroup>
|
|
48
|
+
): ReadonlyMap<Fqn, ComputedNode> {
|
|
49
|
+
const nodesMap = new Map<Fqn, ComputedNode>()
|
|
50
|
+
|
|
51
|
+
const elementToGroup = new Map<Fqn, NodeId>()
|
|
52
|
+
|
|
53
|
+
groups?.forEach(({ id, parent, viewRule, elements }) => {
|
|
54
|
+
if (parent) {
|
|
55
|
+
nonNullable(nodesMap.get(parent), `Parent group node ${parent} not found`).children.push(id)
|
|
56
|
+
}
|
|
57
|
+
nodesMap.set(id, {
|
|
58
|
+
id,
|
|
59
|
+
parent,
|
|
60
|
+
kind: ElementKind.Group,
|
|
61
|
+
title: viewRule.title ?? '',
|
|
62
|
+
color: viewRule.color ?? 'muted',
|
|
63
|
+
shape: 'rectangle',
|
|
64
|
+
children: [],
|
|
65
|
+
inEdges: [],
|
|
66
|
+
outEdges: [],
|
|
67
|
+
level: 0,
|
|
68
|
+
depth: 0,
|
|
69
|
+
description: null,
|
|
70
|
+
technology: null,
|
|
71
|
+
tags: null,
|
|
72
|
+
links: null,
|
|
73
|
+
style: {
|
|
74
|
+
border: viewRule.border ?? 'dashed',
|
|
75
|
+
opacity: viewRule.opacity ?? 0
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
for (const e of elements) {
|
|
79
|
+
elementToGroup.set(e.id, id)
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
// Ensure that parent nodes are created before child nodes
|
|
84
|
+
Array.from(elements)
|
|
85
|
+
.sort(compareByFqnHierarchically)
|
|
86
|
+
.forEach(({ id, style, kind, title, color, shape, ...el }) => {
|
|
87
|
+
let parent = parentFqn(id)
|
|
88
|
+
let level = 0
|
|
89
|
+
let parentNd: ComputedNode | undefined
|
|
90
|
+
// Find the first ancestor that is already in the map
|
|
91
|
+
while (parent) {
|
|
92
|
+
parentNd = nodesMap.get(parent)
|
|
93
|
+
if (parentNd) {
|
|
94
|
+
break
|
|
95
|
+
}
|
|
96
|
+
parent = parentFqn(parent)
|
|
97
|
+
}
|
|
98
|
+
// If parent is not found in the map, check if it is in a group
|
|
99
|
+
if (!parentNd && elementToGroup.has(id)) {
|
|
100
|
+
parent = elementToGroup.get(id)!
|
|
101
|
+
parentNd = nodesMap.get(parent)!
|
|
102
|
+
}
|
|
103
|
+
if (parentNd) {
|
|
104
|
+
// if parent has no children and we are about to add first one
|
|
105
|
+
// we need to set its depth to 1
|
|
106
|
+
if (parentNd.children.length == 0) {
|
|
107
|
+
parentNd.depth = 1
|
|
108
|
+
// go up the tree and update depth of all parents
|
|
109
|
+
updateDepthOfAncestors(parentNd, nodesMap)
|
|
110
|
+
}
|
|
111
|
+
parentNd.children.push(id)
|
|
112
|
+
level = parentNd.level + 1
|
|
113
|
+
}
|
|
114
|
+
const node: ComputedNode = {
|
|
115
|
+
id,
|
|
116
|
+
parent,
|
|
117
|
+
kind,
|
|
118
|
+
title,
|
|
119
|
+
level,
|
|
120
|
+
color: color ?? DefaultThemeColor,
|
|
121
|
+
shape: shape ?? DefaultElementShape,
|
|
122
|
+
description: null,
|
|
123
|
+
technology: null,
|
|
124
|
+
tags: null,
|
|
125
|
+
links: null,
|
|
126
|
+
children: [],
|
|
127
|
+
inEdges: [],
|
|
128
|
+
outEdges: [],
|
|
129
|
+
...el,
|
|
130
|
+
style: {
|
|
131
|
+
...style
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
nodesMap.set(id, node)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
// Create new map and add elements in the same order as they were in the input
|
|
138
|
+
const orderedMap = new Map<Fqn, ComputedNode>()
|
|
139
|
+
|
|
140
|
+
groups?.forEach(({ id }) => {
|
|
141
|
+
orderedMap.set(id, nonNullable(nodesMap.get(id)))
|
|
142
|
+
})
|
|
143
|
+
elements.forEach(({ id }) => {
|
|
144
|
+
orderedMap.set(id, nonNullable(nodesMap.get(id)))
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
return orderedMap
|
|
148
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { entries, flatMap, groupBy, map, mapValues, pipe, piped, prop, sortBy, unique } from 'remeda'
|
|
2
|
+
import type { ComputedNode, ElementNotation } from '../../types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Build element notations from computed nodes:
|
|
6
|
+
* 1. Group by notation
|
|
7
|
+
* 2. Group by shape
|
|
8
|
+
* 3. Group by color
|
|
9
|
+
* 4. For each group get unique kinds
|
|
10
|
+
* 5. Unwind the groups
|
|
11
|
+
*/
|
|
12
|
+
export function buildElementNotations(nodes: ComputedNode[]): ElementNotation[] {
|
|
13
|
+
return pipe(
|
|
14
|
+
nodes,
|
|
15
|
+
groupBy(prop('notation')),
|
|
16
|
+
mapValues(
|
|
17
|
+
piped(
|
|
18
|
+
groupBy(prop('shape')),
|
|
19
|
+
mapValues(
|
|
20
|
+
piped(
|
|
21
|
+
groupBy(prop('color')),
|
|
22
|
+
mapValues(
|
|
23
|
+
piped(
|
|
24
|
+
map(prop('kind')),
|
|
25
|
+
unique()
|
|
26
|
+
)
|
|
27
|
+
),
|
|
28
|
+
entries(),
|
|
29
|
+
map(([color, kinds]) => ({
|
|
30
|
+
kinds,
|
|
31
|
+
color
|
|
32
|
+
}))
|
|
33
|
+
)
|
|
34
|
+
),
|
|
35
|
+
entries(),
|
|
36
|
+
flatMap(([shape, colors]) =>
|
|
37
|
+
colors.map(({ color, kinds }) => ({
|
|
38
|
+
shape,
|
|
39
|
+
color,
|
|
40
|
+
kinds
|
|
41
|
+
}))
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
),
|
|
45
|
+
entries(),
|
|
46
|
+
flatMap(([title, shapes]) =>
|
|
47
|
+
shapes.map(({ shape, color, kinds }) => ({
|
|
48
|
+
title,
|
|
49
|
+
shape,
|
|
50
|
+
color,
|
|
51
|
+
kinds
|
|
52
|
+
}))
|
|
53
|
+
),
|
|
54
|
+
sortBy(
|
|
55
|
+
prop('shape'),
|
|
56
|
+
prop('title'),
|
|
57
|
+
[
|
|
58
|
+
n => n.kinds.length,
|
|
59
|
+
'desc'
|
|
60
|
+
]
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { isNullish } from 'remeda'
|
|
2
|
+
import { nonexhaustive } from '../../errors'
|
|
3
|
+
import {
|
|
4
|
+
type ElementPredicateExpression,
|
|
5
|
+
isCustomElement,
|
|
6
|
+
isElementKindExpr,
|
|
7
|
+
isElementRef,
|
|
8
|
+
isElementTagExpr,
|
|
9
|
+
isElementWhere,
|
|
10
|
+
isExpandedElementExpr,
|
|
11
|
+
isWildcard,
|
|
12
|
+
whereOperatorAsPredicate
|
|
13
|
+
} from '../../types'
|
|
14
|
+
import { parentFqn } from '../../utils'
|
|
15
|
+
|
|
16
|
+
type Predicate<T> = (x: T) => boolean
|
|
17
|
+
|
|
18
|
+
export function elementExprToPredicate<T extends { id: string; tags?: readonly string[] | null; kind: string }>(
|
|
19
|
+
target: ElementPredicateExpression
|
|
20
|
+
): Predicate<T> {
|
|
21
|
+
if (isElementWhere(target)) {
|
|
22
|
+
const predicate = elementExprToPredicate(target.where.expr)
|
|
23
|
+
const where = whereOperatorAsPredicate(target.where.condition)
|
|
24
|
+
return n => predicate(n) && where(n)
|
|
25
|
+
}
|
|
26
|
+
if (isWildcard(target)) {
|
|
27
|
+
return () => true
|
|
28
|
+
}
|
|
29
|
+
if (isElementKindExpr(target)) {
|
|
30
|
+
return target.isEqual ? n => n.kind === target.elementKind : n => n.kind !== target.elementKind
|
|
31
|
+
}
|
|
32
|
+
if (isElementTagExpr(target)) {
|
|
33
|
+
return target.isEqual
|
|
34
|
+
? ({ tags }) => !!tags && tags.includes(target.elementTag)
|
|
35
|
+
: ({ tags }) => isNullish(tags) || !tags.includes(target.elementTag)
|
|
36
|
+
}
|
|
37
|
+
if (isExpandedElementExpr(target)) {
|
|
38
|
+
return n => n.id === target.expanded || parentFqn(n.id) === target.expanded
|
|
39
|
+
}
|
|
40
|
+
if (isElementRef(target)) {
|
|
41
|
+
const { element, isChildren, isDescendants } = target
|
|
42
|
+
return isChildren || isDescendants
|
|
43
|
+
? n => n.id.startsWith(element + '.')
|
|
44
|
+
: n => (n.id as string) === element
|
|
45
|
+
}
|
|
46
|
+
if (isCustomElement(target)) {
|
|
47
|
+
return elementExprToPredicate(target.custom.expr)
|
|
48
|
+
}
|
|
49
|
+
nonexhaustive(target)
|
|
50
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { last, reverse } from 'remeda'
|
|
2
|
+
import { invariant } from '../../errors'
|
|
3
|
+
import type { Fqn } from '../../types/element'
|
|
4
|
+
import type { ComputedEdge, ComputedNode } from '../../types/view'
|
|
5
|
+
import { commonHead } from '../../utils/common-head'
|
|
6
|
+
import { ancestorsOfNode } from './ancestorsOfNode'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Update `inEdges` and `outEdges` props of nodes based on the edges
|
|
10
|
+
* Mutates nodes and updates their in/out edges
|
|
11
|
+
*/
|
|
12
|
+
export function linkNodesWithEdges(nodesMap: ReadonlyMap<Fqn, ComputedNode>, edges: ComputedEdge[]) {
|
|
13
|
+
for (const edge of edges) {
|
|
14
|
+
const source = nodesMap.get(edge.source)
|
|
15
|
+
const target = nodesMap.get(edge.target)
|
|
16
|
+
invariant(source, `Source node ${edge.source} not found`)
|
|
17
|
+
invariant(target, `Target node ${edge.target} not found`)
|
|
18
|
+
|
|
19
|
+
source.outEdges.push(edge.id)
|
|
20
|
+
target.inEdges.push(edge.id)
|
|
21
|
+
|
|
22
|
+
// These ancestors are reversed: from bottom to top
|
|
23
|
+
// Ensure that ancestors are in computed nodes
|
|
24
|
+
const sourceAncestors = ancestorsOfNode(source, nodesMap)
|
|
25
|
+
const targetAncestors = ancestorsOfNode(target, nodesMap)
|
|
26
|
+
|
|
27
|
+
const hasAncestors = sourceAncestors.length > 0 && targetAncestors.length > 0
|
|
28
|
+
|
|
29
|
+
const edgeParent = hasAncestors
|
|
30
|
+
? last(
|
|
31
|
+
commonHead(
|
|
32
|
+
reverse(sourceAncestors),
|
|
33
|
+
reverse(targetAncestors),
|
|
34
|
+
),
|
|
35
|
+
)
|
|
36
|
+
: null
|
|
37
|
+
edge.parent = edgeParent ? edgeParent.id : null
|
|
38
|
+
|
|
39
|
+
// Process edge source ancestors
|
|
40
|
+
for (const sourceAncestor of sourceAncestors) {
|
|
41
|
+
if (sourceAncestor === edgeParent) {
|
|
42
|
+
break
|
|
43
|
+
}
|
|
44
|
+
sourceAncestor.outEdges.push(edge.id)
|
|
45
|
+
}
|
|
46
|
+
// Process target hierarchy
|
|
47
|
+
for (const targetAncestor of targetAncestors) {
|
|
48
|
+
if (targetAncestor === edgeParent) {
|
|
49
|
+
break
|
|
50
|
+
}
|
|
51
|
+
targetAncestor.inEdges.push(edge.id)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|