@likec4/core 1.17.1 → 1.18.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 (112) hide show
  1. package/dist/compute-view/index.d.mts +91 -0
  2. package/dist/compute-view/index.d.ts +91 -0
  3. package/dist/compute-view/index.mjs +79 -0
  4. package/dist/index.d.mts +246 -434
  5. package/dist/index.d.ts +246 -434
  6. package/dist/index.mjs +418 -1322
  7. package/dist/model/index.d.mts +642 -0
  8. package/dist/model/index.d.ts +642 -0
  9. package/dist/model/index.mjs +2 -0
  10. package/dist/shared/core.BBgiAAv1.mjs +144 -0
  11. package/dist/shared/core.BPyJvuMU.mjs +2420 -0
  12. package/dist/shared/core.BQ4AbfvB.mjs +9228 -0
  13. package/dist/shared/{core.CoLQkcYL.d.mts → core.DMYY7C8s.d.mts} +398 -249
  14. package/dist/shared/{core.CoLQkcYL.d.ts → core.DMYY7C8s.d.ts} +398 -249
  15. package/dist/shared/core.k12s_keD.mjs +203 -0
  16. package/dist/types/index.d.mts +69 -1
  17. package/dist/types/index.d.ts +69 -1
  18. package/dist/types/index.mjs +21 -1
  19. package/package.json +38 -40
  20. package/src/builder/Builder.deployment.ts +219 -0
  21. package/src/builder/Builder.deploymentModel.ts +360 -0
  22. package/src/builder/Builder.element.ts +17 -19
  23. package/src/builder/Builder.model.ts +13 -2
  24. package/src/builder/Builder.test-d.ts +59 -4
  25. package/src/builder/Builder.ts +238 -53
  26. package/src/builder/Builder.view.ts +238 -53
  27. package/src/builder/_types.ts +87 -15
  28. package/src/compute-view/LikeC4ModelGraph.ts +330 -0
  29. package/src/compute-view/compute-view.ts +92 -0
  30. package/src/compute-view/deployment-view/Memory.ts +108 -0
  31. package/src/compute-view/deployment-view/Stage.ts +145 -0
  32. package/src/compute-view/deployment-view/__test__/fixture.ts +210 -0
  33. package/src/compute-view/deployment-view/_types.ts +29 -0
  34. package/src/compute-view/deployment-view/clean-connections.ts +229 -0
  35. package/src/compute-view/deployment-view/compute.ts +111 -0
  36. package/src/compute-view/deployment-view/predicates/deployment-ref.ts +146 -0
  37. package/src/compute-view/deployment-view/predicates/relation-direct.ts +130 -0
  38. package/src/compute-view/deployment-view/predicates/relation-in-out.ts +43 -0
  39. package/src/compute-view/deployment-view/predicates/relation-incoming.ts +46 -0
  40. package/src/compute-view/deployment-view/predicates/relation-outgoing.ts +49 -0
  41. package/src/compute-view/deployment-view/predicates/wildcard.ts +29 -0
  42. package/src/compute-view/deployment-view/utils.ts +212 -0
  43. package/src/compute-view/dynamic-view/__test__/fixture.ts +58 -0
  44. package/src/compute-view/dynamic-view/compute.ts +310 -0
  45. package/src/compute-view/element-view/__test__/__snapshots__/legacy.spec.ts.snap +456 -0
  46. package/src/compute-view/element-view/__test__/fixture.ts +645 -0
  47. package/src/compute-view/element-view/compute.ts +734 -0
  48. package/src/compute-view/element-view/predicates.ts +517 -0
  49. package/src/compute-view/index.ts +5 -0
  50. package/src/compute-view/utils/__snapshots__/sortNodes.spec.ts.snap +113 -0
  51. package/src/compute-view/utils/ancestorsOfNode.ts +19 -0
  52. package/src/compute-view/utils/applyCustomElementProperties.ts +66 -0
  53. package/src/compute-view/utils/applyCustomRelationProperties.ts +41 -0
  54. package/src/compute-view/utils/applyViewRuleStyles.ts +53 -0
  55. package/src/compute-view/utils/buildComputedNodes.ts +148 -0
  56. package/src/compute-view/utils/buildElementNotations.ts +63 -0
  57. package/src/compute-view/utils/derive-edge-props-from-relationships.ts +97 -0
  58. package/src/compute-view/utils/elementExpressionToPredicate.ts +52 -0
  59. package/src/compute-view/utils/link-nodes-with-edges.ts +58 -0
  60. package/src/compute-view/utils/relationExpressionToPredicates.ts +55 -0
  61. package/src/compute-view/utils/resolve-extended-views.ts +65 -0
  62. package/src/compute-view/utils/resolve-global-rules.ts +83 -0
  63. package/src/compute-view/utils/sortNodes.ts +101 -0
  64. package/src/compute-view/utils/topological-sort.ts +160 -0
  65. package/src/compute-view/utils/uniqueTags.test.ts +42 -0
  66. package/src/compute-view/utils/uniqueTags.ts +21 -0
  67. package/src/compute-view/utils/view-hash.ts +27 -0
  68. package/src/compute-view/utils/with-readable-edges.ts +31 -0
  69. package/src/index.ts +5 -1
  70. package/src/model/DeploymentElementModel.ts +534 -0
  71. package/src/model/DeploymentModel.ts +364 -0
  72. package/src/model/ElementModel.ts +195 -0
  73. package/src/model/LikeC4Model.test-d.ts +151 -0
  74. package/src/model/LikeC4Model.ts +280 -457
  75. package/src/model/RelationModel.ts +84 -0
  76. package/src/model/__test__/fixture.ts +153 -338
  77. package/src/model/connection/ConnectionModel.ts +94 -0
  78. package/src/model/connection/DeploymentConnectionModel.ts +99 -0
  79. package/src/model/connection/deployment.ts +124 -0
  80. package/src/model/connection/model.ts +123 -0
  81. package/src/model/index.ts +4 -2
  82. package/src/model/types.ts +95 -15
  83. package/src/model/view/EdgeModel.ts +103 -0
  84. package/src/model/view/LikeC4ViewModel.ts +220 -0
  85. package/src/model/view/NodeModel.ts +228 -0
  86. package/src/types/_common.ts +2 -0
  87. package/src/types/deployments.ts +237 -0
  88. package/src/types/element.ts +7 -3
  89. package/src/types/expression.ts +8 -6
  90. package/src/types/index.ts +1 -0
  91. package/src/types/model.ts +68 -9
  92. package/src/types/overview-graph.ts +2 -2
  93. package/src/types/relation.ts +4 -4
  94. package/src/types/view-notation.ts +2 -2
  95. package/src/types/view.ts +104 -30
  96. package/src/utils/{commonHead.ts → common-head.ts} +2 -0
  97. package/src/utils/fqn.ts +83 -27
  98. package/src/utils/getOrCreate.ts +42 -0
  99. package/src/utils/graphlib.ts +10 -0
  100. package/src/utils/index.ts +4 -1
  101. package/src/utils/object-hash.ts +3 -0
  102. package/src/utils/set.ts +76 -0
  103. package/src/utils/string-hash.ts +16 -0
  104. package/dist/index.cjs +0 -5452
  105. package/dist/index.d.cts +0 -1053
  106. package/dist/shared/core.BXTs-8n7.mjs +0 -293
  107. package/dist/shared/core.CoLQkcYL.d.cts +0 -734
  108. package/dist/shared/core.DIYZvpVg.cjs +0 -353
  109. package/dist/types/index.cjs +0 -70
  110. package/dist/types/index.d.cts +0 -2
  111. package/src/model/LikeC4DiagramModel.ts +0 -338
  112. package/src/model/LikeC4ViewModel.ts +0 -320
@@ -0,0 +1,734 @@
1
+ import {
2
+ anyPass,
3
+ concat,
4
+ difference,
5
+ filter,
6
+ findLast,
7
+ first,
8
+ hasAtLeast,
9
+ isNonNull,
10
+ isTruthy,
11
+ map,
12
+ only,
13
+ pipe,
14
+ reduce,
15
+ sort,
16
+ unique
17
+ } from 'remeda'
18
+ import { invariant, nonexhaustive, nonNullable } from '../../errors'
19
+ import {
20
+ type Color,
21
+ type ComputedEdge,
22
+ type ComputedElementView,
23
+ type ComputedNode,
24
+ type EdgeId,
25
+ type Element,
26
+ ElementKind,
27
+ type ElementPredicateExpression,
28
+ type ElementView,
29
+ isScopedElementView,
30
+ isViewRuleAutoLayout,
31
+ isViewRuleGroup,
32
+ isViewRulePredicate,
33
+ type NodeId,
34
+ type NonEmptyArray,
35
+ type Relation,
36
+ type RelationPredicateExpression,
37
+ type RelationshipArrowType,
38
+ type RelationshipKind,
39
+ type RelationshipLineType,
40
+ type Tag,
41
+ type ViewId,
42
+ type ViewRuleGroup,
43
+ type ViewRulePredicate,
44
+ whereOperatorAsPredicate
45
+ } from '../../types'
46
+ import * as Expr from '../../types/expression'
47
+ import { stringHash } from '../../utils'
48
+ import { commonAncestor, isAncestor } from '../../utils/fqn'
49
+ import { compareRelations } from '../../utils/relations'
50
+ import type { LikeC4ModelGraph } from '../LikeC4ModelGraph'
51
+ import { applyCustomElementProperties } from '../utils/applyCustomElementProperties'
52
+ import { applyCustomRelationProperties } from '../utils/applyCustomRelationProperties'
53
+ import { applyViewRuleStyles } from '../utils/applyViewRuleStyles'
54
+ import { buildComputedNodesFromElements } from '../utils/buildComputedNodes'
55
+ import { buildElementNotations } from '../utils/buildElementNotations'
56
+ import { linkNodesWithEdges } from '../utils/link-nodes-with-edges'
57
+ import { resolveGlobalRulesInElementView } from '../utils/resolve-global-rules'
58
+ import { sortNodes } from '../utils/sortNodes'
59
+ import { uniqueTags } from '../utils/uniqueTags'
60
+ import { calcViewLayoutHash } from '../utils/view-hash'
61
+ import {
62
+ type ElementPredicateFn,
63
+ excludeElementKindOrTag,
64
+ excludeElementRef,
65
+ excludeExpandedElementExpr,
66
+ excludeIncomingExpr,
67
+ excludeInOutExpr,
68
+ excludeOutgoingExpr,
69
+ excludeRelationExpr,
70
+ excludeWildcardRef,
71
+ includeElementKindOrTag,
72
+ includeElementRef,
73
+ includeExpandedElementExpr,
74
+ includeIncomingExpr,
75
+ includeInOutExpr,
76
+ includeOutgoingExpr,
77
+ includeRelationExpr,
78
+ includeWildcardRef,
79
+ type RelationPredicateFn
80
+ } from './predicates'
81
+
82
+ // eslint-disable-next-line @typescript-eslint/no-namespace
83
+ export namespace ComputeCtx {
84
+ // Intermediate ComputedEdge
85
+ export interface Edge {
86
+ source: Element
87
+ target: Element
88
+ relations: Relation[]
89
+ }
90
+ }
91
+
92
+ function compareEdges(a: ComputeCtx.Edge, b: ComputeCtx.Edge) {
93
+ return compareRelations(
94
+ { source: a.source.id, target: a.target.id },
95
+ { source: b.source.id, target: b.target.id }
96
+ )
97
+ }
98
+
99
+ // If there is only one relation and it has same source and target as edge
100
+ function isDirectEdge({ relations: [rel, ...tail], source, target }: ComputeCtx.Edge) {
101
+ if (rel && tail.length === 0) {
102
+ return rel.source === source.id && rel.target === target.id
103
+ }
104
+ return false
105
+ }
106
+
107
+ export class NodesGroup {
108
+ static readonly kind = ElementKind.Group
109
+
110
+ static root() {
111
+ return new NodesGroup('@root' as NodeId, { title: null, groupRules: [] })
112
+ }
113
+
114
+ static is(node: ComputedNode) {
115
+ return node.kind === NodesGroup.kind
116
+ }
117
+
118
+ readonly explicits = new Set<Element>()
119
+ readonly implicits = new Set<Element>()
120
+
121
+ constructor(
122
+ public id: NodeId,
123
+ public viewRule: ViewRuleGroup,
124
+ public parent: NodeId | null = null
125
+ ) {
126
+ }
127
+
128
+ /**
129
+ * Add element explicitly
130
+ * Included even without relationships
131
+ */
132
+ addElement(...el: Element[]) {
133
+ for (const r of el) {
134
+ this.explicits.add(r)
135
+ this.implicits.add(r)
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Add element implicitly
141
+ * Included if only has relationships
142
+ */
143
+ addImplicit(...el: Element[]) {
144
+ for (const r of el) {
145
+ this.implicits.add(r)
146
+ }
147
+ }
148
+
149
+ excludeElement(...excludes: Element[]) {
150
+ for (const el of excludes) {
151
+ this.explicits.delete(el)
152
+ this.implicits.delete(el)
153
+ }
154
+ }
155
+
156
+ isEmpty() {
157
+ return this.explicits.size === 0 && this.implicits.size === 0
158
+ }
159
+ }
160
+
161
+ export class ComputeCtx {
162
+ // Intermediate state
163
+ private explicits = new Set<Element>()
164
+ private implicits = new Set<Element>()
165
+ private ctxEdges = [] as ComputeCtx.Edge[]
166
+
167
+ /**
168
+ * Root group - not included in the groups
169
+ * But used to accumulate elements that are not in any group
170
+ */
171
+ private __rootGroup = NodesGroup.root()
172
+ private groups = [] as NodesGroup[]
173
+ private activeGroupStack = [] as NodesGroup[]
174
+
175
+ protected get activeGroup() {
176
+ return this.activeGroupStack[0] ?? this.__rootGroup
177
+ }
178
+
179
+ protected get includedElements() {
180
+ return new Set([
181
+ ...this.explicits,
182
+ ...this.ctxEdges.flatMap(e => [e.source, e.target])
183
+ ]) as ReadonlySet<Element>
184
+ }
185
+
186
+ protected get resolvedElements() {
187
+ return new Set([
188
+ ...this.explicits,
189
+ ...this.implicits,
190
+ ...this.ctxEdges.flatMap(e => [e.source, e.target])
191
+ ]) as ReadonlySet<Element>
192
+ }
193
+
194
+ protected get edges() {
195
+ return this.ctxEdges
196
+ }
197
+
198
+ protected get root() {
199
+ return isScopedElementView(this.view) ? this.view.viewOf : null
200
+ }
201
+
202
+ public static elementView(view: ElementView, graph: LikeC4ModelGraph) {
203
+ return new ComputeCtx(view, graph).compute()
204
+ }
205
+
206
+ private constructor(
207
+ protected view: ElementView,
208
+ protected graph: LikeC4ModelGraph
209
+ ) {}
210
+
211
+ protected compute(): ComputedElementView {
212
+ this.reset()
213
+ const {
214
+ docUri: _docUri, // exclude docUri
215
+ rules: _rules, // exclude rules
216
+ ...view
217
+ } = this.view
218
+
219
+ const rules = resolveGlobalRulesInElementView(this.view, this.graph.globals)
220
+
221
+ const viewPredicates = rules.filter(anyPass([isViewRulePredicate, isViewRuleGroup])) as Array<
222
+ ViewRulePredicate | ViewRuleGroup
223
+ >
224
+ if (this.root && viewPredicates.length == 0) {
225
+ this.addElement(this.graph.element(this.root))
226
+ }
227
+ this.processPredicates(viewPredicates)
228
+ this.removeRedundantImplicitEdges()
229
+ if (this.groups.length > 0) {
230
+ this.assignElementsToGroups()
231
+ }
232
+
233
+ const elements = [...this.includedElements]
234
+ const nodesMap = buildComputedNodesFromElements(elements, this.groups)
235
+
236
+ const edges = this.computeEdges()
237
+ const edgesMap = new Map<EdgeId, ComputedEdge>(edges.map(edge => [edge.id, edge]))
238
+
239
+ linkNodesWithEdges(nodesMap, edges)
240
+
241
+ // nodesMap sorted hierarchically,
242
+ // but we need to keep the initial sort
243
+ let initialSort = elements.map(e => nonNullable(nodesMap.get(e.id), `Node ${e.id} not found in nodesMap`))
244
+
245
+ if (this.groups.length > 0) {
246
+ initialSort = concat(
247
+ this.groups.map(g => nonNullable(nodesMap.get(g.id), `Node ${g.id} not found in nodesMap`)),
248
+ initialSort
249
+ )
250
+ }
251
+
252
+ const nodes = applyCustomElementProperties(
253
+ rules,
254
+ applyViewRuleStyles(
255
+ rules,
256
+ // Build graph and apply postorder sort
257
+ sortNodes({
258
+ nodes: initialSort,
259
+ edges
260
+ })
261
+ )
262
+ )
263
+ const sortedEdges = new Set([
264
+ ...nodes.flatMap(n => n.children.length === 0 ? n.outEdges.flatMap(id => edgesMap.get(id) ?? []) : []),
265
+ ...edges
266
+ ])
267
+
268
+ const autoLayoutRule = findLast(this.view.rules, isViewRuleAutoLayout)
269
+
270
+ const elementNotations = buildElementNotations(nodes)
271
+
272
+ return calcViewLayoutHash({
273
+ ...view,
274
+ autoLayout: {
275
+ direction: autoLayoutRule?.direction ?? 'TB',
276
+ ...(autoLayoutRule?.nodeSep && { nodeSep: autoLayoutRule.nodeSep }),
277
+ ...(autoLayoutRule?.rankSep && { rankSep: autoLayoutRule.rankSep })
278
+ },
279
+ nodes: map(nodes, n => {
280
+ // omit notation
281
+ delete n.notation
282
+ if (n.icon === 'none') {
283
+ delete n.icon
284
+ }
285
+ return n
286
+ }),
287
+ edges: applyCustomRelationProperties(rules, nodes, sortedEdges),
288
+ ...(elementNotations.length > 0 && {
289
+ notation: {
290
+ elements: elementNotations
291
+ }
292
+ })
293
+ })
294
+ }
295
+
296
+ protected computeEdges(): ComputedEdge[] {
297
+ return this.ctxEdges.map((e): ComputedEdge => {
298
+ invariant(hasAtLeast(e.relations, 1), 'Edge must have at least one relation')
299
+ const relations = sort(e.relations, compareRelations)
300
+ const source = e.source.id
301
+ const target = e.target.id
302
+
303
+ const edge: ComputedEdge = {
304
+ id: stringHash(`${source}:${target}`) as EdgeId,
305
+ parent: commonAncestor(source, target),
306
+ source,
307
+ target,
308
+ label: null,
309
+ relations: relations.map(r => r.id)
310
+ }
311
+
312
+ let relation: {
313
+ // TODO refactor with type-fest
314
+ title: string
315
+ description?: string | undefined
316
+ technology?: string | undefined
317
+ kind?: RelationshipKind | undefined
318
+ color?: Color | undefined
319
+ line?: RelationshipLineType | undefined
320
+ head?: RelationshipArrowType | undefined
321
+ tail?: RelationshipArrowType | undefined
322
+ tags?: NonEmptyArray<Tag>
323
+ navigateTo?: ViewId | undefined
324
+ } | undefined
325
+ relation = only(relations) ?? pipe(
326
+ relations,
327
+ filter(r => r.source === source && r.target === target),
328
+ only()
329
+ )
330
+
331
+ // This edge represents mutliple relations
332
+ // We use label if only it is the same for all relations
333
+ if (!relation) {
334
+ const allprops = pipe(
335
+ relations,
336
+ reduce((acc, r) => {
337
+ if (isTruthy(r.title) && !acc.title.includes(r.title)) {
338
+ acc.title.push(r.title)
339
+ }
340
+ if (isTruthy(r.description) && !acc.description.includes(r.description)) {
341
+ acc.description.push(r.description)
342
+ }
343
+ if (isTruthy(r.technology) && !acc.technology.includes(r.technology)) {
344
+ acc.technology.push(r.technology)
345
+ }
346
+ if (isTruthy(r.kind) && !acc.kind.includes(r.kind)) {
347
+ acc.kind.push(r.kind)
348
+ }
349
+ if (isTruthy(r.color) && !acc.color.includes(r.color)) {
350
+ acc.color.push(r.color)
351
+ }
352
+ if (isTruthy(r.line) && !acc.line.includes(r.line)) {
353
+ acc.line.push(r.line)
354
+ }
355
+ if (isTruthy(r.head) && !acc.head.includes(r.head)) {
356
+ acc.head.push(r.head)
357
+ }
358
+ if (isTruthy(r.tail) && !acc.tail.includes(r.tail)) {
359
+ acc.tail.push(r.tail)
360
+ }
361
+ if (isTruthy(r.navigateTo) && !acc.navigateTo.includes(r.navigateTo)) {
362
+ acc.navigateTo.push(r.navigateTo)
363
+ }
364
+ return acc
365
+ }, {
366
+ title: [] as string[],
367
+ description: [] as string[],
368
+ technology: [] as string[],
369
+ kind: [] as RelationshipKind[],
370
+ head: [] as RelationshipArrowType[],
371
+ tail: [] as RelationshipArrowType[],
372
+ color: [] as Color[],
373
+ line: [] as RelationshipLineType[],
374
+ navigateTo: [] as ViewId[]
375
+ })
376
+ )
377
+ relation = {
378
+ title: only(allprops.title) ?? '[...]',
379
+ description: only(allprops.description),
380
+ technology: only(allprops.technology),
381
+ kind: only(allprops.kind),
382
+ head: only(allprops.head),
383
+ tail: only(allprops.tail),
384
+ color: only(allprops.color),
385
+ line: only(allprops.line),
386
+ navigateTo: only(allprops.navigateTo)
387
+ }
388
+ }
389
+
390
+ const tags = uniqueTags(relations)
391
+
392
+ return Object.assign(
393
+ edge,
394
+ this.getEdgeLabel(relation),
395
+ isTruthy(relation.description) && { description: relation.description },
396
+ isTruthy(relation.technology) && { technology: relation.technology },
397
+ isTruthy(relation.kind) && { kind: relation.kind },
398
+ relation.color && { color: relation.color },
399
+ relation.line && { line: relation.line },
400
+ relation.head && { head: relation.head },
401
+ relation.tail && { tail: relation.tail },
402
+ relation.navigateTo && { navigateTo: relation.navigateTo },
403
+ tags && { tags }
404
+ )
405
+ })
406
+ }
407
+
408
+ protected addEdges(edges: ComputeCtx.Edge[]) {
409
+ const added = [] as ComputeCtx.Edge[]
410
+ for (const e of edges) {
411
+ if (!hasAtLeast(e.relations, 1)) {
412
+ continue
413
+ }
414
+ const existing = this.ctxEdges.find(
415
+ _e => _e.source.id === e.source.id && _e.target.id === e.target.id
416
+ )
417
+ if (existing) {
418
+ existing.relations = unique([...existing.relations, ...e.relations])
419
+ added.push(existing)
420
+ continue
421
+ }
422
+ added.push(e)
423
+ this.ctxEdges.push(e)
424
+ }
425
+ return added
426
+ }
427
+
428
+ /**
429
+ * Add element explicitly
430
+ * Included even without relationships
431
+ */
432
+ protected addElement(...el: Element[]) {
433
+ for (const r of el) {
434
+ if (!this.explicits.has(r)) {
435
+ this.activeGroup.addElement(r)
436
+ } else if (this.activeGroup !== this.__rootGroup) {
437
+ this.groups.forEach(g => {
438
+ g.implicits.delete(r)
439
+ })
440
+ this.activeGroup.addImplicit(r)
441
+ }
442
+ this.explicits.add(r)
443
+ this.implicits.add(r)
444
+ }
445
+ }
446
+
447
+ /**
448
+ * Add element implicitly
449
+ * Included if only has relationships
450
+ */
451
+ protected addImplicit(...el: Element[]) {
452
+ for (const r of el) {
453
+ this.implicits.add(r)
454
+ // Remove implicits from other groups
455
+ if (this.activeGroup !== this.__rootGroup) {
456
+ this.groups.forEach(g => {
457
+ g.implicits.delete(r)
458
+ })
459
+ }
460
+ }
461
+ this.activeGroup.addImplicit(...el)
462
+ }
463
+
464
+ protected excludeElement(...excludes: Element[]) {
465
+ for (const el of excludes) {
466
+ this.ctxEdges = this.ctxEdges.filter(e => e.source.id !== el.id && e.target.id !== el.id)
467
+ this.explicits.delete(el)
468
+ this.implicits.delete(el)
469
+ }
470
+ this.__rootGroup.excludeElement(...excludes)
471
+ this.groups.forEach(g => {
472
+ g.excludeElement(...excludes)
473
+ })
474
+ }
475
+
476
+ protected excludeRelation(...relations: Relation[]) {
477
+ if (relations.length === 0) {
478
+ return
479
+ }
480
+ const excludedImplicits = new Set<Element>()
481
+ const ctxEdges = pipe(
482
+ this.ctxEdges,
483
+ map(edge => {
484
+ const edgerelations = edge.relations.filter(r => !relations.includes(r))
485
+ if (edgerelations.length === 0) {
486
+ excludedImplicits.add(edge.source)
487
+ excludedImplicits.add(edge.target)
488
+ return null
489
+ }
490
+ if (edgerelations.length !== edge.relations.length) {
491
+ return {
492
+ ...edge,
493
+ relations: edgerelations
494
+ }
495
+ }
496
+ return edge
497
+ }),
498
+ filter(isNonNull)
499
+ )
500
+ this.ctxEdges = ctxEdges
501
+ const remaining = this.includedElements
502
+ if (remaining.size === 0) {
503
+ this.implicits.clear()
504
+ this.__rootGroup.implicits.clear()
505
+ this.groups.forEach(g => g.implicits.clear())
506
+ return
507
+ }
508
+ for (const el of excludedImplicits) {
509
+ if (!remaining.has(el)) {
510
+ this.implicits.delete(el)
511
+ this.__rootGroup.implicits.delete(el)
512
+ this.groups.forEach(g => g.implicits.delete(el))
513
+ }
514
+ }
515
+ }
516
+
517
+ protected reset() {
518
+ this.explicits.clear()
519
+ this.implicits.clear()
520
+ this.ctxEdges = []
521
+ // Reset groups
522
+ this.__rootGroup = NodesGroup.root()
523
+ this.groups = []
524
+ this.activeGroupStack = []
525
+ }
526
+
527
+ // Filter out edges if there are edges between descendants
528
+ // i.e. remove implicit edges, derived from childs
529
+ protected removeRedundantImplicitEdges() {
530
+ const processedRelations = new Set<Relation>()
531
+
532
+ // Returns relations, that are not processed/included
533
+ const excludeProcessed = (relations: Relation[]) =>
534
+ relations.reduce((acc, rel) => {
535
+ if (!processedRelations.has(rel)) {
536
+ acc.push(rel)
537
+ processedRelations.add(rel)
538
+ }
539
+ return acc
540
+ }, [] as Relation[])
541
+
542
+ // Returns predicate
543
+ const isNestedEdgeOf = (parent: ComputeCtx.Edge) => {
544
+ const { source, target } = parent
545
+ // Checks if edge is between descendants of source and target of the parent edge
546
+ return (edge: ComputeCtx.Edge) => {
547
+ const isSameSource = source.id === edge.source.id
548
+ const isSameTarget = target.id === edge.target.id
549
+ if (isSameSource && isSameTarget) {
550
+ return true
551
+ }
552
+ const isSourceNested = isAncestor(source.id, edge.source.id)
553
+ const isTargetNested = isAncestor(target.id, edge.target.id)
554
+ return (
555
+ (isSourceNested && isTargetNested)
556
+ || (isSameSource && isTargetNested)
557
+ || (isSameTarget && isSourceNested)
558
+ )
559
+ }
560
+ }
561
+
562
+ // Sort edges from bottom to top (i.e. from more specific edges to implicit or between ancestors)
563
+ const edges = [...this.ctxEdges].sort(compareEdges).reverse()
564
+ this.ctxEdges = edges.reduce((acc, e) => {
565
+ const relations = excludeProcessed(e.relations)
566
+ if (relations.length === 0) {
567
+ return acc
568
+ }
569
+ // If there is an edge between descendants of current edge,
570
+ // then we don't add this edge
571
+ if (acc.length > 0 && acc.some(isNestedEdgeOf(e))) {
572
+ return acc
573
+ }
574
+ acc.push({
575
+ source: e.source,
576
+ target: e.target,
577
+ relations
578
+ })
579
+ return acc
580
+ }, [] as ComputeCtx.Edge[])
581
+ }
582
+
583
+ /**
584
+ * Clean up group.explicits and group.implicits
585
+ */
586
+ protected assignElementsToGroups() {
587
+ const unprocessed = new Set<Element>(difference(
588
+ [...this.includedElements],
589
+ [...this.__rootGroup.explicits]
590
+ ))
591
+ // Step 1 - Process explicits
592
+ for (const group of this.groups) {
593
+ const explicits = [...group.explicits]
594
+ group.explicits.clear()
595
+ for (const el of explicits) {
596
+ if (unprocessed.delete(el)) {
597
+ group.explicits.add(el)
598
+ }
599
+ }
600
+ }
601
+ // Step 2 - Process implicits (make them explicits)
602
+ for (const group of this.groups) {
603
+ for (const el of group.implicits) {
604
+ if (unprocessed.delete(el)) {
605
+ group.explicits.add(el)
606
+ }
607
+ }
608
+ group.implicits.clear()
609
+ }
610
+ }
611
+
612
+ protected processPredicates(viewRules: Array<ViewRulePredicate | ViewRuleGroup>): this {
613
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
614
+ for (const rule of viewRules) {
615
+ if (isViewRuleGroup(rule)) {
616
+ const parent = first(this.activeGroupStack)
617
+ const groupId = `@gr${this.groups.length + 1}` as NodeId
618
+ const group = new NodesGroup(groupId, rule, parent?.id ?? null)
619
+ this.groups.push(group)
620
+ this.activeGroupStack.unshift(group)
621
+ this.processPredicates(rule.groupRules)
622
+ this.activeGroupStack.shift()
623
+ continue
624
+ }
625
+ const isInclude = 'include' in rule
626
+ const exprs = rule.include ?? rule.exclude
627
+ for (const expr of exprs) {
628
+ if (Expr.isElementPredicateExpr(expr)) {
629
+ this.processElementPredicate(expr, isInclude)
630
+ continue
631
+ }
632
+ if (Expr.isRelationPredicateExpr(expr)) {
633
+ this.processRelationPredicate(expr, isInclude)
634
+ continue
635
+ }
636
+ nonexhaustive(expr)
637
+ }
638
+ }
639
+ return this
640
+ }
641
+
642
+ protected processElementPredicate(
643
+ expr: ElementPredicateExpression,
644
+ isInclude: boolean,
645
+ where?: ElementPredicateFn
646
+ ): this {
647
+ if (Expr.isCustomElement(expr)) {
648
+ if (isInclude) {
649
+ this.processElementPredicate(expr.custom.expr, isInclude)
650
+ }
651
+ return this
652
+ }
653
+ if (Expr.isElementWhere(expr)) {
654
+ const where = whereOperatorAsPredicate(expr.where.condition)
655
+ this.processElementPredicate(expr.where.expr, isInclude, where)
656
+ return this
657
+ }
658
+ if (Expr.isExpandedElementExpr(expr)) {
659
+ isInclude
660
+ ? includeExpandedElementExpr.call(this, expr, where)
661
+ : excludeExpandedElementExpr.call(this, expr, where)
662
+ return this
663
+ }
664
+ if (Expr.isElementKindExpr(expr) || Expr.isElementTagExpr(expr)) {
665
+ isInclude
666
+ ? includeElementKindOrTag.call(this, expr, where)
667
+ : excludeElementKindOrTag.call(this, expr, where)
668
+ return this
669
+ }
670
+ if (Expr.isElementRef(expr)) {
671
+ isInclude ? includeElementRef.call(this, expr, where) : excludeElementRef.call(this, expr, where)
672
+ return this
673
+ }
674
+ if (Expr.isWildcard(expr)) {
675
+ isInclude ? includeWildcardRef.call(this, expr, where) : excludeWildcardRef.call(this, expr, where)
676
+ return this
677
+ }
678
+ nonexhaustive(expr)
679
+ }
680
+
681
+ protected processRelationPredicate(
682
+ expr: RelationPredicateExpression,
683
+ isInclude: boolean,
684
+ where?: RelationPredicateFn
685
+ ): this {
686
+ if (Expr.isCustomRelationExpr(expr)) {
687
+ if (isInclude) {
688
+ this.processRelationPredicate(expr.customRelation.relation, isInclude)
689
+ }
690
+ return this
691
+ }
692
+ if (Expr.isRelationWhere(expr)) {
693
+ const where = whereOperatorAsPredicate(expr.where.condition)
694
+ this.processRelationPredicate(expr.where.expr, isInclude, where)
695
+ return this
696
+ }
697
+ if (Expr.isIncoming(expr)) {
698
+ isInclude ? includeIncomingExpr.call(this, expr, where) : excludeIncomingExpr.call(this, expr, where)
699
+ return this
700
+ }
701
+ if (Expr.isOutgoing(expr)) {
702
+ isInclude ? includeOutgoingExpr.call(this, expr, where) : excludeOutgoingExpr.call(this, expr, where)
703
+ return this
704
+ }
705
+ if (Expr.isInOut(expr)) {
706
+ isInclude ? includeInOutExpr.call(this, expr, where) : excludeInOutExpr.call(this, expr, where)
707
+ return this
708
+ }
709
+ if (Expr.isRelation(expr)) {
710
+ isInclude ? includeRelationExpr.call(this, expr, where) : excludeRelationExpr.call(this, expr, where)
711
+ return this
712
+ }
713
+ nonexhaustive(expr)
714
+ }
715
+
716
+ protected getEdgeLabel(
717
+ relation: {
718
+ title: string
719
+ technology?: string | undefined
720
+ }
721
+ ) {
722
+ const labelParts: string[] = []
723
+
724
+ if (isTruthy(relation.title)) {
725
+ labelParts.push(relation.title)
726
+ }
727
+
728
+ if (isTruthy(relation.technology)) {
729
+ labelParts.push(`[${relation.technology}]`)
730
+ }
731
+
732
+ return labelParts.length > 0 ? { label: labelParts.join('\n') } : {}
733
+ }
734
+ }