@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,517 @@
1
+ import { allPass, filter as remedaFilter, flatMap, isNullish as isNil, map, pipe, unique } from 'remeda'
2
+ import type { Writable } from 'type-fest'
3
+ import { nonexhaustive } from '../../errors'
4
+ import type { Element, Relation } from '../../types'
5
+ import * as Expr from '../../types/expression'
6
+ import { isAncestor, parentFqn } from '../../utils/fqn'
7
+ import { elementExprToPredicate } from '../utils/elementExpressionToPredicate'
8
+ import type { ComputeCtx } from './compute'
9
+
10
+ type Predicate<T> = (x: T) => boolean
11
+ export type ElementPredicateFn = Predicate<Element>
12
+ export type RelationPredicateFn = Predicate<Relation>
13
+ // // type ElementPredicate = Predicate<Element>
14
+ // type ElementPredicates<T> = T extends Element[] ? (x: T) => T : (T extends Element ? (x: T) => (Element | null) : never)
15
+ // type ElementPredicate = ElementPredicates<Element | Element[]>
16
+
17
+ const NoFilter: ElementPredicateFn = () => true
18
+ const Identity = <T>(x: T) => x
19
+ const filterBy = <T>(pred: Predicate<T>) => pred === NoFilter ? Identity : remedaFilter(pred)
20
+ const filterOne = <T>(pred: Predicate<T>) => pred === NoFilter ? Identity : (x: T) => pred(x) ? x : null
21
+
22
+ export function expandWildcardRef(this: ComputeCtx, expr: Expr.ElementRefExpr) {
23
+ if (expr.isChildren) {
24
+ const children = this.graph.children(expr.element)
25
+ return children.length > 0 ? children : [this.graph.element(expr.element)]
26
+ } else if (expr.isDescendants) {
27
+ return this.graph.descendants(expr.element)
28
+ } else {
29
+ return [this.graph.element(expr.element)]
30
+ }
31
+ }
32
+
33
+ export function includeElementRef(this: ComputeCtx, expr: Expr.ElementRefExpr, where = NoFilter) {
34
+ // Get the elements that are already in the Ctx before any mutations
35
+ // Because we need to add edges between them and the new elements
36
+ const currentElements = [...this.resolvedElements]
37
+ const filter = filterBy(where)
38
+
39
+ const elements = filter(expandWildcardRef.call(this, expr))
40
+ if (elements.length === 0) {
41
+ return
42
+ }
43
+
44
+ this.addElement(...elements)
45
+
46
+ if (elements.length > 1) {
47
+ this.addEdges(this.graph.edgesWithin(elements))
48
+ }
49
+
50
+ if (currentElements.length > 0 && elements.length > 0) {
51
+ for (const el of elements) {
52
+ this.addEdges(this.graph.anyEdgesBetween(el, currentElements))
53
+ }
54
+ }
55
+ }
56
+
57
+ export function excludeElementRef(this: ComputeCtx, expr: Expr.ElementRefExpr, where = NoFilter) {
58
+ const elements = [...this.resolvedElements].filter(allPass([
59
+ elementExprToPredicate(expr),
60
+ where
61
+ ]))
62
+ this.excludeElement(...elements)
63
+ }
64
+
65
+ export function includeWildcardRef(this: ComputeCtx, _expr: Expr.WildcardExpr, where = NoFilter) {
66
+ const root = this.root
67
+ if (!root) {
68
+ // Take root elements
69
+ const elements = this.graph.rootElements.filter(where)
70
+ if (elements.length <= 0) {
71
+ return
72
+ }
73
+ const currentElements = [...this.resolvedElements]
74
+ this.addElement(...elements)
75
+ this.addEdges(this.graph.edgesWithin(elements))
76
+ if (currentElements.length > 0) {
77
+ for (const el of elements) {
78
+ this.addEdges(this.graph.anyEdgesBetween(el, currentElements))
79
+ }
80
+ }
81
+ return
82
+ }
83
+
84
+ const currentElements = [...this.resolvedElements]
85
+ const _elRoot = filterOne(where)(this.graph.element(root))
86
+ if (_elRoot) {
87
+ this.addElement(_elRoot)
88
+ }
89
+ const filter = filterBy(where)
90
+
91
+ const children = filter(this.graph.children(root))
92
+ const hasChildren = children.length > 0
93
+ if (hasChildren) {
94
+ this.addElement(...children)
95
+ this.addEdges(this.graph.edgesWithin(children))
96
+ } else if (_elRoot) {
97
+ children.push(_elRoot)
98
+ }
99
+
100
+ // All neighbours that may have relations with root or its children
101
+ const neighbours = [
102
+ ...currentElements,
103
+ ...filter([
104
+ ...this.graph.siblings(root),
105
+ ...this.graph.ancestors(root).flatMap(a => this.graph.siblings(a.id))
106
+ ])
107
+ ]
108
+
109
+ for (const el of children) {
110
+ this.addEdges(this.graph.anyEdgesBetween(el, neighbours)).forEach(edge => {
111
+ this.addImplicit(edge.source, edge.target)
112
+ })
113
+ }
114
+
115
+ // If root has no children
116
+ if (!hasChildren && _elRoot) {
117
+ // Any edges with siblings?
118
+ const edgesWithSiblings = this.graph.anyEdgesBetween(_elRoot, this.graph.siblings(root))
119
+ if (edgesWithSiblings.length === 0) {
120
+ // If no edges with siblings, i.e. root is orphan
121
+ // Lets add parent for better view
122
+ const _parentId = parentFqn(root)
123
+ const parent = _parentId && this.graph.element(_parentId)
124
+ if (parent && where(parent)) {
125
+ this.addElement(parent)
126
+ }
127
+ }
128
+ }
129
+ }
130
+
131
+ export function excludeWildcardRef(this: ComputeCtx, _expr: Expr.WildcardExpr, where = NoFilter) {
132
+ if (where !== NoFilter) {
133
+ const elements = [...this.resolvedElements].filter(where)
134
+ this.excludeElement(...elements)
135
+ return
136
+ }
137
+ const root = this.root
138
+ if (root) {
139
+ this.excludeElement(
140
+ this.graph.element(root),
141
+ ...this.graph.children(root)
142
+ )
143
+ this.excludeRelation(
144
+ ...this.graph.connectedRelations(root)
145
+ )
146
+ } else {
147
+ this.reset()
148
+ }
149
+ }
150
+
151
+ export function includeExpandedElementExpr(
152
+ this: ComputeCtx,
153
+ expr: Expr.ExpandedElementExpr,
154
+ where = NoFilter
155
+ ) {
156
+ const filter = filterBy(where)
157
+ const currentElements = [...this.resolvedElements]
158
+
159
+ // Always add parent
160
+ const parent = this.graph.element(expr.expanded)
161
+ if (where(parent)) {
162
+ this.addElement(parent)
163
+ const anyEdgesBetween = this.graph.anyEdgesBetween(parent, currentElements)
164
+ this.addEdges(anyEdgesBetween)
165
+ }
166
+
167
+ const expanded = [] as Element[]
168
+
169
+ for (const el of filter(this.graph.children(expr.expanded))) {
170
+ this.addImplicit(el)
171
+ const edges = this.graph.anyEdgesBetween(el, currentElements)
172
+ if (edges.length > 0) {
173
+ this.addEdges(edges)
174
+ expanded.push(el)
175
+ }
176
+ }
177
+ if (expanded.length > 1) {
178
+ this.addEdges(this.graph.edgesWithin(expanded))
179
+ }
180
+ }
181
+
182
+ export function excludeExpandedElementExpr(
183
+ this: ComputeCtx,
184
+ expr: Expr.ExpandedElementExpr,
185
+ where = NoFilter
186
+ ) {
187
+ const elements = [...this.resolvedElements].filter(allPass([
188
+ elementExprToPredicate(expr),
189
+ where
190
+ ]))
191
+ this.excludeElement(...elements)
192
+ }
193
+
194
+ const asElementPredicate = (
195
+ expr: Expr.ElementKindExpr | Expr.ElementTagExpr
196
+ ): Predicate<Element> => {
197
+ if (expr.isEqual) {
198
+ if (Expr.isElementKindExpr(expr)) {
199
+ return e => e.kind === expr.elementKind
200
+ } else {
201
+ return ({ tags }) => !!tags && tags.includes(expr.elementTag)
202
+ }
203
+ } else {
204
+ if (Expr.isElementKindExpr(expr)) {
205
+ return e => e.kind !== expr.elementKind
206
+ } else {
207
+ return ({ tags }) => isNil(tags) || tags.length === 0 || !tags.includes(expr.elementTag)
208
+ }
209
+ }
210
+ }
211
+ export function includeElementKindOrTag(
212
+ this: ComputeCtx,
213
+ expr: Expr.ElementKindExpr | Expr.ElementTagExpr,
214
+ where = NoFilter
215
+ ) {
216
+ const elements = this.graph.elements.filter(asElementPredicate(expr)).filter(where)
217
+ if (elements.length > 0) {
218
+ const currentElements = [...this.resolvedElements]
219
+ this.addElement(...elements)
220
+ this.addEdges(this.graph.edgesWithin(elements))
221
+ for (const el of elements) {
222
+ this.addEdges(this.graph.anyEdgesBetween(el, currentElements))
223
+ }
224
+ }
225
+ }
226
+
227
+ export function excludeElementKindOrTag(
228
+ this: ComputeCtx,
229
+ expr: Expr.ElementKindExpr | Expr.ElementTagExpr,
230
+ where = NoFilter
231
+ ) {
232
+ const elements = [...this.resolvedElements].filter(asElementPredicate(expr)).filter(where)
233
+ if (elements.length > 0) {
234
+ this.excludeElement(...elements)
235
+ }
236
+ }
237
+
238
+ function resolveNeighbours(this: ComputeCtx, expr: Expr.ElementExpression): Element[] {
239
+ if (Expr.isElementRef(expr)) {
240
+ return this.graph.ascendingSiblings(expr.element) as Writable<Element[]>
241
+ }
242
+ return this.root ? this.graph.ascendingSiblings(this.root) as Writable<Element[]> : this.graph.rootElements
243
+ }
244
+
245
+ function resolveElements(this: ComputeCtx, expr: Expr.ElementExpression): Element[] {
246
+ if (Expr.isWildcard(expr)) {
247
+ if (this.root) {
248
+ return [...this.graph.children(this.root), this.graph.element(this.root)]
249
+ } else {
250
+ return this.graph.rootElements
251
+ }
252
+ }
253
+ if (Expr.isElementKindExpr(expr)) {
254
+ return this.graph.elements.filter(el => {
255
+ if (expr.isEqual) {
256
+ return el.kind === expr.elementKind
257
+ }
258
+ return el.kind !== expr.elementKind
259
+ })
260
+ }
261
+ if (Expr.isElementTagExpr(expr)) {
262
+ return this.graph.elements.filter(el => {
263
+ const tags = el.tags
264
+ if (expr.isEqual) {
265
+ return !!tags && tags.includes(expr.elementTag)
266
+ }
267
+ return isNil(tags) || tags.length === 0 || !tags.includes(expr.elementTag)
268
+ })
269
+ }
270
+ if (Expr.isExpandedElementExpr(expr)) {
271
+ return [this.graph.element(expr.expanded)]
272
+ }
273
+
274
+ // Type guard
275
+ if (!Expr.isElementRef(expr)) {
276
+ return nonexhaustive(expr)
277
+ }
278
+
279
+ if (this.root === expr.element && expr.isChildren !== true && expr.isDescendants !== true) {
280
+ return [...this.graph.children(this.root), this.graph.element(this.root)]
281
+ }
282
+
283
+ return expandWildcardRef.call(this, expr)
284
+ }
285
+
286
+ // --------------------------------
287
+ // Incoming Expr
288
+
289
+ function edgesIncomingExpr(this: ComputeCtx, expr: Expr.ElementExpression) {
290
+ if (Expr.isWildcard(expr)) {
291
+ if (!this.root) {
292
+ return []
293
+ }
294
+ const sources = this.graph.ascendingSiblings(this.root)
295
+ const targets = [...this.graph.children(this.root), this.graph.element(this.root)]
296
+ return this.graph.edgesBetween(sources, targets)
297
+ }
298
+ const targets = resolveElements.call(this, expr)
299
+ if (targets.length === 0) {
300
+ return []
301
+ }
302
+ let sources = [...this.resolvedElements]
303
+ if (Expr.isElementRef(expr) || Expr.isExpandedElementExpr(expr)) {
304
+ const exprElement = expr.element ?? expr.expanded
305
+ const isChildren = expr.isChildren ?? false
306
+ sources = sources.filter(el =>
307
+ // allow elements, that are not nested or are direct children
308
+ !isAncestor(exprElement, el.id) || (isChildren && parentFqn(el.id) === exprElement)
309
+ )
310
+ }
311
+ if (sources.length === 0) {
312
+ sources = resolveNeighbours.call(this, expr)
313
+ }
314
+ return this.graph.edgesBetween(sources, targets)
315
+ }
316
+
317
+ const filterEdges = (edges: ReadonlyArray<ComputeCtx.Edge>, where?: RelationPredicateFn) => {
318
+ if (!where) {
319
+ return edges as ComputeCtx.Edge[]
320
+ }
321
+ return pipe(
322
+ edges,
323
+ map(e => ({ ...e, relations: e.relations.filter(where) })),
324
+ remedaFilter(e => e.relations.length > 0)
325
+ ) as ComputeCtx.Edge[]
326
+ }
327
+
328
+ const filterRelations = (edges: ComputeCtx.Edge[], where?: RelationPredicateFn) => {
329
+ return pipe(
330
+ edges,
331
+ flatMap(e => e.relations),
332
+ where ? remedaFilter(where) : Identity,
333
+ unique()
334
+ )
335
+ }
336
+
337
+ export function includeIncomingExpr(this: ComputeCtx, expr: Expr.IncomingExpr, where?: RelationPredicateFn) {
338
+ const edges = filterEdges(edgesIncomingExpr.call(this, expr.incoming), where)
339
+ if (edges.length === 0) {
340
+ return
341
+ }
342
+ this.addEdges(edges).forEach(edge => {
343
+ this.addImplicit(edge.target)
344
+ })
345
+ }
346
+ export function excludeIncomingExpr(this: ComputeCtx, expr: Expr.IncomingExpr, where?: RelationPredicateFn) {
347
+ let relations = filterRelations(edgesIncomingExpr.call(this, expr.incoming), where)
348
+ this.excludeRelation(...relations)
349
+ }
350
+
351
+ // --------------------------------
352
+ // Outgoing Expr
353
+
354
+ function edgesOutgoingExpr(this: ComputeCtx, expr: Expr.ElementExpression) {
355
+ if (Expr.isWildcard(expr)) {
356
+ if (!this.root) {
357
+ return []
358
+ }
359
+ const targets = this.graph.ascendingSiblings(this.root)
360
+ const sources = [...this.graph.children(this.root), this.graph.element(this.root)]
361
+ return this.graph.edgesBetween(sources, targets)
362
+ }
363
+ const sources = resolveElements.call(this, expr)
364
+ if (sources.length === 0) {
365
+ return []
366
+ }
367
+ let targets = [...this.resolvedElements]
368
+ if (Expr.isElementRef(expr) || Expr.isExpandedElementExpr(expr)) {
369
+ const sourceElement = expr.element ?? expr.expanded
370
+ const isChildren = expr.isChildren ?? false
371
+ targets = targets.filter(el =>
372
+ // allow elements, that are not nested or are direct children
373
+ !isAncestor(sourceElement, el.id) || (isChildren && parentFqn(el.id) === sourceElement)
374
+ )
375
+ }
376
+ if (targets.length === 0) {
377
+ targets = [...resolveNeighbours.call(this, expr)]
378
+ }
379
+ return this.graph.edgesBetween(sources, targets)
380
+ }
381
+
382
+ export function includeOutgoingExpr(this: ComputeCtx, expr: Expr.OutgoingExpr, where?: RelationPredicateFn) {
383
+ const edges = filterEdges(edgesOutgoingExpr.call(this, expr.outgoing), where)
384
+ if (edges.length === 0) {
385
+ return
386
+ }
387
+ this.addEdges(edges).forEach(edge => {
388
+ this.addImplicit(edge.source)
389
+ })
390
+ }
391
+ export function excludeOutgoingExpr(this: ComputeCtx, expr: Expr.OutgoingExpr, where?: RelationPredicateFn) {
392
+ const relations = filterRelations(edgesOutgoingExpr.call(this, expr.outgoing), where)
393
+ this.excludeRelation(...relations)
394
+ }
395
+
396
+ // --------------------------------
397
+ // InOut Expr
398
+ type EdgePredicateResult = {
399
+ implicits: Element[]
400
+ edges: ComputeCtx.Edge[]
401
+ }
402
+
403
+ namespace EdgePredicateResult {
404
+ export const Empty: EdgePredicateResult = {
405
+ implicits: [],
406
+ edges: []
407
+ }
408
+ }
409
+ function edgesInOutExpr(this: ComputeCtx, { inout }: Expr.InOutExpr, where?: RelationPredicateFn): EdgePredicateResult {
410
+ if (Expr.isWildcard(inout)) {
411
+ if (!this.root) {
412
+ return EdgePredicateResult.Empty
413
+ }
414
+ const neighbours = this.graph.ascendingSiblings(this.root)
415
+ return {
416
+ edges: filterEdges(this.graph.anyEdgesBetween(this.graph.element(this.root), neighbours), where),
417
+ implicits: []
418
+ }
419
+ }
420
+ const elements = resolveElements.call(this, inout)
421
+ if (elements.length === 0) {
422
+ return EdgePredicateResult.Empty
423
+ }
424
+ let currentElements = [...this.resolvedElements]
425
+
426
+ if (Expr.isElementRef(inout) || Expr.isExpandedElementExpr(inout)) {
427
+ const exprElement = inout.element ?? inout.expanded
428
+ const isChildren = inout.isChildren ?? false
429
+ currentElements = currentElements.filter(el =>
430
+ // allow elements, that are not nested or are direct children
431
+ !isAncestor(exprElement, el.id) || (isChildren && parentFqn(el.id) === exprElement)
432
+ )
433
+ }
434
+ if (currentElements.length === 0) {
435
+ currentElements = resolveNeighbours.call(this, inout)
436
+ }
437
+ return elements.reduce((acc, el) => {
438
+ const edges = filterEdges(this.graph.anyEdgesBetween(el, currentElements), where)
439
+ if (edges.length > 0) {
440
+ acc.implicits.push(el)
441
+ acc.edges.push(...edges)
442
+ }
443
+ return acc
444
+ }, { implicits: [], edges: [] } as EdgePredicateResult)
445
+ }
446
+
447
+ export function includeInOutExpr(this: ComputeCtx, expr: Expr.InOutExpr, where?: RelationPredicateFn) {
448
+ const { implicits, edges } = edgesInOutExpr.call(this, expr, where)
449
+ this.addEdges(edges)
450
+ this.addImplicit(...implicits)
451
+ }
452
+ export function excludeInOutExpr(this: ComputeCtx, expr: Expr.InOutExpr, where?: RelationPredicateFn) {
453
+ const { edges } = edgesInOutExpr.call(this, expr, where)
454
+ this.excludeRelation(...edges.flatMap(e => e.relations))
455
+ }
456
+
457
+ /**
458
+ * Expand element to its children and itself, if it is the root (and not ".*")
459
+ * Example:
460
+ *
461
+ * view of api {
462
+ * include some -> api
463
+ * }
464
+ *
465
+ * Transform to:
466
+ *
467
+ * view of api {
468
+ * include some -> api.*, some -> api
469
+ * }
470
+ */
471
+ function resolveRelationExprElements(this: ComputeCtx, expr: Expr.ElementExpression) {
472
+ if (
473
+ Expr.isElementRef(expr) && this.root === expr.element && expr.isChildren !== true && expr.isDescendants !== true
474
+ ) {
475
+ return [...this.graph.children(expr.element), this.graph.element(expr.element)]
476
+ }
477
+ if (Expr.isExpandedElementExpr(expr) && this.root === expr.expanded) {
478
+ return [...this.graph.children(expr.expanded), this.graph.element(expr.expanded)]
479
+ }
480
+ return resolveElements.call(this, expr)
481
+ }
482
+
483
+ export function includeRelationExpr(this: ComputeCtx, expr: Expr.RelationExpr, where?: RelationPredicateFn) {
484
+ let sources, targets
485
+ if (Expr.isWildcard(expr.source) && !Expr.isWildcard(expr.target)) {
486
+ sources = resolveNeighbours.call(this, expr.target)
487
+ targets = resolveRelationExprElements.call(this, expr.target)
488
+ } else if (!Expr.isWildcard(expr.source) && Expr.isWildcard(expr.target)) {
489
+ sources = resolveRelationExprElements.call(this, expr.source)
490
+ targets = resolveNeighbours.call(this, expr.source)
491
+ } else {
492
+ sources = resolveRelationExprElements.call(this, expr.source)
493
+ targets = resolveRelationExprElements.call(this, expr.target)
494
+ }
495
+ const edges = this.graph.edgesBetween(sources, targets)
496
+ if (expr.isBidirectional === true) {
497
+ edges.push(...this.graph.edgesBetween(targets, sources))
498
+ }
499
+ this.addEdges(filterEdges(edges, where)).forEach(edge => {
500
+ this.activeGroup.addImplicit(edge.source, edge.target)
501
+ })
502
+ }
503
+
504
+ export function excludeRelationExpr(this: ComputeCtx, expr: Expr.RelationExpr, where?: RelationPredicateFn) {
505
+ const isSource = elementExprToPredicate(expr.source)
506
+ const isTarget = elementExprToPredicate(expr.target)
507
+ const satisfies = (edge: ComputeCtx.Edge) => {
508
+ let result = isSource(edge.source) && isTarget(edge.target)
509
+ if (!result && expr.isBidirectional) {
510
+ result = isSource(edge.target) && isTarget(edge.source)
511
+ }
512
+ return result
513
+ }
514
+ const edges = this.edges.filter(satisfies)
515
+ const relations = filterRelations(edges, where)
516
+ this.excludeRelation(...relations)
517
+ }
@@ -0,0 +1,5 @@
1
+ export { computeViews, mkComputeView } from './compute-view'
2
+ export { LikeC4ModelGraph } from './LikeC4ModelGraph'
3
+ export { resolveRulesExtendedViews } from './utils/resolve-extended-views'
4
+ export { withReadableEdges } from './utils/with-readable-edges'
5
+ export { viewsWithReadableEdges } from './utils/with-readable-edges'
@@ -0,0 +1,113 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`sortNodes > should generate valid testnodes 1`] = `
4
+ {
5
+ "edges": [
6
+ {
7
+ "id": "cloud.frontend.ui:cloud.backend",
8
+ "parent": "cloud",
9
+ "source": "cloud.frontend.ui",
10
+ "target": "cloud.backend",
11
+ },
12
+ {
13
+ "id": "cloud.backend:cloud.db",
14
+ "parent": "cloud",
15
+ "source": "cloud.backend",
16
+ "target": "cloud.db",
17
+ },
18
+ {
19
+ "id": "cloud.db:amazon",
20
+ "parent": null,
21
+ "source": "cloud.db",
22
+ "target": "amazon",
23
+ },
24
+ {
25
+ "id": "customer:cloud.frontend.ui",
26
+ "parent": null,
27
+ "source": "customer",
28
+ "target": "cloud.frontend.ui",
29
+ },
30
+ ],
31
+ "nodes": [
32
+ {
33
+ "children": [],
34
+ "id": "customer",
35
+ "inEdges": [],
36
+ "outEdges": [
37
+ "customer:cloud.frontend.ui",
38
+ ],
39
+ "parent": null,
40
+ },
41
+ {
42
+ "children": [],
43
+ "id": "amazon",
44
+ "inEdges": [
45
+ "cloud.db:amazon",
46
+ ],
47
+ "outEdges": [],
48
+ "parent": null,
49
+ },
50
+ {
51
+ "children": [
52
+ "cloud.frontend.ui",
53
+ ],
54
+ "id": "cloud.frontend",
55
+ "inEdges": [
56
+ "customer:cloud.frontend.ui",
57
+ ],
58
+ "outEdges": [
59
+ "cloud.frontend.ui:cloud.backend",
60
+ ],
61
+ "parent": "cloud",
62
+ },
63
+ {
64
+ "children": [],
65
+ "id": "cloud.frontend.ui",
66
+ "inEdges": [
67
+ "customer:cloud.frontend.ui",
68
+ ],
69
+ "outEdges": [
70
+ "cloud.frontend.ui:cloud.backend",
71
+ ],
72
+ "parent": "cloud.frontend",
73
+ },
74
+ {
75
+ "children": [
76
+ "cloud.frontend",
77
+ "cloud.db",
78
+ "cloud.backend",
79
+ ],
80
+ "id": "cloud",
81
+ "inEdges": [
82
+ "customer:cloud.frontend.ui",
83
+ ],
84
+ "outEdges": [
85
+ "cloud.db:amazon",
86
+ ],
87
+ "parent": null,
88
+ },
89
+ {
90
+ "children": [],
91
+ "id": "cloud.db",
92
+ "inEdges": [
93
+ "cloud.backend:cloud.db",
94
+ ],
95
+ "outEdges": [
96
+ "cloud.db:amazon",
97
+ ],
98
+ "parent": "cloud",
99
+ },
100
+ {
101
+ "children": [],
102
+ "id": "cloud.backend",
103
+ "inEdges": [
104
+ "cloud.frontend.ui:cloud.backend",
105
+ ],
106
+ "outEdges": [
107
+ "cloud.backend:cloud.db",
108
+ ],
109
+ "parent": "cloud",
110
+ },
111
+ ],
112
+ }
113
+ `;
@@ -0,0 +1,19 @@
1
+ import { nonNullable } from '../../errors'
2
+ import type { ComputedNode, Fqn } from '../../types'
3
+
4
+ /**
5
+ * Returns the ancestors of given computed node, starting with the direct parent and ending with the root node.
6
+ */
7
+ export function ancestorsOfNode(
8
+ node: ComputedNode,
9
+ nodes: ReadonlyMap<Fqn, ComputedNode>
10
+ ): ReadonlyArray<ComputedNode> {
11
+ const ancestors = [] as ComputedNode[]
12
+ let parentId = node.parent
13
+ while (parentId) {
14
+ const parentNode = nonNullable(nodes.get(parentId), `Parent node ${parentId} not found`)
15
+ ancestors.push(parentNode)
16
+ parentId = parentNode.parent
17
+ }
18
+ return ancestors
19
+ }