@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,147 @@
1
+ import type { Connection } from '../../model/connection'
2
+ import type { DeploymentElementModel } from '../../model/DeploymentElementModel'
3
+ import type { ElementModel } from '../../model/ElementModel'
4
+
5
+ type Element = ElementModel | DeploymentElementModel
6
+
7
+ export interface ComputeCtx<
8
+ E extends Element,
9
+ C extends Connection<E, any>,
10
+ M extends ComputeMemory<any>,
11
+ Inc extends StageInclude<any>,
12
+ Exc extends StageExclude<any>,
13
+ Expr = unknown,
14
+ > {
15
+ Element: E
16
+ Connection: C
17
+ Memory: M
18
+ StageInclude: Inc
19
+ StageExclude: Exc
20
+ Expr: Expr
21
+ MutableState: State<this>
22
+ }
23
+
24
+ // export type AnyCtx = ComputeCtx<any, any, ComputeMemory<any>, StageInclude<any>, StageExclude<any>, any>
25
+ export type AnyCtx = GenericCtx
26
+
27
+ export type GenericCtx = ComputeCtx<
28
+ any,
29
+ Connection<any, any>,
30
+ ComputeMemory<GenericCtx>,
31
+ StageInclude<GenericCtx>,
32
+ StageExclude<GenericCtx>,
33
+ unknown
34
+ >
35
+
36
+ export interface State<T extends AnyCtx> {
37
+ elements: Set<CtxElement<T>>
38
+ explicits: Set<CtxElement<T>>
39
+ final: Set<CtxElement<T>>
40
+ connections: Array<CtxConnection<T>>
41
+ }
42
+
43
+ export type MutableState<T extends AnyCtx> = T['MutableState']
44
+
45
+ export type StageExpression<T extends AnyCtx> = T['Expr']
46
+
47
+ export type CtxElement<T extends AnyCtx> = T['Element']
48
+ export type CtxConnection<T extends AnyCtx> = T['Connection']
49
+ export type CtxMemory<T extends AnyCtx> = T['Memory']
50
+
51
+ export interface ComputeMemory<T extends AnyCtx> {
52
+ /**
53
+ * All resolved elements, includes:
54
+ * - explicit elements (included by element predicates, always appear in the view unless excluded)
55
+ * - elements from resolved connections (may be excluded, if connection is redundant @see excludeRedundantRelationships
56
+ * - implicit elements (not added directly, not included in the view, used for resolving connections)
57
+ */
58
+ readonly elements: ReadonlySet<CtxElement<T>>
59
+
60
+ /**
61
+ * Explicit elements
62
+ */
63
+ readonly explicits: ReadonlySet<CtxElement<T>>
64
+
65
+ /**
66
+ * Final set of elements to be included in the view
67
+ * (`elements` excluding implicits)
68
+ * Keeps order in which elements were added
69
+ */
70
+ readonly final: ReadonlySet<CtxElement<T>>
71
+
72
+ /**
73
+ * Resolved connections
74
+ */
75
+ readonly connections: ReadonlyArray<CtxConnection<T>>
76
+
77
+ isEmpty(): boolean
78
+
79
+ stageInclude(expression: T['Expr']): T['StageInclude']
80
+
81
+ stageExclude(expression: T['Expr']): T['StageExclude']
82
+
83
+ /**
84
+ * Returns shallow copy of state
85
+ */
86
+ mutableState(): T['MutableState']
87
+
88
+ /**
89
+ * Returns new memory with updated state
90
+ */
91
+ update(newstate: Partial<T['MutableState']>): T['Memory']
92
+ }
93
+
94
+ interface Stage<T extends AnyCtx> {
95
+ readonly memory: CtxMemory<T>
96
+
97
+ readonly expression: StageExpression<T>
98
+
99
+ /**
100
+ * Has changes
101
+ */
102
+ isDirty(): boolean
103
+
104
+ /**
105
+ * Has no changes
106
+ */
107
+ isEmpty(): boolean
108
+
109
+ commit(): T['Memory']
110
+ }
111
+
112
+ export interface StageInclude<T extends AnyCtx> extends Stage<T> {
113
+ readonly elements: ReadonlySet<CtxElement<T>>
114
+
115
+ readonly connections: ReadonlyArray<CtxConnection<T>>
116
+
117
+ addExplicit(element: CtxElement<T> | Iterable<CtxElement<T>> | false | undefined | null): this
118
+
119
+ addImplicit(element: CtxElement<T> | Iterable<CtxElement<T>> | false | undefined | null): this
120
+
121
+ /**
122
+ * Connects element with existing ones in the memory
123
+ *
124
+ * @param direction - direction of the connection
125
+ * @default 'both'
126
+ */
127
+ connectWithExisting(
128
+ element: CtxElement<T> | Iterable<CtxElement<T>>,
129
+ direction?: 'in' | 'out' | 'both',
130
+ ): boolean
131
+
132
+ addConnections(connection: CtxConnection<T> | Iterable<CtxConnection<T>>): this
133
+ }
134
+
135
+ export interface StageExclude<T extends AnyCtx> extends Stage<T> {
136
+ /**
137
+ * Excludes from the memory relationships from given connections (still connection may be included, but without given relationships)
138
+ * @param moveExplicitToImplicit - if true, disconnected explicit elements will be moved to implicit
139
+ * @default false
140
+ */
141
+ excludeConnections(
142
+ connection: CtxConnection<T> | Iterable<CtxConnection<T>>,
143
+ moveExplicitToImplicit?: boolean,
144
+ ): this
145
+
146
+ exclude(element: CtxElement<T> | Iterable<CtxElement<T>> | false | undefined | null): this
147
+ }
@@ -0,0 +1,5 @@
1
+ export type * from './_types'
2
+ export { AbstractMemory } from './AbstractMemory'
3
+ export { AbstractStageExclude } from './AbstractStageExclude'
4
+ export { AbstractStageInclude } from './AbstractStageInclude'
5
+ export { treeFromMemoryState } from './ops'
@@ -0,0 +1,43 @@
1
+ import DefaultMap from 'mnemonist/default-map'
2
+ import { Connection } from '../../model'
3
+ import { isAncestor, sortParentsFirst } from '../../utils'
4
+ import { toArray } from '../../utils/iterable'
5
+ import type { AnyCtx, ComputeMemory, CtxElement } from './_types'
6
+
7
+ export function treeFromMemoryState<T extends AnyCtx, M extends ComputeMemory<T> = ComputeMemory<T>>(
8
+ memory: M,
9
+ filter: 'all' | 'final' = 'final',
10
+ ) {
11
+ const sorted = sortParentsFirst(toArray(filter === 'final' ? memory.final : memory.elements))
12
+ const connected = new Set(memory.connections.flatMap(c => [c.source, c.target]))
13
+ const root = new Set(sorted)
14
+ const parents = new DefaultMap<CtxElement<T>, CtxElement<T> | null>(() => null)
15
+ const children = sorted.reduce((acc, parent, index, all) => {
16
+ acc.set(
17
+ parent,
18
+ all
19
+ .slice(index + 1)
20
+ .filter(e => isAncestor(parent, e))
21
+ .map(e => {
22
+ root.delete(e)
23
+ return e
24
+ })
25
+ .reduce((acc, el) => {
26
+ if (!acc.some(e => isAncestor(e, el))) {
27
+ acc.push(el)
28
+ parents.set(el, parent)
29
+ }
30
+ return acc
31
+ }, [] as CtxElement<T>[]),
32
+ )
33
+ return acc
34
+ }, new DefaultMap<CtxElement<T>, ReadonlyArray<CtxElement<T>>>(() => []))
35
+
36
+ return {
37
+ root: root as ReadonlySet<CtxElement<T>>,
38
+ connected: connected as ReadonlySet<CtxElement<T>>,
39
+ hasInOut: (el: CtxElement<T>) => memory.connections.some(Connection.isAnyInOut(el.id)),
40
+ parents: (el: CtxElement<T>) => children.get(el),
41
+ children: (el: CtxElement<T>) => children.get(el),
42
+ }
43
+ }
@@ -10,7 +10,7 @@ import {
10
10
  type NodeId
11
11
  } from '../../types'
12
12
  import { compareByFqnHierarchically, parentFqn } from '../../utils/fqn'
13
- import { NodesGroup } from '../element-view/compute'
13
+ import { NodesGroup } from '../element-view/memory'
14
14
 
15
15
  function updateDepthOfAncestors(node: ComputedNode, nodes: ReadonlyMap<Fqn, ComputedNode>) {
16
16
  let parentNd
@@ -44,13 +44,13 @@ export function buildComputedNodesFromElements(elements: ReadonlyArray<Element>,
44
44
 
45
45
  export function buildComputedNodes(
46
46
  elements: ReadonlyArray<ComputedNodeSource>,
47
- groups?: NodesGroup[]
47
+ groups?: ReadonlyArray<NodesGroup>
48
48
  ): ReadonlyMap<Fqn, ComputedNode> {
49
49
  const nodesMap = new Map<Fqn, ComputedNode>()
50
50
 
51
51
  const elementToGroup = new Map<Fqn, NodeId>()
52
52
 
53
- groups?.forEach(({ id, parent, viewRule, explicits }) => {
53
+ groups?.forEach(({ id, parent, viewRule, elements }) => {
54
54
  if (parent) {
55
55
  nonNullable(nodesMap.get(parent), `Parent group node ${parent} not found`).children.push(id)
56
56
  }
@@ -75,7 +75,7 @@ export function buildComputedNodes(
75
75
  opacity: viewRule.opacity ?? 0
76
76
  }
77
77
  })
78
- for (const e of explicits) {
78
+ for (const e of elements) {
79
79
  elementToGroup.set(e.id, id)
80
80
  }
81
81
  })
@@ -1,8 +1,6 @@
1
1
  import { isNullish } from 'remeda'
2
2
  import { nonexhaustive } from '../../errors'
3
3
  import {
4
- type ComputedNode,
5
- type Element,
6
4
  type ElementPredicateExpression,
7
5
  isCustomElement,
8
6
  isElementKindExpr,
@@ -17,7 +15,7 @@ import { parentFqn } from '../../utils'
17
15
 
18
16
  type Predicate<T> = (x: T) => boolean
19
17
 
20
- export function elementExprToPredicate<T extends Pick<ComputedNode, 'id' | 'kind' | 'tags'>>(
18
+ export function elementExprToPredicate<T extends { id: string; tags?: readonly string[] | null; kind: string }>(
21
19
  target: ElementPredicateExpression
22
20
  ): Predicate<T> {
23
21
  if (isElementWhere(target)) {
@@ -19,12 +19,8 @@ export function linkNodesWithEdges(nodesMap: ReadonlyMap<Fqn, ComputedNode>, edg
19
19
  source.outEdges.push(edge.id)
20
20
  target.inEdges.push(edge.id)
21
21
 
22
- if (edge.dir === 'both') {
23
- source.inEdges.push(edge.id)
24
- target.outEdges.push(edge.id)
25
- }
26
-
27
22
  // These ancestors are reversed: from bottom to top
23
+ // Ensure that ancestors are in computed nodes
28
24
  const sourceAncestors = ancestorsOfNode(source, nodesMap)
29
25
  const targetAncestors = ancestorsOfNode(target, nodesMap)
30
26
 
@@ -34,8 +30,8 @@ export function linkNodesWithEdges(nodesMap: ReadonlyMap<Fqn, ComputedNode>, edg
34
30
  ? last(
35
31
  commonHead(
36
32
  reverse(sourceAncestors),
37
- reverse(targetAncestors)
38
- )
33
+ reverse(targetAncestors),
34
+ ),
39
35
  )
40
36
  : null
41
37
  edge.parent = edgeParent ? edgeParent.id : null
@@ -1,27 +1,45 @@
1
- import { isTruthy, only, pick, pickBy, pipe, reduce, unique } from 'remeda'
1
+ import { isTruthy, only, pickBy, pipe, reduce, unique } from 'remeda'
2
2
  import type { Color, DeploymentRelation, Link, Tag, ViewId } from '../../types'
3
- import type { Relation, RelationshipArrowType, RelationshipKind, RelationshipLineType } from '../../types/relation'
3
+ import type { ModelRelation, RelationshipArrowType, RelationshipKind, RelationshipLineType } from '../../types/relation'
4
4
  import { isNonEmptyArray } from '../../utils'
5
5
 
6
- export function pickRelationshipProps(relation: Relation | DeploymentRelation) {
7
- return pick(relation, [
8
- 'title',
9
- 'description',
10
- 'technology',
11
- 'tags',
12
- 'links',
13
- 'kind',
14
- 'color',
15
- 'line',
16
- 'head',
17
- 'tail',
18
- 'navigateTo'
19
- ])
6
+ function pickRelationshipProps(relation: ModelRelation | DeploymentRelation) {
7
+ const {
8
+ title,
9
+ description = null,
10
+ technology = null,
11
+ kind = null,
12
+ color = null,
13
+ line = null,
14
+ head = null,
15
+ tail = null,
16
+ navigateTo = null
17
+ } = relation
18
+ return {
19
+ // Pick description only if title is present
20
+ ...(title && {
21
+ title,
22
+ description
23
+ }),
24
+ technology,
25
+ kind,
26
+ color,
27
+ line,
28
+ head,
29
+ tail,
30
+ navigateTo
31
+ }
20
32
  }
21
- type RelationshipProps = ReturnType<typeof pickRelationshipProps>
22
33
 
23
- export function deriveEdgePropsFromRelationships(relations: Array<Relation | DeploymentRelation>) {
24
- // TODO:
34
+ /**
35
+ * Merges properties from multiple relationships into a single object.
36
+ * @param relations - The relationships to merge.
37
+ * @param prefer - The relationship to prefer when merging.
38
+ */
39
+ export function mergePropsFromRelationships(
40
+ relations: Array<ModelRelation | DeploymentRelation>,
41
+ prefer?: ModelRelation | DeploymentRelation
42
+ ) {
25
43
  const allprops = pipe(
26
44
  relations,
27
45
  reduce(
@@ -76,12 +94,17 @@ export function deriveEdgePropsFromRelationships(relations: Array<Relation | Dep
76
94
  }
77
95
  )
78
96
  )
97
+
98
+ let technology = only(allprops.technology)
99
+ let title = only(allprops.title) ?? (allprops.title.length > 1 ? '[...]' : null)
100
+
79
101
  const tags = unique(allprops.tags)
80
- return pickBy(
102
+ let merged = pickBy(
81
103
  {
82
- title: only(allprops.title) ?? (allprops.title.length > 1 ? '[...]' : null),
104
+ // If there is no title, but there is technology, use technology as title
105
+ title: title ?? (technology ? `[${technology}]` : null),
83
106
  description: only(allprops.description),
84
- technology: only(allprops.technology),
107
+ technology,
85
108
  kind: only(allprops.kind),
86
109
  head: only(allprops.head),
87
110
  tail: only(allprops.tail),
@@ -93,5 +116,13 @@ export function deriveEdgePropsFromRelationships(relations: Array<Relation | Dep
93
116
  },
94
117
  isTruthy
95
118
  )
119
+
120
+ if (prefer) {
121
+ return {
122
+ ...merged,
123
+ ...pickBy(pickRelationshipProps(prefer), isTruthy)
124
+ }
125
+ }
126
+ return merged
96
127
  }
97
- export type MergedRelationshipProps = ReturnType<typeof deriveEdgePropsFromRelationships>
128
+ export type MergedRelationshipProps = ReturnType<typeof mergePropsFromRelationships>
@@ -10,12 +10,12 @@ import {
10
10
  type RelationWhereExpr
11
11
  } from '../../types/expression'
12
12
  import { whereOperatorAsPredicate } from '../../types/operators'
13
- import type { Relation } from '../../types/relation'
13
+ import type { ModelRelation } from '../../types/relation'
14
14
  import type { ComputedNode } from '../../types/view'
15
15
  import { elementExprToPredicate } from './elementExpressionToPredicate'
16
16
 
17
17
  type Predicate<T> = (x: T) => boolean
18
- export type FilterableEdge = Pick<Relation, 'kind' | 'tags'> & {
18
+ export type FilterableEdge = Pick<ModelRelation, 'kind' | 'tags'> & {
19
19
  source: ComputedNode
20
20
  target: ComputedNode
21
21
  }
@@ -1,65 +1,69 @@
1
- import { first, last, values } from 'remeda'
2
- import { isExtendsElementView, type LikeC4View } from '../../types/view'
3
- import { findCycles, Graph, isAcyclic, postorder } from '../../utils/graphlib'
1
+ import { values } from 'remeda'
2
+ import { type ExtendsElementView, isElementView, isExtendsElementView, type LikeC4View } from '../../types/view'
4
3
 
4
+ import Graph from 'graphology'
5
+ import { topologicalSort } from 'graphology-dag/topological-sort'
6
+ import willCreateCycle from 'graphology-dag/will-create-cycle'
5
7
  /**
6
8
  * Resolve rules of extended views
7
9
  * (Removes invalid views)
8
10
  */
9
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
11
  export function resolveRulesExtendedViews<V extends Record<any, LikeC4View>>(
11
12
  unresolvedViews: V
12
13
  ): V {
13
- const g = new Graph({
14
- directed: true,
15
- multigraph: false,
16
- compound: false
14
+ const g = new Graph<{ view: LikeC4View }>({
15
+ type: 'directed'
17
16
  })
17
+ const extendedViews = [] as ExtendsElementView<any, any>[]
18
18
  for (const view of values(unresolvedViews)) {
19
- g.setNode(view.id, view.id)
19
+ g.addNode(view.id, { view })
20
20
  if (isExtendsElementView(view)) {
21
- // view -> parent
22
- g.setEdge(view.id, view.extends)
21
+ extendedViews.push(view)
23
22
  }
24
23
  }
25
- if (g.edgeCount() === 0) {
24
+
25
+ if (extendedViews.length === 0) {
26
26
  return unresolvedViews
27
27
  }
28
28
 
29
- // Remove circular dependencies
30
- while (!isAcyclic(g)) {
31
- const firstCycle = first(findCycles(g))
32
- if (!firstCycle) {
33
- break
29
+ for (const view of extendedViews) {
30
+ if (!g.hasNode(view.extends)) {
31
+ console.warn(`View "${view.id}" extends from "${view.extends}" which does not exist`)
32
+ continue
34
33
  }
35
- const cycledNode = last(firstCycle)
36
- if (!cycledNode) {
37
- break
34
+ if (willCreateCycle(g, view.id, view.extends)) {
35
+ console.warn(`View "${view.id}" extends from "${view.extends}" which creates a cycle`)
36
+ continue
38
37
  }
39
- g.removeNode(cycledNode)
38
+
39
+ // view -> parent
40
+ g.addDirectedEdge(view.id, view.extends)
40
41
  }
41
42
 
42
- const ordered = postorder(g, g.sources() as unknown as string[])
43
+ const sorted = topologicalSort(g).reverse()
44
+ return sorted.reduce((acc, id) => {
45
+ const view = g.getNodeAttribute(id, 'view')
46
+ if (!isExtendsElementView(view)) {
47
+ acc[view.id] = view
48
+ return acc
49
+ }
43
50
 
44
- return ordered.reduce((acc, id) => {
45
- const view = unresolvedViews[id]
46
- if (!view) {
51
+ const extendsFrom = acc[view.extends]
52
+ if (!extendsFrom || !isElementView(extendsFrom)) {
47
53
  return acc
48
54
  }
49
- if (isExtendsElementView(view)) {
50
- const extendsFrom = acc[view.extends]
51
- if (!extendsFrom) {
52
- console.warn(`View "${view.id}" extends from "${view.extends}" which does not exist`)
53
- return acc
54
- }
55
- return Object.assign(acc, {
56
- [view.id]: {
57
- ...extendsFrom,
58
- ...view,
59
- rules: [...extendsFrom.rules, ...view.rules]
60
- }
61
- })
55
+
56
+ acc[view.id] = {
57
+ ...extendsFrom,
58
+ ...view,
59
+ rules: [...extendsFrom.rules, ...view.rules]
62
60
  }
63
- return Object.assign(acc, { [view.id]: view })
64
- }, {} as V)
61
+ return acc
62
+ }, {} as Record<string, LikeC4View>) as V
63
+
64
+ // forEachNodeInTopologicalOrder(g, (_id, { view }) => {
65
+
66
+ // })
67
+
68
+ // return ordered as V
65
69
  }
@@ -21,12 +21,12 @@ export function resolveGlobalRules<V extends DynamicView | ElementView>(
21
21
  if (isElementView(view)) {
22
22
  return {
23
23
  ...view,
24
- rules: resolveGlobalRulesInElementView(view, globals)
24
+ rules: resolveGlobalRulesInElementView(view.rules, globals)
25
25
  }
26
26
  } else if (isDynamicView(view)) {
27
27
  return {
28
28
  ...view,
29
- rules: resolveGlobalRulesInDynamicView(view, globals)
29
+ rules: resolveGlobalRulesInDynamicView(view.rules, globals)
30
30
  }
31
31
  }
32
32
  nonexhaustive(view)
@@ -35,10 +35,10 @@ export function resolveGlobalRules<V extends DynamicView | ElementView>(
35
35
  type ViewRuleGlobal = ViewRuleGlobalPredicateRef | ViewRuleGlobalStyle
36
36
 
37
37
  export function resolveGlobalRulesInElementView(
38
- view: ElementView,
38
+ rules: ViewRule[],
39
39
  globals: ModelGlobals
40
40
  ): Array<Exclude<ViewRule, ViewRuleGlobal>> {
41
- return view.rules.reduce((acc, rule) => {
41
+ return rules.reduce((acc, rule) => {
42
42
  if (isViewRuleGlobalPredicateRef(rule)) {
43
43
  const globalPredicates = globals.predicates[rule.predicateId]
44
44
  if (isNullish(globalPredicates)) {
@@ -59,10 +59,10 @@ export function resolveGlobalRulesInElementView(
59
59
  }
60
60
 
61
61
  export function resolveGlobalRulesInDynamicView(
62
- view: DynamicView,
62
+ rules: DynamicViewRule[],
63
63
  globals: ModelGlobals
64
64
  ): Array<Exclude<DynamicViewRule, ViewRuleGlobal>> {
65
- return view.rules.reduce((acc, rule) => {
65
+ return rules.reduce((acc, rule) => {
66
66
  if (isViewRuleGlobalPredicateRef(rule)) {
67
67
  const globalPredicates = globals.dynamicPredicates[rule.predicateId]
68
68
  if (isNullish(globalPredicates)) {