@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.
Files changed (184) hide show
  1. package/LICENSE +1 -1
  2. package/dist/builder/index.d.mts +502 -0
  3. package/dist/builder/index.d.ts +502 -0
  4. package/dist/builder/index.js +871 -0
  5. package/dist/compute-view/index.d.mts +22 -0
  6. package/dist/compute-view/index.d.ts +22 -0
  7. package/dist/compute-view/index.js +78 -0
  8. package/dist/index.d.mts +10 -919
  9. package/dist/index.d.ts +10 -919
  10. package/dist/{index.mjs → index.js} +7 -1688
  11. package/dist/model/index.d.mts +254 -0
  12. package/dist/model/index.d.ts +254 -0
  13. package/dist/model/index.js +1 -0
  14. package/dist/shared/chunk-RAAYCPUM.M-JWF7SS.js +21 -0
  15. package/dist/shared/core.BIfgabEw.d.ts +719 -0
  16. package/dist/shared/core.BZ9Msq7w.d.mts +719 -0
  17. package/dist/shared/{core.CoLQkcYL.d.cts → core.C05RDLhi.d.ts} +460 -362
  18. package/dist/shared/{core.CoLQkcYL.d.mts → core.CmYMqgha.d.mts} +460 -362
  19. package/dist/shared/core.D6-fWbM3.d.ts +127 -0
  20. package/dist/shared/core.D74xkKkG.d.mts +148 -0
  21. package/dist/shared/core.D74xkKkG.d.ts +148 -0
  22. package/dist/shared/core.sLaWHBjR.d.mts +127 -0
  23. package/dist/shared/index.-BdzdI2C.js +7443 -0
  24. package/dist/shared/object_hash.CE_oF3I3.js +1222 -0
  25. package/dist/shared/{core.BXTs-8n7.mjs → view.CyZrG8BJ.js} +108 -47
  26. package/dist/types/index.d.mts +71 -1
  27. package/dist/types/index.d.ts +71 -1
  28. package/dist/types/index.js +20 -0
  29. package/dist/utils/index.d.mts +404 -0
  30. package/dist/utils/index.d.ts +404 -0
  31. package/dist/utils/index.js +330 -0
  32. package/package.json +59 -47
  33. package/src/builder/Builder-style1.test-d.ts +190 -0
  34. package/src/builder/Builder-style2.test-d.ts +175 -0
  35. package/src/builder/Builder.deployment.ts +224 -0
  36. package/src/builder/Builder.deploymentModel.ts +385 -0
  37. package/src/builder/Builder.element.ts +19 -19
  38. package/src/builder/Builder.model.ts +67 -42
  39. package/src/builder/Builder.ts +533 -367
  40. package/src/builder/Builder.view-common.ts +234 -0
  41. package/src/builder/Builder.view-deployment.ts +135 -0
  42. package/src/builder/Builder.view-element.ts +189 -0
  43. package/src/builder/Builder.views.ts +109 -33
  44. package/src/builder/Builder.with.ts +475 -0
  45. package/src/builder/__snapshots__/Builder-style1.spec.ts.snap +374 -0
  46. package/src/builder/__snapshots__/Builder-style2.spec.ts.snap +183 -0
  47. package/src/builder/__snapshots__/Builder.spec.ts.snap +374 -0
  48. package/src/builder/_types.ts +113 -18
  49. package/src/builder/index.ts +10 -4
  50. package/src/colors/index.ts +2 -2
  51. package/src/compute-view/compute-view.ts +83 -0
  52. package/src/compute-view/deployment-view/__test__/TestHelper.ts +236 -0
  53. package/src/compute-view/deployment-view/__test__/fixture.ts +198 -0
  54. package/src/compute-view/deployment-view/_types.ts +37 -0
  55. package/src/compute-view/deployment-view/clean-connections.ts +238 -0
  56. package/src/compute-view/deployment-view/compute.ts +125 -0
  57. package/src/compute-view/deployment-view/memory/index.ts +9 -0
  58. package/src/compute-view/deployment-view/memory/memory.ts +97 -0
  59. package/src/compute-view/deployment-view/predicates/elements.ts +144 -0
  60. package/src/compute-view/deployment-view/predicates/relation-direct.ts +237 -0
  61. package/src/compute-view/deployment-view/predicates/relation-in-out.ts +61 -0
  62. package/src/compute-view/deployment-view/predicates/relation-incoming.ts +95 -0
  63. package/src/compute-view/deployment-view/predicates/relation-outgoing.ts +88 -0
  64. package/src/compute-view/deployment-view/predicates/wildcard.ts +29 -0
  65. package/src/compute-view/deployment-view/stages/stage-exclude.ts +5 -0
  66. package/src/compute-view/deployment-view/stages/stage-final.ts +245 -0
  67. package/src/compute-view/deployment-view/stages/stage-include.ts +95 -0
  68. package/src/compute-view/deployment-view/utils.ts +276 -0
  69. package/src/compute-view/dynamic-view/__test__/fixture.ts +58 -0
  70. package/src/compute-view/dynamic-view/compute.ts +324 -0
  71. package/src/compute-view/element-view/__test__/TestHelper.ts +258 -0
  72. package/src/compute-view/element-view/__test__/__snapshots__/legacy.spec.ts.snap +458 -0
  73. package/src/compute-view/element-view/__test__/fixture.ts +675 -0
  74. package/src/compute-view/element-view/_types.ts +48 -0
  75. package/src/compute-view/element-view/clean-connections.ts +113 -0
  76. package/src/compute-view/element-view/compute.ts +315 -0
  77. package/src/compute-view/element-view/memory/NodeGroup.ts +31 -0
  78. package/src/compute-view/element-view/memory/index.ts +10 -0
  79. package/src/compute-view/element-view/memory/memory.ts +177 -0
  80. package/src/compute-view/element-view/memory/stage-exclude.ts +59 -0
  81. package/src/compute-view/element-view/memory/stage-final.ts +129 -0
  82. package/src/compute-view/element-view/memory/stage-include.ts +128 -0
  83. package/src/compute-view/element-view/predicates/_utils.ts +86 -0
  84. package/src/compute-view/element-view/predicates/element-expand.ts +33 -0
  85. package/src/compute-view/element-view/predicates/element-kind-tag.ts +26 -0
  86. package/src/compute-view/element-view/predicates/element-ref.ts +37 -0
  87. package/src/compute-view/element-view/predicates/relation-direct.ts +309 -0
  88. package/src/compute-view/element-view/predicates/relation-in-out.ts +66 -0
  89. package/src/compute-view/element-view/predicates/relation-in.ts +118 -0
  90. package/src/compute-view/element-view/predicates/relation-out.ts +116 -0
  91. package/src/compute-view/element-view/predicates/wildcard.ts +88 -0
  92. package/src/compute-view/element-view/utils.ts +59 -0
  93. package/src/compute-view/index.ts +4 -0
  94. package/src/compute-view/memory/AbstractMemory.ts +45 -0
  95. package/src/compute-view/memory/AbstractStageExclude.ts +216 -0
  96. package/src/compute-view/memory/AbstractStageInclude.ts +200 -0
  97. package/src/compute-view/memory/_types.ts +147 -0
  98. package/src/compute-view/memory/index.ts +5 -0
  99. package/src/compute-view/memory/ops.ts +43 -0
  100. package/src/compute-view/utils/__snapshots__/sortNodes.spec.ts.snap +113 -0
  101. package/src/compute-view/utils/ancestorsOfNode.ts +19 -0
  102. package/src/compute-view/utils/applyCustomElementProperties.ts +66 -0
  103. package/src/compute-view/utils/applyCustomRelationProperties.ts +41 -0
  104. package/src/compute-view/utils/applyViewRuleStyles.ts +53 -0
  105. package/src/compute-view/utils/buildComputedNodes.ts +148 -0
  106. package/src/compute-view/utils/buildElementNotations.ts +63 -0
  107. package/src/compute-view/utils/elementExpressionToPredicate.ts +50 -0
  108. package/src/compute-view/utils/link-nodes-with-edges.ts +54 -0
  109. package/src/compute-view/utils/merge-props-from-relationships.ts +128 -0
  110. package/src/compute-view/utils/relationExpressionToPredicates.ts +55 -0
  111. package/src/compute-view/utils/resolve-extended-views.ts +69 -0
  112. package/src/compute-view/utils/resolve-global-rules.ts +83 -0
  113. package/src/compute-view/utils/topological-sort.ts +256 -0
  114. package/src/compute-view/utils/uniqueTags.test.ts +42 -0
  115. package/src/compute-view/utils/uniqueTags.ts +21 -0
  116. package/src/compute-view/utils/view-hash.ts +27 -0
  117. package/src/compute-view/utils/with-readable-edges.ts +36 -0
  118. package/src/index.ts +22 -12
  119. package/src/model/DeploymentElementModel.ts +599 -0
  120. package/src/model/DeploymentModel.ts +370 -0
  121. package/src/model/ElementModel.ts +221 -0
  122. package/src/model/LikeC4Model.test-d.ts +152 -0
  123. package/src/model/LikeC4Model.ts +336 -457
  124. package/src/model/RelationModel.ts +85 -0
  125. package/src/model/__test__/fixture.ts +149 -338
  126. package/src/model/connection/Connection.ts +72 -0
  127. package/src/model/connection/deployment/DeploymentConnectionModel.ts +178 -0
  128. package/src/model/connection/deployment/find.ts +120 -0
  129. package/src/model/connection/deployment/index.ts +8 -0
  130. package/src/model/connection/index.ts +22 -0
  131. package/src/model/connection/model/ConnectionModel.ts +157 -0
  132. package/src/model/connection/model/find.ts +117 -0
  133. package/src/model/connection/model/index.ts +8 -0
  134. package/src/model/connection/ops.ts +274 -0
  135. package/src/model/guards.ts +9 -0
  136. package/src/model/index.ts +46 -2
  137. package/src/model/types.ts +112 -15
  138. package/src/model/view/EdgeModel.ts +103 -0
  139. package/src/model/view/LikeC4ViewModel.ts +226 -0
  140. package/src/model/view/NodeModel.ts +229 -0
  141. package/src/types/_common.ts +55 -1
  142. package/src/types/deployments.ts +169 -0
  143. package/src/types/element.ts +9 -5
  144. package/src/types/expression-v2.ts +172 -0
  145. package/src/types/expression.ts +17 -11
  146. package/src/types/global.ts +3 -3
  147. package/src/types/index.ts +220 -12
  148. package/src/types/model.ts +75 -12
  149. package/src/types/operators.ts +1 -1
  150. package/src/types/overview-graph.ts +2 -2
  151. package/src/types/relation.ts +18 -14
  152. package/src/types/theme.ts +0 -1
  153. package/src/types/view-notation.ts +2 -2
  154. package/src/types/view.ts +117 -41
  155. package/src/utils/{commonHead.ts → common-head.ts} +2 -0
  156. package/src/utils/const.ts +1 -0
  157. package/src/utils/fqn.ts +203 -56
  158. package/src/utils/getOrCreate.ts +42 -0
  159. package/src/utils/graphlib.ts +10 -0
  160. package/src/utils/index.ts +74 -6
  161. package/src/utils/iterable/_types.ts +5 -0
  162. package/src/utils/iterable/filter.ts +55 -0
  163. package/src/utils/iterable/flat.ts +17 -0
  164. package/src/utils/iterable/index.ts +12 -0
  165. package/src/utils/iterable/map.ts +40 -0
  166. package/src/utils/iterable/reduce.ts +24 -0
  167. package/src/utils/iterable/some.ts +36 -0
  168. package/src/utils/iterable/to.ts +19 -0
  169. package/src/utils/iterable/unique.ts +29 -0
  170. package/src/utils/object-hash.ts +3 -0
  171. package/src/utils/relations.ts +3 -64
  172. package/src/utils/set.ts +78 -0
  173. package/src/utils/string-hash.ts +16 -0
  174. package/dist/index.cjs +0 -5452
  175. package/dist/index.d.cts +0 -1053
  176. package/dist/shared/core.CoLQkcYL.d.ts +0 -734
  177. package/dist/shared/core.DIYZvpVg.cjs +0 -353
  178. package/dist/types/index.cjs +0 -70
  179. package/dist/types/index.d.cts +0 -2
  180. package/dist/types/index.mjs +0 -1
  181. package/src/builder/Builder.test-d.ts +0 -69
  182. package/src/builder/Builder.view.ts +0 -358
  183. package/src/model/LikeC4DiagramModel.ts +0 -338
  184. 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
+ }