@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,113 @@
1
+ import { allPass, anyPass, pipe, prop, reduce } from 'remeda'
2
+ import { Connection, ConnectionModel } from '../../model/connection'
3
+ import {
4
+ differenceConnections,
5
+ findDeepestNestedConnection,
6
+ findDescendantConnections,
7
+ isAnyInOut,
8
+ isIncoming,
9
+ isOutgoing,
10
+ mergeConnections,
11
+ } from '../../model/connection/ops'
12
+ import type { RelationshipModel } from '../../model/RelationModel'
13
+ import { intersection, union } from '../../utils'
14
+ import { ifilter, toSet } from '../../utils/iterable'
15
+
16
+ export function allRelationshipsFrom(connections: Iterable<ConnectionModel>): Set<RelationshipModel> {
17
+ const relations = new Set<RelationshipModel>()
18
+ for (const c of connections) {
19
+ for (const rel of c.relations) {
20
+ relations.add(rel)
21
+ }
22
+ }
23
+ return relations
24
+ }
25
+
26
+ function isInOutToDescendant(id: string): (c: ConnectionModel) => boolean {
27
+ return anyPass([
28
+ allPass([
29
+ Connection.isOutgoing(id),
30
+ c => c.source.id !== id,
31
+ ]),
32
+ allPass([
33
+ Connection.isIncoming(id),
34
+ c => c.target.id !== id,
35
+ ]),
36
+ ])
37
+ }
38
+
39
+ export function findRedundantConnections(connections: Iterable<ConnectionModel>): Array<ConnectionModel> {
40
+ return pipe(
41
+ [...connections],
42
+ mergeConnections,
43
+ reduce((reducedConnections, connection, _, all) => {
44
+ const descendants = findDescendantConnections(all, connection)
45
+ const nestedRelations = union(
46
+ ...descendants.map(prop('relations')),
47
+ )
48
+
49
+ let accum = intersection(connection.relations, nestedRelations)
50
+
51
+ if (descendants.length > 0) {
52
+ accum = union(accum, connection.directRelations)
53
+ }
54
+ // Check if there is any reversed connection to descendant
55
+ if (findDeepestNestedConnection(all, connection.reversed(false))) {
56
+ accum = union(accum, connection.directRelations)
57
+ }
58
+
59
+ if (accum.size < connection.relations.size) {
60
+ const isSourceExpanded = all.some(isAnyInOut(connection.source))
61
+ const isTargetExpanded = all.some(isAnyInOut(connection.target))
62
+
63
+ // Check if source expandaded
64
+ if (isSourceExpanded) {
65
+ accum = union(
66
+ accum,
67
+ toSet(
68
+ ifilter(
69
+ connection.relations,
70
+ isOutgoing(connection.source),
71
+ ),
72
+ ),
73
+ )
74
+ }
75
+
76
+ // Check if target expandaded
77
+ if (isTargetExpanded) {
78
+ accum = union(
79
+ accum,
80
+ toSet(
81
+ ifilter(
82
+ connection.relations,
83
+ isIncoming(connection.target),
84
+ ),
85
+ ),
86
+ )
87
+ }
88
+ }
89
+
90
+ if (accum.size > 0) {
91
+ reducedConnections.push(connection.update(accum))
92
+ }
93
+
94
+ return reducedConnections
95
+ }, [] as ConnectionModel[]),
96
+ )
97
+ }
98
+
99
+ /**
100
+ * Remove relationships from connection model, that are already included in the connections between descendants.
101
+ * In other words - if there is same connection down the hierarchy.
102
+ *
103
+ * @returns New connection without redundant relationships
104
+ * Connection may be empty if all relationships are redundant, in this case it should be removed
105
+ */
106
+ export function cleanRedundantRelationships(
107
+ connections: Iterable<ConnectionModel>,
108
+ ): Array<ConnectionModel> {
109
+ return differenceConnections(
110
+ connections,
111
+ findRedundantConnections(connections),
112
+ )
113
+ }
@@ -0,0 +1,315 @@
1
+ import DefaultMap from 'mnemonist/default-map'
2
+ import { filter, findLast, forEach, map, pipe } from 'remeda'
3
+ import { invariant, nonexhaustive, nonNullable } from '../../errors'
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,
15
+ } from '../../types'
16
+ import { isViewRuleAutoLayout, isViewRuleGroup, isViewRulePredicate, whereOperatorAsPredicate } from '../../types'
17
+ import * as Expr from '../../types/expression'
18
+ import { sortNaturalByFqn, sortParentsFirst } from '../../utils'
19
+ import { applyCustomElementProperties } from '../utils/applyCustomElementProperties'
20
+ import { applyCustomRelationProperties } from '../utils/applyCustomRelationProperties'
21
+ import { applyViewRuleStyles } from '../utils/applyViewRuleStyles'
22
+ import { buildElementNotations } from '../utils/buildElementNotations'
23
+ import { linkNodesWithEdges } from '../utils/link-nodes-with-edges'
24
+ import { resolveGlobalRulesInElementView } from '../utils/resolve-global-rules'
25
+ import { topologicalSort } from '../utils/topological-sort'
26
+ import { calcViewLayoutHash } from '../utils/view-hash'
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
55
+ }
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)
74
+ }
75
+ }
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
87
+ }
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))
92
+ }
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()),
98
+ )
99
+ }
100
+ return processRelationtPredicate(expr.where.expr, op, {
101
+ ...ctx,
102
+ where,
103
+ filterWhere,
104
+ })
105
+ }
106
+ case Expr.isInOut(expr): {
107
+ return InOutRelationPredicate[op]({ ...ctx, expr } as any) ?? ctx.stage
108
+ }
109
+ case Expr.isRelation(expr): {
110
+ return DirectRelationExprPredicate[op]({ ...ctx, expr } as any) ?? ctx.stage
111
+ }
112
+ case Expr.isOutgoing(expr): {
113
+ return OutgoingExprPredicate[op]({ ...ctx, expr } as any) ?? ctx.stage
114
+ }
115
+ case Expr.isIncoming(expr): {
116
+ return IncomingExprPredicate[op]({ ...ctx, expr } as any) ?? ctx.stage
117
+ }
118
+ default:
119
+ nonexhaustive(expr)
120
+ }
121
+ }
122
+
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)
164
+ }
165
+ memory = stage.commit()
166
+ }
167
+ }
168
+ }
169
+ return StageFinal.for(memory).commit()
170
+ }
171
+
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
+ })
191
+ }
192
+ memory = assignElementsToGroups(memory)
193
+
194
+ const nodesMap = buildNodes(memory)
195
+
196
+ const computedEdges = toComputedEdges(memory.connections)
197
+
198
+ linkNodesWithEdges(nodesMap, computedEdges)
199
+
200
+ const sorted = topologicalSort({
201
+ nodes: nodesMap,
202
+ edges: computedEdges,
203
+ })
204
+
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
230
+ }
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
247
+ }
248
+ const groupAssignments = new DefaultMap<NodeId, Set<Elem>>(() => new Set())
249
+
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
259
+ }
260
+ }
261
+ return false
262
+ }
263
+
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
271
+ }
272
+ }
273
+ return false
274
+ }
275
+
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)
283
+ }
284
+ }),
285
+ )
286
+
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
+ )
302
+
303
+ if (groupAssignments.size === 0) {
304
+ return memory
305
+ }
306
+
307
+ let groups = memory.groups.map(group => {
308
+ const explicits = groupAssignments.get(group.id)
309
+ if (!explicits) {
310
+ return group
311
+ }
312
+ return group.update(explicits)
313
+ })
314
+ return memory.update({ groups })
315
+ }
@@ -0,0 +1,31 @@
1
+ import { ElementKind, type NodeId, type ViewRuleGroup } from '../../../types'
2
+ import type { Elem } from '../_types'
3
+
4
+ export class NodesGroup {
5
+ static readonly kind = ElementKind.Group
6
+
7
+ constructor(
8
+ public readonly id: NodeId,
9
+ public readonly viewRule: ViewRuleGroup,
10
+ public readonly parent: NodeId | null = null,
11
+ public readonly elements: ReadonlySet<Elem> = new Set<Elem>()
12
+ ) {
13
+ }
14
+
15
+ isEmpty() {
16
+ return this.elements.size === 0
17
+ }
18
+
19
+ update(elements: ReadonlySet<Elem>): NodesGroup {
20
+ return new NodesGroup(
21
+ this.id,
22
+ this.viewRule,
23
+ this.parent,
24
+ elements
25
+ )
26
+ }
27
+
28
+ clone() {
29
+ return new NodesGroup(this.id, this.viewRule, this.parent, new Set(this.elements))
30
+ }
31
+ }
@@ -0,0 +1,10 @@
1
+ import type { StageExclude } from './stage-exclude'
2
+ import type { StageInclude } from './stage-include'
3
+
4
+ export type { Ctx } from './memory'
5
+ export { Memory } from './memory'
6
+ export { NodesGroup } from './NodeGroup'
7
+ export { StageExclude } from './stage-exclude'
8
+ export { StageInclude } from './stage-include'
9
+
10
+ export type Stage = StageInclude | StageExclude
@@ -0,0 +1,177 @@
1
+ import Stack from 'mnemonist/stack'
2
+ import { nonNullable } from '../../../errors'
3
+ import type { ConnectionModel } from '../../../model/connection'
4
+ import type { ElementModel } from '../../../model/ElementModel'
5
+ import type { Expression, NodeId, ViewRuleGroup } from '../../../types'
6
+ import { AbstractMemory, type ComputeCtx, type CtxElement, type MutableState, type StageExpression } from '../../memory'
7
+ import { NodesGroup } from './NodeGroup'
8
+ import { ActiveGroupStageExclude, StageExclude } from './stage-exclude'
9
+ import { ActiveGroupStageInclude, StageInclude } from './stage-include'
10
+
11
+ export interface Ctx extends
12
+ ComputeCtx<
13
+ ElementModel,
14
+ ConnectionModel,
15
+ Memory<Ctx>,
16
+ StageInclude<Ctx>,
17
+ StageExclude,
18
+ Expression
19
+ >
20
+ {
21
+ MutableState: {
22
+ elements: Set<ElementModel>
23
+ explicits: Set<ElementModel>
24
+ final: Set<ElementModel>
25
+ connections: ConnectionModel[]
26
+ groups: NodesGroup[]
27
+ /*
28
+ * The group where explict element was added first time
29
+ * May be a root group
30
+ */
31
+ explicitFirstSeenIn: Map<ElementModel, NodesGroup['id']>
32
+ /**
33
+ * The group where element (implicit or explicit) was last seen (added or updated)
34
+ * Exclude root group
35
+ */
36
+ lastSeenIn: Map<ElementModel, NodesGroup['id']>
37
+ }
38
+ }
39
+
40
+ export class Memory<C extends Ctx = Ctx> extends AbstractMemory<C> {
41
+ static empty(scope: CtxElement<Ctx> | null): Memory {
42
+ return new Memory({
43
+ elements: new Set(),
44
+ explicits: new Set(),
45
+ final: new Set(),
46
+ connections: [],
47
+ groups: [],
48
+ explicitFirstSeenIn: new Map(),
49
+ lastSeenIn: new Map(),
50
+ }, scope)
51
+ }
52
+
53
+ protected constructor(
54
+ protected override state: MutableState<C>,
55
+ public readonly scope: CtxElement<C> | null,
56
+ ) {
57
+ super(state)
58
+ }
59
+
60
+ public get groups(): ReadonlyArray<NodesGroup> {
61
+ return this.state.groups
62
+ }
63
+
64
+ public get explicitFirstSeenIn(): ReadonlyMap<ElementModel, NodesGroup['id']> {
65
+ return this.state.explicitFirstSeenIn
66
+ }
67
+
68
+ public get lastSeenIn(): ReadonlyMap<ElementModel, NodesGroup['id']> {
69
+ return this.state.lastSeenIn
70
+ }
71
+
72
+ override stageInclude(expr: StageExpression<Ctx>): StageInclude {
73
+ return new StageInclude(this, expr)
74
+ }
75
+ override stageExclude(expr: StageExpression<Ctx>): StageExclude {
76
+ return new StageExclude(this, expr)
77
+ }
78
+
79
+ override mutableState(): MutableState<C> {
80
+ return ({
81
+ elements: new Set(this.state.elements),
82
+ explicits: new Set(this.state.explicits),
83
+ final: new Set(this.state.final),
84
+ connections: [...this.state.connections],
85
+ groups: this.state.groups.map((g) => g.clone()),
86
+ explicitFirstSeenIn: new Map(this.state.explicitFirstSeenIn),
87
+ lastSeenIn: new Map(this.state.lastSeenIn),
88
+ })
89
+ }
90
+
91
+ override update(newstate: Partial<C['MutableState']>): Memory {
92
+ return new Memory({
93
+ ...this.state,
94
+ ...newstate,
95
+ }, this.scope)
96
+ }
97
+ }
98
+
99
+ type ActiveGroupState = Ctx['MutableState'] & {
100
+ // activeGroup: NodesGroup
101
+ }
102
+
103
+ export interface ActiveGroupCtx extends Ctx {
104
+ MutableState: ActiveGroupState
105
+ }
106
+
107
+ export class ActiveGroupMemory extends Memory<ActiveGroupCtx> {
108
+ static enter(
109
+ memory: Memory,
110
+ rule: ViewRuleGroup,
111
+ ): ActiveGroupMemory {
112
+ const groupId = `@gr${memory.groups.length + 1}` as NodeId
113
+ if (memory instanceof ActiveGroupMemory) {
114
+ const stack = Stack.from(memory.stack)
115
+ const state = memory.mutableState()
116
+ state.groups.push(new NodesGroup(groupId, rule, memory.activeGroupId))
117
+ stack.push(groupId)
118
+ return new ActiveGroupMemory(state, memory.scope, stack)
119
+ }
120
+ const state = memory.mutableState() as ActiveGroupState
121
+ state.groups.push(new NodesGroup(groupId, rule, null))
122
+ const stack = Stack.of(groupId)
123
+ return new ActiveGroupMemory(state, memory.scope, stack)
124
+ }
125
+
126
+ protected constructor(
127
+ protected override state: ActiveGroupState,
128
+ public override readonly scope: CtxElement<ActiveGroupCtx> | null,
129
+ // Stack of group ids
130
+ protected stack: Stack<NodeId>,
131
+ ) {
132
+ super(state, scope)
133
+ }
134
+
135
+ get activeGroupId(): NodeId {
136
+ return nonNullable(this.stack.peek(), 'Stack must not be empty')
137
+ }
138
+
139
+ override mutableState(): ActiveGroupState {
140
+ const state = super.mutableState()
141
+ return ({
142
+ ...state,
143
+ // activeGroup: this.findActiveGroup(state.groups)
144
+ })
145
+ }
146
+
147
+ // private findActiveGroup(groups: NodesGroup[]): NodesGroup {
148
+ // return nonNullable(groups.find(g => g.id === this.activeGroup.id), 'Active group not found in groups')
149
+ // }
150
+
151
+ override update(newstate: Partial<ActiveGroupState>): ActiveGroupMemory {
152
+ const nextstate = {
153
+ ...this.state,
154
+ ...newstate,
155
+ }
156
+ // const activeGroup = this.findActiveGroup(nextstate.groups)
157
+ // invariant(Object.is(activeGroup, nextstate.activeGroup), 'Active group must strictly equal to the group in groups')
158
+ return new ActiveGroupMemory(nextstate, this.scope, this.stack)
159
+ }
160
+
161
+ override stageInclude(expr: StageExpression<Ctx>): ActiveGroupStageInclude {
162
+ return new ActiveGroupStageInclude(this, expr)
163
+ }
164
+ override stageExclude(expr: StageExpression<Ctx>): ActiveGroupStageExclude {
165
+ return new ActiveGroupStageExclude(this, expr)
166
+ }
167
+
168
+ public leave(): Memory {
169
+ const state = this.mutableState()
170
+ this.stack.pop()
171
+ const prevgroup = this.stack.peek()
172
+ if (prevgroup) {
173
+ return new ActiveGroupMemory(state, this.scope, this.stack)
174
+ }
175
+ return new Memory(state, this.scope)
176
+ }
177
+ }
@@ -0,0 +1,59 @@
1
+ import { filter, forEach, pipe } from 'remeda'
2
+ import type { RelationshipModel } from '../../../model/RelationModel'
3
+ import { difference, hasIntersection, intersection } from '../../../utils/set'
4
+ import { AbstractStageExclude, type MutableState, type StageExpression } from '../../memory'
5
+ import { type ActiveGroupCtx, type ActiveGroupMemory, type Ctx } from './memory'
6
+
7
+ export class StageExclude<C extends Ctx = Ctx> extends AbstractStageExclude<C> {
8
+ public excludeRelations(excluded: ReadonlySet<RelationshipModel<any>>): this {
9
+ pipe(
10
+ this.memory.connections,
11
+ filter(c => hasIntersection(c.relations, excluded)),
12
+ forEach(c => {
13
+ this.excludeConnections(
14
+ c.update(intersection(c.relations, excluded)),
15
+ )
16
+ }),
17
+ )
18
+ return this
19
+ }
20
+
21
+ /**
22
+ * Precommit hook
23
+ */
24
+ protected override precommit(state: MutableState<C>): MutableState<C> {
25
+ if (this.excluded.elements.size > 0) {
26
+ const excludeRelationships = new Set(
27
+ [...this.excluded.elements].flatMap(el => [
28
+ ...el.incoming('direct'),
29
+ ...el.outgoing('direct'),
30
+ ]),
31
+ )
32
+ this.excludeRelations(excludeRelationships)
33
+ }
34
+ return state
35
+ }
36
+
37
+ protected override postcommit(state: C['MutableState']) {
38
+ const leftExplicits = difference(this.memory.explicits, state.explicits)
39
+ for (const explicit of leftExplicits) {
40
+ state.explicitFirstSeenIn.delete(explicit)
41
+ }
42
+
43
+ // Left elements
44
+ const left = difference(this.memory.elements, state.elements)
45
+ for (const el of left) {
46
+ state.lastSeenIn.delete(el)
47
+ }
48
+ return state
49
+ }
50
+ }
51
+
52
+ export class ActiveGroupStageExclude extends StageExclude {
53
+ constructor(
54
+ public override readonly memory: ActiveGroupMemory,
55
+ public override readonly expression: StageExpression<ActiveGroupCtx>,
56
+ ) {
57
+ super(memory, expression)
58
+ }
59
+ }