@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,645 @@
1
+ import { forEach, indexBy, isString, map, mapToObj, prop, values } from 'remeda'
2
+ import {
3
+ type BorderStyle,
4
+ type Color,
5
+ type ComputedView,
6
+ type CustomElementExpr as C4CustomElementExpr,
7
+ type CustomRelationExpr as C4CustomRelationExpr,
8
+ type EdgeId,
9
+ type Element,
10
+ type ElementExpression as C4ElementExpression,
11
+ type ElementKind,
12
+ type ElementShape,
13
+ type ElementView,
14
+ type ElementWhereExpr,
15
+ type Expression as C4Expression,
16
+ type Fqn,
17
+ type GlobalStyleID,
18
+ type IconUrl,
19
+ type IncomingExpr as C4IncomingExpr,
20
+ type InOutExpr as C4InOutExpr,
21
+ isElementRef,
22
+ isElementWhere,
23
+ isRelationExpression,
24
+ isRelationWhere,
25
+ type NonEmptyArray,
26
+ type OutgoingExpr as C4OutgoingExpr,
27
+ type Relation,
28
+ type RelationExpr as C4RelationExpr,
29
+ type RelationId,
30
+ type RelationshipArrowType,
31
+ type RelationshipLineType,
32
+ type RelationWhereExpr,
33
+ type Tag,
34
+ type ViewId,
35
+ type ViewRule,
36
+ type ViewRuleGlobalStyle,
37
+ type ViewRuleGroup,
38
+ type ViewRulePredicate,
39
+ type ViewRuleStyle,
40
+ type WhereOperator
41
+ } from '../../../types'
42
+ import { LikeC4ModelGraph } from '../../LikeC4ModelGraph'
43
+ import { withReadableEdges } from '../../utils/with-readable-edges'
44
+ import { ComputeCtx } from '../compute'
45
+
46
+ function computeElementView(view: ElementView, graph: LikeC4ModelGraph) {
47
+ return ComputeCtx.elementView(view, graph)
48
+ }
49
+
50
+ /**
51
+ ┌──────────────────────────────────────────────────┐
52
+ │ cloud │
53
+ │ ┌───────────────────────────────────────────┐ │
54
+ │ │ frontend │ │
55
+ ┏━━━━━━━━━━┓ │ │ ┏━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━━┓ │ │ ┏━━━━━━━━━━━┓
56
+ ┃ ┃ │ │ ┃ ┃ ┃ ┃ │ │ ┃ ┃
57
+ ┃ customer ┃──┼──┼──▶┃ dashboard ┃ ┃ adminpanel ┃◀───┼───┼───┃ support ┃
58
+ ┃ ┃ │ │ ┃ ┃ ┃ ┃ │ │ ┃ ┃
59
+ ┗━━━━━━━━━━┛ │ │ ┗━━━━━━┳━━━━━━┛ ┗━━━━━━━━┳━━━━━━━┛ │ │ ┗━━━━━━━━━━━┛
60
+ │ └──────────┼───────────────────┼────────────┘ │
61
+ │ ├───────────────────┘ │
62
+ │ │ │
63
+ │ ┌──────────┼────────────────────────────────┐ │
64
+ │ │ ▼ backend │ │
65
+ │ │ ┏━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━┓ │ │
66
+ │ │ ┃ ┃ ┃ ┃ │ │
67
+ │ │ ┃ graphlql ┃──────▶┃ storage ┃ │ │
68
+ │ │ ┃ ┃ ┃ ┃ │ │
69
+ │ │ ┗━━━━━━━━━━━━━┛ ┗━━━━━━┳━━━━━━┛ │ │
70
+ │ └────────────────────────────────┼──────────┘ │
71
+ └───────────────────────────────────┼──────────────┘
72
+
73
+ ┌─────────┼─────────┐
74
+ │ amazon │ │
75
+ │ ▼ │
76
+ │ ┏━━━━━━━━━━━━━━┓ │
77
+ │ ┃ ┃ │
78
+ │ ┃ s3 ┃ │
79
+ │ ┃ ┃ │
80
+ │ ┗━━━━━━━━━━━━━━┛ │
81
+ └───────────────────┘
82
+
83
+ specification {
84
+ element actor
85
+ element system
86
+ element container
87
+ element component
88
+
89
+ tag old
90
+ }
91
+
92
+ model {
93
+
94
+ actor customer
95
+ actor support
96
+
97
+ system cloud {
98
+ container backend {
99
+ component graphql
100
+ component storage {
101
+ #old
102
+ }
103
+
104
+ graphql -> storage
105
+ }
106
+
107
+ container frontend {
108
+ component dashboard {
109
+ -> graphql
110
+ }
111
+ component adminPanel {
112
+ #old
113
+ -> graphql
114
+ }
115
+ }
116
+ }
117
+
118
+ customer -> dashboard
119
+ support -> adminPanel
120
+
121
+ system amazon {
122
+ component s3
123
+
124
+ cloud.backend.storage -> s3
125
+ }
126
+
127
+ }
128
+
129
+ */
130
+ type TestTag = 'old' | 'next' | 'aws' | 'storage' | 'communication' | 'legacy'
131
+
132
+ const el = ({
133
+ id,
134
+ kind,
135
+ title,
136
+ style,
137
+ tags,
138
+ ...props
139
+ }: Partial<Omit<Element, 'id' | 'kind' | 'tags'>> & {
140
+ id: string
141
+ kind: string
142
+ tags?: NonEmptyArray<TestTag>
143
+ }): Element => ({
144
+ id: id as Fqn,
145
+ kind: kind as ElementKind,
146
+ title: title ?? id,
147
+ description: null,
148
+ technology: null,
149
+ tags: tags as NonEmptyArray<Tag> ?? null,
150
+ links: null,
151
+ style: {
152
+ ...style
153
+ },
154
+ ...props
155
+ })
156
+
157
+ export const fakeElements = {
158
+ 'customer': el({
159
+ id: 'customer',
160
+ kind: 'actor',
161
+ title: 'customer',
162
+ shape: 'person'
163
+ }),
164
+ 'support': el({
165
+ id: 'support',
166
+ kind: 'actor',
167
+ title: 'support',
168
+ shape: 'person'
169
+ }),
170
+ 'cloud': el({
171
+ id: 'cloud',
172
+ kind: 'system',
173
+ title: 'cloud',
174
+ icon: 'none',
175
+ tags: ['next', 'old']
176
+ }),
177
+ 'cloud.backend': el({
178
+ id: 'cloud.backend',
179
+ kind: 'container',
180
+ title: 'backend'
181
+ }),
182
+ 'cloud.frontend': el({
183
+ id: 'cloud.frontend',
184
+ kind: 'container',
185
+ title: 'frontend',
186
+ shape: 'browser'
187
+ }),
188
+ 'cloud.backend.graphql': el({
189
+ id: 'cloud.backend.graphql',
190
+ kind: 'component',
191
+ icon: 'tech:graphql' as IconUrl,
192
+ title: 'graphql'
193
+ }),
194
+ 'email': el({
195
+ id: 'email',
196
+ kind: 'system',
197
+ title: 'email'
198
+ }),
199
+ 'cloud.backend.storage': el({
200
+ id: 'cloud.backend.storage',
201
+ kind: 'component',
202
+ title: 'storage',
203
+ tags: ['storage', 'old']
204
+ }),
205
+ 'cloud.frontend.adminPanel': el({
206
+ id: 'cloud.frontend.adminPanel',
207
+ kind: 'component',
208
+ title: 'adminPanel',
209
+ tags: ['old']
210
+ }),
211
+ 'cloud.frontend.dashboard': el({
212
+ id: 'cloud.frontend.dashboard',
213
+ kind: 'component',
214
+ title: 'dashboard',
215
+ icon: 'tech:react' as IconUrl,
216
+ tags: ['next']
217
+ }),
218
+ 'amazon': el({
219
+ id: 'amazon',
220
+ kind: 'system',
221
+ title: 'amazon',
222
+ icon: 'tech:aws' as IconUrl,
223
+ tags: ['aws']
224
+ }),
225
+ 'amazon.s3': el({
226
+ id: 'amazon.s3',
227
+ kind: 'component',
228
+ title: 's3',
229
+ shape: 'storage',
230
+ icon: 'aws:s3' as IconUrl,
231
+ tags: ['aws', 'storage']
232
+ })
233
+ } satisfies Record<string, Element>
234
+
235
+ export type FakeElementIds = keyof typeof fakeElements
236
+
237
+ const rel = <Source extends FakeElementIds, Target extends FakeElementIds>({
238
+ source,
239
+ target,
240
+ title,
241
+ ...props
242
+ }: {
243
+ source: Source
244
+ target: Target
245
+ title?: string
246
+ kind?: string
247
+ color?: Color
248
+ line?: RelationshipLineType
249
+ head?: RelationshipArrowType
250
+ tail?: RelationshipArrowType
251
+ tags?: NonEmptyArray<TestTag>
252
+ }) =>
253
+ ({
254
+ id: `${source}:${target}` as RelationId,
255
+ title: title ?? '',
256
+ source: source as Fqn,
257
+ target: target as Fqn,
258
+ ...(props as any)
259
+ }) as Omit<Relation, 'id'> & { id: `${Source}:${Target}` }
260
+
261
+ export const fakeRelations = [
262
+ rel({
263
+ source: 'customer',
264
+ target: 'cloud.frontend.dashboard',
265
+ title: 'opens in browser'
266
+ }),
267
+ rel({
268
+ source: 'support',
269
+ target: 'cloud.frontend.adminPanel',
270
+ title: 'manages'
271
+ }),
272
+ rel({
273
+ source: 'cloud.backend.storage',
274
+ target: 'amazon.s3',
275
+ title: 'uploads',
276
+ tags: ['aws', 'storage', 'legacy']
277
+ }),
278
+ rel({
279
+ source: 'customer',
280
+ target: 'cloud',
281
+ title: 'uses'
282
+ }),
283
+ rel({
284
+ source: 'cloud.backend.graphql',
285
+ target: 'cloud.backend.storage',
286
+ title: 'stores',
287
+ tags: ['old', 'storage']
288
+ }),
289
+ // rel({
290
+ // source: 'cloud.backend',
291
+ // target: 'cloud.email',
292
+ // title: 'schedule emails'
293
+ // }),
294
+ // rel({
295
+ // source: 'cloud.email',
296
+ // target: 'customer',
297
+ // title: 'send emails'
298
+ // }),
299
+ rel({
300
+ source: 'cloud.frontend',
301
+ target: 'cloud.backend',
302
+ title: 'requests'
303
+ }),
304
+ rel({
305
+ source: 'cloud.frontend.dashboard',
306
+ target: 'cloud.backend.graphql',
307
+ kind: 'graphlql',
308
+ title: 'requests',
309
+ line: 'solid',
310
+ tags: ['next']
311
+ }),
312
+ rel({
313
+ source: 'cloud.frontend.adminPanel',
314
+ target: 'cloud.backend.graphql',
315
+ kind: 'graphlql',
316
+ title: 'fetches',
317
+ line: 'dashed',
318
+ tail: 'odiamond',
319
+ tags: ['old']
320
+ }),
321
+ rel({
322
+ source: 'cloud',
323
+ target: 'amazon',
324
+ title: 'uses',
325
+ head: 'diamond',
326
+ tail: 'odiamond',
327
+ tags: ['aws']
328
+ }),
329
+ rel({
330
+ source: 'cloud.backend',
331
+ target: 'email',
332
+ title: 'schedule',
333
+ tags: ['communication']
334
+ }),
335
+ rel({
336
+ source: 'cloud',
337
+ target: 'email',
338
+ title: 'uses',
339
+ tags: ['communication']
340
+ }),
341
+ rel({
342
+ source: 'email',
343
+ target: 'cloud',
344
+ title: 'notifies',
345
+ tags: ['communication']
346
+ })
347
+ ]
348
+
349
+ export const globalStyles = {
350
+ 'mute_old': [{
351
+ targets: [$expr({
352
+ elementTag: 'old' as Tag,
353
+ isEqual: true
354
+ })],
355
+ style: {
356
+ color: 'muted'
357
+ }
358
+ }],
359
+ 'red_next': [{
360
+ targets: [$expr({
361
+ elementTag: 'next' as Tag,
362
+ isEqual: true
363
+ })],
364
+ style: {
365
+ color: 'red'
366
+ }
367
+ }]
368
+ } as const
369
+
370
+ export type FakeRelationIds = (typeof fakeRelations)[number]['id']
371
+
372
+ export const fakeModel = new LikeC4ModelGraph({
373
+ elements: fakeElements,
374
+ relations: indexBy(fakeRelations, r => r.id),
375
+ globals: {
376
+ predicates: {},
377
+ dynamicPredicates: {},
378
+ styles: globalStyles
379
+ }
380
+ })
381
+
382
+ const emptyView = {
383
+ __: 'element' as const,
384
+ id: 'index' as ViewId,
385
+ title: null,
386
+ description: null,
387
+ tags: null,
388
+ links: null,
389
+ customColorDefinitions: {},
390
+ rules: []
391
+ }
392
+
393
+ export const includeWildcard = {
394
+ include: [
395
+ {
396
+ wildcard: true
397
+ }
398
+ ]
399
+ } satisfies ViewRule
400
+
401
+ export type ElementRefExpr = '*' | FakeElementIds | `${FakeElementIds}.*` | `${FakeElementIds}._`
402
+
403
+ type InOutExpr = `-> ${ElementRefExpr} ->`
404
+ type IncomingExpr = `-> ${ElementRefExpr}`
405
+ type OutgoingExpr = `${ElementRefExpr} ->`
406
+ type RelationKeyword = '->' | '<->'
407
+ type RelationExpr = `${ElementRefExpr} ${RelationKeyword} ${ElementRefExpr}`
408
+
409
+ export type Expression =
410
+ | ElementRefExpr
411
+ | InOutExpr
412
+ | IncomingExpr
413
+ | OutgoingExpr
414
+ | RelationExpr
415
+
416
+ export function $custom(
417
+ expr: ElementRefExpr,
418
+ props: {
419
+ title?: string
420
+ description?: string
421
+ technology?: string
422
+ shape?: ElementShape
423
+ color?: Color
424
+ border?: BorderStyle
425
+ icon?: string
426
+ opacity?: number
427
+ navigateTo?: string
428
+ }
429
+ ): C4CustomElementExpr {
430
+ return {
431
+ custom: {
432
+ expr: $expr(expr) as any,
433
+ ...props as any
434
+ }
435
+ }
436
+ }
437
+
438
+ export function $customRelation(
439
+ relation: RelationExpr,
440
+ props: Omit<C4CustomRelationExpr['customRelation'], 'relation'>
441
+ ): C4CustomRelationExpr {
442
+ return {
443
+ customRelation: {
444
+ relation: $expr(relation) as any,
445
+ ...props
446
+ }
447
+ }
448
+ }
449
+
450
+ export function $where(
451
+ expr: Expression | C4Expression,
452
+ operator: WhereOperator<TestTag, string>
453
+ ): ElementWhereExpr | RelationWhereExpr {
454
+ return {
455
+ where: {
456
+ expr: $expr(expr) as any,
457
+ condition: operator
458
+ }
459
+ }
460
+ }
461
+
462
+ export function $inout(
463
+ expr: InOutExpr | C4ElementExpression
464
+ ): C4InOutExpr {
465
+ const innerExpression = !isString(expr)
466
+ ? expr as C4Expression
467
+ : $expr(expr.replace(/->/g, '').trim() as ElementRefExpr) as any
468
+
469
+ return { inout: innerExpression }
470
+ }
471
+
472
+ export function $incoming(
473
+ expr: IncomingExpr | C4ElementExpression
474
+ ): C4IncomingExpr {
475
+ const innerExpression = !isString(expr)
476
+ ? expr as C4Expression
477
+ : $expr(expr.replace('-> ', '') as ElementRefExpr) as any
478
+
479
+ return { incoming: innerExpression }
480
+ }
481
+
482
+ export function $outgoing(
483
+ expr: OutgoingExpr | C4ElementExpression
484
+ ): C4OutgoingExpr {
485
+ const innerExpression = !isString(expr)
486
+ ? expr as C4Expression
487
+ : $expr(expr.replace(' ->', '') as ElementRefExpr) as any
488
+
489
+ return { outgoing: innerExpression }
490
+ }
491
+
492
+ export function $relation(
493
+ expr: RelationExpr
494
+ ): C4RelationExpr {
495
+ const [source, target] = expr.split(/ -> | <-> /)
496
+ const isBidirectional = expr.includes(' <-> ')
497
+
498
+ return {
499
+ source: $expr(source as ElementRefExpr) as any,
500
+ target: $expr(target as ElementRefExpr) as any,
501
+ ...(isBidirectional && { isBidirectional })
502
+ }
503
+ }
504
+
505
+ export function $expr(expr: Expression | C4Expression): C4Expression {
506
+ if (!isString(expr)) {
507
+ return expr as C4Expression
508
+ }
509
+ if (expr === '*') {
510
+ return { wildcard: true }
511
+ }
512
+ if (expr.startsWith('->')) {
513
+ return expr.endsWith('->') ? $inout(expr as InOutExpr) : $incoming(expr as IncomingExpr)
514
+ }
515
+ if (expr.endsWith(' ->')) {
516
+ return $outgoing(expr as OutgoingExpr)
517
+ }
518
+ if (expr.includes(' -> ') || expr.includes(' <-> ')) {
519
+ return $relation(expr as RelationExpr)
520
+ }
521
+ if (expr.endsWith('._')) {
522
+ return {
523
+ expanded: expr.replace('._', '') as Fqn
524
+ }
525
+ }
526
+ if (expr.endsWith('.*')) {
527
+ return {
528
+ element: expr.replace('.*', '') as Fqn,
529
+ isChildren: true
530
+ }
531
+ }
532
+ if (expr.endsWith('.**')) {
533
+ return {
534
+ element: expr.replace('.*', '') as Fqn,
535
+ isDescendants: true
536
+ }
537
+ }
538
+ return {
539
+ element: expr as Fqn
540
+ }
541
+ }
542
+
543
+ type CustomProps = {
544
+ where?: WhereOperator<TestTag, string>
545
+ with?: {
546
+ title?: string
547
+ description?: string
548
+ technology?: string
549
+ shape?: ElementShape
550
+ color?: Color
551
+ border?: BorderStyle
552
+ icon?: string
553
+ opacity?: number
554
+ navigateTo?: string
555
+ } & Omit<C4CustomRelationExpr['customRelation'], 'relation' | 'navigateTo'>
556
+ }
557
+ export function $include(expr: Expression | C4Expression, props?: CustomProps): ViewRulePredicate {
558
+ let _expr = props?.where ? $where(expr, props.where) : $expr(expr)
559
+ _expr = props?.with ? $with(_expr, props.with) : _expr
560
+ return {
561
+ include: [_expr]
562
+ }
563
+ }
564
+ export function $with(expr: C4Expression, props?: CustomProps['with']): C4CustomRelationExpr | C4CustomElementExpr {
565
+ if (isRelationExpression(expr) || isRelationWhere(expr)) {
566
+ return {
567
+ customRelation: {
568
+ relation: expr,
569
+ ...props as any
570
+ }
571
+ }
572
+ } else if (isElementRef(expr) || isElementWhere(expr)) {
573
+ return {
574
+ custom: {
575
+ expr: expr,
576
+ ...props as any
577
+ }
578
+ }
579
+ }
580
+
581
+ throw 'Unsupported type of internal expression'
582
+ }
583
+ export function $exclude(expr: Expression | C4Expression, where?: WhereOperator<TestTag, string>): ViewRulePredicate {
584
+ let _expr = where ? $where(expr, where) : $expr(expr)
585
+ return {
586
+ exclude: [_expr]
587
+ }
588
+ }
589
+ export function $group(groupRules: ViewRuleGroup['groupRules']): ViewRuleGroup {
590
+ return {
591
+ title: null,
592
+ groupRules
593
+ }
594
+ }
595
+
596
+ export function $style(element: ElementRefExpr, style: ViewRuleStyle['style']): ViewRuleStyle {
597
+ return {
598
+ targets: [$expr(element) as C4ElementExpression],
599
+ style: Object.assign({}, style)
600
+ }
601
+ }
602
+
603
+ type GlobalStyles = keyof typeof globalStyles
604
+ type GlobalExpr = `style ${GlobalStyles}`
605
+ export function $global(expr: GlobalExpr): ViewRuleGlobalStyle {
606
+ const [_t, id] = expr.split(' ') as [string, string]
607
+ if (_t === 'style') {
608
+ return {
609
+ styleId: id as GlobalStyleID
610
+ }
611
+ }
612
+ throw new Error('Invalid global expression')
613
+ }
614
+
615
+ export function computeView(
616
+ ...args: [FakeElementIds, ViewRule | ViewRule[]] | [ViewRule | ViewRule[]]
617
+ ) {
618
+ let result: ComputedView
619
+ if (args.length === 1) {
620
+ result = computeElementView(
621
+ {
622
+ ...emptyView,
623
+ rules: [args[0]].flat()
624
+ },
625
+ fakeModel
626
+ )
627
+ } else {
628
+ result = computeElementView(
629
+ {
630
+ ...emptyView,
631
+ id: 'index' as ViewId,
632
+ viewOf: args[0] as Fqn,
633
+ rules: [args[1]].flat()
634
+ },
635
+ fakeModel
636
+ )
637
+ }
638
+
639
+ result = withReadableEdges(result)
640
+
641
+ return Object.assign(result, {
642
+ nodeIds: map(result.nodes, prop('id')) as string[],
643
+ edgeIds: map(result.edges, prop('id')) as string[]
644
+ })
645
+ }