@likec4/core 1.19.0 → 1.19.2

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 (47) hide show
  1. package/dist/builder/index.d.mts +3 -3
  2. package/dist/builder/index.d.ts +3 -3
  3. package/dist/builder/index.js +2 -2
  4. package/dist/compute-view/index.d.mts +2 -2
  5. package/dist/compute-view/index.d.ts +2 -2
  6. package/dist/compute-view/index.js +3 -3
  7. package/dist/index.d.mts +3 -3
  8. package/dist/index.d.ts +3 -3
  9. package/dist/index.js +3 -3
  10. package/dist/model/index.d.mts +8 -3
  11. package/dist/model/index.d.ts +8 -3
  12. package/dist/model/index.js +1 -1
  13. package/dist/shared/{core.BZ9Msq7w.d.mts → core.2I-l5UtM.d.mts} +5 -1
  14. package/dist/shared/{core.CmYMqgha.d.mts → core.BL1oTk7-.d.mts} +13 -3
  15. package/dist/shared/{core.BIfgabEw.d.ts → core.CqXs9Frh.d.ts} +5 -1
  16. package/dist/shared/{core.C05RDLhi.d.ts → core.DyJ41fBO.d.ts} +13 -3
  17. package/dist/shared/{index.-BdzdI2C.js → index.KOeaSlL8.js} +333 -209
  18. package/dist/shared/{view.CyZrG8BJ.js → view.DrmaOfjr.js} +3 -0
  19. package/dist/types/index.d.mts +2 -2
  20. package/dist/types/index.d.ts +2 -2
  21. package/dist/types/index.js +1 -1
  22. package/dist/utils/index.d.mts +16 -1
  23. package/dist/utils/index.d.ts +16 -1
  24. package/dist/utils/index.js +18 -1
  25. package/package.json +9 -9
  26. package/src/builder/Builder.view-common.ts +1 -0
  27. package/src/compute-view/deployment-view/__test__/fixture.ts +31 -3
  28. package/src/compute-view/deployment-view/_types.ts +2 -1
  29. package/src/compute-view/deployment-view/compute.ts +4 -36
  30. package/src/compute-view/deployment-view/predicates/index.ts +8 -0
  31. package/src/compute-view/deployment-view/predicates/relation-direct.ts +117 -88
  32. package/src/compute-view/deployment-view/predicates/relation-in-out.ts +16 -8
  33. package/src/compute-view/deployment-view/predicates/relation-incoming.ts +18 -12
  34. package/src/compute-view/deployment-view/predicates/relation-outgoing.ts +16 -9
  35. package/src/compute-view/deployment-view/predicates/relation-where.ts +18 -0
  36. package/src/compute-view/deployment-view/predicates/utils.ts +183 -0
  37. package/src/compute-view/memory/ops.ts +1 -1
  38. package/src/model/DeploymentElementModel.ts +14 -1
  39. package/src/model/RelationModel.ts +4 -0
  40. package/src/model/connection/deployment/DeploymentConnectionModel.ts +11 -1
  41. package/src/types/deployments.ts +1 -0
  42. package/src/types/expression-v2.ts +12 -0
  43. package/src/types/index.ts +1 -0
  44. package/src/types/operators.ts +5 -5
  45. package/src/utils/index.ts +1 -0
  46. package/src/utils/iterable/find.ts +36 -0
  47. package/src/utils/iterable/index.ts +1 -0
@@ -1,22 +1,22 @@
1
- import { filter, flatMap, isNonNullish, map, pipe } from 'remeda'
1
+ import { filter, flatMap, isNonNullish, pick, pipe } from 'remeda'
2
2
  import { invariant } from '../../../errors'
3
- import type { LikeC4DeploymentModel } from '../../../model'
3
+ import type {
4
+ ConnectionModel,
5
+ DeploymentElementModel,
6
+ DeploymentRelationModel,
7
+ LikeC4DeploymentModel,
8
+ } from '../../../model'
4
9
  import type { DeploymentConnectionModel } from '../../../model/connection/deployment'
5
10
  import { findConnection, findConnectionsBetween, findConnectionsWithin } from '../../../model/connection/deployment'
6
11
  import { findConnectionsBetween as findModelConnectionsBetween } from '../../../model/connection/model'
7
- import type { ConnectionModel } from '../../../model/connection/model/ConnectionModel'
8
- import type { DeploymentElementModel } from '../../../model/DeploymentElementModel'
9
12
  import type { RelationshipModel } from '../../../model/RelationModel'
10
13
  import type { AnyAux } from '../../../model/types'
11
- import { type RelationExpr, FqnExpr } from '../../../types'
12
- import { hasIntersection, intersection } from '../../../utils/set'
13
- import type { ExcludePredicateCtx, PredicateExecutor } from '../_types'
14
- import type { StageExclude } from '../memory'
14
+ import { type Filterable, type OperatorPredicate, type RelationExpr, FqnExpr } from '../../../types'
15
+ import type { PredicateExecutor } from '../_types'
15
16
  import { deploymentExpressionToPredicate, resolveElements, resolveModelElements } from '../utils'
16
17
  import { filterIncomingConnections, resolveAllImcomingRelations } from './relation-incoming'
17
18
  import { filterOutgoingConnections, resolveAllOutgoingRelations } from './relation-outgoing'
18
-
19
- // const isRef = DeploymentElementExpression.isRef
19
+ import { applyPredicate, excludeModelRelations } from './utils'
20
20
 
21
21
  export const resolveAscendingSiblings = (element: DeploymentElementModel) => {
22
22
  const siblings = new Set<DeploymentElementModel>()
@@ -40,7 +40,7 @@ const resolveWildcard = (model: LikeC4DeploymentModel, nonWildcard: FqnExpr.Depl
40
40
  }
41
41
 
42
42
  export const DirectRelationPredicate: PredicateExecutor<RelationExpr.Direct> = {
43
- include: ({ expr: { source, target, isBidirectional = false }, model, stage }) => {
43
+ include: ({ expr: { source, target, isBidirectional = false }, model, stage, where }) => {
44
44
  invariant(!FqnExpr.isModelRef(source), 'Invalid source model ref in direct relation')
45
45
  invariant(!FqnExpr.isModelRef(target), 'Invalid target model ref in direct relation')
46
46
  const sourceIsWildcard = FqnExpr.isWildcard(source)
@@ -109,7 +109,7 @@ export const DirectRelationPredicate: PredicateExecutor<RelationExpr.Direct> = {
109
109
 
110
110
  connections = pipe(
111
111
  sources,
112
- flatMap(s => findConnectionsBetween(s, targets, dir)),
112
+ flatMap(s => matchConnections(findConnectionsBetween(s, targets, dir), where)),
113
113
  filter(c => isSource(c) && isTarget(c)),
114
114
  )
115
115
  }
@@ -126,90 +126,72 @@ export const DirectRelationPredicate: PredicateExecutor<RelationExpr.Direct> = {
126
126
 
127
127
  return stage
128
128
  },
129
- exclude: ({ expr, model, memory, stage }) => {
130
- // * -> *
131
- // Exclude all connections
132
- if (
133
- FqnExpr.isWildcard(expr.source)
134
- && FqnExpr.isWildcard(expr.target)
135
- ) {
136
- stage.excludeConnections(memory.connections)
137
- return stage
138
- }
129
+ exclude: ({ expr, model, memory, stage, where }) => {
130
+ const isTarget = deploymentExpressionToPredicate(expr.target)
131
+ const isSource = deploymentExpressionToPredicate(expr.source)
139
132
 
140
- if (FqnExpr.isModelRef(expr.source) && FqnExpr.isModelRef(expr.target)) {
141
- const toExclude = resolveRelationsBetweenModelElements({
142
- source: expr.source,
143
- target: expr.target,
144
- expr,
145
- model,
146
- })
147
- return excludeModelRelations(toExclude, { stage, memory })
148
- }
133
+ let modelRelationsToExclude: ReadonlySet<RelationshipModel<AnyAux>>
134
+ switch (true) {
135
+ // * -> *
136
+ case FqnExpr.isWildcard(expr.source) && FqnExpr.isWildcard(expr.target):
137
+ stage.excludeConnections(matchConnections(memory.connections, where))
138
+ return stage
149
139
 
150
- if (FqnExpr.isModelRef(expr.source)) {
151
- if (FqnExpr.isWildcard(expr.target)) {
152
- const toExclude = resolveAllOutgoingRelations(model, expr.source)
153
- return excludeModelRelations(toExclude, { stage, memory })
154
- }
155
- const allOutgoing = resolveAllOutgoingRelations(model, expr.source)
156
- const isTarget = deploymentExpressionToPredicate(expr.target)
157
- return excludeModelRelations(
158
- allOutgoing,
159
- { stage, memory },
160
- c => isTarget(c.target),
161
- )
162
- }
140
+ // model -> model
141
+ case FqnExpr.isModelRef(expr.source) && FqnExpr.isModelRef(expr.target):
142
+ modelRelationsToExclude = resolveRelationsBetweenModelElements({
143
+ source: expr.source,
144
+ target: expr.target,
145
+ expr,
146
+ model,
147
+ })
148
+ return excludeModelRelations(modelRelationsToExclude, { stage, memory }, where)
163
149
 
164
- if (FqnExpr.isModelRef(expr.target)) {
165
- const toExclude = resolveAllImcomingRelations(model, expr.target)
166
- const isSource = deploymentExpressionToPredicate(expr.source)
167
- return excludeModelRelations(
168
- toExclude,
169
- { stage, memory },
170
- c => isSource(c.source),
171
- )
172
- }
150
+ // model -> *
151
+ case FqnExpr.isModelRef(expr.source) && FqnExpr.isWildcard(expr.target):
152
+ modelRelationsToExclude = resolveAllOutgoingRelations(model, expr.source)
153
+ return excludeModelRelations(modelRelationsToExclude, { stage, memory }, where)
173
154
 
174
- const isSource = deploymentExpressionToPredicate(expr.source)
175
- const isTarget = deploymentExpressionToPredicate(expr.target)
155
+ // model -> deployment
156
+ case FqnExpr.isModelRef(expr.source):
157
+ modelRelationsToExclude = resolveAllOutgoingRelations(model, expr.source)
158
+ return excludeModelRelations(
159
+ modelRelationsToExclude,
160
+ { stage, memory },
161
+ where,
162
+ c => isTarget(c.target),
163
+ )
176
164
 
177
- const satisfies = (connection: DeploymentConnectionModel) => {
178
- return (isSource(connection.source) && isTarget(connection.target))
179
- || (expr.isBidirectional && isSource(connection.target) && isTarget(connection.source))
180
- }
165
+ // deployment -> model
166
+ case FqnExpr.isModelRef(expr.target):
167
+ modelRelationsToExclude = resolveAllImcomingRelations(model, expr.target)
168
+ return excludeModelRelations(
169
+ modelRelationsToExclude,
170
+ { stage, memory },
171
+ where,
172
+ c => isSource(c.source),
173
+ )
181
174
 
182
- const toExclude = memory.connections.filter(satisfies)
175
+ // deployment -> deployment
176
+ default:
177
+ const satisfies = (connection: DeploymentConnectionModel) => {
178
+ return (isSource(connection.source) && isTarget(connection.target))
179
+ || (expr.isBidirectional === true && isSource(connection.target) && isTarget(connection.source))
180
+ }
183
181
 
184
- stage.excludeConnections(toExclude)
185
- return stage
186
- },
187
- }
182
+ const deploymentRelationsToExclude = pipe(
183
+ memory.connections,
184
+ filter(satisfies),
185
+ applyPredicate(where),
186
+ )
187
+ if (deploymentRelationsToExclude.length === 0) {
188
+ return stage
189
+ }
188
190
 
189
- export function excludeModelRelations(
190
- relationsToExclude: ReadonlySet<RelationshipModel<AnyAux>>,
191
- { stage, memory }: Pick<ExcludePredicateCtx, 'stage' | 'memory'>,
192
- // Optional filter to scope the connections to exclude
193
- filterConnections: (c: DeploymentConnectionModel) => boolean = () => true,
194
- ): StageExclude {
195
- if (relationsToExclude.size === 0) {
196
- return stage
197
- }
198
- const toExclude = pipe(
199
- memory.connections,
200
- // Find connections that have at least one relation in common with the excluded relations
201
- filter(c => filterConnections(c) && hasIntersection(c.relations.model, relationsToExclude)),
202
- map(c =>
203
- c.update({
204
- deployment: null,
205
- model: intersection(c.relations.model, relationsToExclude),
206
- })
207
- ),
208
- )
209
- if (toExclude.length === 0) {
210
- return stage
211
- }
212
- return stage.excludeConnections(toExclude)
191
+ stage.excludeConnections(deploymentRelationsToExclude)
192
+ return stage
193
+ }
194
+ },
213
195
  }
214
196
 
215
197
  function resolveRelationsBetweenModelElements({
@@ -235,3 +217,50 @@ function resolveRelationsBetweenModelElements({
235
217
 
236
218
  return new Set(modelConnections.flatMap(c => [...c.relations]))
237
219
  }
220
+
221
+ function elementToFilterable<M extends AnyAux>(element: DeploymentElementModel<M>) {
222
+ return pick(element, ['tags', 'kind'])
223
+ }
224
+
225
+ function toFilterableRelation<M extends AnyAux>(
226
+ source: DeploymentElementModel<M>,
227
+ target: DeploymentElementModel<M>,
228
+ ) {
229
+ return (
230
+ relation: RelationshipModel<M> | DeploymentRelationModel<M>,
231
+ ) => ({
232
+ tags: relation.tags,
233
+ kind: relation.kind,
234
+ source: elementToFilterable(source),
235
+ target: elementToFilterable(target),
236
+ })
237
+ }
238
+
239
+ function matchConnection<M extends AnyAux>(
240
+ c: DeploymentConnectionModel<M>,
241
+ where: OperatorPredicate<Filterable> | null,
242
+ ): boolean {
243
+ if (!where) {
244
+ return true
245
+ }
246
+
247
+ return [
248
+ ...Array.from(c.relations.deployment.values()).map(toFilterableRelation(c.source, c.target)),
249
+ ...Array.from(c.relations.model.values()).map(toFilterableRelation(c.source, c.target)),
250
+ ]
251
+ .filter(where).length > 0
252
+ }
253
+
254
+ function matchConnections<M extends AnyAux>(
255
+ connections: readonly DeploymentConnectionModel<M>[],
256
+ where: OperatorPredicate<Filterable> | null,
257
+ ): readonly DeploymentConnectionModel[] {
258
+ if (!where) {
259
+ return connections
260
+ }
261
+
262
+ return pipe(
263
+ connections,
264
+ filter(c => matchConnection(c, where)),
265
+ )
266
+ }
@@ -1,24 +1,27 @@
1
+ import { filter, pipe } from 'remeda'
1
2
  import { invariant } from '../../../errors'
2
3
  import { findConnectionsBetween } from '../../../model/connection/deployment'
3
4
  import type { RelationshipModel } from '../../../model/RelationModel'
4
5
  import type { AnyAux } from '../../../model/types'
5
- import { FqnExpr, type RelationExpr } from '../../../types/expression-v2'
6
+ import { type RelationExpr, FqnExpr } from '../../../types/expression-v2'
6
7
  import { union } from '../../../utils/set'
7
8
  import type { PredicateExecutor } from '../_types'
8
9
  import { resolveElements, resolveModelElements } from '../utils'
9
- import { excludeModelRelations, resolveAscendingSiblings } from './relation-direct'
10
+ import { resolveAscendingSiblings } from './relation-direct'
10
11
  import { filterIncomingConnections } from './relation-incoming'
11
12
  import { filterOutgoingConnections } from './relation-outgoing'
13
+ import { applyPredicate, excludeModelRelations, matchConnections } from './utils'
12
14
 
13
15
  //
14
16
  export const InOutRelationPredicate: PredicateExecutor<RelationExpr.InOut> = {
15
- include: ({ expr, model, memory, stage }) => {
17
+ include: ({ expr, model, memory, stage, where }) => {
16
18
  const sources = [...memory.elements]
17
19
 
18
20
  if (FqnExpr.isWildcard(expr.inout)) {
19
21
  for (const source of sources) {
20
22
  const targets = [...resolveAscendingSiblings(source)]
21
- stage.addConnections(findConnectionsBetween(source, targets, 'both'))
23
+ const toInclude = matchConnections(findConnectionsBetween(source, targets, 'both'), where)
24
+ stage.addConnections(toInclude)
22
25
  }
23
26
  return stage
24
27
  }
@@ -26,12 +29,13 @@ export const InOutRelationPredicate: PredicateExecutor<RelationExpr.InOut> = {
26
29
 
27
30
  const targets = resolveElements(model, expr.inout)
28
31
  for (const source of sources) {
29
- stage.addConnections(findConnectionsBetween(source, targets, 'both'))
32
+ const toInclude = matchConnections(findConnectionsBetween(source, targets, 'both'), where)
33
+ stage.addConnections(toInclude)
30
34
  }
31
35
 
32
36
  return stage
33
37
  },
34
- exclude: ({ expr, model, memory, stage }) => {
38
+ exclude: ({ expr, model, memory, stage, where }) => {
35
39
  // Exclude all connections that have model relationshps with the elements
36
40
  if (FqnExpr.isModelRef(expr.inout)) {
37
41
  const elements = resolveModelElements(model, expr.inout)
@@ -42,7 +46,7 @@ export const InOutRelationPredicate: PredicateExecutor<RelationExpr.InOut> = {
42
46
  new Set<RelationshipModel<AnyAux>>(),
43
47
  ...elements.flatMap(e => [e.allIncoming, e.allOutgoing]),
44
48
  )
45
- return excludeModelRelations(excludedRelations, { stage, memory })
49
+ return excludeModelRelations(excludedRelations, { stage, memory }, where)
46
50
  }
47
51
 
48
52
  if (FqnExpr.isWildcard(expr.inout)) {
@@ -54,7 +58,11 @@ export const InOutRelationPredicate: PredicateExecutor<RelationExpr.InOut> = {
54
58
  const isIncoming = filterIncomingConnections(elements)
55
59
  const isOutgoing = filterOutgoingConnections(elements)
56
60
 
57
- const toExclude = memory.connections.filter(c => isIncoming(c) !== isOutgoing(c))
61
+ const toExclude = pipe(
62
+ memory.connections,
63
+ filter(c => isIncoming(c) !== isOutgoing(c)),
64
+ applyPredicate(where),
65
+ )
58
66
  stage.excludeConnections(toExclude)
59
67
  return stage
60
68
  },
@@ -1,18 +1,18 @@
1
- import { anyPass } from 'remeda'
1
+ import { anyPass, filter, pipe } from 'remeda'
2
2
  import { invariant } from '../../../errors'
3
+ import type { AnyAux, RelationshipModel } from '../../../model'
3
4
  import { findConnectionsBetween } from '../../../model/connection/deployment'
4
5
  import type { LikeC4DeploymentModel } from '../../../model/DeploymentModel'
5
- import type { RelationshipModel } from '../../../model/RelationModel'
6
- import type { AnyAux } from '../../../model/types'
7
- import { FqnExpr, type RelationExpr } from '../../../types'
8
- import { isAncestor } from '../../../utils/fqn'
6
+ import { type RelationExpr, FqnExpr } from '../../../types'
7
+ import { isAncestor } from '../../../utils'
9
8
  import type { Connection, Elem, PredicateExecutor } from '../_types'
10
9
  import { resolveElements, resolveModelElements } from '../utils'
11
- import { excludeModelRelations, resolveAscendingSiblings } from './relation-direct'
10
+ import { resolveAscendingSiblings } from './relation-direct'
11
+ import { applyPredicate, excludeModelRelations, matchConnection, matchConnections } from './utils'
12
12
 
13
13
  // from visible element incoming to this
14
14
  export const IncomingRelationPredicate: PredicateExecutor<RelationExpr.Incoming> = {
15
- include: ({ expr, model, memory, stage }) => {
15
+ include: ({ expr, model, memory, stage, where }) => {
16
16
  const sources = [...memory.elements]
17
17
 
18
18
  if (FqnExpr.isWildcard(expr.incoming)) {
@@ -21,7 +21,8 @@ export const IncomingRelationPredicate: PredicateExecutor<RelationExpr.Incoming>
21
21
  continue
22
22
  }
23
23
  const targets = [...resolveAscendingSiblings(source)]
24
- stage.addConnections(findConnectionsBetween(source, targets, 'directed'))
24
+ const toInclude = applyPredicate(findConnectionsBetween(source, targets, 'directed'), where)
25
+ stage.addConnections(toInclude)
25
26
  }
26
27
  return stage
27
28
  }
@@ -29,16 +30,17 @@ export const IncomingRelationPredicate: PredicateExecutor<RelationExpr.Incoming>
29
30
 
30
31
  const targets = resolveElements(model, expr.incoming)
31
32
  for (const source of sources) {
32
- stage.addConnections(findConnectionsBetween(source, targets, 'directed'))
33
+ const toInclude = applyPredicate(findConnectionsBetween(source, targets, 'directed'), where)
34
+ stage.addConnections(toInclude)
33
35
  }
34
36
 
35
37
  return stage
36
38
  },
37
- exclude: ({ expr, model, memory, stage }) => {
39
+ exclude: ({ expr, model, memory, stage, where }) => {
38
40
  // Exclude all connections that have model relationshps with the elements
39
41
  if (FqnExpr.isModelRef(expr.incoming)) {
40
42
  const excludedRelations = resolveAllImcomingRelations(model, expr.incoming)
41
- return excludeModelRelations(excludedRelations, { stage, memory })
43
+ return excludeModelRelations(excludedRelations, { stage, memory }, where)
42
44
  }
43
45
  if (FqnExpr.isWildcard(expr.incoming)) {
44
46
  // non-sense
@@ -46,7 +48,11 @@ export const IncomingRelationPredicate: PredicateExecutor<RelationExpr.Incoming>
46
48
  }
47
49
 
48
50
  const isIncoming = filterIncomingConnections(resolveElements(model, expr.incoming))
49
- const toExclude = memory.connections.filter(isIncoming)
51
+ const toExclude = pipe(
52
+ memory.connections,
53
+ filter(isIncoming),
54
+ applyPredicate(where),
55
+ )
50
56
  stage.excludeConnections(toExclude)
51
57
  return stage
52
58
  },
@@ -1,16 +1,17 @@
1
- import { anyPass } from 'remeda'
1
+ import { anyPass, filter, pipe } from 'remeda'
2
2
  import { invariant } from '../../../errors'
3
3
  import type { LikeC4DeploymentModel } from '../../../model'
4
4
  import { findConnection, findConnectionsBetween } from '../../../model/connection/deployment'
5
5
  import type { RelationshipModel } from '../../../model/RelationModel'
6
- import { FqnExpr, type RelationExpr } from '../../../types'
6
+ import { type RelationExpr, FqnExpr } from '../../../types'
7
7
  import { isAncestor } from '../../../utils'
8
8
  import type { Connection, Elem, PredicateExecutor } from '../_types'
9
9
  import { resolveElements, resolveModelElements } from '../utils'
10
- import { excludeModelRelations, resolveAscendingSiblings } from './relation-direct'
10
+ import { resolveAscendingSiblings } from './relation-direct'
11
+ import { applyPredicate, excludeModelRelations } from './utils'
11
12
 
12
13
  export const OutgoingRelationPredicate: PredicateExecutor<RelationExpr.Outgoing> = {
13
- include: ({ expr, model, memory, stage }) => {
14
+ include: ({ expr, model, memory, stage, where }) => {
14
15
  const targets = [...memory.elements]
15
16
 
16
17
  // * -> (to visible elements)
@@ -21,7 +22,8 @@ export const OutgoingRelationPredicate: PredicateExecutor<RelationExpr.Outgoing>
21
22
  continue
22
23
  }
23
24
  for (const source of resolveAscendingSiblings(target)) {
24
- stage.addConnections(findConnection(source, target, 'directed'))
25
+ const toInclude = applyPredicate(findConnection(source, target, 'directed'), where)
26
+ stage.addConnections(toInclude)
25
27
  }
26
28
  }
27
29
  return stage
@@ -30,16 +32,17 @@ export const OutgoingRelationPredicate: PredicateExecutor<RelationExpr.Outgoing>
30
32
 
31
33
  const sources = resolveElements(model, expr.outgoing)
32
34
  for (const source of sources) {
33
- stage.addConnections(findConnectionsBetween(source, targets, 'directed'))
35
+ const toInclude = applyPredicate(findConnectionsBetween(source, targets, 'directed'), where)
36
+ stage.addConnections(toInclude)
34
37
  }
35
38
 
36
39
  return stage
37
40
  },
38
- exclude: ({ expr, model, memory, stage }) => {
41
+ exclude: ({ expr, model, memory, stage, where }) => {
39
42
  // Exclude all connections that have model relationshps with the elements
40
43
  if (FqnExpr.isModelRef(expr.outgoing)) {
41
44
  const excludedRelations = resolveAllOutgoingRelations(model, expr.outgoing)
42
- return excludeModelRelations(excludedRelations, { stage, memory })
45
+ return excludeModelRelations(excludedRelations, { stage, memory }, where)
43
46
  }
44
47
  if (FqnExpr.isWildcard(expr.outgoing)) {
45
48
  // non-sense
@@ -47,7 +50,11 @@ export const OutgoingRelationPredicate: PredicateExecutor<RelationExpr.Outgoing>
47
50
  }
48
51
 
49
52
  const isOutgoing = filterOutgoingConnections(resolveElements(model, expr.outgoing))
50
- const toExclude = memory.connections.filter(isOutgoing)
53
+ const toExclude = pipe(
54
+ memory.connections,
55
+ filter(isOutgoing),
56
+ applyPredicate(where),
57
+ )
51
58
  stage.excludeConnections(toExclude)
52
59
  return stage
53
60
  },
@@ -0,0 +1,18 @@
1
+ import { whereOperatorAsPredicate, type RelationExpr } from '../../../types'
2
+ import type { PredicateExecutor } from '../_types'
3
+ import type { StageExclude, StageInclude } from '../memory'
4
+ import { predicateToPatch } from './utils'
5
+
6
+ // relation matches the condition
7
+ export const WhereRelationPredicate: PredicateExecutor<RelationExpr.Where> = {
8
+ include: ({ expr, model, memory, stage }) => {
9
+ const where = whereOperatorAsPredicate(expr.where.condition)
10
+
11
+ return predicateToPatch('include', { expr: expr.where.expr, model, stage, memory, where }) as StageInclude
12
+ },
13
+ exclude: ({ expr, model, memory, stage }) => {
14
+ const where = whereOperatorAsPredicate(expr.where.condition)
15
+
16
+ return predicateToPatch('exclude', { expr: expr.where.expr, model, stage, memory, where }) as StageExclude
17
+ }
18
+ }
@@ -0,0 +1,183 @@
1
+ import { filter, isArray, map, pick, pipe } from 'remeda'
2
+ import { nonexhaustive } from '../../../errors'
3
+ import type { DeploymentConnectionModel } from '../../../model'
4
+ import type { DeploymentElementModel, DeploymentRelationModel } from '../../../model/DeploymentElementModel'
5
+ import type { RelationshipModel } from '../../../model/RelationModel'
6
+ import type { AnyAux } from '../../../model/types'
7
+ import { type Filterable, type OperatorPredicate, FqnExpr, RelationExpr } from '../../../types'
8
+ import { hasIntersection, intersection } from '../../../utils/set'
9
+ import type { ExcludePredicateCtx, PredicateCtx } from '../_types'
10
+ import type { StageExclude, StageInclude } from '../memory'
11
+ import { DeploymentRefPredicate } from './elements'
12
+ import { DirectRelationPredicate } from './relation-direct'
13
+ import { InOutRelationPredicate } from './relation-in-out'
14
+ import { IncomingRelationPredicate } from './relation-incoming'
15
+ import { OutgoingRelationPredicate } from './relation-outgoing'
16
+ import { WhereRelationPredicate } from './relation-where'
17
+ import { WildcardPredicate } from './wildcard'
18
+
19
+ /**
20
+ * Builds a patch object from an expression
21
+ */
22
+ export function predicateToPatch(
23
+ op: 'include' | 'exclude',
24
+ { expr, where, ...ctx }: PredicateCtx,
25
+ ): StageExclude | StageInclude | undefined {
26
+ switch (true) {
27
+ case FqnExpr.isModelRef(expr):
28
+ // Ignore model refs in deployment view
29
+ return undefined
30
+ case FqnExpr.isDeploymentRef(expr):
31
+ return DeploymentRefPredicate[op]({ ...ctx, expr, where } as any)
32
+ case FqnExpr.isWildcard(expr):
33
+ return WildcardPredicate[op]({ ...ctx, expr, where } as any)
34
+ case RelationExpr.isDirect(expr):
35
+ return DirectRelationPredicate[op]({ ...ctx, expr, where } as any)
36
+ case RelationExpr.isInOut(expr):
37
+ return InOutRelationPredicate[op]({ ...ctx, expr, where } as any)
38
+ case RelationExpr.isOutgoing(expr):
39
+ return OutgoingRelationPredicate[op]({ ...ctx, expr, where } as any)
40
+ case RelationExpr.isIncoming(expr):
41
+ return IncomingRelationPredicate[op]({ ...ctx, expr, where } as any)
42
+ case RelationExpr.isWhere(expr):
43
+ return WhereRelationPredicate[op]({ ...ctx, expr, where } as any)
44
+ default:
45
+ nonexhaustive(expr)
46
+ }
47
+ }
48
+
49
+ export function excludeModelRelations(
50
+ relationsToExclude: ReadonlySet<RelationshipModel<AnyAux>>,
51
+ { stage, memory }: Pick<ExcludePredicateCtx, 'stage' | 'memory'>,
52
+ where: OperatorPredicate<Filterable> | null,
53
+ // Optional filter to scope the connections to exclude
54
+ filterConnections: (c: DeploymentConnectionModel) => boolean = () => true,
55
+ ): StageExclude {
56
+ if (relationsToExclude.size === 0) {
57
+ return stage
58
+ }
59
+ const toExclude = pipe(
60
+ memory.connections,
61
+ filter(c => filterConnections(c)),
62
+ // Find connections that have at least one relation in common with the excluded relations
63
+ filter(c => hasIntersection(c.relations.model, relationsToExclude)),
64
+ map(c =>
65
+ c.update({
66
+ deployment: null,
67
+ model: intersection(c.relations.model, relationsToExclude),
68
+ })
69
+ ),
70
+ applyPredicate(where),
71
+ filter(c => c.nonEmpty()),
72
+ )
73
+ if (toExclude.length === 0) {
74
+ return stage
75
+ }
76
+ return stage.excludeConnections(toExclude)
77
+ }
78
+
79
+ export function matchConnection<M extends AnyAux>(
80
+ c: DeploymentConnectionModel<M>,
81
+ where: OperatorPredicate<Filterable> | null,
82
+ ): boolean {
83
+ return applyPredicate(c, where).nonEmpty()
84
+ }
85
+
86
+ /**
87
+ * Filters relations of the provided connections using the provided predicate.
88
+ * And copy of the connection with the filtered relations will be created.
89
+ * Connections left without relations are removed from resulting collection.
90
+ *
91
+ * @param c The connection to apply the predicate to
92
+ * @param where The predicate
93
+ * @returns A copy of the connection with the filtered relations
94
+ */
95
+ export function applyPredicate<M extends AnyAux>(
96
+ c: readonly DeploymentConnectionModel<M>[],
97
+ where: OperatorPredicate<Filterable> | null,
98
+ ): readonly DeploymentConnectionModel<M>[]
99
+ /**
100
+ * Filters relations of the connection using the provided predicate.
101
+ *
102
+ * @param c The connection to apply the predicate to
103
+ * @param where The predicate
104
+ * @returns A deep copy of the original collection with the filtered relations
105
+ */
106
+ export function applyPredicate<M extends AnyAux>(
107
+ c: DeploymentConnectionModel<M>,
108
+ where: OperatorPredicate<Filterable> | null,
109
+ ): DeploymentConnectionModel<M>
110
+ /**
111
+ * Creates a function that filters relations of the provided connections using the provided predicate.
112
+ * Connections left without relations are removed from resulting collection.
113
+ *
114
+ * @param c The connection to apply the predicate to
115
+ * @param where The predicate
116
+ * @returns A function to create a filtered copy of connections
117
+ */
118
+ export function applyPredicate<M extends AnyAux>(
119
+ where: OperatorPredicate<Filterable> | null,
120
+ ): (data: readonly DeploymentConnectionModel<M>[]) => readonly DeploymentConnectionModel<M>[]
121
+ export function applyPredicate<M extends AnyAux>(
122
+ ...args:
123
+ | [readonly DeploymentConnectionModel<M>[], OperatorPredicate<Filterable> | null]
124
+ | [OperatorPredicate<Filterable> | null]
125
+ | [c: DeploymentConnectionModel<M>, OperatorPredicate<Filterable> | null]
126
+ ):
127
+ | DeploymentConnectionModel<M>
128
+ | readonly DeploymentConnectionModel<M>[]
129
+ | ((data: readonly DeploymentConnectionModel<M>[]) => readonly DeploymentConnectionModel<M>[])
130
+ {
131
+ if (args.length === 1) {
132
+ return x => applyPredicate(x, args[0])
133
+ }
134
+
135
+ const [c, where] = args
136
+ if (where === null) {
137
+ return c
138
+ }
139
+
140
+ if (isArray(c)) {
141
+ return c
142
+ .map(x => applyPredicate(x, where))
143
+ .filter(x => x.nonEmpty())
144
+ }
145
+
146
+ const map = toFilterableRelation(c.source, c.target)
147
+ return c.update({
148
+ model: new Set([...c.relations.model.values()].filter(r => where(map(r)))),
149
+ deployment: new Set([...c.relations.deployment.values()].filter(r => where(map(r)))),
150
+ })
151
+ }
152
+
153
+ export function matchConnections<M extends AnyAux>(
154
+ connections: readonly DeploymentConnectionModel<M>[],
155
+ where: OperatorPredicate<Filterable> | null,
156
+ ): readonly DeploymentConnectionModel[] {
157
+ if (!where) {
158
+ return connections
159
+ }
160
+
161
+ return pipe(
162
+ connections,
163
+ filter(c => matchConnection(c, where)),
164
+ )
165
+ }
166
+
167
+ function elementToFilterable<M extends AnyAux>(element: DeploymentElementModel<M>) {
168
+ return pick(element, ['tags', 'kind'])
169
+ }
170
+
171
+ function toFilterableRelation<M extends AnyAux>(
172
+ source: DeploymentElementModel<M>,
173
+ target: DeploymentElementModel<M>,
174
+ ) {
175
+ return (
176
+ relation: RelationshipModel<M> | DeploymentRelationModel<M>,
177
+ ) => ({
178
+ tags: relation.tags,
179
+ kind: relation.kind,
180
+ source: elementToFilterable(source),
181
+ target: elementToFilterable(target),
182
+ })
183
+ }
@@ -37,7 +37,7 @@ export function treeFromMemoryState<T extends AnyCtx, M extends ComputeMemory<T>
37
37
  root: root as ReadonlySet<CtxElement<T>>,
38
38
  connected: connected as ReadonlySet<CtxElement<T>>,
39
39
  hasInOut: (el: CtxElement<T>) => memory.connections.some(Connection.isAnyInOut(el.id)),
40
- parents: (el: CtxElement<T>) => children.get(el),
40
+ parent: (el: CtxElement<T>) => parents.get(el),
41
41
  children: (el: CtxElement<T>) => children.get(el),
42
42
  }
43
43
  }