@likec4/core 1.27.3 → 1.28.1

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 (71) hide show
  1. package/dist/builder/index.d.mts +6 -6
  2. package/dist/builder/index.mjs +23 -13
  3. package/dist/compute-view/index.d.mts +3 -3
  4. package/dist/compute-view/index.mjs +3 -3
  5. package/dist/compute-view/relationships-view/index.d.mts +3 -3
  6. package/dist/index.d.mts +6 -6
  7. package/dist/index.mjs +2 -2
  8. package/dist/model/index.d.mts +6 -6
  9. package/dist/model/index.mjs +1 -1
  10. package/dist/shared/{core.BpuZeqlX.d.mts → core.CZR9J1Qx.d.mts} +2 -2
  11. package/dist/shared/{core.Djro-kuQ.d.mts → core.CiFKryGW.d.mts} +7 -2
  12. package/dist/shared/{core.HKn0jlo_.d.mts → core.D2830qgg.d.mts} +1 -1
  13. package/dist/shared/{core.CFDwncxs.d.mts → core.DJvmfnEi.d.mts} +481 -186
  14. package/dist/shared/{core.BMep80lJ.mjs → core.DgfwjBtu.mjs} +198 -87
  15. package/dist/shared/core.SjLhMA7a.d.mts +62 -0
  16. package/dist/shared/{core.RjpTh3vE.mjs → core.w6BcP9z5.mjs} +263 -168
  17. package/dist/types/index.d.mts +4 -4
  18. package/dist/types/index.mjs +1 -1
  19. package/dist/utils/index.d.mts +2 -2
  20. package/package.json +7 -7
  21. package/src/builder/Builder.ts +1 -0
  22. package/src/builder/Builder.view-common.ts +10 -10
  23. package/src/builder/Builder.view-element.ts +26 -14
  24. package/src/builder/__snapshots__/Builder-style1.spec.ts.snap +23 -8
  25. package/src/builder/__snapshots__/Builder-style2.spec.ts.snap +9 -4
  26. package/src/compute-view/deployment-view/predicates/deploymentRefs-where.ts +18 -0
  27. package/src/compute-view/deployment-view/predicates/relation-direct.ts +6 -0
  28. package/src/compute-view/deployment-view/predicates/relation-in-out.ts +3 -0
  29. package/src/compute-view/deployment-view/predicates/relation-incoming.ts +3 -0
  30. package/src/compute-view/deployment-view/predicates/relation-outgoing.ts +3 -0
  31. package/src/compute-view/deployment-view/predicates/utils.ts +10 -3
  32. package/src/compute-view/deployment-view/utils.ts +7 -4
  33. package/src/compute-view/element-view/__test__/fixture.ts +72 -65
  34. package/src/compute-view/element-view/_types.ts +13 -9
  35. package/src/compute-view/element-view/compute.ts +27 -27
  36. package/src/compute-view/element-view/memory/NodeGroup.ts +4 -3
  37. package/src/compute-view/element-view/memory/memory.ts +3 -2
  38. package/src/compute-view/element-view/memory/stage-include.ts +2 -2
  39. package/src/compute-view/element-view/predicates/_utils.ts +25 -24
  40. package/src/compute-view/element-view/predicates/element-expand.ts +7 -10
  41. package/src/compute-view/element-view/predicates/element-kind-tag.ts +5 -3
  42. package/src/compute-view/element-view/predicates/element-ref.ts +7 -19
  43. package/src/compute-view/element-view/predicates/relation-direct.ts +15 -13
  44. package/src/compute-view/element-view/predicates/relation-in-out.ts +5 -6
  45. package/src/compute-view/element-view/predicates/relation-in.ts +36 -32
  46. package/src/compute-view/element-view/predicates/relation-out.ts +37 -33
  47. package/src/compute-view/element-view/predicates/wildcard.ts +2 -2
  48. package/src/compute-view/utils/applyCustomElementProperties.ts +9 -4
  49. package/src/compute-view/utils/applyCustomRelationProperties.ts +10 -7
  50. package/src/compute-view/utils/elementExpressionToPredicate.ts +27 -26
  51. package/src/compute-view/utils/link-nodes-with-edges.ts +1 -1
  52. package/src/compute-view/utils/relationExpressionToPredicates.ts +10 -17
  53. package/src/compute-view/utils/uniqueTags.ts +3 -3
  54. package/src/index.ts +0 -2
  55. package/src/model/ElementModel.ts +25 -6
  56. package/src/model/LikeC4Model.ts +47 -6
  57. package/src/model/RelationModel.ts +4 -2
  58. package/src/model/view/LikeC4ViewModel.ts +3 -2
  59. package/src/types/_common.ts +0 -4
  60. package/src/types/deployments.ts +3 -2
  61. package/src/types/element.ts +2 -10
  62. package/src/types/expression-v2-model.ts +314 -0
  63. package/src/types/expression-v2.ts +238 -30
  64. package/src/types/index.ts +15 -46
  65. package/src/types/model-data.ts +4 -1
  66. package/src/types/relation.ts +2 -1
  67. package/src/types/scalars.ts +39 -0
  68. package/src/types/view-changes.ts +2 -1
  69. package/src/types/view.ts +7 -8
  70. package/dist/shared/core.30yYKXcZ.d.mts +0 -186
  71. package/src/types/expression.ts +0 -207
@@ -1,15 +1,5 @@
1
- import { pick } from 'remeda'
2
1
  import { nonexhaustive } from '../../errors'
3
- import type { Element } from '../../types/element'
4
- import {
5
- type RelationExpression,
6
- type RelationWhereExpr,
7
- isIncoming,
8
- isInOut,
9
- isOutgoing,
10
- isRelation,
11
- isRelationWhere,
12
- } from '../../types/expression'
2
+ import { ModelLayer } from '../../types'
13
3
  import { whereOperatorAsPredicate } from '../../types/operators'
14
4
  import type { ModelRelation } from '../../types/relation'
15
5
  import type { ComputedNode } from '../../types/view'
@@ -22,10 +12,13 @@ export type FilterableEdge = Pick<ModelRelation, 'kind' | 'tags'> & {
22
12
  }
23
13
 
24
14
  export function relationExpressionToPredicates<T extends FilterableEdge>(
25
- expr: RelationExpression | RelationWhereExpr,
15
+ expr: ModelLayer.AnyRelationExpr,
26
16
  ): Predicate<T> {
27
17
  switch (true) {
28
- case isRelationWhere(expr): {
18
+ case ModelLayer.RelationExpr.isCustom(expr): {
19
+ return relationExpressionToPredicates(expr.customRelation.expr)
20
+ }
21
+ case ModelLayer.RelationExpr.isWhere(expr): {
29
22
  const predicate = relationExpressionToPredicates(expr.where.expr)
30
23
  const where = whereOperatorAsPredicate(expr.where.condition)
31
24
  return e =>
@@ -36,7 +29,7 @@ export function relationExpressionToPredicates<T extends FilterableEdge>(
36
29
  ...(e.kind && { kind: e.kind }),
37
30
  })
38
31
  }
39
- case isRelation(expr): {
32
+ case ModelLayer.RelationExpr.isDirect(expr): {
40
33
  const isSource = elementExprToPredicate(expr.source)
41
34
  const isTarget = elementExprToPredicate(expr.target)
42
35
  return edge => {
@@ -44,15 +37,15 @@ export function relationExpressionToPredicates<T extends FilterableEdge>(
44
37
  || (!!expr.isBidirectional && isSource(edge.target) && isTarget(edge.source))
45
38
  }
46
39
  }
47
- case isInOut(expr): {
40
+ case ModelLayer.RelationExpr.isInOut(expr): {
48
41
  const isInOut = elementExprToPredicate(expr.inout)
49
42
  return edge => isInOut(edge.source) || isInOut(edge.target)
50
43
  }
51
- case isIncoming(expr): {
44
+ case ModelLayer.RelationExpr.isIncoming(expr): {
52
45
  const isTarget = elementExprToPredicate(expr.incoming)
53
46
  return edge => isTarget(edge.target)
54
47
  }
55
- case isOutgoing(expr): {
48
+ case ModelLayer.RelationExpr.isOutgoing(expr): {
56
49
  const isSource = elementExprToPredicate(expr.outgoing)
57
50
  return edge => isSource(edge.source)
58
51
  }
@@ -1,7 +1,7 @@
1
1
  import { flatMap, hasAtLeast, pipe, sort, unique } from 'remeda'
2
2
  import type { LiteralUnion } from 'type-fest'
3
3
  import type { NonEmptyReadonlyArray } from '../../types/_common'
4
- import type { Tag } from '../../types/element'
4
+ import type { Tag } from '../../types/scalars'
5
5
  import { compareNatural } from '../../utils/compare-natural'
6
6
 
7
7
  /**
@@ -9,13 +9,13 @@ import { compareNatural } from '../../utils/compare-natural'
9
9
  * and sort in natural order; returns null if no tags are present.
10
10
  */
11
11
  export function uniqueTags<T extends { tags?: NonEmptyReadonlyArray<LiteralUnion<Tag, string>> | null }>(
12
- elements: ReadonlyArray<T>
12
+ elements: ReadonlyArray<T>,
13
13
  ) {
14
14
  const tags = pipe(
15
15
  elements,
16
16
  flatMap(e => e.tags ?? []),
17
17
  unique(),
18
- sort(compareNatural)
18
+ sort(compareNatural),
19
19
  )
20
20
  return hasAtLeast(tags, 1) ? tags as NonEmptyReadonlyArray<Tag> : null
21
21
  }
package/src/index.ts CHANGED
@@ -33,8 +33,6 @@ export * from './types'
33
33
 
34
34
  export * from './utils'
35
35
 
36
- export * as Expr from './types/expression'
37
-
38
36
  export {
39
37
  computeView,
40
38
  computeViews,
@@ -1,19 +1,23 @@
1
1
  import { isTruthy } from 'remeda'
2
2
  import type { SetRequired } from 'type-fest'
3
+ import type { IteratorLike } from '../types/_common'
3
4
  import {
4
5
  type Element as C4Element,
5
6
  type ElementKind as C4ElementKind,
6
7
  type ElementShape as C4ElementShape,
7
8
  type ElementStyle,
8
- type IconUrl,
9
- type IteratorLike,
10
9
  type Link,
11
- type Tag as C4Tag,
12
- type ThemeColor,
13
10
  DefaultElementShape,
14
11
  DefaultThemeColor,
15
- } from '../types'
12
+ } from '../types/element'
16
13
  import { DefaultShapeSize } from '../types/element'
14
+ import {
15
+ type IconUrl,
16
+ type ProjectId,
17
+ type Tag as C4Tag,
18
+ splitGlobalFqn,
19
+ } from '../types/scalars'
20
+ import type { ThemeColor } from '../types/theme'
17
21
  import { commonAncestor, hierarchyLevel, isAncestor, sortNaturalByFqn } from '../utils'
18
22
  import { type DeployedInstancesIterator } from './DeploymentElementModel'
19
23
  import type { LikeC4Model } from './LikeC4Model'
@@ -28,13 +32,28 @@ export class ElementModel<M extends AnyAux = AnyAux> {
28
32
  readonly _literalId: M['Element']
29
33
  readonly hierarchyLevel: number
30
34
 
35
+ readonly imported: null | {
36
+ from: ProjectId
37
+ fqn: string
38
+ }
39
+
31
40
  constructor(
32
41
  public readonly $model: LikeC4Model<M>,
33
42
  public readonly $element: C4Element,
34
43
  ) {
35
44
  this.id = this.$element.id
36
45
  this._literalId = this.$element.id
37
- this.hierarchyLevel = hierarchyLevel(this.id)
46
+ const [projectId, fqn] = splitGlobalFqn(this.id)
47
+ if (projectId) {
48
+ this.imported = {
49
+ from: projectId,
50
+ fqn,
51
+ }
52
+ this.hierarchyLevel = hierarchyLevel(fqn)
53
+ } else {
54
+ this.imported = null
55
+ this.hierarchyLevel = hierarchyLevel(this.id)
56
+ }
38
57
  }
39
58
 
40
59
  get parent(): ElementModel<M> | null {
@@ -1,15 +1,17 @@
1
- import { mapValues, pipe, sort, values } from 'remeda'
1
+ import { entries, mapValues, pipe, sort, values } from 'remeda'
2
2
  import type { LiteralUnion } from 'type-fest'
3
3
  import { computeView, unsafeComputeView } from '../compute-view'
4
4
  import type { ComputeViewResult } from '../compute-view/compute-view'
5
5
  import { invariant, nonNullable } from '../errors'
6
- import type { ComputedView, DiagramView, IteratorLike, LikeC4View, ModelGlobals } from '../types'
6
+ import type { IteratorLike } from '../types/_common'
7
7
  import type { Element } from '../types/element'
8
- import { type Tag as C4Tag } from '../types/element'
8
+ import type { ModelGlobals } from '../types/global'
9
9
  import type { AnyParsedLikeC4ModelData, GenericLikeC4ModelData, LikeC4ModelDump } from '../types/model-data'
10
10
  import type { ModelRelation } from '../types/relation'
11
+ import { type ProjectId, type Tag as C4Tag, GlobalFqn, isGlobalFqn } from '../types/scalars'
12
+ import type { ComputedView, DiagramView, LikeC4View } from '../types/view'
11
13
  import { compareNatural } from '../utils'
12
- import { ancestorsFqn, commonAncestor, parentFqn } from '../utils/fqn'
14
+ import { ancestorsFqn, commonAncestor, parentFqn, sortParentsFirst } from '../utils/fqn'
13
15
  import { DefaultMap } from '../utils/mnemonist'
14
16
  import type {
15
17
  DeployedInstanceModel,
@@ -121,6 +123,14 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
121
123
  this.#allTags.get(tag).add(el)
122
124
  }
123
125
  }
126
+ for (const [projectId, elements] of entries($model.imports)) {
127
+ for (const element of sortParentsFirst(elements)) {
128
+ const el = this.addImportedElement(projectId as ProjectId, element)
129
+ for (const tag of el.tags) {
130
+ this.#allTags.get(tag).add(el)
131
+ }
132
+ }
133
+ }
124
134
  for (const relation of values($model.relations)) {
125
135
  const el = this.addRelation(relation)
126
136
  for (const tag of el.tags) {
@@ -365,7 +375,7 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
365
375
  if (this.#elements.has(element.id)) {
366
376
  throw new Error(`Element ${element.id} already exists`)
367
377
  }
368
- const el = new ElementModel(this, Object.freeze(structuredClone(element)))
378
+ const el = new ElementModel(this, Object.freeze(element))
369
379
  this.#elements.set(el.id, el)
370
380
  const parentId = parentFqn(el.id)
371
381
  if (parentId) {
@@ -378,13 +388,42 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
378
388
  return el
379
389
  }
380
390
 
391
+ private addImportedElement(projectId: ProjectId, element: Element) {
392
+ invariant(!isGlobalFqn(element.id), `Imported element already has global FQN`)
393
+ const id = GlobalFqn(projectId, element.id)
394
+ if (this.#elements.has(id)) {
395
+ throw new Error(`Element ${id} already exists`)
396
+ }
397
+ const el = new ElementModel(
398
+ this,
399
+ Object.freeze({
400
+ ...element,
401
+ id,
402
+ }),
403
+ )
404
+ this.#elements.set(el.id, el)
405
+ let parentId = parentFqn(el.id)
406
+ while (parentId) {
407
+ // For imported elements - id has format `@projectId.fqn`
408
+ // We need to exclude `@projectId` from the parentId
409
+ if (parentId.includes('.') && this.#elements.has(parentId)) {
410
+ this.#parents.set(el.id, this.element(parentId))
411
+ this.#children.get(parentId).add(el)
412
+ return el
413
+ }
414
+ parentId = parentFqn(parentId)
415
+ }
416
+ this.#rootElements.add(el)
417
+ return el
418
+ }
419
+
381
420
  private addRelation(relation: ModelRelation) {
382
421
  if (this.#relations.has(relation.id)) {
383
422
  throw new Error(`Relation ${relation.id} already exists`)
384
423
  }
385
424
  const rel = new RelationshipModel(
386
425
  this,
387
- Object.freeze(structuredClone(relation)),
426
+ Object.freeze(relation),
388
427
  )
389
428
  const { source, target } = rel
390
429
  this.#relations.set(rel.id, rel)
@@ -418,6 +457,7 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
418
457
 
419
458
  export namespace LikeC4Model {
420
459
  export const EMPTY = LikeC4Model.create({
460
+ projectId: 'default' as ProjectId,
421
461
  specification: {
422
462
  elements: {},
423
463
  relationships: {},
@@ -436,6 +476,7 @@ export namespace LikeC4Model {
436
476
  elements: {},
437
477
  relations: {},
438
478
  views: {},
479
+ imports: {},
439
480
  }) as LikeC4Model<AnyAux>
440
481
 
441
482
  export type Any = Aux<
@@ -1,12 +1,14 @@
1
1
  import { isEmpty } from 'remeda'
2
- import type { Color, IteratorLike } from '../types'
3
- import type { Link, Tag } from '../types/element'
2
+ import type { IteratorLike } from '../types/_common'
3
+ import type { Link } from '../types/element'
4
4
  import {
5
5
  type ModelRelation,
6
6
  type RelationshipLineType,
7
7
  DefaultLineStyle,
8
8
  DefaultRelationshipColor,
9
9
  } from '../types/relation'
10
+ import type { Tag } from '../types/scalars'
11
+ import type { Color } from '../types/theme'
10
12
  import { commonAncestor } from '../utils/fqn'
11
13
  import type { DeploymentRelationModel } from './DeploymentElementModel'
12
14
  import type { ElementModel } from './ElementModel'
@@ -1,6 +1,7 @@
1
1
  import { nonNullable } from '../../errors'
2
- import type { IteratorLike } from '../../types'
3
- import type { Link, Tag } from '../../types/element'
2
+ import type { IteratorLike } from '../../types/_common'
3
+ import type { Link } from '../../types/element'
4
+ import type { Tag } from '../../types/scalars'
4
5
  import {
5
6
  type ComputedDeploymentView,
6
7
  type ComputedDynamicView,
@@ -1,13 +1,9 @@
1
1
  import type { Simplify, Tagged, UnionToIntersection } from 'type-fest'
2
2
 
3
- export type ProjectId = Tagged<string, 'ProjectID'>
4
-
5
3
  export type NonEmptyArray<T> = [T, ...T[]]
6
4
 
7
5
  export type NonEmptyReadonlyArray<T> = readonly [T, ...T[]]
8
6
 
9
- export type IconUrl = Tagged<string, 'IconUrl'> | 'none'
10
-
11
7
  export type CustomColor = string
12
8
 
13
9
  export type Point = readonly [x: number, y: number]
@@ -1,7 +1,8 @@
1
1
  import type { MergeExclusive, Simplify, Tagged } from 'type-fest'
2
- import type { IconUrl, NonEmptyArray } from './_common'
3
- import type { ElementShape, ElementStyle, Fqn, Link, Tag } from './element'
2
+ import type { NonEmptyArray } from './_common'
3
+ import type { ElementShape, ElementStyle, Link } from './element'
4
4
  import type { AbstractRelation, RelationId } from './relation'
5
+ import type { Fqn, IconUrl, Tag } from './scalars'
5
6
  import type { Color } from './theme'
6
7
 
7
8
  export type DeploymentNodeKind<Kinds extends string = string> = Tagged<Kinds, 'DeploymentNodeKind'>
@@ -1,14 +1,8 @@
1
1
  import type { Tagged, TupleToUnion } from 'type-fest'
2
- import type { IconUrl, NonEmptyArray } from './_common'
2
+ import type { NonEmptyArray } from './_common'
3
+ import type { Fqn, IconUrl, Tag } from './scalars'
3
4
  import type { Color, ShapeSize, SpacingSize, TextSize, ThemeColor } from './theme'
4
5
 
5
- // Full-qualified-name
6
- export type Fqn<Id extends string = string> = Tagged<Id, 'Fqn'>
7
-
8
- export function AsFqn(name: string, parent?: Fqn | null) {
9
- return (parent ? parent + '.' + name : name) as Fqn
10
- }
11
-
12
6
  export const BorderStyles = ['solid', 'dashed', 'dotted', 'none'] as const
13
7
 
14
8
  export type BorderStyle = TupleToUnion<typeof BorderStyles>
@@ -60,8 +54,6 @@ export interface ElementStyle {
60
54
  readonly textSize?: TextSize
61
55
  }
62
56
 
63
- export type Tag<Tags extends string = string> = Tagged<Tags, 'Tag'>
64
-
65
57
  export interface TagSpec {
66
58
  readonly id: Tag
67
59
  readonly style: ElementStyle
@@ -0,0 +1,314 @@
1
+ import type { ExclusiveUnion } from './_common'
2
+ import type { PredicateSelector } from './deployments'
3
+ import type { BorderStyle, ElementKind, ElementShape } from './element'
4
+ import type { WhereOperator } from './operators'
5
+ import type { RelationshipArrowType, RelationshipLineType } from './relation'
6
+ import { type Fqn, type IconUrl, type ProjectId, type Tag, GlobalFqn } from './scalars'
7
+ import type { Color, ShapeSize } from './theme'
8
+ import type { ViewId } from './view'
9
+
10
+ export namespace ModelLayer {
11
+ export namespace FqnRef {
12
+ /**
13
+ * Reference to logical model element
14
+ */
15
+ export type ModelRef<F = Fqn> = {
16
+ model: F
17
+ }
18
+ export const isModelRef = (ref: FqnRef): ref is ModelRef => {
19
+ return 'model' in ref && !('project' in ref)
20
+ }
21
+
22
+ /**
23
+ * Reference to imported logical model element
24
+ */
25
+ export type ImportRef<F = Fqn> = {
26
+ project: ProjectId
27
+ model: F
28
+ }
29
+ export const isImportRef = (ref: FqnRef): ref is ImportRef => {
30
+ return 'project' in ref && 'model' in ref
31
+ }
32
+
33
+ export const toFqn = (ref: FqnRef): Fqn => {
34
+ if (isImportRef(ref)) {
35
+ return GlobalFqn(ref.project, ref.model)
36
+ }
37
+ if (isModelRef(ref)) {
38
+ return ref.model
39
+ }
40
+ throw new Error('Expected FqnRef.ModelRef or FqnRef.ImportRef')
41
+ }
42
+ }
43
+
44
+ export type FqnRef<M = Fqn> = ExclusiveUnion<{
45
+ ModelRef: FqnRef.ModelRef<M>
46
+ ImportRef: FqnRef.ImportRef<M>
47
+ }>
48
+
49
+ export namespace FqnExpr {
50
+ export type Wildcard = {
51
+ wildcard: true
52
+ }
53
+ export const isWildcard = (expr: Expression): expr is FqnExpr.Wildcard => {
54
+ return 'wildcard' in expr && expr.wildcard === true
55
+ }
56
+
57
+ export type ModelRef<M = Fqn> = {
58
+ ref: FqnRef.ModelRef<M> | FqnRef.ImportRef<M>
59
+ selector?: PredicateSelector
60
+ }
61
+ export const isModelRef = (ref: Expression): ref is FqnExpr.ModelRef => {
62
+ return 'ref' in ref
63
+ }
64
+
65
+ export type ElementKindExpr = {
66
+ elementKind: ElementKind
67
+ isEqual: boolean
68
+ }
69
+ export function isElementKindExpr(expr: Expression): expr is ElementKindExpr {
70
+ return 'elementKind' in expr && 'isEqual' in expr
71
+ }
72
+
73
+ export type ElementTagExpr = {
74
+ elementTag: Tag
75
+ isEqual: boolean
76
+ }
77
+ export function isElementTagExpr(expr: Expression): expr is ElementTagExpr {
78
+ return 'elementTag' in expr && 'isEqual' in expr
79
+ }
80
+
81
+ export type NonWildcard<M = Fqn> = ExclusiveUnion<{
82
+ ModelRef: ModelRef<M>
83
+ ElementKind: ElementKindExpr
84
+ ElementTag: ElementTagExpr
85
+ }>
86
+
87
+ export type Where<M = Fqn> = {
88
+ where: {
89
+ expr: ExclusiveUnion<{
90
+ Wildcard: Wildcard
91
+ ModelRef: ModelRef<M>
92
+ ElementKind: ElementKindExpr
93
+ ElementTag: ElementTagExpr
94
+ }>
95
+ condition: WhereOperator<string, string>
96
+ }
97
+ }
98
+
99
+ export const isWhere = (expr: Expression): expr is FqnExpr.Where => {
100
+ return 'where' in expr && is(expr.where.expr)
101
+ }
102
+
103
+ export type Custom<M = Fqn> = {
104
+ custom: {
105
+ expr: FqnExprOrWhere<M>
106
+ title?: string
107
+ description?: string
108
+ technology?: string
109
+ notation?: string
110
+ shape?: ElementShape
111
+ color?: Color
112
+ icon?: IconUrl
113
+ border?: BorderStyle
114
+ opacity?: number
115
+ navigateTo?: ViewId
116
+ multiple?: boolean
117
+ size?: ShapeSize
118
+ padding?: ShapeSize
119
+ textSize?: ShapeSize
120
+ }
121
+ }
122
+
123
+ export const isCustom = (expr: Expression): expr is FqnExpr.Custom => {
124
+ return 'custom' in expr && (is(expr.custom.expr) || isWhere(expr.custom.expr))
125
+ }
126
+
127
+ export const is = (expr: Expression): expr is FqnExpr => {
128
+ return isWildcard(expr)
129
+ || isModelRef(expr)
130
+ || isElementKindExpr(expr)
131
+ || isElementTagExpr(expr)
132
+ }
133
+ }
134
+
135
+ export type FqnExpr<M = Fqn> = ExclusiveUnion<{
136
+ Wildcard: FqnExpr.Wildcard
137
+ ModelRef: FqnExpr.ModelRef<M>
138
+ ElementKind: FqnExpr.ElementKindExpr
139
+ ElementTag: FqnExpr.ElementTagExpr
140
+ }>
141
+
142
+ export type FqnExprOrWhere<M = Fqn> = ExclusiveUnion<{
143
+ Wildcard: FqnExpr.Wildcard
144
+ ModelRef: FqnExpr.ModelRef<M>
145
+ ElementKind: FqnExpr.ElementKindExpr
146
+ ElementTag: FqnExpr.ElementTagExpr
147
+ Where: FqnExpr.Where<M>
148
+ }>
149
+
150
+ export type AnyFqnExpr<M = Fqn> = ExclusiveUnion<{
151
+ Wildcard: FqnExpr.Wildcard
152
+ ModelRef: FqnExpr.ModelRef<M>
153
+ ElementKind: FqnExpr.ElementKindExpr
154
+ ElementTag: FqnExpr.ElementTagExpr
155
+ Where: FqnExpr.Where<M>
156
+ Custom: FqnExpr.Custom<M>
157
+ }>
158
+
159
+ export function isAnyFqnExpr(expr: Expression): expr is AnyFqnExpr {
160
+ return FqnExpr.is(expr)
161
+ || FqnExpr.isWhere(expr)
162
+ || FqnExpr.isCustom(expr)
163
+ }
164
+
165
+ export namespace RelationExpr {
166
+ export type Direct<M = Fqn> = {
167
+ source: FqnExpr<M>
168
+ target: FqnExpr<M>
169
+ isBidirectional?: boolean
170
+ }
171
+ export const isDirect = (expr: Expression): expr is RelationExpr.Direct => {
172
+ return 'source' in expr && 'target' in expr
173
+ }
174
+ export type Incoming<M = Fqn> = {
175
+ incoming: FqnExpr<M>
176
+ }
177
+ export const isIncoming = (expr: Expression): expr is RelationExpr.Incoming => {
178
+ return 'incoming' in expr
179
+ }
180
+ export type Outgoing<M = Fqn> = {
181
+ outgoing: FqnExpr<M>
182
+ }
183
+ export const isOutgoing = (expr: Expression): expr is RelationExpr.Outgoing => {
184
+ return 'outgoing' in expr
185
+ }
186
+ export type InOut<M = Fqn> = {
187
+ inout: FqnExpr<M>
188
+ }
189
+ export const isInOut = (expr: Expression): expr is RelationExpr.InOut => {
190
+ return 'inout' in expr
191
+ }
192
+ export type Where<M = Fqn> = {
193
+ where: {
194
+ expr: ExclusiveUnion<{
195
+ Direct: RelationExpr.Direct<M>
196
+ Incoming: RelationExpr.Incoming<M>
197
+ Outgoing: RelationExpr.Outgoing<M>
198
+ InOut: RelationExpr.InOut<M>
199
+ }>
200
+ condition: WhereOperator<string, string>
201
+ }
202
+ }
203
+ export const isWhere = (expr: Expression): expr is RelationExpr.Where => {
204
+ return 'where' in expr && is(expr.where.expr)
205
+ }
206
+
207
+ export type Custom<M = Fqn> = {
208
+ customRelation: {
209
+ expr: RelationExprOrWhere<M>
210
+ title?: string
211
+ description?: string
212
+ technology?: string
213
+ notation?: string
214
+ // Link to dynamic view
215
+ navigateTo?: ViewId
216
+ // Notes for walkthrough
217
+ notes?: string
218
+ color?: Color
219
+ line?: RelationshipLineType
220
+ head?: RelationshipArrowType
221
+ tail?: RelationshipArrowType
222
+ }
223
+ }
224
+
225
+ export const isCustom = (expr: Expression): expr is Custom => {
226
+ return 'customRelation' in expr
227
+ }
228
+
229
+ export const is = (expr: Expression): expr is RelationExpr => {
230
+ return isDirect(expr)
231
+ || isIncoming(expr)
232
+ || isOutgoing(expr)
233
+ || isInOut(expr)
234
+ }
235
+ }
236
+
237
+ export type RelationExpr<M = Fqn> = ExclusiveUnion<{
238
+ Direct: RelationExpr.Direct<M>
239
+ Incoming: RelationExpr.Incoming<M>
240
+ Outgoing: RelationExpr.Outgoing<M>
241
+ InOut: RelationExpr.InOut<M>
242
+ }>
243
+
244
+ export type RelationExprOrWhere<M = Fqn> = ExclusiveUnion<{
245
+ Direct: RelationExpr.Direct<M>
246
+ Incoming: RelationExpr.Incoming<M>
247
+ Outgoing: RelationExpr.Outgoing<M>
248
+ InOut: RelationExpr.InOut<M>
249
+ Where: RelationExpr.Where<M>
250
+ }>
251
+
252
+ export type AnyRelationExpr<M = Fqn> = ExclusiveUnion<{
253
+ Direct: RelationExpr.Direct<M>
254
+ Incoming: RelationExpr.Incoming<M>
255
+ Outgoing: RelationExpr.Outgoing<M>
256
+ InOut: RelationExpr.InOut<M>
257
+ Where: RelationExpr.Where<M>
258
+ CustomRelation: RelationExpr.Custom<M>
259
+ }>
260
+
261
+ export function isAnyRelationExpr(expr: Expression): expr is AnyRelationExpr {
262
+ return RelationExpr.is(expr)
263
+ || RelationExpr.isWhere(expr)
264
+ || RelationExpr.isCustom(expr)
265
+ }
266
+
267
+ /**
268
+ * Represents a version 2 expression which can be one of several types.
269
+ *
270
+ * @template M - The type for the model FQN, defaults to `Fqn`.
271
+ */
272
+ export type Expression<M = Fqn> = ExclusiveUnion<{
273
+ Wildcard: FqnExpr.Wildcard
274
+ ModelRef: FqnExpr.ModelRef<M>
275
+ ElementKind: FqnExpr.ElementKindExpr
276
+ ElementTag: FqnExpr.ElementTagExpr
277
+ Custom: FqnExpr.Custom<M>
278
+ Direct: RelationExpr.Direct<M>
279
+ Incoming: RelationExpr.Incoming<M>
280
+ Outgoing: RelationExpr.Outgoing<M>
281
+ InOut: RelationExpr.InOut<M>
282
+ CustomRelation: RelationExpr.Custom<M>
283
+ Where: Expression.Where<M>
284
+ }>
285
+
286
+ export function isExpression(expr: any): expr is Expression {
287
+ return isAnyFqnExpr(expr)
288
+ || isAnyRelationExpr(expr)
289
+ }
290
+
291
+ export namespace Expression {
292
+ export type Where<M = Fqn> = FqnExpr.Where<M> | RelationExpr.Where<M>
293
+
294
+ export const isWhere = (expr: Expression): expr is Expression.Where => {
295
+ return 'where' in expr
296
+ }
297
+
298
+ export const isCustomFqnExpr = (expr: Expression): expr is FqnExpr.Custom => {
299
+ return FqnExpr.isCustom(expr)
300
+ }
301
+
302
+ export const isCustomRelationExpr = (expr: Expression): expr is RelationExpr.Custom => {
303
+ return RelationExpr.isCustom(expr)
304
+ }
305
+
306
+ export const isFqnExpr = (expr: Expression): expr is FqnExpr => {
307
+ return FqnExpr.is(expr)
308
+ }
309
+
310
+ export const isRelation = (expr: Expression): expr is RelationExpr => {
311
+ return RelationExpr.is(expr)
312
+ }
313
+ }
314
+ }