@likec4/core 1.18.0 → 1.19.1

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 (177) 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 +9 -78
  6. package/dist/compute-view/index.d.ts +9 -78
  7. package/dist/compute-view/index.js +78 -0
  8. package/dist/index.d.mts +9 -730
  9. package/dist/index.d.ts +9 -730
  10. package/dist/{index.mjs → index.js} +7 -784
  11. package/dist/model/index.d.mts +218 -606
  12. package/dist/model/index.d.ts +218 -606
  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.DMYY7C8s.d.mts → core.C05RDLhi.d.ts} +179 -230
  18. package/dist/shared/{core.DMYY7C8s.d.ts → core.CmYMqgha.d.mts} +179 -230
  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.DafVOuVd.js +7443 -0
  24. package/dist/shared/object_hash.CE_oF3I3.js +1222 -0
  25. package/dist/shared/view.CyZrG8BJ.js +354 -0
  26. package/dist/types/index.d.mts +4 -2
  27. package/dist/types/index.d.ts +4 -2
  28. package/dist/types/index.js +20 -0
  29. package/dist/utils/index.d.mts +419 -0
  30. package/dist/utils/index.d.ts +419 -0
  31. package/dist/utils/index.js +347 -0
  32. package/package.json +47 -33
  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 +25 -20
  36. package/src/builder/Builder.deploymentModel.ts +80 -55
  37. package/src/builder/Builder.element.ts +2 -0
  38. package/src/builder/Builder.model.ts +57 -43
  39. package/src/builder/Builder.ts +412 -431
  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 +28 -5
  49. package/src/builder/index.ts +10 -4
  50. package/src/colors/index.ts +2 -2
  51. package/src/compute-view/compute-view.ts +45 -54
  52. package/src/compute-view/deployment-view/__test__/TestHelper.ts +236 -0
  53. package/src/compute-view/deployment-view/__test__/fixture.ts +50 -62
  54. package/src/compute-view/deployment-view/_types.ts +26 -18
  55. package/src/compute-view/deployment-view/clean-connections.ts +173 -164
  56. package/src/compute-view/deployment-view/compute.ts +51 -37
  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 +183 -76
  61. package/src/compute-view/deployment-view/predicates/relation-in-out.ts +38 -20
  62. package/src/compute-view/deployment-view/predicates/relation-incoming.ts +72 -23
  63. package/src/compute-view/deployment-view/predicates/relation-outgoing.ts +61 -22
  64. package/src/compute-view/deployment-view/predicates/wildcard.ts +15 -15
  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 +95 -31
  69. package/src/compute-view/dynamic-view/__test__/fixture.ts +4 -4
  70. package/src/compute-view/dynamic-view/compute.ts +59 -45
  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 +64 -62
  73. package/src/compute-view/element-view/__test__/fixture.ts +147 -117
  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 +264 -683
  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 +3 -4
  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/buildComputedNodes.ts +4 -4
  101. package/src/compute-view/utils/elementExpressionToPredicate.ts +1 -3
  102. package/src/compute-view/utils/link-nodes-with-edges.ts +3 -7
  103. package/src/compute-view/utils/{derive-edge-props-from-relationships.ts → merge-props-from-relationships.ts} +54 -23
  104. package/src/compute-view/utils/relationExpressionToPredicates.ts +2 -2
  105. package/src/compute-view/utils/resolve-extended-views.ts +44 -40
  106. package/src/compute-view/utils/resolve-global-rules.ts +6 -6
  107. package/src/compute-view/utils/topological-sort.ts +167 -71
  108. package/src/compute-view/utils/with-readable-edges.ts +14 -9
  109. package/src/index.ts +22 -16
  110. package/src/model/DeploymentElementModel.ts +150 -85
  111. package/src/model/DeploymentModel.ts +27 -21
  112. package/src/model/ElementModel.ts +51 -25
  113. package/src/model/LikeC4Model.test-d.ts +4 -3
  114. package/src/model/LikeC4Model.ts +98 -42
  115. package/src/model/RelationModel.ts +5 -4
  116. package/src/model/__test__/fixture.ts +5 -9
  117. package/src/model/connection/Connection.ts +72 -0
  118. package/src/model/connection/deployment/DeploymentConnectionModel.ts +178 -0
  119. package/src/model/connection/{deployment.ts → deployment/find.ts} +14 -18
  120. package/src/model/connection/deployment/index.ts +8 -0
  121. package/src/model/connection/index.ts +22 -0
  122. package/src/model/connection/model/ConnectionModel.ts +157 -0
  123. package/src/model/connection/{model.ts → model/find.ts} +43 -49
  124. package/src/model/connection/model/index.ts +8 -0
  125. package/src/model/connection/ops.ts +274 -0
  126. package/src/model/guards.ts +9 -0
  127. package/src/model/index.ts +45 -3
  128. package/src/model/types.ts +35 -18
  129. package/src/model/view/EdgeModel.ts +6 -6
  130. package/src/model/view/LikeC4ViewModel.ts +14 -8
  131. package/src/model/view/NodeModel.ts +6 -5
  132. package/src/types/_common.ts +53 -1
  133. package/src/types/deployments.ts +88 -156
  134. package/src/types/element.ts +2 -2
  135. package/src/types/expression-v2.ts +172 -0
  136. package/src/types/expression.ts +9 -5
  137. package/src/types/global.ts +3 -3
  138. package/src/types/index.ts +220 -13
  139. package/src/types/model.ts +28 -24
  140. package/src/types/operators.ts +1 -1
  141. package/src/types/relation.ts +14 -10
  142. package/src/types/theme.ts +0 -1
  143. package/src/types/view.ts +20 -18
  144. package/src/utils/const.ts +1 -0
  145. package/src/utils/fqn.ts +140 -49
  146. package/src/utils/getOrCreate.ts +1 -1
  147. package/src/utils/index.ts +75 -9
  148. package/src/utils/iterable/_types.ts +5 -0
  149. package/src/utils/iterable/filter.ts +55 -0
  150. package/src/utils/iterable/find.ts +36 -0
  151. package/src/utils/iterable/flat.ts +17 -0
  152. package/src/utils/iterable/index.ts +13 -0
  153. package/src/utils/iterable/map.ts +40 -0
  154. package/src/utils/iterable/reduce.ts +24 -0
  155. package/src/utils/iterable/some.ts +36 -0
  156. package/src/utils/iterable/to.ts +19 -0
  157. package/src/utils/iterable/unique.ts +29 -0
  158. package/src/utils/relations.ts +3 -64
  159. package/src/utils/set.ts +11 -9
  160. package/src/utils/string-hash.ts +1 -1
  161. package/dist/compute-view/index.mjs +0 -79
  162. package/dist/model/index.mjs +0 -2
  163. package/dist/shared/core.BBgiAAv1.mjs +0 -144
  164. package/dist/shared/core.BPyJvuMU.mjs +0 -2420
  165. package/dist/shared/core.BQ4AbfvB.mjs +0 -9228
  166. package/dist/shared/core.k12s_keD.mjs +0 -203
  167. package/dist/types/index.mjs +0 -21
  168. package/src/builder/Builder.test-d.ts +0 -124
  169. package/src/builder/Builder.view.ts +0 -543
  170. package/src/compute-view/LikeC4ModelGraph.ts +0 -330
  171. package/src/compute-view/deployment-view/Memory.ts +0 -108
  172. package/src/compute-view/deployment-view/Stage.ts +0 -145
  173. package/src/compute-view/deployment-view/predicates/deployment-ref.ts +0 -146
  174. package/src/compute-view/element-view/predicates.ts +0 -517
  175. package/src/compute-view/utils/sortNodes.ts +0 -101
  176. package/src/model/connection/ConnectionModel.ts +0 -94
  177. package/src/model/connection/DeploymentConnectionModel.ts +0 -99
@@ -1,734 +1,315 @@
1
- import {
2
- anyPass,
3
- concat,
4
- difference,
5
- filter,
6
- findLast,
7
- first,
8
- hasAtLeast,
9
- isNonNull,
10
- isTruthy,
11
- map,
12
- only,
13
- pipe,
14
- reduce,
15
- sort,
16
- unique
17
- } from 'remeda'
1
+ import DefaultMap from 'mnemonist/default-map'
2
+ import { filter, findLast, forEach, map, pipe } from 'remeda'
18
3
  import { invariant, nonexhaustive, nonNullable } from '../../errors'
19
- import {
20
- type Color,
21
- type ComputedEdge,
22
- type ComputedElementView,
23
- type ComputedNode,
24
- type EdgeId,
25
- type Element,
26
- ElementKind,
27
- type ElementPredicateExpression,
28
- type ElementView,
29
- isScopedElementView,
30
- isViewRuleAutoLayout,
31
- isViewRuleGroup,
32
- isViewRulePredicate,
33
- type NodeId,
34
- type NonEmptyArray,
35
- type Relation,
36
- type RelationPredicateExpression,
37
- type RelationshipArrowType,
38
- type RelationshipKind,
39
- type RelationshipLineType,
40
- type Tag,
41
- type ViewId,
42
- type ViewRuleGroup,
43
- type ViewRulePredicate,
44
- whereOperatorAsPredicate
4
+ import { type ElementModel, LikeC4Model } from '../../model'
5
+ import { ConnectionModel } from '../../model/connection/model/ConnectionModel'
6
+ import type { RelationshipModel } from '../../model/RelationModel'
7
+ import type { AnyAux } from '../../model/types'
8
+ import type {
9
+ ComputedElementView,
10
+ ElementPredicateExpression,
11
+ ElementView,
12
+ NodeId,
13
+ RelationPredicateExpression,
14
+ ViewRule,
45
15
  } from '../../types'
16
+ import { isViewRuleAutoLayout, isViewRuleGroup, isViewRulePredicate, whereOperatorAsPredicate } from '../../types'
46
17
  import * as Expr from '../../types/expression'
47
- import { stringHash } from '../../utils'
48
- import { commonAncestor, isAncestor } from '../../utils/fqn'
49
- import { compareRelations } from '../../utils/relations'
50
- import type { LikeC4ModelGraph } from '../LikeC4ModelGraph'
18
+ import { sortNaturalByFqn, sortParentsFirst } from '../../utils'
51
19
  import { applyCustomElementProperties } from '../utils/applyCustomElementProperties'
52
20
  import { applyCustomRelationProperties } from '../utils/applyCustomRelationProperties'
53
21
  import { applyViewRuleStyles } from '../utils/applyViewRuleStyles'
54
- import { buildComputedNodesFromElements } from '../utils/buildComputedNodes'
55
22
  import { buildElementNotations } from '../utils/buildElementNotations'
56
23
  import { linkNodesWithEdges } from '../utils/link-nodes-with-edges'
57
24
  import { resolveGlobalRulesInElementView } from '../utils/resolve-global-rules'
58
- import { sortNodes } from '../utils/sortNodes'
59
- import { uniqueTags } from '../utils/uniqueTags'
25
+ import { topologicalSort } from '../utils/topological-sort'
60
26
  import { calcViewLayoutHash } from '../utils/view-hash'
61
- import {
62
- type ElementPredicateFn,
63
- excludeElementKindOrTag,
64
- excludeElementRef,
65
- excludeExpandedElementExpr,
66
- excludeIncomingExpr,
67
- excludeInOutExpr,
68
- excludeOutgoingExpr,
69
- excludeRelationExpr,
70
- excludeWildcardRef,
71
- includeElementKindOrTag,
72
- includeElementRef,
73
- includeExpandedElementExpr,
74
- includeIncomingExpr,
75
- includeInOutExpr,
76
- includeOutgoingExpr,
77
- includeRelationExpr,
78
- includeWildcardRef,
79
- type RelationPredicateFn
80
- } from './predicates'
81
-
82
- // eslint-disable-next-line @typescript-eslint/no-namespace
83
- export namespace ComputeCtx {
84
- // Intermediate ComputedEdge
85
- export interface Edge {
86
- source: Element
87
- target: Element
88
- relations: Relation[]
89
- }
90
- }
91
-
92
- function compareEdges(a: ComputeCtx.Edge, b: ComputeCtx.Edge) {
93
- return compareRelations(
94
- { source: a.source.id, target: a.target.id },
95
- { source: b.source.id, target: b.target.id }
96
- )
97
- }
98
-
99
- // If there is only one relation and it has same source and target as edge
100
- function isDirectEdge({ relations: [rel, ...tail], source, target }: ComputeCtx.Edge) {
101
- if (rel && tail.length === 0) {
102
- return rel.source === source.id && rel.target === target.id
103
- }
104
- return false
105
- }
106
-
107
- export class NodesGroup {
108
- static readonly kind = ElementKind.Group
109
-
110
- static root() {
111
- return new NodesGroup('@root' as NodeId, { title: null, groupRules: [] })
112
- }
113
-
114
- static is(node: ComputedNode) {
115
- return node.kind === NodesGroup.kind
116
- }
117
-
118
- readonly explicits = new Set<Element>()
119
- readonly implicits = new Set<Element>()
120
-
121
- constructor(
122
- public id: NodeId,
123
- public viewRule: ViewRuleGroup,
124
- public parent: NodeId | null = null
125
- ) {
126
- }
127
-
128
- /**
129
- * Add element explicitly
130
- * Included even without relationships
131
- */
132
- addElement(...el: Element[]) {
133
- for (const r of el) {
134
- this.explicits.add(r)
135
- this.implicits.add(r)
136
- }
137
- }
138
-
139
- /**
140
- * Add element implicitly
141
- * Included if only has relationships
142
- */
143
- addImplicit(...el: Element[]) {
144
- for (const r of el) {
145
- this.implicits.add(r)
146
- }
147
- }
148
-
149
- excludeElement(...excludes: Element[]) {
150
- for (const el of excludes) {
151
- this.explicits.delete(el)
152
- this.implicits.delete(el)
27
+ import { type Connection, type Elem, type PredicateCtx } from './_types'
28
+ import { type NodesGroup, type Stage, Memory } from './memory'
29
+ import { ActiveGroupMemory } from './memory/memory'
30
+ import { StageFinal } from './memory/stage-final'
31
+ import { ExpandedElementPredicate } from './predicates/element-expand'
32
+ import { ElementKindOrTagPredicate } from './predicates/element-kind-tag'
33
+ import { ElementRefPredicate } from './predicates/element-ref'
34
+ import { DirectRelationExprPredicate } from './predicates/relation-direct'
35
+ import { IncomingExprPredicate } from './predicates/relation-in'
36
+ import { InOutRelationPredicate } from './predicates/relation-in-out'
37
+ import { OutgoingExprPredicate } from './predicates/relation-out'
38
+ import { WildcardPredicate } from './predicates/wildcard'
39
+ import { buildNodes, NoFilter, NoWhere, toComputedEdges } from './utils'
40
+
41
+ function processElementPredicate(
42
+ // ...args:
43
+ // | [expr: ElementPredicateExpression, op: 'include', IncludePredicateCtx<ElementPredicateExpression>]
44
+ // | [expr: ElementPredicateExpression, op: 'exclude', ExcludePredicateCtx<ElementPredicateExpression>]
45
+ expr: ElementPredicateExpression,
46
+ op: 'include' | 'exclude',
47
+ ctx: Omit<PredicateCtx<RelationPredicateExpression>, 'expr'>,
48
+ ): Stage {
49
+ switch (true) {
50
+ case Expr.isCustomElement(expr): {
51
+ if (op === 'include') {
52
+ return processElementPredicate(expr.custom.expr, op, ctx)
53
+ }
54
+ return ctx.stage
153
55
  }
154
- }
155
-
156
- isEmpty() {
157
- return this.explicits.size === 0 && this.implicits.size === 0
56
+ case Expr.isElementWhere(expr): {
57
+ const where = whereOperatorAsPredicate(expr.where.condition)
58
+ const filterWhere = filter<Elem>(where)
59
+ return processElementPredicate(expr.where.expr, op, { ...ctx, where, filterWhere } as any)
60
+ }
61
+ case Expr.isExpandedElementExpr(expr): {
62
+ return ExpandedElementPredicate[op]({ ...ctx, expr } as any) ?? ctx.stage
63
+ }
64
+ case Expr.isElementRef(expr): {
65
+ return ElementRefPredicate[op]({ ...ctx, expr } as any) ?? ctx.stage
66
+ }
67
+ case Expr.isWildcard(expr):
68
+ return WildcardPredicate[op]({ ...ctx, expr } as any) ?? ctx.stage
69
+ case Expr.isElementKindExpr(expr):
70
+ case Expr.isElementTagExpr(expr):
71
+ return ElementKindOrTagPredicate[op]({ ...ctx, expr } as any) ?? ctx.stage
72
+ default:
73
+ nonexhaustive(expr)
158
74
  }
159
75
  }
160
-
161
- export class ComputeCtx {
162
- // Intermediate state
163
- private explicits = new Set<Element>()
164
- private implicits = new Set<Element>()
165
- private ctxEdges = [] as ComputeCtx.Edge[]
166
-
167
- /**
168
- * Root group - not included in the groups
169
- * But used to accumulate elements that are not in any group
170
- */
171
- private __rootGroup = NodesGroup.root()
172
- private groups = [] as NodesGroup[]
173
- private activeGroupStack = [] as NodesGroup[]
174
-
175
- protected get activeGroup() {
176
- return this.activeGroupStack[0] ?? this.__rootGroup
177
- }
178
-
179
- protected get includedElements() {
180
- return new Set([
181
- ...this.explicits,
182
- ...this.ctxEdges.flatMap(e => [e.source, e.target])
183
- ]) as ReadonlySet<Element>
184
- }
185
-
186
- protected get resolvedElements() {
187
- return new Set([
188
- ...this.explicits,
189
- ...this.implicits,
190
- ...this.ctxEdges.flatMap(e => [e.source, e.target])
191
- ]) as ReadonlySet<Element>
192
- }
193
-
194
- protected get edges() {
195
- return this.ctxEdges
196
- }
197
-
198
- protected get root() {
199
- return isScopedElementView(this.view) ? this.view.viewOf : null
200
- }
201
-
202
- public static elementView(view: ElementView, graph: LikeC4ModelGraph) {
203
- return new ComputeCtx(view, graph).compute()
204
- }
205
-
206
- private constructor(
207
- protected view: ElementView,
208
- protected graph: LikeC4ModelGraph
209
- ) {}
210
-
211
- protected compute(): ComputedElementView {
212
- this.reset()
213
- const {
214
- docUri: _docUri, // exclude docUri
215
- rules: _rules, // exclude rules
216
- ...view
217
- } = this.view
218
-
219
- const rules = resolveGlobalRulesInElementView(this.view, this.graph.globals)
220
-
221
- const viewPredicates = rules.filter(anyPass([isViewRulePredicate, isViewRuleGroup])) as Array<
222
- ViewRulePredicate | ViewRuleGroup
223
- >
224
- if (this.root && viewPredicates.length == 0) {
225
- this.addElement(this.graph.element(this.root))
226
- }
227
- this.processPredicates(viewPredicates)
228
- this.removeRedundantImplicitEdges()
229
- if (this.groups.length > 0) {
230
- this.assignElementsToGroups()
231
- }
232
-
233
- const elements = [...this.includedElements]
234
- const nodesMap = buildComputedNodesFromElements(elements, this.groups)
235
-
236
- const edges = this.computeEdges()
237
- const edgesMap = new Map<EdgeId, ComputedEdge>(edges.map(edge => [edge.id, edge]))
238
-
239
- linkNodesWithEdges(nodesMap, edges)
240
-
241
- // nodesMap sorted hierarchically,
242
- // but we need to keep the initial sort
243
- let initialSort = elements.map(e => nonNullable(nodesMap.get(e.id), `Node ${e.id} not found in nodesMap`))
244
-
245
- if (this.groups.length > 0) {
246
- initialSort = concat(
247
- this.groups.map(g => nonNullable(nodesMap.get(g.id), `Node ${g.id} not found in nodesMap`)),
248
- initialSort
249
- )
76
+ function processRelationtPredicate(
77
+ expr: RelationPredicateExpression,
78
+ op: 'include' | 'exclude',
79
+ ctx: Omit<PredicateCtx<RelationPredicateExpression>, 'expr'>,
80
+ ): Stage {
81
+ switch (true) {
82
+ case Expr.isCustomRelationExpr(expr): {
83
+ if (op === 'include') {
84
+ return processRelationtPredicate(expr.customRelation.relation, op, ctx)
85
+ }
86
+ return ctx.stage
250
87
  }
251
-
252
- const nodes = applyCustomElementProperties(
253
- rules,
254
- applyViewRuleStyles(
255
- rules,
256
- // Build graph and apply postorder sort
257
- sortNodes({
258
- nodes: initialSort,
259
- edges
260
- })
261
- )
262
- )
263
- const sortedEdges = new Set([
264
- ...nodes.flatMap(n => n.children.length === 0 ? n.outEdges.flatMap(id => edgesMap.get(id) ?? []) : []),
265
- ...edges
266
- ])
267
-
268
- const autoLayoutRule = findLast(this.view.rules, isViewRuleAutoLayout)
269
-
270
- const elementNotations = buildElementNotations(nodes)
271
-
272
- return calcViewLayoutHash({
273
- ...view,
274
- autoLayout: {
275
- direction: autoLayoutRule?.direction ?? 'TB',
276
- ...(autoLayoutRule?.nodeSep && { nodeSep: autoLayoutRule.nodeSep }),
277
- ...(autoLayoutRule?.rankSep && { rankSep: autoLayoutRule.rankSep })
278
- },
279
- nodes: map(nodes, n => {
280
- // omit notation
281
- delete n.notation
282
- if (n.icon === 'none') {
283
- delete n.icon
284
- }
285
- return n
286
- }),
287
- edges: applyCustomRelationProperties(rules, nodes, sortedEdges),
288
- ...(elementNotations.length > 0 && {
289
- notation: {
290
- elements: elementNotations
291
- }
292
- })
293
- })
294
- }
295
-
296
- protected computeEdges(): ComputedEdge[] {
297
- return this.ctxEdges.map((e): ComputedEdge => {
298
- invariant(hasAtLeast(e.relations, 1), 'Edge must have at least one relation')
299
- const relations = sort(e.relations, compareRelations)
300
- const source = e.source.id
301
- const target = e.target.id
302
-
303
- const edge: ComputedEdge = {
304
- id: stringHash(`${source}:${target}`) as EdgeId,
305
- parent: commonAncestor(source, target),
306
- source,
307
- target,
308
- label: null,
309
- relations: relations.map(r => r.id)
88
+ case Expr.isRelationWhere(expr): {
89
+ const where = whereOperatorAsPredicate(expr.where.condition)
90
+ const filterRelations = (relations: ReadonlySet<RelationshipModel>) => {
91
+ return new Set(filter([...relations], where))
310
92
  }
311
-
312
- let relation: {
313
- // TODO refactor with type-fest
314
- title: string
315
- description?: string | undefined
316
- technology?: string | undefined
317
- kind?: RelationshipKind | undefined
318
- color?: Color | undefined
319
- line?: RelationshipLineType | undefined
320
- head?: RelationshipArrowType | undefined
321
- tail?: RelationshipArrowType | undefined
322
- tags?: NonEmptyArray<Tag>
323
- navigateTo?: ViewId | undefined
324
- } | undefined
325
- relation = only(relations) ?? pipe(
326
- relations,
327
- filter(r => r.source === source && r.target === target),
328
- only()
329
- )
330
-
331
- // This edge represents mutliple relations
332
- // We use label if only it is the same for all relations
333
- if (!relation) {
334
- const allprops = pipe(
335
- relations,
336
- reduce((acc, r) => {
337
- if (isTruthy(r.title) && !acc.title.includes(r.title)) {
338
- acc.title.push(r.title)
339
- }
340
- if (isTruthy(r.description) && !acc.description.includes(r.description)) {
341
- acc.description.push(r.description)
342
- }
343
- if (isTruthy(r.technology) && !acc.technology.includes(r.technology)) {
344
- acc.technology.push(r.technology)
345
- }
346
- if (isTruthy(r.kind) && !acc.kind.includes(r.kind)) {
347
- acc.kind.push(r.kind)
348
- }
349
- if (isTruthy(r.color) && !acc.color.includes(r.color)) {
350
- acc.color.push(r.color)
351
- }
352
- if (isTruthy(r.line) && !acc.line.includes(r.line)) {
353
- acc.line.push(r.line)
354
- }
355
- if (isTruthy(r.head) && !acc.head.includes(r.head)) {
356
- acc.head.push(r.head)
357
- }
358
- if (isTruthy(r.tail) && !acc.tail.includes(r.tail)) {
359
- acc.tail.push(r.tail)
360
- }
361
- if (isTruthy(r.navigateTo) && !acc.navigateTo.includes(r.navigateTo)) {
362
- acc.navigateTo.push(r.navigateTo)
363
- }
364
- return acc
365
- }, {
366
- title: [] as string[],
367
- description: [] as string[],
368
- technology: [] as string[],
369
- kind: [] as RelationshipKind[],
370
- head: [] as RelationshipArrowType[],
371
- tail: [] as RelationshipArrowType[],
372
- color: [] as Color[],
373
- line: [] as RelationshipLineType[],
374
- navigateTo: [] as ViewId[]
375
- })
93
+ const filterWhere = (connections: ReadonlyArray<Connection>) => {
94
+ return pipe(
95
+ connections,
96
+ map(c => new ConnectionModel(c.source, c.target, filterRelations(c.relations))),
97
+ filter(c => c.nonEmpty()),
376
98
  )
377
- relation = {
378
- title: only(allprops.title) ?? '[...]',
379
- description: only(allprops.description),
380
- technology: only(allprops.technology),
381
- kind: only(allprops.kind),
382
- head: only(allprops.head),
383
- tail: only(allprops.tail),
384
- color: only(allprops.color),
385
- line: only(allprops.line),
386
- navigateTo: only(allprops.navigateTo)
387
- }
388
- }
389
-
390
- const tags = uniqueTags(relations)
391
-
392
- return Object.assign(
393
- edge,
394
- this.getEdgeLabel(relation),
395
- isTruthy(relation.description) && { description: relation.description },
396
- isTruthy(relation.technology) && { technology: relation.technology },
397
- isTruthy(relation.kind) && { kind: relation.kind },
398
- relation.color && { color: relation.color },
399
- relation.line && { line: relation.line },
400
- relation.head && { head: relation.head },
401
- relation.tail && { tail: relation.tail },
402
- relation.navigateTo && { navigateTo: relation.navigateTo },
403
- tags && { tags }
404
- )
405
- })
406
- }
407
-
408
- protected addEdges(edges: ComputeCtx.Edge[]) {
409
- const added = [] as ComputeCtx.Edge[]
410
- for (const e of edges) {
411
- if (!hasAtLeast(e.relations, 1)) {
412
- continue
413
99
  }
414
- const existing = this.ctxEdges.find(
415
- _e => _e.source.id === e.source.id && _e.target.id === e.target.id
416
- )
417
- if (existing) {
418
- existing.relations = unique([...existing.relations, ...e.relations])
419
- added.push(existing)
420
- continue
421
- }
422
- added.push(e)
423
- this.ctxEdges.push(e)
100
+ return processRelationtPredicate(expr.where.expr, op, {
101
+ ...ctx,
102
+ where,
103
+ filterWhere,
104
+ })
424
105
  }
425
- return added
426
- }
427
-
428
- /**
429
- * Add element explicitly
430
- * Included even without relationships
431
- */
432
- protected addElement(...el: Element[]) {
433
- for (const r of el) {
434
- if (!this.explicits.has(r)) {
435
- this.activeGroup.addElement(r)
436
- } else if (this.activeGroup !== this.__rootGroup) {
437
- this.groups.forEach(g => {
438
- g.implicits.delete(r)
439
- })
440
- this.activeGroup.addImplicit(r)
441
- }
442
- this.explicits.add(r)
443
- this.implicits.add(r)
106
+ case Expr.isInOut(expr): {
107
+ return InOutRelationPredicate[op]({ ...ctx, expr } as any) ?? ctx.stage
444
108
  }
445
- }
446
-
447
- /**
448
- * Add element implicitly
449
- * Included if only has relationships
450
- */
451
- protected addImplicit(...el: Element[]) {
452
- for (const r of el) {
453
- this.implicits.add(r)
454
- // Remove implicits from other groups
455
- if (this.activeGroup !== this.__rootGroup) {
456
- this.groups.forEach(g => {
457
- g.implicits.delete(r)
458
- })
459
- }
109
+ case Expr.isRelation(expr): {
110
+ return DirectRelationExprPredicate[op]({ ...ctx, expr } as any) ?? ctx.stage
460
111
  }
461
- this.activeGroup.addImplicit(...el)
462
- }
463
-
464
- protected excludeElement(...excludes: Element[]) {
465
- for (const el of excludes) {
466
- this.ctxEdges = this.ctxEdges.filter(e => e.source.id !== el.id && e.target.id !== el.id)
467
- this.explicits.delete(el)
468
- this.implicits.delete(el)
112
+ case Expr.isOutgoing(expr): {
113
+ return OutgoingExprPredicate[op]({ ...ctx, expr } as any) ?? ctx.stage
469
114
  }
470
- this.__rootGroup.excludeElement(...excludes)
471
- this.groups.forEach(g => {
472
- g.excludeElement(...excludes)
473
- })
115
+ case Expr.isIncoming(expr): {
116
+ return IncomingExprPredicate[op]({ ...ctx, expr } as any) ?? ctx.stage
117
+ }
118
+ default:
119
+ nonexhaustive(expr)
474
120
  }
121
+ }
475
122
 
476
- protected excludeRelation(...relations: Relation[]) {
477
- if (relations.length === 0) {
478
- return
479
- }
480
- const excludedImplicits = new Set<Element>()
481
- const ctxEdges = pipe(
482
- this.ctxEdges,
483
- map(edge => {
484
- const edgerelations = edge.relations.filter(r => !relations.includes(r))
485
- if (edgerelations.length === 0) {
486
- excludedImplicits.add(edge.source)
487
- excludedImplicits.add(edge.target)
488
- return null
489
- }
490
- if (edgerelations.length !== edge.relations.length) {
491
- return {
492
- ...edge,
493
- relations: edgerelations
494
- }
123
+ export function processPredicates<M extends AnyAux>(
124
+ model: LikeC4Model<M>,
125
+ memory: Memory,
126
+ rules: ViewRule[],
127
+ ): Memory {
128
+ const ctx = {
129
+ model,
130
+ scope: memory.scope,
131
+ where: NoWhere,
132
+ filterWhere: NoFilter,
133
+ }
134
+ for (const rule of rules) {
135
+ if (isViewRuleGroup(rule)) {
136
+ const groupMemory = ActiveGroupMemory.enter(memory, rule)
137
+ memory = processPredicates(model, groupMemory, rule.groupRules)
138
+ invariant(memory instanceof ActiveGroupMemory, 'processPredicates must return ActiveGroupMemory')
139
+ memory = memory.leave()
140
+ continue
141
+ }
142
+ if (isViewRulePredicate(rule)) {
143
+ const op = 'include' in rule ? 'include' : 'exclude'
144
+ const exprs = rule.include ?? rule.exclude
145
+ for (const expr of exprs) {
146
+ let stage = op === 'include' ? memory.stageInclude(expr) : memory.stageExclude(expr)
147
+ switch (true) {
148
+ case Expr.isElementPredicateExpr(expr):
149
+ stage = processElementPredicate(expr, op, {
150
+ ...ctx,
151
+ stage,
152
+ memory,
153
+ } as any) ?? stage
154
+ break
155
+ case Expr.isRelationPredicateExpr(expr):
156
+ stage = processRelationtPredicate(expr, op, {
157
+ ...ctx,
158
+ stage,
159
+ memory,
160
+ }) ?? stage
161
+ break
162
+ default:
163
+ nonexhaustive(expr)
495
164
  }
496
- return edge
497
- }),
498
- filter(isNonNull)
499
- )
500
- this.ctxEdges = ctxEdges
501
- const remaining = this.includedElements
502
- if (remaining.size === 0) {
503
- this.implicits.clear()
504
- this.__rootGroup.implicits.clear()
505
- this.groups.forEach(g => g.implicits.clear())
506
- return
507
- }
508
- for (const el of excludedImplicits) {
509
- if (!remaining.has(el)) {
510
- this.implicits.delete(el)
511
- this.__rootGroup.implicits.delete(el)
512
- this.groups.forEach(g => g.implicits.delete(el))
165
+ memory = stage.commit()
513
166
  }
514
167
  }
515
168
  }
169
+ return StageFinal.for(memory).commit()
170
+ }
516
171
 
517
- protected reset() {
518
- this.explicits.clear()
519
- this.implicits.clear()
520
- this.ctxEdges = []
521
- // Reset groups
522
- this.__rootGroup = NodesGroup.root()
523
- this.groups = []
524
- this.activeGroupStack = []
172
+ export function computeElementView<M extends AnyAux>(
173
+ likec4model: LikeC4Model<M>,
174
+ {
175
+ docUri: _docUri, // exclude docUri
176
+ rules, // exclude rules
177
+ ...view
178
+ }: ElementView,
179
+ ): ComputedElementView<M['ViewId']> {
180
+ rules = resolveGlobalRulesInElementView(rules, likec4model.globals())
181
+ const scope = view.viewOf ? likec4model.element(view.viewOf) : null
182
+ let memory = processPredicates<M>(
183
+ likec4model,
184
+ Memory.empty(scope),
185
+ rules,
186
+ )
187
+ if (memory.isEmpty() && scope) {
188
+ memory = memory.update({
189
+ final: new Set([scope]),
190
+ })
525
191
  }
192
+ memory = assignElementsToGroups(memory)
526
193
 
527
- // Filter out edges if there are edges between descendants
528
- // i.e. remove implicit edges, derived from childs
529
- protected removeRedundantImplicitEdges() {
530
- const processedRelations = new Set<Relation>()
194
+ const nodesMap = buildNodes(memory)
531
195
 
532
- // Returns relations, that are not processed/included
533
- const excludeProcessed = (relations: Relation[]) =>
534
- relations.reduce((acc, rel) => {
535
- if (!processedRelations.has(rel)) {
536
- acc.push(rel)
537
- processedRelations.add(rel)
538
- }
539
- return acc
540
- }, [] as Relation[])
196
+ const computedEdges = toComputedEdges(memory.connections)
541
197
 
542
- // Returns predicate
543
- const isNestedEdgeOf = (parent: ComputeCtx.Edge) => {
544
- const { source, target } = parent
545
- // Checks if edge is between descendants of source and target of the parent edge
546
- return (edge: ComputeCtx.Edge) => {
547
- const isSameSource = source.id === edge.source.id
548
- const isSameTarget = target.id === edge.target.id
549
- if (isSameSource && isSameTarget) {
550
- return true
551
- }
552
- const isSourceNested = isAncestor(source.id, edge.source.id)
553
- const isTargetNested = isAncestor(target.id, edge.target.id)
554
- return (
555
- (isSourceNested && isTargetNested)
556
- || (isSameSource && isTargetNested)
557
- || (isSameTarget && isSourceNested)
558
- )
559
- }
560
- }
198
+ linkNodesWithEdges(nodesMap, computedEdges)
561
199
 
562
- // Sort edges from bottom to top (i.e. from more specific edges to implicit or between ancestors)
563
- const edges = [...this.ctxEdges].sort(compareEdges).reverse()
564
- this.ctxEdges = edges.reduce((acc, e) => {
565
- const relations = excludeProcessed(e.relations)
566
- if (relations.length === 0) {
567
- return acc
568
- }
569
- // If there is an edge between descendants of current edge,
570
- // then we don't add this edge
571
- if (acc.length > 0 && acc.some(isNestedEdgeOf(e))) {
572
- return acc
573
- }
574
- acc.push({
575
- source: e.source,
576
- target: e.target,
577
- relations
578
- })
579
- return acc
580
- }, [] as ComputeCtx.Edge[])
581
- }
200
+ const sorted = topologicalSort({
201
+ nodes: nodesMap,
202
+ edges: computedEdges,
203
+ })
582
204
 
583
- /**
584
- * Clean up group.explicits and group.implicits
585
- */
586
- protected assignElementsToGroups() {
587
- const unprocessed = new Set<Element>(difference(
588
- [...this.includedElements],
589
- [...this.__rootGroup.explicits]
590
- ))
591
- // Step 1 - Process explicits
592
- for (const group of this.groups) {
593
- const explicits = [...group.explicits]
594
- group.explicits.clear()
595
- for (const el of explicits) {
596
- if (unprocessed.delete(el)) {
597
- group.explicits.add(el)
598
- }
599
- }
600
- }
601
- // Step 2 - Process implicits (make them explicits)
602
- for (const group of this.groups) {
603
- for (const el of group.implicits) {
604
- if (unprocessed.delete(el)) {
605
- group.explicits.add(el)
606
- }
205
+ const nodes = applyCustomElementProperties(
206
+ rules,
207
+ applyViewRuleStyles(
208
+ rules,
209
+ sorted.nodes,
210
+ ),
211
+ )
212
+
213
+ const autoLayoutRule = findLast(rules, isViewRuleAutoLayout)
214
+
215
+ const elementNotations = buildElementNotations(nodes)
216
+
217
+ return calcViewLayoutHash({
218
+ ...view,
219
+ autoLayout: {
220
+ direction: autoLayoutRule?.direction ?? 'TB',
221
+ ...(autoLayoutRule?.nodeSep && { nodeSep: autoLayoutRule.nodeSep }),
222
+ ...(autoLayoutRule?.rankSep && { rankSep: autoLayoutRule.rankSep }),
223
+ },
224
+ edges: applyCustomRelationProperties(rules, nodes, sorted.edges),
225
+ nodes: map(nodes, n => {
226
+ // omit notation
227
+ delete n.notation
228
+ if (n.icon === 'none') {
229
+ delete n.icon
607
230
  }
608
- group.implicits.clear()
609
- }
231
+ return n
232
+ }),
233
+ ...(elementNotations.length > 0 && {
234
+ notation: {
235
+ elements: elementNotations,
236
+ },
237
+ }),
238
+ })
239
+ }
240
+
241
+ /**
242
+ * Clean up group.explicits and group.implicits
243
+ */
244
+ function assignElementsToGroups(memory: Memory) {
245
+ if (memory.groups.length === 0) {
246
+ return memory
610
247
  }
248
+ const groupAssignments = new DefaultMap<NodeId, Set<Elem>>(() => new Set())
611
249
 
612
- protected processPredicates(viewRules: Array<ViewRulePredicate | ViewRuleGroup>): this {
613
- // eslint-disable-next-line @typescript-eslint/no-this-alias
614
- for (const rule of viewRules) {
615
- if (isViewRuleGroup(rule)) {
616
- const parent = first(this.activeGroupStack)
617
- const groupId = `@gr${this.groups.length + 1}` as NodeId
618
- const group = new NodesGroup(groupId, rule, parent?.id ?? null)
619
- this.groups.push(group)
620
- this.activeGroupStack.unshift(group)
621
- this.processPredicates(rule.groupRules)
622
- this.activeGroupStack.shift()
623
- continue
624
- }
625
- const isInclude = 'include' in rule
626
- const exprs = rule.include ?? rule.exclude
627
- for (const expr of exprs) {
628
- if (Expr.isElementPredicateExpr(expr)) {
629
- this.processElementPredicate(expr, isInclude)
630
- continue
631
- }
632
- if (Expr.isRelationPredicateExpr(expr)) {
633
- this.processRelationPredicate(expr, isInclude)
634
- continue
635
- }
636
- nonexhaustive(expr)
250
+ const assignedTo = new Map<ElementModel, NodesGroup['id']>()
251
+
252
+ const isAncestorAssigned = (el: Elem) => {
253
+ for (const parent of el.ancestors()) {
254
+ const groupId = assignedTo.get(parent)
255
+ if (groupId) {
256
+ assignedTo.set(el, groupId)
257
+ groupAssignments.get(groupId).add(el)
258
+ return true
637
259
  }
638
260
  }
639
- return this
261
+ return false
640
262
  }
641
263
 
642
- protected processElementPredicate(
643
- expr: ElementPredicateExpression,
644
- isInclude: boolean,
645
- where?: ElementPredicateFn
646
- ): this {
647
- if (Expr.isCustomElement(expr)) {
648
- if (isInclude) {
649
- this.processElementPredicate(expr.custom.expr, isInclude)
264
+ const isDescendantAssigned = (el: Elem) => {
265
+ for (const descendant of el.descendants('asc')) {
266
+ const groupId = assignedTo.get(descendant)
267
+ if (groupId) {
268
+ assignedTo.set(el, groupId)
269
+ groupAssignments.get(groupId).add(el)
270
+ return true
650
271
  }
651
- return this
652
- }
653
- if (Expr.isElementWhere(expr)) {
654
- const where = whereOperatorAsPredicate(expr.where.condition)
655
- this.processElementPredicate(expr.where.expr, isInclude, where)
656
- return this
657
- }
658
- if (Expr.isExpandedElementExpr(expr)) {
659
- isInclude
660
- ? includeExpandedElementExpr.call(this, expr, where)
661
- : excludeExpandedElementExpr.call(this, expr, where)
662
- return this
663
- }
664
- if (Expr.isElementKindExpr(expr) || Expr.isElementTagExpr(expr)) {
665
- isInclude
666
- ? includeElementKindOrTag.call(this, expr, where)
667
- : excludeElementKindOrTag.call(this, expr, where)
668
- return this
669
- }
670
- if (Expr.isElementRef(expr)) {
671
- isInclude ? includeElementRef.call(this, expr, where) : excludeElementRef.call(this, expr, where)
672
- return this
673
272
  }
674
- if (Expr.isWildcard(expr)) {
675
- isInclude ? includeWildcardRef.call(this, expr, where) : excludeWildcardRef.call(this, expr, where)
676
- return this
677
- }
678
- nonexhaustive(expr)
273
+ return false
679
274
  }
680
275
 
681
- protected processRelationPredicate(
682
- expr: RelationPredicateExpression,
683
- isInclude: boolean,
684
- where?: RelationPredicateFn
685
- ): this {
686
- if (Expr.isCustomRelationExpr(expr)) {
687
- if (isInclude) {
688
- this.processRelationPredicate(expr.customRelation.relation, isInclude)
276
+ pipe(
277
+ sortParentsFirst([...memory.explicitFirstSeenIn.keys()]),
278
+ forEach((el) => {
279
+ if (!isAncestorAssigned(el)) {
280
+ const groupId = nonNullable(memory.explicitFirstSeenIn.get(el))
281
+ assignedTo.set(el, groupId)
282
+ groupAssignments.get(groupId).add(el)
689
283
  }
690
- return this
691
- }
692
- if (Expr.isRelationWhere(expr)) {
693
- const where = whereOperatorAsPredicate(expr.where.condition)
694
- this.processRelationPredicate(expr.where.expr, isInclude, where)
695
- return this
696
- }
697
- if (Expr.isIncoming(expr)) {
698
- isInclude ? includeIncomingExpr.call(this, expr, where) : excludeIncomingExpr.call(this, expr, where)
699
- return this
700
- }
701
- if (Expr.isOutgoing(expr)) {
702
- isInclude ? includeOutgoingExpr.call(this, expr, where) : excludeOutgoingExpr.call(this, expr, where)
703
- return this
704
- }
705
- if (Expr.isInOut(expr)) {
706
- isInclude ? includeInOutExpr.call(this, expr, where) : excludeInOutExpr.call(this, expr, where)
707
- return this
708
- }
709
- if (Expr.isRelation(expr)) {
710
- isInclude ? includeRelationExpr.call(this, expr, where) : excludeRelationExpr.call(this, expr, where)
711
- return this
712
- }
713
- nonexhaustive(expr)
714
- }
284
+ }),
285
+ )
715
286
 
716
- protected getEdgeLabel(
717
- relation: {
718
- title: string
719
- technology?: string | undefined
720
- }
721
- ) {
722
- const labelParts: string[] = []
287
+ pipe(
288
+ sortParentsFirst([...memory.lastSeenIn.keys()]),
289
+ filter(el => !assignedTo.has(el)),
290
+ forEach((el) => {
291
+ if (isAncestorAssigned(el)) {
292
+ return
293
+ }
294
+ if (isDescendantAssigned(el)) {
295
+ return
296
+ }
297
+ const groupId = nonNullable(memory.lastSeenIn.get(el))
298
+ assignedTo.set(el, groupId)
299
+ groupAssignments.get(groupId).add(el)
300
+ }),
301
+ )
723
302
 
724
- if (isTruthy(relation.title)) {
725
- labelParts.push(relation.title)
726
- }
303
+ if (groupAssignments.size === 0) {
304
+ return memory
305
+ }
727
306
 
728
- if (isTruthy(relation.technology)) {
729
- labelParts.push(`[${relation.technology}]`)
307
+ let groups = memory.groups.map(group => {
308
+ const explicits = groupAssignments.get(group.id)
309
+ if (!explicits) {
310
+ return group
730
311
  }
731
-
732
- return labelParts.length > 0 ? { label: labelParts.join('\n') } : {}
733
- }
312
+ return group.update(explicits)
313
+ })
314
+ return memory.update({ groups })
734
315
  }