@likec4/core 1.17.1 → 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.
Files changed (112) hide show
  1. package/dist/compute-view/index.d.mts +91 -0
  2. package/dist/compute-view/index.d.ts +91 -0
  3. package/dist/compute-view/index.mjs +79 -0
  4. package/dist/index.d.mts +246 -434
  5. package/dist/index.d.ts +246 -434
  6. package/dist/index.mjs +418 -1322
  7. package/dist/model/index.d.mts +642 -0
  8. package/dist/model/index.d.ts +642 -0
  9. package/dist/model/index.mjs +2 -0
  10. package/dist/shared/core.BBgiAAv1.mjs +144 -0
  11. package/dist/shared/core.BPyJvuMU.mjs +2420 -0
  12. package/dist/shared/core.BQ4AbfvB.mjs +9228 -0
  13. package/dist/shared/{core.CoLQkcYL.d.mts → core.DMYY7C8s.d.mts} +398 -249
  14. package/dist/shared/{core.CoLQkcYL.d.ts → core.DMYY7C8s.d.ts} +398 -249
  15. package/dist/shared/core.k12s_keD.mjs +203 -0
  16. package/dist/types/index.d.mts +69 -1
  17. package/dist/types/index.d.ts +69 -1
  18. package/dist/types/index.mjs +21 -1
  19. package/package.json +38 -40
  20. package/src/builder/Builder.deployment.ts +219 -0
  21. package/src/builder/Builder.deploymentModel.ts +360 -0
  22. package/src/builder/Builder.element.ts +17 -19
  23. package/src/builder/Builder.model.ts +13 -2
  24. package/src/builder/Builder.test-d.ts +59 -4
  25. package/src/builder/Builder.ts +238 -53
  26. package/src/builder/Builder.view.ts +238 -53
  27. package/src/builder/_types.ts +87 -15
  28. package/src/compute-view/LikeC4ModelGraph.ts +330 -0
  29. package/src/compute-view/compute-view.ts +92 -0
  30. package/src/compute-view/deployment-view/Memory.ts +108 -0
  31. package/src/compute-view/deployment-view/Stage.ts +145 -0
  32. package/src/compute-view/deployment-view/__test__/fixture.ts +210 -0
  33. package/src/compute-view/deployment-view/_types.ts +29 -0
  34. package/src/compute-view/deployment-view/clean-connections.ts +229 -0
  35. package/src/compute-view/deployment-view/compute.ts +111 -0
  36. package/src/compute-view/deployment-view/predicates/deployment-ref.ts +146 -0
  37. package/src/compute-view/deployment-view/predicates/relation-direct.ts +130 -0
  38. package/src/compute-view/deployment-view/predicates/relation-in-out.ts +43 -0
  39. package/src/compute-view/deployment-view/predicates/relation-incoming.ts +46 -0
  40. package/src/compute-view/deployment-view/predicates/relation-outgoing.ts +49 -0
  41. package/src/compute-view/deployment-view/predicates/wildcard.ts +29 -0
  42. package/src/compute-view/deployment-view/utils.ts +212 -0
  43. package/src/compute-view/dynamic-view/__test__/fixture.ts +58 -0
  44. package/src/compute-view/dynamic-view/compute.ts +310 -0
  45. package/src/compute-view/element-view/__test__/__snapshots__/legacy.spec.ts.snap +456 -0
  46. package/src/compute-view/element-view/__test__/fixture.ts +645 -0
  47. package/src/compute-view/element-view/compute.ts +734 -0
  48. package/src/compute-view/element-view/predicates.ts +517 -0
  49. package/src/compute-view/index.ts +5 -0
  50. package/src/compute-view/utils/__snapshots__/sortNodes.spec.ts.snap +113 -0
  51. package/src/compute-view/utils/ancestorsOfNode.ts +19 -0
  52. package/src/compute-view/utils/applyCustomElementProperties.ts +66 -0
  53. package/src/compute-view/utils/applyCustomRelationProperties.ts +41 -0
  54. package/src/compute-view/utils/applyViewRuleStyles.ts +53 -0
  55. package/src/compute-view/utils/buildComputedNodes.ts +148 -0
  56. package/src/compute-view/utils/buildElementNotations.ts +63 -0
  57. package/src/compute-view/utils/derive-edge-props-from-relationships.ts +97 -0
  58. package/src/compute-view/utils/elementExpressionToPredicate.ts +52 -0
  59. package/src/compute-view/utils/link-nodes-with-edges.ts +58 -0
  60. package/src/compute-view/utils/relationExpressionToPredicates.ts +55 -0
  61. package/src/compute-view/utils/resolve-extended-views.ts +65 -0
  62. package/src/compute-view/utils/resolve-global-rules.ts +83 -0
  63. package/src/compute-view/utils/sortNodes.ts +101 -0
  64. package/src/compute-view/utils/topological-sort.ts +160 -0
  65. package/src/compute-view/utils/uniqueTags.test.ts +42 -0
  66. package/src/compute-view/utils/uniqueTags.ts +21 -0
  67. package/src/compute-view/utils/view-hash.ts +27 -0
  68. package/src/compute-view/utils/with-readable-edges.ts +31 -0
  69. package/src/index.ts +5 -1
  70. package/src/model/DeploymentElementModel.ts +534 -0
  71. package/src/model/DeploymentModel.ts +364 -0
  72. package/src/model/ElementModel.ts +195 -0
  73. package/src/model/LikeC4Model.test-d.ts +151 -0
  74. package/src/model/LikeC4Model.ts +280 -457
  75. package/src/model/RelationModel.ts +84 -0
  76. package/src/model/__test__/fixture.ts +153 -338
  77. package/src/model/connection/ConnectionModel.ts +94 -0
  78. package/src/model/connection/DeploymentConnectionModel.ts +99 -0
  79. package/src/model/connection/deployment.ts +124 -0
  80. package/src/model/connection/model.ts +123 -0
  81. package/src/model/index.ts +4 -2
  82. package/src/model/types.ts +95 -15
  83. package/src/model/view/EdgeModel.ts +103 -0
  84. package/src/model/view/LikeC4ViewModel.ts +220 -0
  85. package/src/model/view/NodeModel.ts +228 -0
  86. package/src/types/_common.ts +2 -0
  87. package/src/types/deployments.ts +237 -0
  88. package/src/types/element.ts +7 -3
  89. package/src/types/expression.ts +8 -6
  90. package/src/types/index.ts +1 -0
  91. package/src/types/model.ts +68 -9
  92. package/src/types/overview-graph.ts +2 -2
  93. package/src/types/relation.ts +4 -4
  94. package/src/types/view-notation.ts +2 -2
  95. package/src/types/view.ts +104 -30
  96. package/src/utils/{commonHead.ts → common-head.ts} +2 -0
  97. package/src/utils/fqn.ts +83 -27
  98. package/src/utils/getOrCreate.ts +42 -0
  99. package/src/utils/graphlib.ts +10 -0
  100. package/src/utils/index.ts +4 -1
  101. package/src/utils/object-hash.ts +3 -0
  102. package/src/utils/set.ts +76 -0
  103. package/src/utils/string-hash.ts +16 -0
  104. package/dist/index.cjs +0 -5452
  105. package/dist/index.d.cts +0 -1053
  106. package/dist/shared/core.BXTs-8n7.mjs +0 -293
  107. package/dist/shared/core.CoLQkcYL.d.cts +0 -734
  108. package/dist/shared/core.DIYZvpVg.cjs +0 -353
  109. package/dist/types/index.cjs +0 -70
  110. package/dist/types/index.d.cts +0 -2
  111. package/src/model/LikeC4DiagramModel.ts +0 -338
  112. 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
+ }