@likec4/core 1.26.1 → 1.27.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.
- package/dist/builder/index.d.mts +3 -1
- package/dist/builder/index.mjs +6 -4
- package/dist/compute-view/index.d.mts +1 -2
- package/dist/compute-view/index.mjs +19 -7
- package/dist/compute-view/relationships-view/index.d.mts +42 -0
- package/dist/compute-view/relationships-view/index.mjs +181 -0
- package/dist/index.d.mts +3 -5
- package/dist/index.mjs +5 -5
- package/dist/model/index.d.mts +3 -3
- package/dist/model/index.mjs +1 -1
- package/dist/shared/{core.yND74qFI.mjs → core.BMep80lJ.mjs} +2 -1
- package/dist/shared/{core.DRxv3HIl.mjs → core.C-YXI-43.mjs} +1 -5
- package/dist/shared/core.CQXU9DF7.mjs +477 -0
- package/dist/shared/{core.Brmu7VBL.d.mts → core.CtloVwbo.d.mts} +3 -1
- package/dist/shared/{core.BqezHsjR.mjs → core.Cu-UdkSl.mjs} +16 -477
- package/dist/shared/core.DbRvwARP.mjs +5 -0
- package/dist/shared/{core.oI7caxVx.d.mts → core.Uz54YXx0.d.mts} +1 -1
- package/dist/shared/{core.CcjDE4j7.mjs → core.zj2huD5x.mjs} +17 -30
- package/dist/types/index.mjs +1 -1
- package/dist/utils/index.d.mts +3 -2
- package/dist/utils/index.mjs +5 -3
- package/package.json +19 -9
- package/src/builder/Builder.model.ts +74 -0
- package/src/builder/Builder.with.ts +20 -339
- package/src/compute-view/deployment-view/_types.ts +1 -1
- package/src/compute-view/deployment-view/predicates/relation-direct.ts +6 -7
- package/src/compute-view/deployment-view/predicates/relation-incoming.ts +2 -1
- package/src/compute-view/deployment-view/predicates/relation-outgoing.ts +1 -1
- package/src/compute-view/dynamic-view/compute.ts +22 -43
- package/src/compute-view/index.ts +0 -1
- package/src/compute-view/relationships-view/_types.ts +7 -6
- package/src/compute-view/relationships-view/compute.ts +140 -52
- package/src/compute-view/relationships-view/index.ts +3 -0
- package/src/compute-view/relationships-view/utils.ts +44 -11
- package/src/compute-view/utils/resolve-extended-views.ts +20 -5
- package/src/compute-view/utils/view-hash.ts +1 -4
- package/src/index.ts +1 -1
- package/src/model/ElementModel.ts +2 -0
- package/src/model/view/NodeModel.ts +5 -0
- package/src/utils/object-hash.ts +13 -2
- package/dist/builder/index.d.ts +0 -509
- package/dist/compute-view/index.d.ts +0 -23
- package/dist/index.d.ts +0 -147
- package/dist/model/index.d.ts +0 -118
- package/dist/shared/core.-qjOvsEL.d.ts +0 -146
- package/dist/shared/core.30yYKXcZ.d.ts +0 -186
- package/dist/shared/core.BBJd-FqJ.mjs +0 -37
- package/dist/shared/core.BH59D8Ji.d.ts +0 -858
- package/dist/shared/core.CIbWBWW3.d.ts +0 -127
- package/dist/shared/core.Cpd5mZgF.d.ts +0 -732
- package/dist/shared/core.Dd1nCiNx.d.mts +0 -12
- package/dist/shared/core.mD0TIdz_.d.ts +0 -12
- package/dist/types/index.d.ts +0 -72
- package/dist/utils/index.d.ts +0 -458
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { filter, flatMap, isNonNullish, map, pick, pipe } from 'remeda'
|
|
2
2
|
import { invariant } from '../../../errors'
|
|
3
|
-
import type {
|
|
4
|
-
ConnectionModel,
|
|
5
|
-
DeploymentElementModel,
|
|
6
|
-
DeploymentRelationModel,
|
|
7
|
-
LikeC4DeploymentModel,
|
|
8
|
-
} from '../../../model'
|
|
9
3
|
import type { DeploymentConnectionModel } from '../../../model/connection/deployment'
|
|
10
4
|
import { findConnection, findConnectionsBetween, findConnectionsWithin } from '../../../model/connection/deployment'
|
|
11
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
type ConnectionModel,
|
|
7
|
+
findConnectionsBetween as findModelConnectionsBetween,
|
|
8
|
+
} from '../../../model/connection/model'
|
|
9
|
+
import type { DeploymentElementModel, DeploymentRelationModel } from '../../../model/DeploymentElementModel'
|
|
10
|
+
import type { LikeC4DeploymentModel } from '../../../model/DeploymentModel'
|
|
12
11
|
import type { RelationshipModel } from '../../../model/RelationModel'
|
|
13
12
|
import type { AnyAux } from '../../../model/types'
|
|
14
13
|
import { type Filterable, type OperatorPredicate, type RelationExpr, FqnExpr } from '../../../types'
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { anyPass, filter, pipe } from 'remeda'
|
|
2
2
|
import { invariant } from '../../../errors'
|
|
3
|
-
import type { AnyAux, RelationshipModel } from '../../../model'
|
|
4
3
|
import { findConnectionsBetween } from '../../../model/connection/deployment'
|
|
5
4
|
import type { LikeC4DeploymentModel } from '../../../model/DeploymentModel'
|
|
5
|
+
import type { RelationshipModel } from '../../../model/RelationModel'
|
|
6
|
+
import type { AnyAux } from '../../../model/types'
|
|
6
7
|
import { type RelationExpr, FqnExpr } from '../../../types'
|
|
7
8
|
import { isAncestor } from '../../../utils'
|
|
8
9
|
import type { Connection, Elem, PredicateExecutor } from '../_types'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { anyPass, filter, pipe } from 'remeda'
|
|
2
2
|
import { invariant } from '../../../errors'
|
|
3
|
-
import type { LikeC4DeploymentModel } from '../../../model'
|
|
4
3
|
import { findConnection, findConnectionsBetween } from '../../../model/connection/deployment'
|
|
4
|
+
import type { LikeC4DeploymentModel } from '../../../model/DeploymentModel'
|
|
5
5
|
import type { RelationshipModel } from '../../../model/RelationModel'
|
|
6
6
|
import { type RelationExpr, FqnExpr } from '../../../types'
|
|
7
7
|
import { isAncestor } from '../../../utils'
|
|
@@ -8,22 +8,22 @@ import {
|
|
|
8
8
|
type Color,
|
|
9
9
|
type ComputedDynamicView,
|
|
10
10
|
type ComputedEdge,
|
|
11
|
-
DefaultArrowType,
|
|
12
|
-
DefaultLineStyle,
|
|
13
|
-
DefaultRelationshipColor,
|
|
14
11
|
type DynamicView,
|
|
15
12
|
type DynamicViewStep,
|
|
16
|
-
isDynamicViewParallelSteps,
|
|
17
|
-
isViewRuleAutoLayout,
|
|
18
|
-
isViewRulePredicate,
|
|
19
13
|
type NonEmptyArray,
|
|
20
14
|
type RelationId,
|
|
21
15
|
type RelationshipArrowType,
|
|
22
16
|
type RelationshipLineType,
|
|
23
17
|
type StepEdgeId,
|
|
24
|
-
stepEdgeId,
|
|
25
18
|
type Tag,
|
|
26
|
-
type ViewId
|
|
19
|
+
type ViewId,
|
|
20
|
+
DefaultArrowType,
|
|
21
|
+
DefaultLineStyle,
|
|
22
|
+
DefaultRelationshipColor,
|
|
23
|
+
isDynamicViewParallelSteps,
|
|
24
|
+
isViewRuleAutoLayout,
|
|
25
|
+
isViewRulePredicate,
|
|
26
|
+
stepEdgeId,
|
|
27
27
|
} from '../../types'
|
|
28
28
|
import { ancestorsFqn, commonAncestor, parentFqn } from '../../utils/fqn'
|
|
29
29
|
import { applyCustomElementProperties } from '../utils/applyCustomElementProperties'
|
|
@@ -62,7 +62,7 @@ class DynamicViewCompute {
|
|
|
62
62
|
|
|
63
63
|
constructor(
|
|
64
64
|
protected model: LikeC4Model,
|
|
65
|
-
protected view: DynamicView
|
|
65
|
+
protected view: DynamicView,
|
|
66
66
|
) {}
|
|
67
67
|
|
|
68
68
|
private addStep(
|
|
@@ -75,7 +75,7 @@ class DynamicViewCompute {
|
|
|
75
75
|
...step
|
|
76
76
|
}: DynamicViewStep,
|
|
77
77
|
index: number,
|
|
78
|
-
parent?: number
|
|
78
|
+
parent?: number,
|
|
79
79
|
) {
|
|
80
80
|
const id = parent ? stepEdgeId(parent, index) : stepEdgeId(index)
|
|
81
81
|
const source = this.model.element(stepSource)
|
|
@@ -89,11 +89,8 @@ class DynamicViewCompute {
|
|
|
89
89
|
relations,
|
|
90
90
|
tags,
|
|
91
91
|
navigateTo: derivedNavigateTo,
|
|
92
|
-
head,
|
|
93
|
-
tail,
|
|
94
92
|
color,
|
|
95
93
|
line,
|
|
96
|
-
notation
|
|
97
94
|
} = this.findRelations(source, target)
|
|
98
95
|
|
|
99
96
|
const navigateTo = isTruthy(stepNavigateTo) && stepNavigateTo !== this.view.id ? stepNavigateTo : derivedNavigateTo
|
|
@@ -108,11 +105,8 @@ class DynamicViewCompute {
|
|
|
108
105
|
isBackward: isBackward ?? false,
|
|
109
106
|
...(navigateTo ? { navigateTo } : {}),
|
|
110
107
|
...(tags ? { tags } : {}),
|
|
111
|
-
...(head ? { head } : {}),
|
|
112
|
-
...(tail ? { tail } : {}),
|
|
113
108
|
...(color ? { color } : {}),
|
|
114
109
|
...(line ? { line } : {}),
|
|
115
|
-
...(notation ? { notation } : {})
|
|
116
110
|
})
|
|
117
111
|
}
|
|
118
112
|
|
|
@@ -171,7 +165,7 @@ class DynamicViewCompute {
|
|
|
171
165
|
color: DefaultRelationshipColor,
|
|
172
166
|
line: DefaultLineStyle,
|
|
173
167
|
head: DefaultArrowType,
|
|
174
|
-
...step
|
|
168
|
+
...step,
|
|
175
169
|
}
|
|
176
170
|
if (isBackward) {
|
|
177
171
|
edge.dir = 'back'
|
|
@@ -204,8 +198,8 @@ class DynamicViewCompute {
|
|
|
204
198
|
applyViewRuleStyles(
|
|
205
199
|
rules,
|
|
206
200
|
// Keep order of elements
|
|
207
|
-
elements.map(e => nonNullable(nodesMap.get(e.id)))
|
|
208
|
-
)
|
|
201
|
+
elements.map(e => nonNullable(nodesMap.get(e.id))),
|
|
202
|
+
),
|
|
209
203
|
)
|
|
210
204
|
|
|
211
205
|
const autoLayoutRule = findLast(rules, isViewRuleAutoLayout)
|
|
@@ -217,7 +211,7 @@ class DynamicViewCompute {
|
|
|
217
211
|
autoLayout: {
|
|
218
212
|
direction: autoLayoutRule?.direction ?? 'LR',
|
|
219
213
|
...(autoLayoutRule?.nodeSep && { nodeSep: autoLayoutRule.nodeSep }),
|
|
220
|
-
...(autoLayoutRule?.rankSep && { rankSep: autoLayoutRule.rankSep })
|
|
214
|
+
...(autoLayoutRule?.rankSep && { rankSep: autoLayoutRule.rankSep }),
|
|
221
215
|
},
|
|
222
216
|
nodes: map(nodes, n => {
|
|
223
217
|
// omit notation
|
|
@@ -230,9 +224,9 @@ class DynamicViewCompute {
|
|
|
230
224
|
edges,
|
|
231
225
|
...(elementNotations.length > 0 && {
|
|
232
226
|
notation: {
|
|
233
|
-
elements: elementNotations
|
|
234
|
-
}
|
|
235
|
-
})
|
|
227
|
+
elements: elementNotations,
|
|
228
|
+
},
|
|
229
|
+
}),
|
|
236
230
|
})
|
|
237
231
|
}
|
|
238
232
|
|
|
@@ -241,11 +235,8 @@ class DynamicViewCompute {
|
|
|
241
235
|
tags: NonEmptyArray<Tag> | null
|
|
242
236
|
relations: NonEmptyArray<RelationId> | null
|
|
243
237
|
navigateTo: ViewId | null
|
|
244
|
-
tail: RelationshipArrowType | null
|
|
245
|
-
head: RelationshipArrowType | null
|
|
246
238
|
color: Color | null
|
|
247
239
|
line: RelationshipLineType | null
|
|
248
|
-
notation: string | null
|
|
249
240
|
} {
|
|
250
241
|
const relationships = findConnection(source, target, 'directed').flatMap(r => [...r.relations])
|
|
251
242
|
if (relationships.length === 0) {
|
|
@@ -254,18 +245,15 @@ class DynamicViewCompute {
|
|
|
254
245
|
tags: null,
|
|
255
246
|
relations: null,
|
|
256
247
|
navigateTo: null,
|
|
257
|
-
tail: null,
|
|
258
|
-
head: null,
|
|
259
248
|
color: null,
|
|
260
249
|
line: null,
|
|
261
|
-
notation: null
|
|
262
250
|
}
|
|
263
251
|
}
|
|
264
252
|
const alltags = pipe(
|
|
265
253
|
relationships,
|
|
266
254
|
flatMap(r => r.tags),
|
|
267
255
|
filter(isTruthy),
|
|
268
|
-
unique()
|
|
256
|
+
unique(),
|
|
269
257
|
)
|
|
270
258
|
const tags = hasAtLeast(alltags, 1) ? alltags : null
|
|
271
259
|
const relations = hasAtLeast(relationships, 1) ? map(relationships, r => r.id) : null
|
|
@@ -280,27 +268,21 @@ class DynamicViewCompute {
|
|
|
280
268
|
filter(isTruthy),
|
|
281
269
|
filter(v => v !== this.view.id),
|
|
282
270
|
unique(),
|
|
283
|
-
only()
|
|
271
|
+
only(),
|
|
284
272
|
)
|
|
285
273
|
|
|
286
274
|
const commonProperties = pipe(
|
|
287
275
|
relationships,
|
|
288
276
|
reduce((acc, { title, $relationship: r }) => {
|
|
289
277
|
isTruthy(title) && acc.title.add(title)
|
|
290
|
-
isTruthy(r.tail) && acc.tail.add(r.tail)
|
|
291
|
-
isTruthy(r.head) && acc.head.add(r.head)
|
|
292
278
|
isTruthy(r.color) && acc.color.add(r.color)
|
|
293
279
|
isTruthy(r.line) && acc.line.add(r.line)
|
|
294
|
-
|
|
295
280
|
return acc
|
|
296
281
|
}, {
|
|
297
|
-
tail: new Set<RelationshipArrowType>(),
|
|
298
|
-
head: new Set<RelationshipArrowType>(),
|
|
299
282
|
color: new Set<Color>(),
|
|
300
283
|
line: new Set<RelationshipLineType>(),
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
})
|
|
284
|
+
title: new Set<string>(),
|
|
285
|
+
}),
|
|
304
286
|
)
|
|
305
287
|
|
|
306
288
|
return {
|
|
@@ -308,17 +290,14 @@ class DynamicViewCompute {
|
|
|
308
290
|
relations,
|
|
309
291
|
navigateTo: navigateTo ?? null,
|
|
310
292
|
title: only([...commonProperties.title]) ?? null,
|
|
311
|
-
tail: only([...commonProperties.tail]) ?? null,
|
|
312
|
-
head: only([...commonProperties.head]) ?? null,
|
|
313
293
|
color: only([...commonProperties.color]) ?? null,
|
|
314
294
|
line: only([...commonProperties.line]) ?? null,
|
|
315
|
-
notation: only([...commonProperties.notation]) ?? null
|
|
316
295
|
}
|
|
317
296
|
}
|
|
318
297
|
}
|
|
319
298
|
export function computeDynamicView<M extends AnyAux>(
|
|
320
299
|
model: LikeC4Model<M>,
|
|
321
|
-
view: DynamicView
|
|
300
|
+
view: DynamicView,
|
|
322
301
|
): ComputedDynamicView {
|
|
323
302
|
return new DynamicViewCompute(model, view).compute()
|
|
324
303
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export { computeView, computeViews, unsafeComputeView } from './compute-view'
|
|
2
2
|
export type { ComputeViewResult } from './compute-view'
|
|
3
|
-
export { computeRelationshipsView } from './relationships-view/compute'
|
|
4
3
|
export { resolveRulesExtendedViews } from './utils/resolve-extended-views'
|
|
5
4
|
export { viewsWithReadableEdges, withReadableEdges } from './utils/with-readable-edges'
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ElementModel } from '../../model/ElementModel'
|
|
2
2
|
import type { RelationshipModel } from '../../model/RelationModel'
|
|
3
|
+
import type { AnyAux } from '../../model/types'
|
|
3
4
|
|
|
4
5
|
// type Pair = {
|
|
5
6
|
// left: ReadonlySet<ElementModel>
|
|
@@ -7,10 +8,10 @@ import type { RelationshipModel } from '../../model/RelationModel'
|
|
|
7
8
|
// right: ReadonlySet<ElementModel>
|
|
8
9
|
// }
|
|
9
10
|
|
|
10
|
-
export type RelationshipsViewData = {
|
|
11
|
-
incomers: ReadonlySet<ElementModel
|
|
12
|
-
incoming: ReadonlySet<RelationshipModel
|
|
13
|
-
subjects: ReadonlySet<ElementModel
|
|
14
|
-
outgoing: ReadonlySet<RelationshipModel
|
|
15
|
-
outgoers: ReadonlySet<ElementModel
|
|
11
|
+
export type RelationshipsViewData<M extends AnyAux = AnyAux> = {
|
|
12
|
+
incomers: ReadonlySet<ElementModel<M>>
|
|
13
|
+
incoming: ReadonlySet<RelationshipModel<M>>
|
|
14
|
+
subjects: ReadonlySet<ElementModel<M>>
|
|
15
|
+
outgoing: ReadonlySet<RelationshipModel<M>>
|
|
16
|
+
outgoers: ReadonlySet<ElementModel<M>>
|
|
16
17
|
}
|
|
@@ -1,74 +1,162 @@
|
|
|
1
|
+
import { pipe } from 'remeda'
|
|
2
|
+
import { invariant } from '../../errors'
|
|
1
3
|
import type { ElementModel } from '../../model/ElementModel'
|
|
2
4
|
import type { LikeC4Model } from '../../model/LikeC4Model'
|
|
3
5
|
import type { RelationshipModel } from '../../model/RelationModel'
|
|
4
|
-
import type {
|
|
5
|
-
import {
|
|
6
|
+
import type { AnyAux } from '../../model/types'
|
|
7
|
+
import type { LikeC4ViewModel } from '../../model/view/LikeC4ViewModel'
|
|
8
|
+
import { isDescendantOf, sortParentsFirst } from '../../utils/fqn'
|
|
9
|
+
import { ifilter, imap, toArray, toSet } from '../../utils/iterable'
|
|
10
|
+
import type { RelationshipsViewData } from './_types'
|
|
11
|
+
import { treeFromElements } from './utils'
|
|
6
12
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
): {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
} {
|
|
17
|
-
const subject = likec4model.element(subjectId)
|
|
18
|
-
let relationships = {
|
|
19
|
-
incoming: [...subject.incoming()],
|
|
20
|
-
outgoing: [...subject.outgoing()],
|
|
13
|
+
const finalize = <M extends AnyAux>(
|
|
14
|
+
elements: Set<ElementModel<M>>,
|
|
15
|
+
explicits: Set<ElementModel<M>>,
|
|
16
|
+
): Set<ElementModel<M>> => {
|
|
17
|
+
if (elements.size > 2 && explicits.size !== elements.size) {
|
|
18
|
+
return new Set(sortParentsFirst([
|
|
19
|
+
...treeFromElements(elements).flatten(),
|
|
20
|
+
...explicits,
|
|
21
|
+
]))
|
|
21
22
|
}
|
|
23
|
+
if (elements.size > 1) {
|
|
24
|
+
return new Set(sortParentsFirst([...elements]))
|
|
25
|
+
}
|
|
26
|
+
return elements
|
|
27
|
+
}
|
|
22
28
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
function computeRelationships<const M extends AnyAux>(
|
|
30
|
+
subject: ElementModel<M>,
|
|
31
|
+
others: Set<ElementModel<M>>,
|
|
32
|
+
relationships: {
|
|
33
|
+
incoming: ReadonlyArray<RelationshipModel<M>>
|
|
34
|
+
outgoing: ReadonlyArray<RelationshipModel<M>>
|
|
35
|
+
},
|
|
36
|
+
): Omit<RelationshipsViewData<M>, 'subjectExistsInScope'> {
|
|
37
|
+
const isOther = (e: ElementModel<M>) => others.has(e)
|
|
38
|
+
let subjects = new Set([subject])
|
|
39
|
+
const explicits = {
|
|
40
|
+
incomers: new Set<ElementModel<M>>(),
|
|
41
|
+
subjects: new Set([subject]),
|
|
42
|
+
outgoers: new Set<ElementModel<M>>(),
|
|
43
|
+
}
|
|
26
44
|
|
|
27
45
|
// Iterate over incoming and outgoing relationships
|
|
28
46
|
// and add the source and target elements to appropriate sets.
|
|
29
|
-
// If element is deeper in the hierarchy -
|
|
30
|
-
|
|
47
|
+
// If element is deeper in the hierarchy - add all ancestors until that is a sibling of the subject
|
|
48
|
+
let incomers = new Set(relationships.incoming.flatMap((r) => {
|
|
49
|
+
explicits.subjects.add(r.target)
|
|
50
|
+
explicits.incomers.add(r.source)
|
|
51
|
+
|
|
31
52
|
subjects.add(r.target)
|
|
32
|
-
if (
|
|
33
|
-
|
|
53
|
+
if (r.target !== subject) {
|
|
54
|
+
let target = r.target.parent
|
|
55
|
+
while (target && target !== subject) {
|
|
56
|
+
subjects.add(target)
|
|
57
|
+
target = target.parent
|
|
58
|
+
}
|
|
34
59
|
}
|
|
35
|
-
|
|
36
|
-
|
|
60
|
+
|
|
61
|
+
let source = r.source
|
|
62
|
+
const incomerBranch = [] as ElementModel<M>[]
|
|
63
|
+
while (true) {
|
|
64
|
+
incomerBranch.push(source)
|
|
65
|
+
if (isOther(source) || !source.parent) {
|
|
66
|
+
break
|
|
67
|
+
}
|
|
68
|
+
source = source.parent
|
|
69
|
+
}
|
|
70
|
+
return incomerBranch
|
|
37
71
|
}))
|
|
38
|
-
|
|
72
|
+
|
|
73
|
+
let outgoers = new Set(relationships.outgoing.flatMap((r) => {
|
|
74
|
+
explicits.subjects.add(r.source)
|
|
75
|
+
explicits.outgoers.add(r.target)
|
|
76
|
+
|
|
39
77
|
subjects.add(r.source)
|
|
40
|
-
if (
|
|
41
|
-
|
|
78
|
+
if (r.source !== subject) {
|
|
79
|
+
let source = r.source.parent
|
|
80
|
+
while (source && source !== subject) {
|
|
81
|
+
subjects.add(source)
|
|
82
|
+
source = source.parent
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let target = r.target
|
|
87
|
+
const outgoerBranch = [] as ElementModel<M>[]
|
|
88
|
+
while (true) {
|
|
89
|
+
outgoerBranch.push(target)
|
|
90
|
+
if (isOther(target) || !target.parent) {
|
|
91
|
+
break
|
|
92
|
+
}
|
|
93
|
+
target = target.parent
|
|
42
94
|
}
|
|
43
|
-
|
|
44
|
-
return parent ? [r.target, parent] : [r.target]
|
|
95
|
+
return outgoerBranch
|
|
45
96
|
}))
|
|
46
97
|
|
|
47
98
|
return {
|
|
48
|
-
incomers,
|
|
99
|
+
incomers: finalize(incomers, explicits.incomers),
|
|
49
100
|
incoming: new Set(relationships.incoming),
|
|
50
|
-
subjects,
|
|
101
|
+
subjects: finalize(subjects, explicits.subjects),
|
|
51
102
|
outgoing: new Set(relationships.outgoing),
|
|
52
|
-
outgoers,
|
|
103
|
+
outgoers: finalize(outgoers, explicits.outgoers),
|
|
53
104
|
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function computeRelationshipsView<const M extends AnyAux>(
|
|
108
|
+
subjectId: NoInfer<M['Element']>,
|
|
109
|
+
likec4model: LikeC4Model<M>,
|
|
110
|
+
scopeViewId: NoInfer<M['View']> | null,
|
|
111
|
+
scope: 'global' | 'view' = 'global',
|
|
112
|
+
): RelationshipsViewData<M> {
|
|
113
|
+
let subjectExistsInScope = true
|
|
114
|
+
const view = scopeViewId ? likec4model.findView(scopeViewId) : null
|
|
115
|
+
if (scope === 'view') {
|
|
116
|
+
invariant(
|
|
117
|
+
view,
|
|
118
|
+
'Scope view id is required when scope is "view"',
|
|
119
|
+
)
|
|
120
|
+
return computeScopedRelationshipsView(subjectId, view, likec4model)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const subject = likec4model.element(subjectId)
|
|
124
|
+
const subjectSiblings = toSet(subject.ascendingSiblings())
|
|
125
|
+
return computeRelationships(
|
|
126
|
+
subject,
|
|
127
|
+
subjectSiblings,
|
|
128
|
+
{
|
|
129
|
+
incoming: [...subject.incoming()],
|
|
130
|
+
outgoing: [...subject.outgoing()],
|
|
131
|
+
},
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function computeScopedRelationshipsView<const M extends AnyAux>(
|
|
136
|
+
subjectId: NoInfer<M['Element']>,
|
|
137
|
+
view: LikeC4ViewModel<M>,
|
|
138
|
+
likec4model: LikeC4Model<M>,
|
|
139
|
+
): RelationshipsViewData<M> {
|
|
140
|
+
const subject = likec4model.element(subjectId)
|
|
141
|
+
let relationships = {
|
|
142
|
+
incoming: toArray(ifilter(subject.incoming(), (r) => view.includesRelation(r.id))),
|
|
143
|
+
outgoing: toArray(ifilter(subject.outgoing(), (r) => view.includesRelation(r.id))),
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const isDescendant = isDescendantOf(subject)
|
|
147
|
+
|
|
148
|
+
const others = new Set([
|
|
149
|
+
...subject.ascendingSiblings(),
|
|
150
|
+
...pipe(
|
|
151
|
+
view.elements(),
|
|
152
|
+
imap((e) => e.element),
|
|
153
|
+
ifilter((e): e is ElementModel<M> => e !== subject && isDescendant(e)),
|
|
154
|
+
),
|
|
155
|
+
])
|
|
54
156
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
// direction: 'LR',
|
|
61
|
-
// },
|
|
62
|
-
// tags: null,
|
|
63
|
-
// links: null,
|
|
64
|
-
// hash: 'empty',
|
|
65
|
-
// customColorDefinitions: {},
|
|
66
|
-
// ...layoutRelationshipsView({
|
|
67
|
-
// incomers,
|
|
68
|
-
// incoming: new Set(relationships.incoming),
|
|
69
|
-
// subjects,
|
|
70
|
-
// outgoing: new Set(relationships.outgoing),
|
|
71
|
-
// outgoers,
|
|
72
|
-
// }),
|
|
73
|
-
// }
|
|
157
|
+
return computeRelationships(
|
|
158
|
+
subject,
|
|
159
|
+
others,
|
|
160
|
+
relationships,
|
|
161
|
+
)
|
|
74
162
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { nonNullable } from '../../errors'
|
|
2
2
|
import type { ElementModel } from '../../model/ElementModel'
|
|
3
|
-
import type {
|
|
3
|
+
import type { AnyAux } from '../../model/types'
|
|
4
4
|
import { isAncestor, isDescendantOf, sortParentsFirst } from '../../utils/fqn'
|
|
5
5
|
import { DefaultMap } from '../../utils/mnemonist'
|
|
6
6
|
|
|
7
|
-
export function treeFromElements(elements: Iterable<ElementModel
|
|
8
|
-
const sorted = sortParentsFirst([...elements]) as ReadonlyArray<ElementModel
|
|
7
|
+
export function treeFromElements<const M extends AnyAux>(elements: Iterable<ElementModel<M>>) {
|
|
8
|
+
const sorted = sortParentsFirst([...elements]) as ReadonlyArray<ElementModel<M>>
|
|
9
9
|
const root = new Set(sorted)
|
|
10
|
-
const map = new Map(sorted.map(e => [e.
|
|
11
|
-
const parents = new DefaultMap<ElementModel
|
|
10
|
+
const map = new Map(sorted.map(e => [e._literalId, e]))
|
|
11
|
+
const parents = new DefaultMap<ElementModel<M>, ElementModel<M> | null>(() => null)
|
|
12
12
|
const children = sorted.reduce((acc, parent, index, all) => {
|
|
13
13
|
acc.set(
|
|
14
14
|
parent,
|
|
@@ -25,16 +25,49 @@ export function treeFromElements(elements: Iterable<ElementModel>) {
|
|
|
25
25
|
parents.set(el, parent)
|
|
26
26
|
}
|
|
27
27
|
return acc
|
|
28
|
-
}, [] as ElementModel[]),
|
|
28
|
+
}, [] as ElementModel<M>[]),
|
|
29
29
|
)
|
|
30
30
|
return acc
|
|
31
|
-
}, new DefaultMap<ElementModel
|
|
31
|
+
}, new DefaultMap<ElementModel<M>, ElementModel<M>[]>(() => []))
|
|
32
32
|
|
|
33
33
|
return {
|
|
34
34
|
sorted,
|
|
35
|
-
byId: (id:
|
|
36
|
-
root: root as ReadonlySet<ElementModel
|
|
37
|
-
parent: (el: ElementModel) => parents.get(el),
|
|
38
|
-
children: (el: ElementModel): ReadonlyArray<ElementModel
|
|
35
|
+
byId: (id: M['Element']): ElementModel<M> => nonNullable(map.get(id), `Element not found by id: ${id}`),
|
|
36
|
+
root: root as ReadonlySet<ElementModel<M>>,
|
|
37
|
+
parent: (el: ElementModel<M>): ElementModel<M> | null => parents.get(el),
|
|
38
|
+
children: (el: ElementModel<M>): ReadonlyArray<ElementModel<M>> => children.get(el),
|
|
39
|
+
/**
|
|
40
|
+
* Flattens the tree structure by removing redundant hierarchy levels.
|
|
41
|
+
* @example
|
|
42
|
+
* A
|
|
43
|
+
* └── B
|
|
44
|
+
* ├── C
|
|
45
|
+
* │ └── D
|
|
46
|
+
* │ └── E
|
|
47
|
+
* └── F
|
|
48
|
+
* └── G
|
|
49
|
+
* becomes
|
|
50
|
+
* A
|
|
51
|
+
* ├── C
|
|
52
|
+
* │ └── E
|
|
53
|
+
* └── F
|
|
54
|
+
* └── G
|
|
55
|
+
*/
|
|
56
|
+
flatten: (): Set<ElementModel<M>> => {
|
|
57
|
+
return new Set([
|
|
58
|
+
...root,
|
|
59
|
+
...sorted.reduce((acc, el) => {
|
|
60
|
+
const _children = children.get(el)
|
|
61
|
+
if (_children.length === 0) {
|
|
62
|
+
acc.push(el)
|
|
63
|
+
return acc
|
|
64
|
+
}
|
|
65
|
+
if (_children.length > 1) {
|
|
66
|
+
acc.push(..._children)
|
|
67
|
+
}
|
|
68
|
+
return acc
|
|
69
|
+
}, [] as ElementModel<M>[]),
|
|
70
|
+
])
|
|
71
|
+
},
|
|
39
72
|
}
|
|
40
73
|
}
|
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import { values } from 'remeda'
|
|
2
|
-
import { type ExtendsElementView, isElementView, isExtendsElementView
|
|
1
|
+
import { unique, values } from 'remeda'
|
|
2
|
+
import { type ExtendsElementView, type LikeC4View, isElementView, isExtendsElementView } from '../../types/view'
|
|
3
3
|
|
|
4
4
|
import Graph from 'graphology'
|
|
5
5
|
import { topologicalSort } from 'graphology-dag/topological-sort'
|
|
6
6
|
import willCreateCycle from 'graphology-dag/will-create-cycle'
|
|
7
|
+
import { isNonEmptyArray } from '../../utils'
|
|
7
8
|
/**
|
|
8
9
|
* Resolve rules of extended views
|
|
9
10
|
* (Removes invalid views)
|
|
10
11
|
*/
|
|
11
12
|
export function resolveRulesExtendedViews<V extends Record<any, LikeC4View>>(
|
|
12
|
-
unresolvedViews: V
|
|
13
|
+
unresolvedViews: V,
|
|
13
14
|
): V {
|
|
14
15
|
const g = new Graph<{ view: LikeC4View }>({
|
|
15
|
-
type: 'directed'
|
|
16
|
+
type: 'directed',
|
|
16
17
|
})
|
|
17
18
|
const extendedViews = [] as ExtendsElementView<any, any>[]
|
|
18
19
|
for (const view of values(unresolvedViews)) {
|
|
@@ -53,10 +54,24 @@ export function resolveRulesExtendedViews<V extends Record<any, LikeC4View>>(
|
|
|
53
54
|
return acc
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
const tags = unique([
|
|
58
|
+
...(extendsFrom.tags ?? []),
|
|
59
|
+
...(view.tags ?? []),
|
|
60
|
+
])
|
|
61
|
+
|
|
62
|
+
const links = [
|
|
63
|
+
...(extendsFrom.links ?? []),
|
|
64
|
+
...(view.links ?? []),
|
|
65
|
+
]
|
|
66
|
+
|
|
56
67
|
acc[view.id] = {
|
|
57
68
|
...extendsFrom,
|
|
58
69
|
...view,
|
|
59
|
-
|
|
70
|
+
title: view.title ?? extendsFrom.title ?? null,
|
|
71
|
+
description: view.description ?? extendsFrom.description ?? null,
|
|
72
|
+
tags: isNonEmptyArray(tags) ? tags : null,
|
|
73
|
+
links: isNonEmptyArray(links) ? links : null,
|
|
74
|
+
rules: [...extendsFrom.rules, ...view.rules],
|
|
60
75
|
}
|
|
61
76
|
return acc
|
|
62
77
|
}, {} as Record<string, LikeC4View>) as V
|
|
@@ -19,9 +19,6 @@ export function calcViewLayoutHash<V extends ComputedView>(view: SetOptional<V,
|
|
|
19
19
|
mapToObj(({ source, target, ...edge }) => [`${source}:${target}`, edge]),
|
|
20
20
|
),
|
|
21
21
|
}
|
|
22
|
-
view.hash = objectHash(tohash
|
|
23
|
-
ignoreUnknown: true,
|
|
24
|
-
respectType: false,
|
|
25
|
-
})
|
|
22
|
+
view.hash = objectHash(tohash)
|
|
26
23
|
return view as unknown as V
|
|
27
24
|
}
|
package/src/index.ts
CHANGED
|
@@ -36,9 +36,9 @@ export * from './utils'
|
|
|
36
36
|
export * as Expr from './types/expression'
|
|
37
37
|
|
|
38
38
|
export {
|
|
39
|
-
computeRelationshipsView,
|
|
40
39
|
computeView,
|
|
41
40
|
computeViews,
|
|
42
41
|
unsafeComputeView,
|
|
43
42
|
} from './compute-view'
|
|
43
|
+
|
|
44
44
|
export type { ComputeViewResult } from './compute-view'
|
|
@@ -25,6 +25,7 @@ export type ElementsIterator<M extends AnyAux> = IteratorLike<ElementModel<M>>
|
|
|
25
25
|
|
|
26
26
|
export class ElementModel<M extends AnyAux = AnyAux> {
|
|
27
27
|
readonly id: M['Fqn']
|
|
28
|
+
readonly _literalId: M['Element']
|
|
28
29
|
readonly hierarchyLevel: number
|
|
29
30
|
|
|
30
31
|
constructor(
|
|
@@ -32,6 +33,7 @@ export class ElementModel<M extends AnyAux = AnyAux> {
|
|
|
32
33
|
public readonly $element: C4Element,
|
|
33
34
|
) {
|
|
34
35
|
this.id = this.$element.id
|
|
36
|
+
this._literalId = this.$element.id
|
|
35
37
|
this.hierarchyLevel = hierarchyLevel(this.id)
|
|
36
38
|
}
|
|
37
39
|
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
type DeploymentNodeKind,
|
|
5
5
|
type DiagramView,
|
|
6
6
|
type ElementShape as C4ElementShape,
|
|
7
|
+
type ElementStyle,
|
|
7
8
|
type IconUrl,
|
|
8
9
|
type IteratorLike,
|
|
9
10
|
type Link,
|
|
@@ -114,6 +115,10 @@ export class NodeModel<M extends AnyAux, V extends ComputedView | DiagramView =
|
|
|
114
115
|
return this.$node.navigateTo ? this.$view.$model.view(this.$node.navigateTo) : null
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
get style(): ElementStyle {
|
|
119
|
+
return this.$node.style
|
|
120
|
+
}
|
|
121
|
+
|
|
117
122
|
/**
|
|
118
123
|
* Get all ancestor elements (i.e. parent, parent’s parent, etc.)
|
|
119
124
|
* (from closest to root)
|