@likec4/core 1.19.1 → 1.20.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 (91) hide show
  1. package/dist/builder/index.cjs +881 -0
  2. package/dist/builder/index.d.cts +503 -0
  3. package/dist/builder/index.d.mts +5 -4
  4. package/dist/builder/index.d.ts +5 -4
  5. package/dist/builder/{index.js → index.mjs} +14 -4
  6. package/dist/compute-view/index.cjs +86 -0
  7. package/dist/compute-view/index.d.cts +23 -0
  8. package/dist/compute-view/index.d.mts +3 -2
  9. package/dist/compute-view/index.d.ts +3 -2
  10. package/dist/compute-view/{index.js → index.mjs} +6 -5
  11. package/dist/index.cjs +4320 -0
  12. package/dist/index.d.cts +270 -0
  13. package/dist/index.d.mts +130 -4
  14. package/dist/index.d.ts +130 -4
  15. package/dist/{index.js → index.mjs} +512 -7
  16. package/dist/model/index.cjs +40 -0
  17. package/dist/model/index.d.cts +259 -0
  18. package/dist/model/index.d.mts +8 -3
  19. package/dist/model/index.d.ts +8 -3
  20. package/dist/model/index.mjs +1 -0
  21. package/dist/shared/core.B2QYWlYe.cjs +435 -0
  22. package/dist/shared/core.B72vUaNB.d.cts +850 -0
  23. package/dist/shared/core.BGus30EM.cjs +8027 -0
  24. package/dist/shared/core.BRuJ3Wzf.d.mts +12 -0
  25. package/dist/shared/{core.BZ9Msq7w.d.mts → core.BdbFkE_p.d.cts} +14 -4
  26. package/dist/shared/core.BvdNMbcA.d.cts +12 -0
  27. package/dist/shared/{object_hash.CE_oF3I3.js → core.BxvDITEI.mjs} +26 -389
  28. package/dist/shared/core.ByNgh-mC.d.cts +127 -0
  29. package/dist/shared/core.C9DCnJh8.cjs +908 -0
  30. package/dist/shared/{index.DafVOuVd.js → core.CqCTxzMY.mjs} +1111 -572
  31. package/dist/shared/core.D74xkKkG.d.cts +148 -0
  32. package/dist/shared/{core.C05RDLhi.d.ts → core.D_Gc58Gt.d.ts} +23 -5
  33. package/dist/shared/core.Damrzla-.d.ts +12 -0
  34. package/dist/shared/{core.CmYMqgha.d.mts → core.Dfzrh1VA.d.mts} +23 -5
  35. package/dist/shared/core.DsoCu540.cjs +26 -0
  36. package/dist/shared/{view.CyZrG8BJ.js → core.DunIMHRf.mjs} +27 -1
  37. package/dist/shared/{core.BIfgabEw.d.ts → core.ZWiGANIJ.d.ts} +14 -4
  38. package/dist/shared/core.gv1bMwCC.cjs +39 -0
  39. package/dist/shared/core.jy6z2iRn.d.mts +729 -0
  40. package/dist/shared/core.m3rCQAv6.mjs +37 -0
  41. package/dist/types/index.cjs +107 -0
  42. package/dist/types/index.d.cts +72 -0
  43. package/dist/types/index.d.mts +2 -2
  44. package/dist/types/index.d.ts +2 -2
  45. package/dist/types/{index.js → index.mjs} +1 -1
  46. package/dist/utils/index.cjs +95 -0
  47. package/dist/utils/index.d.cts +356 -0
  48. package/dist/utils/index.d.mts +1 -64
  49. package/dist/utils/index.d.ts +1 -64
  50. package/dist/utils/index.mjs +56 -0
  51. package/package.json +52 -26
  52. package/src/builder/Builder.view-common.ts +21 -1
  53. package/src/compute-view/deployment-view/__test__/fixture.ts +31 -3
  54. package/src/compute-view/deployment-view/_types.ts +2 -1
  55. package/src/compute-view/deployment-view/compute.ts +4 -36
  56. package/src/compute-view/deployment-view/predicates/index.ts +8 -0
  57. package/src/compute-view/deployment-view/predicates/relation-direct.ts +125 -99
  58. package/src/compute-view/deployment-view/predicates/relation-in-out.ts +16 -8
  59. package/src/compute-view/deployment-view/predicates/relation-incoming.ts +18 -12
  60. package/src/compute-view/deployment-view/predicates/relation-outgoing.ts +16 -9
  61. package/src/compute-view/deployment-view/predicates/relation-where.ts +18 -0
  62. package/src/compute-view/deployment-view/predicates/utils.ts +214 -0
  63. package/src/compute-view/element-view/__test__/fixture.ts +17 -4
  64. package/src/compute-view/element-view/compute.ts +3 -2
  65. package/src/compute-view/element-view/predicates.ts +520 -0
  66. package/src/compute-view/element-view/utils.ts +11 -7
  67. package/src/compute-view/index.ts +1 -0
  68. package/src/compute-view/relationships-view/_types.ts +16 -0
  69. package/src/compute-view/relationships-view/compute.ts +74 -0
  70. package/src/compute-view/relationships-view/layout.ts +357 -0
  71. package/src/compute-view/relationships-view/utils.ts +40 -0
  72. package/src/compute-view/utils/relationExpressionToPredicates.ts +11 -4
  73. package/src/index.ts +16 -0
  74. package/src/model/DeploymentElementModel.ts +18 -1
  75. package/src/model/ElementModel.ts +13 -0
  76. package/src/model/LikeC4Model.ts +2 -2
  77. package/src/model/RelationModel.ts +9 -0
  78. package/src/model/connection/deployment/DeploymentConnectionModel.ts +11 -1
  79. package/src/model/guards.ts +17 -1
  80. package/src/model/view/LikeC4ViewModel.ts +7 -1
  81. package/src/model/view/NodeModel.ts +5 -0
  82. package/src/types/deployments.ts +1 -0
  83. package/src/types/expression-v2.ts +12 -0
  84. package/src/types/index.ts +1 -0
  85. package/src/types/operators.ts +46 -6
  86. package/src/types/view.ts +2 -2
  87. package/src/utils/index.ts +0 -5
  88. package/src/utils/iterable/unique.ts +1 -7
  89. package/dist/model/index.js +0 -1
  90. package/dist/utils/index.js +0 -347
  91. /package/dist/shared/{chunk-RAAYCPUM.M-JWF7SS.js → core.M-JWF7SS.mjs} +0 -0
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@likec4/core",
3
- "version": "1.19.1",
3
+ "version": "1.20.0",
4
4
  "license": "MIT",
5
5
  "homepage": "https://likec4.dev",
6
6
  "author": "Denis Davydkov <denis@davydkov.com>",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/likec4/likec4.git",
9
+ "url": "git+https://github.com/likec4/likec4.git",
10
10
  "directory": "packages/core"
11
11
  },
12
12
  "bugs": "https://github.com/likec4/likec4/issues",
@@ -16,7 +16,8 @@
16
16
  "lint": "run -T eslint src/ --fix",
17
17
  "test": "vitest run --no-isolate",
18
18
  "test:watch": "vitest",
19
- "clean": "rm -r -f dist"
19
+ "clean": "rm -r -f dist",
20
+ "package": "yarn pack"
20
21
  },
21
22
  "files": [
22
23
  "dist",
@@ -30,50 +31,74 @@
30
31
  "exports": {
31
32
  ".": {
32
33
  "sources": "./src/index.ts",
34
+ "require": {
35
+ "types": "./dist/index.d.cts",
36
+ "default": "./dist/index.cjs"
37
+ },
33
38
  "default": {
34
- "types": "./dist/index.d.ts",
35
- "import": "./dist/index.js",
36
- "default": "./dist/index.js"
39
+ "types": "./dist/index.d.mts",
40
+ "import": "./dist/index.mjs",
41
+ "default": "./dist/index.mjs"
37
42
  }
38
43
  },
39
44
  "./builder": {
40
45
  "sources": "./src/builder/index.ts",
46
+ "require": {
47
+ "types": "./dist/builder/index.d.cts",
48
+ "default": "./dist/builder/index.cjs"
49
+ },
41
50
  "default": {
42
- "types": "./dist/builder/index.d.ts",
43
- "import": "./dist/builder/index.js",
44
- "default": "./dist/builder/index.js"
51
+ "types": "./dist/builder/index.d.mts",
52
+ "import": "./dist/builder/index.mjs",
53
+ "default": "./dist/builder/index.mjs"
45
54
  }
46
55
  },
47
56
  "./compute-view": {
48
57
  "sources": "./src/compute-view/index.ts",
58
+ "require": {
59
+ "types": "./dist/compute-view/index.d.cts",
60
+ "default": "./dist/compute-view/index.cjs"
61
+ },
49
62
  "default": {
50
- "types": "./dist/compute-view/index.d.ts",
51
- "import": "./dist/compute-view/index.js",
52
- "default": "./dist/compute-view/index.js"
63
+ "types": "./dist/compute-view/index.d.mts",
64
+ "import": "./dist/compute-view/index.mjs",
65
+ "default": "./dist/compute-view/index.mjs"
53
66
  }
54
67
  },
55
68
  "./model": {
56
69
  "sources": "./src/model/index.ts",
70
+ "require": {
71
+ "types": "./dist/model/index.d.cts",
72
+ "default": "./dist/model/index.cjs"
73
+ },
57
74
  "default": {
58
- "types": "./dist/model/index.d.ts",
59
- "import": "./dist/model/index.js",
60
- "default": "./dist/model/index.js"
75
+ "types": "./dist/model/index.d.mts",
76
+ "import": "./dist/model/index.mjs",
77
+ "default": "./dist/model/index.mjs"
61
78
  }
62
79
  },
63
80
  "./utils": {
64
81
  "sources": "./src/utils/index.ts",
82
+ "require": {
83
+ "types": "./dist/utils/index.d.cts",
84
+ "default": "./dist/utils/index.cjs"
85
+ },
65
86
  "default": {
66
- "types": "./dist/utils/index.d.ts",
67
- "import": "./dist/utils/index.js",
68
- "default": "./dist/utils/index.js"
87
+ "types": "./dist/utils/index.d.mts",
88
+ "import": "./dist/utils/index.mjs",
89
+ "default": "./dist/utils/index.mjs"
69
90
  }
70
91
  },
71
92
  "./types": {
72
93
  "sources": "./src/types/index.ts",
94
+ "require": {
95
+ "types": "./dist/types/index.d.cts",
96
+ "default": "./dist/types/index.cjs"
97
+ },
73
98
  "default": {
74
- "types": "./dist/types/index.d.ts",
75
- "import": "./dist/types/index.js",
76
- "default": "./dist/types/index.js"
99
+ "types": "./dist/types/index.d.mts",
100
+ "import": "./dist/types/index.mjs",
101
+ "default": "./dist/types/index.mjs"
77
102
  }
78
103
  }
79
104
  },
@@ -85,7 +110,8 @@
85
110
  "type-fest": "4.28.1"
86
111
  },
87
112
  "devDependencies": {
88
- "@likec4/tsconfig": "1.19.1",
113
+ "@dagrejs/dagre": "1.1.4",
114
+ "@likec4/tsconfig": "1.20.0",
89
115
  "@mantine/colors-generator": "7.15.2",
90
116
  "@types/natural-compare-lite": "^1.4.2",
91
117
  "@types/object-hash": "^3.0.6",
@@ -98,11 +124,11 @@
98
124
  "natural-compare-lite": "^1.4.0",
99
125
  "object-hash": "^3.0.0",
100
126
  "obliterator": "^2.0.4",
101
- "remeda": "^2.17.4",
127
+ "remeda": "^2.19.0",
102
128
  "turbo": "^2.3.3",
103
- "typescript": "^5.7.2",
104
- "unbuild": "^3.1.0",
129
+ "typescript": "^5.7.3",
130
+ "unbuild": "^3.2.0",
105
131
  "vitest": "^2.1.8"
106
132
  },
107
- "packageManager": "yarn@4.5.3"
133
+ "packageManager": "yarn@4.6.0"
108
134
  }
@@ -6,11 +6,12 @@ import {
6
6
  type CustomRelationExpr,
7
7
  type ElementExpression as C4ElementExpression,
8
8
  type Expression as C4Expression,
9
- isElementPredicateExpr,
10
9
  type NonEmptyArray,
11
10
  type ViewRuleStyle,
12
11
  type WhereOperator,
12
+ isElementPredicateExpr,
13
13
  } from '../types'
14
+ import type { KindEqual, Participant, TagEqual } from '../types/operators'
14
15
  import type { AnyTypes, Types } from './_types'
15
16
 
16
17
  export interface LikeC4ViewBuilder<
@@ -59,10 +60,14 @@ export namespace ViewPredicate {
59
60
 
60
61
  export type WhereTag<Tag extends string> = `tag ${'is' | 'is not'} #${Tag}`
61
62
  export type WhereKind<Kind extends string> = `kind ${'is' | 'is not'} ${Kind}`
63
+ export type WhereParticipant<Types extends AnyTypes> = `${Participant}.${
64
+ | WhereTag<Types['Tag']>
65
+ | WhereKind<Types['ElementKind']>}`
62
66
 
63
67
  export type WhereEq<Types extends AnyTypes> =
64
68
  | ViewPredicate.WhereTag<Types['Tag']>
65
69
  | ViewPredicate.WhereKind<Types['ElementKind']>
70
+ | ViewPredicate.WhereParticipant<Types>
66
71
 
67
72
  export type WhereOperator<Types extends AnyTypes> = ViewPredicate.WhereEq<Types> | {
68
73
  and: NonEmptyArray<ViewPredicate.WhereOperator<Types>>
@@ -116,6 +121,20 @@ function parseWhere(where: ViewPredicate.WhereOperator<AnyTypes>): WhereOperator
116
121
  eq: op.replace('kind is ', ''),
117
122
  },
118
123
  }
124
+ case op.startsWith('source.'):
125
+ return {
126
+ operator: parseWhere(op.replace('source.', '') as ViewPredicate.WhereOperator<AnyTypes>) as
127
+ | KindEqual<string>
128
+ | TagEqual<string>,
129
+ participant: 'source',
130
+ }
131
+ case op.startsWith('target.'):
132
+ return {
133
+ operator: parseWhere(op.replace('target.', '') as ViewPredicate.WhereOperator<AnyTypes>) as
134
+ | KindEqual<string>
135
+ | TagEqual<string>,
136
+ participant: 'target',
137
+ }
119
138
  default:
120
139
  throw new Error(`Unknown where operator: ${where}`)
121
140
  }
@@ -189,6 +208,7 @@ function $exclude<B extends LikeC4ViewBuilder<AnyTypes, any, any>>(
189
208
  | [B['Expr']]
190
209
  | [B['TypedExpr']]
191
210
  | [B['Expr'], ViewPredicate.Custom<B['Types']>]
211
+ | [B['TypedExpr'], ViewPredicate.Custom<B['Types']>]
192
212
  ): (b: B) => B {
193
213
  return (b) => {
194
214
  let expr = b.$expr(args[0]) as C4Expression
@@ -57,6 +57,8 @@ const {
57
57
  zone: {},
58
58
  node: {},
59
59
  },
60
+ tags: ['old', 'next', 'temp'],
61
+ relationships: { 'https': {} },
60
62
  })
61
63
 
62
64
  export const builder = b
@@ -91,13 +93,22 @@ export const builder = b
91
93
  model(
92
94
  $m.rel('customer', 'cloud', 'uses services'),
93
95
  $m.rel('customer', 'cloud.frontend.mobile', 'opens mobile app'),
94
- $m.rel('customer', 'cloud.frontend.dashboard', 'opens in browser'),
96
+ $m.rel('customer', 'cloud.frontend.dashboard', {
97
+ title: 'opens in browser',
98
+ tags: ['old'],
99
+ }),
95
100
  $m.rel('cloud.frontend.dashboard', 'cloud.auth', {
96
101
  title: 'authenticates',
97
102
  color: 'green',
98
103
  }),
99
- $m.rel('cloud.frontend.dashboard', 'cloud.backend.api', 'fetches data'),
100
- $m.rel('cloud.frontend.dashboard', 'cloud.media', 'fetches media'),
104
+ $m.rel('cloud.frontend.dashboard', 'cloud.backend.api', {
105
+ title: 'fetches data',
106
+ tags: ['old'],
107
+ }),
108
+ $m.rel('cloud.frontend.dashboard', 'cloud.media', {
109
+ title: 'fetches media',
110
+ tags: ['temp'],
111
+ }),
101
112
  $m.rel('cloud.frontend.mobile', 'cloud.auth', {
102
113
  title: 'authenticates',
103
114
  color: 'amber',
@@ -137,6 +148,14 @@ export const builder = b
137
148
  instanceOf('db', 'aws.rds'),
138
149
  ),
139
150
  ),
151
+ env('dev').with(
152
+ node('devCustomer').with(
153
+ instanceOf('instance', 'customer'),
154
+ ),
155
+ node('devCloud').with(
156
+ instanceOf('instance', 'cloud'),
157
+ ),
158
+ ),
140
159
  env('acc').with(
141
160
  node('testCustomer').with(
142
161
  instanceOf('instance', 'customer'),
@@ -196,3 +215,12 @@ export function computeNodesAndEdges(...rules: DeploymentRulesBuilderOp<Types>[]
196
215
  edges: edgeIds,
197
216
  }
198
217
  }
218
+
219
+ export function createModel() {
220
+ const modelsource = builder.build()
221
+
222
+ return LikeC4Model.create({
223
+ ...modelsource,
224
+ views: {},
225
+ })
226
+ }
@@ -1,6 +1,6 @@
1
1
  import { LikeC4DeploymentModel } from '../../model'
2
2
  import type { RelationshipModel } from '../../model/RelationModel'
3
- import type { ExpressionV2 } from '../../types'
3
+ import type { ExpressionV2, Filterable, OperatorPredicate } from '../../types'
4
4
  import type { Ctx, Memory, Stage, StageExclude, StageInclude } from './memory'
5
5
 
6
6
  export { Memory, type Stage } from './memory'
@@ -23,6 +23,7 @@ export interface PredicateCtx<Expr extends ExpressionV2 = ExpressionV2> {
23
23
  stage: Stage
24
24
  model: LikeC4DeploymentModel
25
25
  memory: Memory
26
+ where: OperatorPredicate<Filterable> | null
26
27
  }
27
28
  export interface IncludePredicateCtx<Expr extends ExpressionV2 = ExpressionV2> extends PredicateCtx<Expr> {
28
29
  stage: StageInclude
@@ -1,27 +1,19 @@
1
1
  import { findLast, map } from 'remeda'
2
- import { nonexhaustive } from '../../errors'
3
2
  import { type LikeC4DeploymentModel, LikeC4Model } from '../../model'
4
3
  import type { AnyAux } from '../../model/types'
5
4
  import type { DeploymentViewRule } from '../../types'
6
5
  import {
7
6
  type ComputedDeploymentView,
8
7
  type DeploymentView,
9
- FqnExpr,
10
8
  isViewRuleAutoLayout,
11
9
  isViewRulePredicate,
12
10
  } from '../../types'
13
- import { RelationExpr } from '../../types/expression-v2'
14
11
  import { buildElementNotations } from '../utils/buildElementNotations'
15
12
  import { linkNodesWithEdges } from '../utils/link-nodes-with-edges'
16
13
  import { topologicalSort } from '../utils/topological-sort'
17
14
  import { calcViewLayoutHash } from '../utils/view-hash'
18
15
  import { Memory } from './memory'
19
- import { DeploymentRefPredicate } from './predicates/elements'
20
- import { DirectRelationPredicate } from './predicates/relation-direct'
21
- import { InOutRelationPredicate } from './predicates/relation-in-out'
22
- import { IncomingRelationPredicate } from './predicates/relation-incoming'
23
- import { OutgoingRelationPredicate } from './predicates/relation-outgoing'
24
- import { WildcardPredicate } from './predicates/wildcard'
16
+ import { predicateToPatch } from './predicates'
25
17
  import { StageFinal } from './stages/stage-final'
26
18
  import { applyDeploymentViewRuleStyles, buildNodes, toComputedEdges } from './utils'
27
19
 
@@ -37,32 +29,8 @@ export function processPredicates<M extends AnyAux>(
37
29
  const exprs = rule.include ?? rule.exclude
38
30
  for (const expr of exprs) {
39
31
  let stage = op === 'include' ? memory.stageInclude(expr) : memory.stageExclude(expr)
40
- const ctx = { model, stage, memory }
41
- switch (true) {
42
- case FqnExpr.isModelRef(expr):
43
- // Ignore model refs in deployment view
44
- break
45
- case FqnExpr.isDeploymentRef(expr):
46
- stage = DeploymentRefPredicate[op]({ ...ctx, expr } as any) ?? stage
47
- break
48
- case FqnExpr.isWildcard(expr):
49
- stage = WildcardPredicate[op]({ ...ctx, expr } as any) ?? stage
50
- break
51
- case RelationExpr.isDirect(expr):
52
- stage = DirectRelationPredicate[op]({ ...ctx, expr } as any) ?? stage
53
- break
54
- case RelationExpr.isInOut(expr):
55
- stage = InOutRelationPredicate[op]({ ...ctx, expr } as any) ?? stage
56
- break
57
- case RelationExpr.isOutgoing(expr):
58
- stage = OutgoingRelationPredicate[op]({ ...ctx, expr } as any) ?? stage
59
- break
60
- case RelationExpr.isIncoming(expr):
61
- stage = IncomingRelationPredicate[op]({ ...ctx, expr } as any) ?? stage
62
- break
63
- default:
64
- nonexhaustive(expr)
65
- }
32
+ const ctx = { expr, model, stage, memory, where: null }
33
+ stage = predicateToPatch(op, ctx) ?? stage
66
34
  memory = stage.commit()
67
35
  }
68
36
  }
@@ -78,7 +46,7 @@ export function computeDeploymentView<M extends AnyAux>(
78
46
  ...view
79
47
  }: DeploymentView,
80
48
  ): ComputedDeploymentView<M['ViewId']> {
81
- let memory = processPredicates<M>(likec4model.deployment, rules)
49
+ const memory = processPredicates<M>(likec4model.deployment, rules)
82
50
 
83
51
  const nodesMap = buildNodes(memory)
84
52
 
@@ -0,0 +1,8 @@
1
+ export * from './elements'
2
+ export * from './relation-direct'
3
+ export * from './relation-in-out'
4
+ export * from './relation-incoming'
5
+ export * from './relation-outgoing'
6
+ export * from './relation-where'
7
+ export * from './utils'
8
+ export * from './wildcard'
@@ -1,22 +1,22 @@
1
- import { filter, flatMap, isNonNullish, map, pipe } from 'remeda'
1
+ import { filter, flatMap, isNonNullish, map, 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)
@@ -53,10 +53,14 @@ export const DirectRelationPredicate: PredicateExecutor<RelationExpr.Direct> = {
53
53
  switch (true) {
54
54
  // * -> *
55
55
  case sourceIsWildcard && targetIsWildcard: {
56
- connections = findConnectionsWithin(model.instances()).map(c => {
57
- stage.addImplicit(c.boundary)
58
- return c
59
- })
56
+ connections = pipe(
57
+ findConnectionsWithin(model.instances()),
58
+ applyPredicate(where),
59
+ map(c => {
60
+ stage.addImplicit(c.boundary)
61
+ return c
62
+ }),
63
+ )
60
64
  break
61
65
  }
62
66
 
@@ -71,12 +75,15 @@ export const DirectRelationPredicate: PredicateExecutor<RelationExpr.Direct> = {
71
75
  postFilter = c => isSource(c) !== isTarget(c)
72
76
  }
73
77
 
74
- connections = sources
75
- .flatMap(source => {
78
+ connections = pipe(
79
+ sources,
80
+ flatMap(source => {
76
81
  const targets = resolveAscendingSiblings(source)
77
82
  return findConnectionsBetween(source, targets, dir)
78
- })
79
- .filter(postFilter)
83
+ }),
84
+ filter(postFilter),
85
+ applyPredicate(where),
86
+ )
80
87
  break
81
88
  }
82
89
  // * -> target; * <-> target
@@ -90,12 +97,15 @@ export const DirectRelationPredicate: PredicateExecutor<RelationExpr.Direct> = {
90
97
  postFilter = c => isSource(c) !== isTarget(c)
91
98
  }
92
99
 
93
- connections = targets
94
- .flatMap(target => {
100
+ connections = pipe(
101
+ targets,
102
+ flatMap(target => {
95
103
  const sources = resolveAscendingSiblings(target)
96
104
  return [...sources].flatMap(source => findConnection(source, target, dir))
97
- })
98
- .filter(postFilter)
105
+ }),
106
+ filter(postFilter),
107
+ applyPredicate(where),
108
+ )
99
109
  break
100
110
  }
101
111
  default: {
@@ -111,6 +121,7 @@ export const DirectRelationPredicate: PredicateExecutor<RelationExpr.Direct> = {
111
121
  sources,
112
122
  flatMap(s => findConnectionsBetween(s, targets, dir)),
113
123
  filter(c => isSource(c) && isTarget(c)),
124
+ applyPredicate(where),
114
125
  )
115
126
  }
116
127
  }
@@ -126,90 +137,72 @@ export const DirectRelationPredicate: PredicateExecutor<RelationExpr.Direct> = {
126
137
 
127
138
  return stage
128
139
  },
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
- }
140
+ exclude: ({ expr, model, memory, stage, where }) => {
141
+ const isTarget = deploymentExpressionToPredicate(expr.target)
142
+ const isSource = deploymentExpressionToPredicate(expr.source)
139
143
 
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
- }
144
+ let modelRelationsToExclude: ReadonlySet<RelationshipModel<AnyAux>>
145
+ switch (true) {
146
+ // * -> *
147
+ case FqnExpr.isWildcard(expr.source) && FqnExpr.isWildcard(expr.target):
148
+ stage.excludeConnections(applyPredicate(memory.connections, where))
149
+ return stage
149
150
 
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
- }
151
+ // model -> model
152
+ case FqnExpr.isModelRef(expr.source) && FqnExpr.isModelRef(expr.target):
153
+ modelRelationsToExclude = resolveRelationsBetweenModelElements({
154
+ source: expr.source,
155
+ target: expr.target,
156
+ expr,
157
+ model,
158
+ })
159
+ return excludeModelRelations(modelRelationsToExclude, { stage, memory }, where)
163
160
 
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
- }
161
+ // model -> *
162
+ case FqnExpr.isModelRef(expr.source) && FqnExpr.isWildcard(expr.target):
163
+ modelRelationsToExclude = resolveAllOutgoingRelations(model, expr.source)
164
+ return excludeModelRelations(modelRelationsToExclude, { stage, memory }, where)
173
165
 
174
- const isSource = deploymentExpressionToPredicate(expr.source)
175
- const isTarget = deploymentExpressionToPredicate(expr.target)
166
+ // model -> deployment
167
+ case FqnExpr.isModelRef(expr.source):
168
+ modelRelationsToExclude = resolveAllOutgoingRelations(model, expr.source)
169
+ return excludeModelRelations(
170
+ modelRelationsToExclude,
171
+ { stage, memory },
172
+ where,
173
+ c => isTarget(c.target),
174
+ )
176
175
 
177
- const satisfies = (connection: DeploymentConnectionModel) => {
178
- return (isSource(connection.source) && isTarget(connection.target))
179
- || (expr.isBidirectional && isSource(connection.target) && isTarget(connection.source))
180
- }
176
+ // deployment -> model
177
+ case FqnExpr.isModelRef(expr.target):
178
+ modelRelationsToExclude = resolveAllImcomingRelations(model, expr.target)
179
+ return excludeModelRelations(
180
+ modelRelationsToExclude,
181
+ { stage, memory },
182
+ where,
183
+ c => isSource(c.source),
184
+ )
181
185
 
182
- const toExclude = memory.connections.filter(satisfies)
186
+ // deployment -> deployment
187
+ default:
188
+ const satisfies = (connection: DeploymentConnectionModel) => {
189
+ return (isSource(connection.source) && isTarget(connection.target))
190
+ || (expr.isBidirectional === true && isSource(connection.target) && isTarget(connection.source))
191
+ }
183
192
 
184
- stage.excludeConnections(toExclude)
185
- return stage
186
- },
187
- }
193
+ const deploymentRelationsToExclude = pipe(
194
+ memory.connections,
195
+ filter(satisfies),
196
+ applyPredicate(where),
197
+ )
198
+ if (deploymentRelationsToExclude.length === 0) {
199
+ return stage
200
+ }
188
201
 
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)
202
+ stage.excludeConnections(deploymentRelationsToExclude)
203
+ return stage
204
+ }
205
+ },
213
206
  }
214
207
 
215
208
  function resolveRelationsBetweenModelElements({
@@ -235,3 +228,36 @@ function resolveRelationsBetweenModelElements({
235
228
 
236
229
  return new Set(modelConnections.flatMap(c => [...c.relations]))
237
230
  }
231
+
232
+ function elementToFilterable<M extends AnyAux>(element: DeploymentElementModel<M>) {
233
+ return pick(element, ['tags', 'kind'])
234
+ }
235
+
236
+ function toFilterableRelation<M extends AnyAux>(
237
+ source: DeploymentElementModel<M>,
238
+ target: DeploymentElementModel<M>,
239
+ ) {
240
+ return (
241
+ relation: RelationshipModel<M> | DeploymentRelationModel<M>,
242
+ ) => ({
243
+ tags: relation.tags,
244
+ kind: relation.kind,
245
+ source: elementToFilterable(source),
246
+ target: elementToFilterable(target),
247
+ })
248
+ }
249
+
250
+ function matchConnection<M extends AnyAux>(
251
+ c: DeploymentConnectionModel<M>,
252
+ where: OperatorPredicate<Filterable> | null,
253
+ ): boolean {
254
+ if (!where) {
255
+ return true
256
+ }
257
+
258
+ return [
259
+ ...Array.from(c.relations.deployment.values()).map(toFilterableRelation(c.source, c.target)),
260
+ ...Array.from(c.relations.model.values()).map(toFilterableRelation(c.source, c.target)),
261
+ ]
262
+ .filter(where).length > 0
263
+ }