@likec4/core 1.18.0 → 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 (176) 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.-BdzdI2C.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 +404 -0
  30. package/dist/utils/index.d.ts +404 -0
  31. package/dist/utils/index.js +330 -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 +74 -9
  148. package/src/utils/iterable/_types.ts +5 -0
  149. package/src/utils/iterable/filter.ts +55 -0
  150. package/src/utils/iterable/flat.ts +17 -0
  151. package/src/utils/iterable/index.ts +12 -0
  152. package/src/utils/iterable/map.ts +40 -0
  153. package/src/utils/iterable/reduce.ts +24 -0
  154. package/src/utils/iterable/some.ts +36 -0
  155. package/src/utils/iterable/to.ts +19 -0
  156. package/src/utils/iterable/unique.ts +29 -0
  157. package/src/utils/relations.ts +3 -64
  158. package/src/utils/set.ts +11 -9
  159. package/src/utils/string-hash.ts +1 -1
  160. package/dist/compute-view/index.mjs +0 -79
  161. package/dist/model/index.mjs +0 -2
  162. package/dist/shared/core.BBgiAAv1.mjs +0 -144
  163. package/dist/shared/core.BPyJvuMU.mjs +0 -2420
  164. package/dist/shared/core.BQ4AbfvB.mjs +0 -9228
  165. package/dist/shared/core.k12s_keD.mjs +0 -203
  166. package/dist/types/index.mjs +0 -21
  167. package/src/builder/Builder.test-d.ts +0 -124
  168. package/src/builder/Builder.view.ts +0 -543
  169. package/src/compute-view/LikeC4ModelGraph.ts +0 -330
  170. package/src/compute-view/deployment-view/Memory.ts +0 -108
  171. package/src/compute-view/deployment-view/Stage.ts +0 -145
  172. package/src/compute-view/deployment-view/predicates/deployment-ref.ts +0 -146
  173. package/src/compute-view/element-view/predicates.ts +0 -517
  174. package/src/compute-view/utils/sortNodes.ts +0 -101
  175. package/src/model/connection/ConnectionModel.ts +0 -94
  176. package/src/model/connection/DeploymentConnectionModel.ts +0 -99
@@ -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
+ }
@@ -0,0 +1,129 @@
1
+ import { forEach, only, pipe } from 'remeda'
2
+ import { isAncestor, sortByFqnHierarchically } from '../../../utils/fqn'
3
+ import { isome, toArray } from '../../../utils/iterable'
4
+ import { difference, union } from '../../../utils/set'
5
+ import { treeFromMemoryState } from '../../memory/ops'
6
+ import type { Elem } from '../_types'
7
+ import { findRedundantConnections } from '../clean-connections'
8
+ import type { Ctx, Memory } from './memory'
9
+
10
+ /**
11
+ * This patch:
12
+ * 1. Keeps connections between leafs or having direct deployment relations
13
+ * 2. Removes cross-boundary model relations, that already exist inside boundaries
14
+ * (e.g. prefer relations inside same deployment node over relations between nodes)
15
+ * 3. Removes implicit connections between elements, if their descendants have same connection
16
+ */
17
+ export class StageFinal {
18
+ static for(memory: Memory) {
19
+ return new StageFinal(memory)
20
+ }
21
+
22
+ private constructor(
23
+ protected readonly memory: Memory,
24
+ ) {
25
+ }
26
+
27
+ public step1CleanConnections(memory: Memory): Memory {
28
+ if (memory.connections.length < 2) {
29
+ return memory
30
+ }
31
+
32
+ const connectionsToExclude = findRedundantConnections(memory.connections)
33
+ if (connectionsToExclude.length === 0) {
34
+ return memory
35
+ }
36
+ const stage = memory.stageExclude({} as any)
37
+
38
+ stage.excludeConnections(connectionsToExclude, true)
39
+
40
+ return stage.commit()
41
+ }
42
+
43
+ public step2ProcessImplicits(memory: Memory): Memory {
44
+ const implicits = union(this.memory.elements, memory.elements)
45
+ const final = union(memory.final, this.memory.explicits)
46
+
47
+ const snapshot = new Set(final)
48
+ const isFinalOrHasIncludedDescendant = (el: Elem) => {
49
+ for (const final of snapshot) {
50
+ if (el === final || isAncestor(el, final)) {
51
+ return true
52
+ }
53
+ }
54
+ return false
55
+ }
56
+
57
+ // Pick from implicit elements
58
+ pipe(
59
+ difference(implicits, snapshot),
60
+ toArray(),
61
+ sortByFqnHierarchically,
62
+ forEach((el) => {
63
+ // if (memory.scope && !memory.scope.isAncestorOf(el)) {
64
+ // return
65
+ // }
66
+ // If element has more 2 or more children included
67
+ // It can "box" around
68
+ const childrensToWrap = [...el.children()].filter(isFinalOrHasIncludedDescendant).length
69
+ if (childrensToWrap >= 2) {
70
+ final.add(el)
71
+ return
72
+ }
73
+ // If Element has only one child included
74
+ // and has "similar" sibling with included descendant
75
+ if (childrensToWrap === 1 && isome(el.siblings(), isFinalOrHasIncludedDescendant)) {
76
+ final.add(el)
77
+ }
78
+ }),
79
+ )
80
+
81
+ return memory.update({ final })
82
+ }
83
+
84
+ public step3ProcessBoundaries(memory: Memory): Memory {
85
+ const boundaries = new Set<Elem>()
86
+ for (const conn of memory.connections) {
87
+ if (conn.boundary && conn.boundary !== conn.source && conn.boundary !== conn.target) {
88
+ boundaries.add(conn.boundary)
89
+ }
90
+ }
91
+ const tree = treeFromMemoryState<Ctx>(memory, 'final')
92
+ const stage = memory.stageExclude({} as any)
93
+
94
+ const isRemovable = (el: Elem) =>
95
+ !(
96
+ boundaries.has(el)
97
+ || memory.explicits.has(el)
98
+ || tree.hasInOut(el)
99
+ || tree.root.has(el)
100
+ )
101
+
102
+ const singleRoot = only([...tree.root])
103
+ if (singleRoot && !memory.explicits.has(singleRoot)) {
104
+ stage.exclude(singleRoot)
105
+ }
106
+
107
+ for (const el of memory.final) {
108
+ const singleChild = only(tree.children(el))
109
+ if (singleChild && !tree.hasInOut(singleChild) && isRemovable(el)) {
110
+ stage.exclude(el)
111
+ }
112
+ }
113
+
114
+ if (stage.isDirty()) {
115
+ return stage.commit()
116
+ }
117
+ return memory
118
+ }
119
+
120
+ public commit(): Memory {
121
+ // return []
122
+ const step1 = this.step1CleanConnections(this.memory)
123
+ const step2 = this.step2ProcessImplicits(step1)
124
+ return this.step3ProcessBoundaries(step2)
125
+ // return step2memory
126
+ // const step3m?emory = this.step3FlatNodes(step2memory)
127
+ // return step3memory
128
+ }
129
+ }
@@ -0,0 +1,128 @@
1
+ import { dropWhile, forEach, pipe, take, zip } from 'remeda'
2
+ import { Expr } from '../../..'
3
+ import { findConnection, findConnectionsBetween } from '../../../model/connection/model'
4
+ import { difference, isAncestor, isIterable } from '../../../utils'
5
+ import { toArray } from '../../../utils/iterable'
6
+ import { type CtxConnection, type StageExpression, AbstractStageInclude } from '../../memory'
7
+ import { type ActiveGroupCtx, type ActiveGroupMemory, type Ctx } from './memory'
8
+
9
+ export type Elem = Ctx['Element']
10
+
11
+ export class StageInclude<C extends Ctx = Ctx> extends AbstractStageInclude<C> {
12
+ /**
13
+ * Connects elements with existing ones in the memory
14
+ */
15
+ public override connectWithExisting(
16
+ elements: Elem | Iterable<Elem>,
17
+ direction: 'in' | 'out' | 'both' = 'both',
18
+ ): boolean {
19
+ const before = this._connections.length
20
+ const hasChanged = () => this._connections.length > before
21
+ if (!isIterable(elements)) {
22
+ if (direction === 'in' || direction === 'both') {
23
+ for (const el of this.memory.elements) {
24
+ this.addConnections(
25
+ findConnection(el, elements, 'directed'),
26
+ )
27
+ }
28
+ }
29
+ if (direction === 'out' || direction === 'both') {
30
+ this.addConnections(
31
+ findConnectionsBetween(elements, this.memory.elements, 'directed'),
32
+ )
33
+ }
34
+ return hasChanged()
35
+ }
36
+
37
+ const targets = [...elements]
38
+ if (direction === 'in' || direction === 'both') {
39
+ for (const el of this.memory.elements) {
40
+ this.addConnections(
41
+ findConnectionsBetween(el, targets, 'directed'),
42
+ )
43
+ }
44
+ }
45
+ if (direction === 'out' || direction === 'both') {
46
+ for (const el of targets) {
47
+ this.addConnections(
48
+ findConnectionsBetween(el, this.memory.elements, 'directed'),
49
+ )
50
+ }
51
+ }
52
+ return hasChanged()
53
+ }
54
+
55
+ protected addImplicitWithinScope(element: Elem | undefined | null) {
56
+ if (!element) {
57
+ return
58
+ }
59
+ if (!this.memory.scope || isAncestor(this.memory.scope, element)) {
60
+ this.addImplicit(element)
61
+ }
62
+ }
63
+
64
+ protected override processConnections(connections: CtxConnection<Ctx>[]) {
65
+ if (Expr.isRelationPredicateExpr(this.expression)) {
66
+ return connections
67
+ }
68
+
69
+ pipe(
70
+ connections,
71
+ forEach(({ source, target, boundary }) => {
72
+ pipe(
73
+ zip(
74
+ [...toArray(source.ancestors()).reverse(), source],
75
+ [...toArray(target.ancestors()).reverse(), target],
76
+ ),
77
+ // Filter out common ancestors
78
+ dropWhile(([sourceAncestor, targetAncestor]) => sourceAncestor === targetAncestor),
79
+ take(1),
80
+ forEach(([sourceAncestor, targetAncestor]) => {
81
+ if (sourceAncestor === source && targetAncestor === target) {
82
+ this.addImplicitWithinScope(boundary)
83
+ return
84
+ }
85
+ if (sourceAncestor !== source) {
86
+ this.addImplicitWithinScope(sourceAncestor)
87
+ }
88
+ if (targetAncestor !== target) {
89
+ this.addImplicitWithinScope(targetAncestor)
90
+ }
91
+ }),
92
+ )
93
+ }),
94
+ )
95
+ return connections
96
+ }
97
+
98
+ protected override postcommit(state: ActiveGroupCtx['MutableState']) {
99
+ const newExplicits = difference(state.explicits, this.memory.explicits)
100
+ for (const explicit of newExplicits) {
101
+ state.explicitFirstSeenIn.set(explicit, '@root' as any)
102
+ }
103
+
104
+ return state
105
+ }
106
+ }
107
+
108
+ export class ActiveGroupStageInclude extends StageInclude<ActiveGroupCtx> {
109
+ constructor(
110
+ public override readonly memory: ActiveGroupMemory,
111
+ public override readonly expression: StageExpression<ActiveGroupCtx>,
112
+ ) {
113
+ super(memory, expression)
114
+ }
115
+
116
+ protected override postcommit(state: ActiveGroupCtx['MutableState']) {
117
+ const newExplicits = difference(state.explicits, this.memory.explicits)
118
+ for (const explicit of newExplicits) {
119
+ state.explicitFirstSeenIn.set(explicit, this.memory.activeGroupId)
120
+ }
121
+
122
+ for (const implicit of [...this.explicits, ...this.implicits]) {
123
+ state.lastSeenIn.set(implicit, this.memory.activeGroupId)
124
+ }
125
+
126
+ return state
127
+ }
128
+ }
@@ -0,0 +1,86 @@
1
+ import { anyPass } from 'remeda'
2
+ import { nonexhaustive } from '../../../errors'
3
+ import type { LikeC4Model } from '../../../model'
4
+ import * as Expr from '../../../types/expression'
5
+ import { isDescendantOf } from '../../../utils/fqn'
6
+ import { ifilter, toArray } from '../../../utils/iterable'
7
+ import type { Elem, Memory, PredicateCtx } from '../_types'
8
+
9
+ /**
10
+ * Resolve elements from the model based on the given expression
11
+ */
12
+
13
+ export function resolveElements(model: LikeC4Model, expr: Exclude<Expr.ElementExpression, Expr.WildcardExpr>): Elem[] {
14
+ switch (true) {
15
+ case Expr.isExpandedElementExpr(expr): {
16
+ const element = model.element(expr.expanded)
17
+ return [
18
+ element,
19
+ ...element.children(),
20
+ ]
21
+ }
22
+ case Expr.isElementRef(expr): {
23
+ const element = model.element(expr.element)
24
+ let children
25
+ if (expr.isChildren) {
26
+ children = toArray(element.children())
27
+ } else if (expr.isDescendants) {
28
+ children = toArray(element.descendants())
29
+ }
30
+ return children && children.length > 0 ? children : [element]
31
+ }
32
+ case Expr.isElementKindExpr(expr):
33
+ return [...ifilter(model.elements(), el => {
34
+ return expr.isEqual === (el.kind === expr.elementKind)
35
+ })]
36
+ case Expr.isElementTagExpr(expr):
37
+ return [...ifilter(model.elements(), el => {
38
+ return expr.isEqual === el.tags.includes(expr.elementTag)
39
+ })]
40
+ default:
41
+ nonexhaustive(expr)
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Include elements that are not in the given set but are descendants of the current set
47
+ * Consider the following example:
48
+ * a1.* -> b1
49
+ * If there are a1.a2.a3 and b1.b2 in the memory, but not connected yet - we connect them
50
+ */
51
+ export function includeDescendantsFromMemory(elements: Elem[], memory: Memory): Elem[] {
52
+ if (memory.isEmpty() || elements.length === 0) {
53
+ return elements
54
+ }
55
+ const descedantsOf = anyPass(elements.map(e => isDescendantOf(e)))
56
+ const fromMemory = toArray(
57
+ ifilter(
58
+ memory.elements,
59
+ el => !elements.includes(el) && descedantsOf(el),
60
+ ),
61
+ )
62
+ return [
63
+ ...elements,
64
+ ...fromMemory,
65
+ ]
66
+ }
67
+
68
+ /**
69
+ * Combination of `resolveElements` and `includeDescendantsFromMemory`
70
+ */
71
+ export function resolveAndIncludeFromMemory(
72
+ nonWildcard: Expr.NonWilcard,
73
+ { memory, model }: Pick<PredicateCtx, 'model' | 'memory'>,
74
+ ): Elem[] {
75
+ const resolved = resolveElements(model, nonWildcard)
76
+ // We include from memory only if the expression is:
77
+ // - expanded element
78
+ // - element reference with or with children (but not descendants - they are already included)
79
+ if (Expr.isExpandedElementExpr(nonWildcard) || Expr.isElementRef(nonWildcard)) {
80
+ if (nonWildcard.isDescendants === true) {
81
+ return resolved
82
+ }
83
+ return includeDescendantsFromMemory(resolved, memory)
84
+ }
85
+ return resolved
86
+ }
@@ -0,0 +1,33 @@
1
+ import { findConnectionsWithin } from '../../../model/connection/model'
2
+ import type * as Expr from '../../../types/expression'
3
+ import type { Elem, PredicateExecutor } from '../_types'
4
+
5
+ export const ExpandedElementPredicate: PredicateExecutor<Expr.ExpandedElementExpr> = {
6
+ include: ({ expr, model, stage, where }) => {
7
+ const parent = model.element(expr.expanded)
8
+ if (where(parent)) {
9
+ stage.addExplicit(parent)
10
+ stage.connectWithExisting(parent)
11
+ }
12
+
13
+ const children = [...parent.children()].filter(where)
14
+ const expanded = [] as Elem[]
15
+ for (const child of children) {
16
+ stage.addImplicit(child)
17
+ if (stage.connectWithExisting(child)) {
18
+ expanded.push(child)
19
+ }
20
+ }
21
+ stage.addConnections(findConnectionsWithin(expanded))
22
+ return stage
23
+ },
24
+ exclude: ({ expr, model, stage, where }) => {
25
+ const parent = model.element(expr.expanded)
26
+ const elements = [
27
+ parent,
28
+ ...parent.children()
29
+ ].filter(where)
30
+ stage.exclude(elements)
31
+ return stage
32
+ }
33
+ }
@@ -0,0 +1,26 @@
1
+ import { findConnectionsWithin } from '../../../model/connection/model'
2
+ import * as Expr from '../../../types/expression'
3
+ import type { PredicateExecutor } from '../_types'
4
+ import { resolveElements } from './_utils'
5
+
6
+ export const ElementKindOrTagPredicate: PredicateExecutor<Expr.ElementKindExpr | Expr.ElementTagExpr> = {
7
+ include: ({ expr, model, stage, filterWhere }) => {
8
+ const elements = filterWhere(resolveElements(model, expr))
9
+ if (elements.length === 0) {
10
+ return
11
+ }
12
+
13
+ stage.addExplicit(elements)
14
+ stage.connectWithExisting(elements)
15
+ stage.addConnections(findConnectionsWithin(elements))
16
+
17
+ return stage
18
+ },
19
+
20
+ exclude: ({ expr, model, stage, filterWhere }) => {
21
+ const elements = filterWhere(resolveElements(model, expr))
22
+ stage.exclude(elements)
23
+
24
+ return stage
25
+ }
26
+ }