@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,178 @@
1
+ import { invariant } from '../../../errors'
2
+ import type { IteratorLike } from '../../../types'
3
+ import { stringHash } from '../../../utils'
4
+ import { customInspectSymbol } from '../../../utils/const'
5
+ import { equals } from '../../../utils/set'
6
+ import {
7
+ type DeployedInstanceModel,
8
+ type DeploymentElementModel,
9
+ type DeploymentRelationModel,
10
+ DeploymentNodeModel,
11
+ RelationshipsAccum,
12
+ } from '../../DeploymentElementModel'
13
+ import type { RelationshipModel } from '../../RelationModel'
14
+ import type { AnyAux } from '../../types'
15
+ import type { Connection } from '../Connection'
16
+
17
+ /**
18
+ * Connection is ephemeral entity, result of a resolving relationships between source and target.
19
+ * Includes direct relationships and/or between their nested elements.
20
+ */
21
+ export class DeploymentConnectionModel<M extends AnyAux = AnyAux>
22
+ implements Connection<DeploymentElementModel<M>, M['EdgeId']>
23
+ {
24
+ constructor(
25
+ public readonly source: DeploymentNodeModel<M> | DeployedInstanceModel<M>,
26
+ public readonly target: DeploymentNodeModel<M> | DeployedInstanceModel<M>,
27
+ public readonly relations: RelationshipsAccum<M>,
28
+ ) {
29
+ this.id = stringHash(`deployment:${source.id}:${target.id}`) as M['EdgeId']
30
+ }
31
+
32
+ readonly id: M['EdgeId']
33
+
34
+ /**
35
+ * Human readable expression of the connection
36
+ * Mostly used for testing and debugging
37
+ */
38
+ get expression(): string {
39
+ return `${this.source.id} -> ${this.target.id}`
40
+ }
41
+
42
+ private _boundary: DeploymentNodeModel<M> | null | undefined
43
+ /**
44
+ * Common ancestor of the source and target elements.
45
+ * Represents the boundary of the connection.
46
+ */
47
+ get boundary(): DeploymentNodeModel<M> | null {
48
+ this._boundary ??= this.source.commonAncestor(this.target)
49
+ return this._boundary
50
+ }
51
+
52
+ nonEmpty(): boolean {
53
+ return this.relations.nonEmpty
54
+ }
55
+
56
+ [customInspectSymbol](
57
+ // @ts-ignore
58
+ depth,
59
+ // @ts-ignore
60
+ inspectOptions,
61
+ // @ts-ignore
62
+ inspect,
63
+ ) {
64
+ const asString = this.toString()
65
+
66
+ // Trick so that node displays the name of the constructor
67
+ Object.defineProperty(asString, 'constructor', {
68
+ value: DeploymentConnectionModel,
69
+ enumerable: false,
70
+ })
71
+
72
+ return asString
73
+ }
74
+
75
+ toString() {
76
+ const model = [...this.relations.model].map(c => ' ' + c.expression)
77
+ if (model.length) {
78
+ model.unshift(' model:')
79
+ } else {
80
+ model.unshift(' model: []')
81
+ }
82
+ const deployment = [...this.relations.deployment].map(c => ' ' + c.expression)
83
+ if (deployment.length) {
84
+ deployment.unshift(' deployment:')
85
+ } else {
86
+ deployment.unshift(' deployment: []')
87
+ }
88
+ return [
89
+ this.expression,
90
+ ...model,
91
+ ...deployment,
92
+ ].join('\n')
93
+ }
94
+
95
+ /**
96
+ * Check if connection contains deployment relation,
97
+ * that is directly connected to source or target.
98
+ */
99
+ public hasDirectDeploymentRelation(): boolean {
100
+ for (const relation of this.relations.deployment) {
101
+ if (relation.source.id === this.source.id || relation.target.id === this.target.id) {
102
+ return true
103
+ }
104
+ }
105
+ return false
106
+ }
107
+
108
+ public *values(): IteratorLike<RelationshipModel<M> | DeploymentRelationModel<M>> {
109
+ yield* this.relations.model
110
+ yield* this.relations.deployment
111
+ }
112
+
113
+ /**
114
+ * Merge with another connection, if it has the same source and target.
115
+ * Returns new connection with union of relationships.
116
+ */
117
+ public mergeWith(other: DeploymentConnectionModel<M>) {
118
+ invariant(this.source.id === other.source.id, 'Cannot merge connections with different sources')
119
+ invariant(this.target.id === other.target.id, 'Cannot merge connections with different targets')
120
+ return new DeploymentConnectionModel(
121
+ this.source,
122
+ this.target,
123
+ this.relations.union(other.relations),
124
+ )
125
+ }
126
+
127
+ public difference(other: DeploymentConnectionModel<M>) {
128
+ return new DeploymentConnectionModel(
129
+ this.source,
130
+ this.target,
131
+ this.relations.difference(other.relations),
132
+ )
133
+ }
134
+
135
+ public intersect(other: DeploymentConnectionModel<M>) {
136
+ return new DeploymentConnectionModel(
137
+ this.source,
138
+ this.target,
139
+ this.relations.intersect(other.relations),
140
+ )
141
+ }
142
+
143
+ public equals(other: Connection): boolean {
144
+ invariant(other instanceof DeploymentConnectionModel, 'Other should ne DeploymentConnectionModel')
145
+ return this.id === other.id
146
+ && this.source.id === other.source.id
147
+ && this.target.id === other.target.id
148
+ && equals(this.relations.model, other.relations.model)
149
+ && equals(this.relations.deployment, other.relations.deployment)
150
+ }
151
+
152
+ /**
153
+ * Creates a clone of the current `DeploymentConnectionModel` instance with optional overrides.
154
+ * if `null` is provided in overrides, the corresponding relation set will be empty.
155
+ */
156
+ public update(overrides?: {
157
+ model?: ReadonlySet<RelationshipModel<M>> | null
158
+ deployment?: ReadonlySet<DeploymentRelationModel<M>> | null
159
+ }): DeploymentConnectionModel<M> {
160
+ if (overrides) {
161
+ overrides = {
162
+ model: this.relations.model,
163
+ deployment: this.relations.deployment,
164
+ ...overrides,
165
+ }
166
+ }
167
+ return new DeploymentConnectionModel(
168
+ this.source,
169
+ this.target,
170
+ overrides
171
+ ? new RelationshipsAccum(
172
+ overrides.model ?? new Set(),
173
+ overrides.deployment ?? new Set(),
174
+ )
175
+ : this.relations,
176
+ )
177
+ }
178
+ }
@@ -0,0 +1,120 @@
1
+ import { isSameHierarchy } from '../../../utils'
2
+ import type { DeploymentElementModel } from '../../DeploymentElementModel'
3
+ import type { AnyAux } from '../../types'
4
+ import { DeploymentConnectionModel } from './DeploymentConnectionModel'
5
+
6
+ /**
7
+ * Resolve connection from source to target
8
+ * If direction is `both`, also look for reverse connection
9
+ *
10
+ * @default direction directed
11
+ */
12
+ export function findConnection<M extends AnyAux>(
13
+ source: DeploymentElementModel<M>,
14
+ target: DeploymentElementModel<NoInfer<M>>,
15
+ direction: 'directed',
16
+ ):
17
+ | readonly [DeploymentConnectionModel<M>]
18
+ | readonly []
19
+ export function findConnection<M extends AnyAux>(
20
+ source: DeploymentElementModel<M>,
21
+ target: DeploymentElementModel<NoInfer<M>>,
22
+ direction: 'both',
23
+ ):
24
+ | readonly [DeploymentConnectionModel<M>, DeploymentConnectionModel<M>]
25
+ | readonly [DeploymentConnectionModel<M>]
26
+ | readonly []
27
+ export function findConnection<M extends AnyAux>(
28
+ source: DeploymentElementModel<M>,
29
+ target: DeploymentElementModel<NoInfer<M>>,
30
+ direction?: 'directed' | 'both',
31
+ ):
32
+ | readonly [DeploymentConnectionModel<M>, DeploymentConnectionModel<M>]
33
+ | readonly [DeploymentConnectionModel<M>]
34
+ | readonly []
35
+ export function findConnection<M extends AnyAux>(
36
+ source: DeploymentElementModel<M>,
37
+ target: DeploymentElementModel<NoInfer<M>>,
38
+ direction: 'directed' | 'both' = 'directed',
39
+ ) {
40
+ if (source === target) {
41
+ return []
42
+ }
43
+ if (isSameHierarchy(source, target)) {
44
+ return []
45
+ }
46
+ const directedIntersection = source.allOutgoing.intersect(target.allIncoming)
47
+
48
+ const directed = directedIntersection.nonEmpty
49
+ ? [
50
+ new DeploymentConnectionModel<M>(
51
+ source,
52
+ target,
53
+ directedIntersection,
54
+ ),
55
+ ] as const
56
+ : [] as const
57
+
58
+ if (direction === 'directed') {
59
+ return directed
60
+ }
61
+ return [
62
+ ...directed,
63
+ ...findConnection(target, source, 'directed'),
64
+ ] as const
65
+ }
66
+
67
+ /**
68
+ * Resolve all connections between element and others
69
+ * By default, look for both directions.
70
+ *
71
+ * @default direction both
72
+ */
73
+ export function findConnectionsBetween<M extends AnyAux>(
74
+ element: DeploymentElementModel<M>,
75
+ others: Iterable<DeploymentElementModel<NoInfer<M>>>,
76
+ direction: 'directed' | 'both' = 'both',
77
+ ): readonly DeploymentConnectionModel<M>[] {
78
+ if (element.allIncoming.isEmpty && element.allOutgoing.isEmpty) {
79
+ return []
80
+ }
81
+
82
+ // We separate resolved connection,
83
+ // because we want return outgoing first
84
+ const outgoing = [] as DeploymentConnectionModel<M>[]
85
+ const incoming = [] as DeploymentConnectionModel<M>[]
86
+ for (const _other of others) {
87
+ if (element === _other) {
88
+ continue
89
+ }
90
+ for (const found of findConnection(element, _other, direction)) {
91
+ if (found.source === element) {
92
+ outgoing.push(found)
93
+ } else {
94
+ incoming.push(found)
95
+ }
96
+ }
97
+ }
98
+ return [
99
+ ...outgoing,
100
+ ...incoming,
101
+ ]
102
+ }
103
+
104
+ /**
105
+ * Resolve all connections within a given set of elements
106
+ */
107
+ export function findConnectionsWithin<M extends AnyAux>(
108
+ elements: Iterable<DeploymentElementModel<M>>,
109
+ ): readonly DeploymentConnectionModel<M>[] {
110
+ return [...elements].reduce((acc, el, index, array) => {
111
+ // skip for last element
112
+ if (index === array.length - 1) {
113
+ return acc
114
+ }
115
+ acc.push(
116
+ ...findConnectionsBetween(el, array.slice(index + 1), 'both'),
117
+ )
118
+ return acc
119
+ }, [] as DeploymentConnectionModel<M>[])
120
+ }
@@ -0,0 +1,8 @@
1
+ export { Connection } from '../Connection'
2
+ export { DeploymentConnectionModel } from './DeploymentConnectionModel'
3
+
4
+ export {
5
+ findConnection,
6
+ findConnectionsBetween,
7
+ findConnectionsWithin,
8
+ } from './find'
@@ -0,0 +1,22 @@
1
+ export { Connection } from './Connection'
2
+ export { DeploymentConnectionModel } from './deployment/DeploymentConnectionModel'
3
+ export { ConnectionModel } from './model/ConnectionModel'
4
+ export {
5
+ differenceConnections,
6
+ findAscendingConnections,
7
+ findDeepestNestedConnection,
8
+ findDescendantConnections,
9
+ hasSameSource,
10
+ hasSameSourceTarget,
11
+ hasSameTarget,
12
+ isAnyInOut,
13
+ isIncoming,
14
+ isNestedConnection,
15
+ isOutgoing,
16
+ mergeConnections,
17
+ sortConnectionsByBoundaryHierarchy,
18
+ sortDeepestFirst,
19
+ } from './ops'
20
+
21
+ export * as deployment from './deployment/find'
22
+ export * as model from './model/find'
@@ -0,0 +1,157 @@
1
+ import { isNot } from 'remeda'
2
+ import { invariant } from '../../../errors'
3
+ import { customInspectSymbol } from '../../../utils/const'
4
+ import { ifilter, isome } from '../../../utils/iterable'
5
+ import { difference, equals, intersection, union } from '../../../utils/set'
6
+ import { stringHash } from '../../../utils/string-hash'
7
+ import type { ElementModel } from '../../ElementModel'
8
+ import type { RelationshipModel } from '../../RelationModel'
9
+ import type { AnyAux } from '../../types'
10
+ import type { Connection } from '../Connection'
11
+ import { hasSameSourceTarget } from '../ops'
12
+ import { findConnection } from './find'
13
+
14
+ /**
15
+ * Connection refers to any relationships between two elements,
16
+ * both direct and implicit ones (between their nested elements).
17
+ *
18
+ * Merges relationships together to an single edge on the diagram.
19
+ */
20
+ export class ConnectionModel<M extends AnyAux = AnyAux> implements Connection<ElementModel<M>, M['EdgeId']> {
21
+ public readonly id: M['EdgeId']
22
+
23
+ constructor(
24
+ public readonly source: ElementModel<M>,
25
+ public readonly target: ElementModel<M>,
26
+ public readonly relations: ReadonlySet<RelationshipModel<M>> = new Set(),
27
+ ) {
28
+ this.id = stringHash(`model:${source.id}:${target.id}`) as M['EdgeId']
29
+ }
30
+
31
+ private _boundary: ElementModel<M> | null | undefined
32
+ /**
33
+ * Common ancestor of the source and target elements.
34
+ * Represents the boundary of the connection.
35
+ */
36
+ get boundary(): ElementModel<M> | null {
37
+ return this._boundary ??= this.source.commonAncestor(this.target)
38
+ }
39
+
40
+ /**
41
+ * Human readable expression of the connection
42
+ * Mostly used for testing and debugging
43
+ */
44
+ get expression(): string {
45
+ return `${this.source.id} -> ${this.target.id}`
46
+ }
47
+
48
+ /**
49
+ * Returns true if only includes relations between the source and target elements.
50
+ */
51
+ get isDirect(): boolean {
52
+ return this.nonEmpty() && !this.isImplicit
53
+ }
54
+
55
+ /**
56
+ * Returns true if includes relations between nested elements of the source and target elements.
57
+ */
58
+ get isImplicit(): boolean {
59
+ return this.nonEmpty() && isome(this.relations, isNot(hasSameSourceTarget(this)))
60
+ }
61
+
62
+ get directRelations(): ReadonlySet<RelationshipModel<M>> {
63
+ return new Set(ifilter(this.relations, hasSameSourceTarget(this)))
64
+ }
65
+
66
+ nonEmpty(): boolean {
67
+ return this.relations.size > 0
68
+ }
69
+
70
+ mergeWith(other: ConnectionModel<M>) {
71
+ invariant(this.source.id === other.source.id, 'Cannot merge connections with different sources')
72
+ invariant(this.target.id === other.target.id, 'Cannot merge connections with different targets')
73
+ return new ConnectionModel(
74
+ this.source,
75
+ this.target,
76
+ union(this.relations, other.relations),
77
+ )
78
+ }
79
+
80
+ difference(other: ConnectionModel<M>) {
81
+ return new ConnectionModel(
82
+ this.source,
83
+ this.target,
84
+ difference(this.relations, other.relations),
85
+ )
86
+ }
87
+
88
+ intersect(other: ConnectionModel<M>) {
89
+ invariant(other instanceof ConnectionModel, 'Cannot intersect connection with different type')
90
+ return new ConnectionModel(
91
+ this.source,
92
+ this.target,
93
+ intersection(this.relations, other.relations),
94
+ )
95
+ }
96
+
97
+ equals(other: Connection): boolean {
98
+ invariant(other instanceof ConnectionModel, 'Cannot merge connection with different type')
99
+ return this.id === other.id
100
+ && this.source.id === other.source.id
101
+ && this.target.id === other.target.id
102
+ && equals(this.relations, other.relations)
103
+ }
104
+
105
+ /**
106
+ * Returns a new instance with the updated relations.
107
+ *
108
+ * @param relations - A readonly set of `RelationshipModel` instances representing the new relations.
109
+ * @returns A new `ConnectionModel` instance with the updated relations.
110
+ */
111
+ update(relations: ReadonlySet<RelationshipModel<M>>): ConnectionModel<M> {
112
+ return new ConnectionModel(
113
+ this.source,
114
+ this.target,
115
+ relations,
116
+ )
117
+ }
118
+
119
+ [customInspectSymbol](
120
+ // @ts-ignore
121
+ depth,
122
+ // @ts-ignore
123
+ inspectOptions,
124
+ // @ts-ignore
125
+ inspect,
126
+ ) {
127
+ const asString = this.toString()
128
+
129
+ // Trick so that node displays the name of the constructor
130
+ Object.defineProperty(asString, 'constructor', {
131
+ value: ConnectionModel,
132
+ enumerable: false,
133
+ })
134
+
135
+ return asString
136
+ }
137
+
138
+ toString() {
139
+ return [
140
+ this.expression,
141
+ this.relations.size ? ' relations:' : ' relations: [ ]',
142
+ ...[...this.relations].map(c => ' ' + c.expression),
143
+ ].join('\n')
144
+ }
145
+
146
+ /**
147
+ * Creates a new connection with reversed direction (target becomes source and vice versa)
148
+ * @param search - When true, attempts to find an existing connection between the reversed nodes
149
+ */
150
+ reversed(search = false): ConnectionModel<M> {
151
+ if (!search) {
152
+ return new ConnectionModel(this.target, this.source)
153
+ }
154
+ const [found] = findConnection(this.target, this.source, 'directed')
155
+ return found ?? new ConnectionModel(this.target, this.source, new Set())
156
+ }
157
+ }
@@ -0,0 +1,117 @@
1
+ import { isSameHierarchy } from '../../../utils/fqn'
2
+ import { intersection } from '../../../utils/set'
3
+ import type { ElementModel } from '../../ElementModel'
4
+ import type { AnyAux } from '../../types'
5
+ import { ConnectionModel } from './ConnectionModel'
6
+
7
+ /**
8
+ * Resolve connection from source to target
9
+ *
10
+ * @param direction - if 'both', also returns connection from target to source
11
+ * @default `directed`
12
+ */
13
+ export function findConnection<M extends AnyAux>(
14
+ source: ElementModel<M>,
15
+ target: ElementModel<M>,
16
+ direction: 'directed' | 'both' = 'directed',
17
+ ):
18
+ | [ConnectionModel<M>, ConnectionModel<M>]
19
+ | [ConnectionModel<M>]
20
+ | []
21
+ {
22
+ if (source === target) {
23
+ return []
24
+ }
25
+ if (isSameHierarchy(source, target)) {
26
+ return []
27
+ }
28
+
29
+ const directedIntersection = intersection(source.allOutgoing, target.allIncoming)
30
+
31
+ const directed = directedIntersection.size > 0
32
+ ? new ConnectionModel(
33
+ source,
34
+ target,
35
+ directedIntersection,
36
+ )
37
+ : null
38
+
39
+ if (direction === 'directed') {
40
+ return directed ? [directed] : []
41
+ }
42
+
43
+ const reverseIntersection = intersection(source.allIncoming, target.allOutgoing)
44
+ const reverse = reverseIntersection.size > 0
45
+ ? new ConnectionModel(
46
+ target,
47
+ source,
48
+ reverseIntersection,
49
+ )
50
+ : null
51
+
52
+ if (directed && reverse) {
53
+ return [directed, reverse]
54
+ }
55
+ if (directed) {
56
+ return [directed]
57
+ }
58
+ if (reverse) {
59
+ return [reverse]
60
+ }
61
+
62
+ return []
63
+ }
64
+
65
+ /**
66
+ * Resolve all connections between element and others
67
+ * @param direction - if 'directed', only look for outgoing connections from the element to others
68
+ * @default `both`
69
+ */
70
+ export function findConnectionsBetween<M extends AnyAux>(
71
+ element: ElementModel<M>,
72
+ others: Iterable<ElementModel<NoInfer<M>>>,
73
+ direction: 'directed' | 'both' = 'both',
74
+ ): readonly ConnectionModel<M>[] {
75
+ if (element.allIncoming.size === 0 && element.allOutgoing.size === 0) {
76
+ return []
77
+ }
78
+
79
+ // We separate resolved connection,
80
+ // because we want return outgoing first
81
+ const outgoing = [] as ConnectionModel<M>[]
82
+ const incoming = [] as ConnectionModel<M>[]
83
+ for (const _other of others) {
84
+ if (element === _other) {
85
+ continue
86
+ }
87
+ for (const found of findConnection(element, _other, direction)) {
88
+ if (found.source === element) {
89
+ outgoing.push(found)
90
+ } else {
91
+ incoming.push(found)
92
+ }
93
+ }
94
+ }
95
+ return [
96
+ ...outgoing,
97
+ ...incoming,
98
+ ]
99
+ }
100
+
101
+ /**
102
+ * Resolve all connections within a given set of elements
103
+ */
104
+ export function findConnectionsWithin<M extends AnyAux>(
105
+ elements: Iterable<ElementModel<M>>,
106
+ ): readonly ConnectionModel<M>[] {
107
+ return [...elements].reduce((acc, el, index, array) => {
108
+ // skip for last element
109
+ if (index === array.length - 1) {
110
+ return acc
111
+ }
112
+ acc.push(
113
+ ...findConnectionsBetween(el, array.slice(index + 1), 'both'),
114
+ )
115
+ return acc
116
+ }, [] as ConnectionModel<M>[])
117
+ }
@@ -0,0 +1,8 @@
1
+ export { Connection } from '../Connection'
2
+ export { ConnectionModel } from './ConnectionModel'
3
+
4
+ export {
5
+ findConnection,
6
+ findConnectionsBetween,
7
+ findConnectionsWithin,
8
+ } from './find'