@likec4/core 1.32.1 → 1.33.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 (101) hide show
  1. package/dist/builder/index.d.mts +10 -6
  2. package/dist/builder/index.mjs +45 -13
  3. package/dist/compute-view/index.d.mts +6 -4
  4. package/dist/compute-view/index.mjs +33 -10
  5. package/dist/compute-view/relationships-view/index.d.mts +6 -4
  6. package/dist/compute-view/relationships-view/index.mjs +6 -3
  7. package/dist/index.d.mts +9 -138
  8. package/dist/index.mjs +31 -3712
  9. package/dist/model/connection/deployment/index.d.mts +10 -0
  10. package/dist/model/connection/deployment/index.mjs +18 -0
  11. package/dist/model/connection/index.d.mts +70 -0
  12. package/dist/model/connection/index.mjs +23 -0
  13. package/dist/model/connection/model/index.d.mts +10 -0
  14. package/dist/model/connection/model/index.mjs +11 -0
  15. package/dist/model/index.d.mts +14 -256
  16. package/dist/model/index.mjs +27 -5
  17. package/dist/shared/{core.DXzRBkuI.d.mts → core.1pNeDdRk.d.mts} +92 -81
  18. package/dist/shared/{core.BuO4ncfY.d.mts → core.668kXpZq.d.mts} +42 -52
  19. package/dist/shared/core.8KM0D6va.mjs +33 -0
  20. package/dist/shared/core.B0oEIXqd.mjs +47 -0
  21. package/dist/shared/core.BH9nN-_I.mjs +22 -0
  22. package/dist/shared/core.BMCb0wJI.mjs +177 -0
  23. package/dist/shared/core.BTnf14gD.mjs +331 -0
  24. package/dist/shared/core.BgvIuGGU.mjs +3 -0
  25. package/dist/shared/core.BoyJPEen.mjs +21 -0
  26. package/dist/shared/{core.CUYDgv2-.mjs → core.Br7Fab9l.mjs} +12 -15
  27. package/dist/shared/{core.BrCnZFTn.mjs → core.Btl47GOn.mjs} +3 -3
  28. package/dist/shared/core.ByrmCl9I.mjs +5 -0
  29. package/dist/shared/core.C80Z37Eu.mjs +29 -0
  30. package/dist/shared/{core.Pf5I9o0e.d.mts → core.CF82MWyh.d.mts} +1 -1
  31. package/dist/shared/{core.rmvsy0n3.mjs → core.C_C-3c1S.mjs} +5 -198
  32. package/dist/shared/core.CeQbbp7K.d.mts +41 -0
  33. package/dist/shared/{core.DoK86JEe.mjs → core.CpwrZi3D.mjs} +2 -73
  34. package/dist/shared/{core.CpXlRyE5.mjs → core.CtKXPqN_.mjs} +5 -36
  35. package/dist/shared/core.CxGKeL-L.mjs +144 -0
  36. package/dist/shared/core.CzdwsPmY.d.mts +69 -0
  37. package/dist/shared/{core.Cy9smucx.mjs → core.D4npX2q8.mjs} +1 -3
  38. package/dist/shared/core.D56nCp7e.mjs +168 -0
  39. package/dist/shared/core.D5qVzkws.mjs +724 -0
  40. package/dist/shared/core.DDAPzd3T.d.mts +87 -0
  41. package/dist/shared/{core.CBpKAlhj.mjs → core.DDynPqbh.mjs} +96 -56
  42. package/dist/shared/{core.Bk7wEZFG.mjs → core.DXRAwwDB.mjs} +3 -2
  43. package/dist/shared/core.DcS-0zys.mjs +10 -0
  44. package/dist/shared/{core.BaYE_r29.d.mts → core.De___iBS.d.mts} +4 -3
  45. package/dist/shared/core.DsMrjY2q.mjs +34 -0
  46. package/dist/shared/core.DuSLYTG6.d.mts +57 -0
  47. package/dist/shared/core.Dy-F3xRP.d.mts +83 -0
  48. package/dist/shared/{core.znRaUX_A.d.mts → core.OLInHaJx.d.mts} +48 -41
  49. package/dist/shared/core.XptQjhMA.mjs +1295 -0
  50. package/dist/shared/{core.cbA9VZ02.mjs → core.hoq9W4rW.mjs} +28 -1073
  51. package/dist/shared/core.zLlww-07.mjs +3 -0
  52. package/dist/theme/index.d.mts +134 -0
  53. package/dist/theme/index.mjs +3709 -0
  54. package/dist/types/aux.d.mts +3 -2
  55. package/dist/types/index.d.mts +9 -5
  56. package/dist/types/index.mjs +17 -146
  57. package/dist/utils/index.d.mts +16 -100
  58. package/dist/utils/index.mjs +25 -1312
  59. package/dist/utils/iterable/index.d.mts +100 -0
  60. package/dist/utils/iterable/index.mjs +24 -0
  61. package/package.json +26 -10
  62. package/src/builder/Builder.ts +13 -4
  63. package/src/builder/__snapshots__/Builder-style1.spec.ts.snap +0 -13
  64. package/src/builder/__snapshots__/Builder-style2.compute-model.json5 +2 -6
  65. package/src/builder/__snapshots__/Builder-style2.spec.ts.snap +0 -7
  66. package/src/builder/_types.ts +6 -2
  67. package/src/compute-view/deployment-view/__test__/fixture.ts +15 -5
  68. package/src/compute-view/deployment-view/utils.ts +13 -13
  69. package/src/compute-view/dynamic-view/compute.ts +2 -1
  70. package/src/compute-view/element-view/__test__/__snapshots__/legacy.spec.ts.snap +2 -2
  71. package/src/compute-view/relationships-view/layout.ts +1 -1
  72. package/src/compute-view/utils/applyCustomElementProperties.ts +8 -0
  73. package/src/compute-view/utils/applyCustomRelationProperties.ts +2 -0
  74. package/src/compute-view/utils/buildComputedNodes.ts +4 -3
  75. package/src/compute-view/utils/merge-props-from-relationships.ts +6 -5
  76. package/src/compute-view/utils/view-hash.ts +24 -3
  77. package/src/model/DeploymentElementModel.ts +22 -18
  78. package/src/model/ElementModel.ts +14 -10
  79. package/src/model/RelationModel.ts +21 -21
  80. package/src/model/index.ts +4 -0
  81. package/src/model/types.ts +11 -0
  82. package/src/model/view/EdgeModel.ts +6 -5
  83. package/src/model/view/LikeC4ViewModel.ts +10 -4
  84. package/src/model/view/NodeModel.ts +6 -4
  85. package/src/theme/index.ts +12 -5
  86. package/src/types/RichText.ts +200 -0
  87. package/src/types/aux.ts +1 -1
  88. package/src/types/index.ts +2 -0
  89. package/src/types/model-deployment.ts +4 -3
  90. package/src/types/model-logical.ts +3 -3
  91. package/src/types/scalar.ts +47 -2
  92. package/src/types/view-common.ts +1 -1
  93. package/src/types/view-computed.ts +2 -4
  94. package/src/types/view-layouted.ts +9 -0
  95. package/src/types/view-parsed.deployment.ts +0 -1
  96. package/src/types/view.ts +0 -27
  97. package/src/utils/index.ts +5 -10
  98. package/src/utils/markdown/index.ts +2 -0
  99. package/src/utils/markdown/to-html.ts +32 -0
  100. package/src/utils/markdown/to-text.ts +14 -0
  101. package/src/utils/promises.ts +4 -0
@@ -317,7 +317,7 @@ export function layoutRelationshipsView(data: RelationshipsViewData): Pick<Diagr
317
317
  title: element.title,
318
318
  ...position,
319
319
  position: [position.x, position.y],
320
- description: element.description,
320
+ description: element.description?.$source ?? null,
321
321
  technology: element.technology,
322
322
  tags: [],
323
323
  links: null,
@@ -47,6 +47,7 @@ export function applyCustomElementProperties<A extends AnyAux>(
47
47
  padding,
48
48
  size,
49
49
  textSize,
50
+ description,
50
51
  ...rest
51
52
  } = omitBy(props, isNullish)
52
53
  const notEmpty = !isEmpty(rest)
@@ -62,6 +63,13 @@ export function applyCustomElementProperties<A extends AnyAux>(
62
63
  ...rest,
63
64
  }
64
65
  }
66
+ if (description !== undefined) {
67
+ node = {
68
+ ...node,
69
+ isCustomized: true,
70
+ description: { txt: description },
71
+ }
72
+ }
65
73
 
66
74
  let styleOverride: ComputedNode['style'] | undefined
67
75
  if (border !== undefined) {
@@ -18,6 +18,7 @@ export function applyCustomRelationProperties<A extends AnyAux>(
18
18
  customRelation: {
19
19
  expr,
20
20
  title,
21
+ description,
21
22
  ...customprops
22
23
  },
23
24
  } of rules
@@ -44,6 +45,7 @@ export function applyCustomRelationProperties<A extends AnyAux>(
44
45
  edges[i] = {
45
46
  ...edge,
46
47
  ...props,
48
+ ...description && { description: { txt: description } },
47
49
  label: title ?? edge.label,
48
50
  isCustomized: true,
49
51
  }
@@ -29,8 +29,8 @@ function updateDepthOfAncestors(node: ComputedNode, nodes: ReadonlyMap<string, C
29
29
  }
30
30
 
31
31
  export type ComputedNodeSource<A extends AnyAux = Unknown> = Simplify<
32
- & Pick<ComputedNode<A>, 'id' | 'title' | 'kind' | 'deploymentRef' | 'modelRef'>
33
- & Partial<Omit<Element<A>, 'id' | 'title' | 'kind'>>
32
+ & Pick<ComputedNode<A>, 'id' | 'title' | 'description' | 'kind' | 'deploymentRef' | 'modelRef'>
33
+ & Partial<Omit<Element<A>, 'id' | 'title' | 'description' | 'kind'>>
34
34
  >
35
35
 
36
36
  export function elementModelToNodeSource<A extends AnyAux>(el: ElementModel<A>): ComputedNodeSource<A> {
@@ -86,7 +86,7 @@ export function buildComputedNodes<A extends AnyAux>(
86
86
  // Ensure that parent nodes are created before child nodes
87
87
  Array.from(elements)
88
88
  .sort(compareByFqnHierarchically)
89
- .forEach(({ id, style, kind, title, color, shape, tags, ...el }) => {
89
+ .forEach(({ id, style, kind, title, color, shape, tags, notation, ...el }) => {
90
90
  let parent = parentFqn(id)
91
91
  let level = 0
92
92
  let parentNd: ComputedNode<A> | undefined
@@ -130,6 +130,7 @@ export function buildComputedNodes<A extends AnyAux>(
130
130
  children: [],
131
131
  inEdges: [],
132
132
  outEdges: [],
133
+ ...notation && { notation },
133
134
  ...el,
134
135
  style: {
135
136
  ...style,
@@ -1,4 +1,4 @@
1
- import { isTruthy, only, pickBy, pipe, reduce, unique } from 'remeda'
1
+ import { isDeepEqual, isTruthy, only, pickBy, pipe, reduce, unique } from 'remeda'
2
2
  import type {
3
3
  AnyAux,
4
4
  aux,
@@ -9,12 +9,13 @@ import type {
9
9
  Relationship,
10
10
  RelationshipArrowType,
11
11
  RelationshipLineType,
12
+ scalar,
12
13
  } from '../../types'
13
14
  import { isNonEmptyArray } from '../../utils'
14
15
 
15
16
  function pickRelationshipProps<A extends AnyAux>(relation: Relationship<A> | DeploymentRelationship<A>): {
16
17
  title?: string
17
- description?: string | null
18
+ description?: scalar.MarkdownOrString | null
18
19
  technology: string | null
19
20
  kind: aux.RelationKind<A> | null
20
21
  color: Color | null
@@ -52,7 +53,7 @@ function pickRelationshipProps<A extends AnyAux>(relation: Relationship<A> | Dep
52
53
 
53
54
  export type MergedRelationshipProps<A extends AnyAux> = {
54
55
  title?: string | null
55
- description?: string
56
+ description?: scalar.MarkdownOrString | null
56
57
  technology?: string
57
58
  kind?: aux.RelationKind<A>
58
59
  color?: Color
@@ -80,7 +81,7 @@ export function mergePropsFromRelationships<A extends AnyAux>(
80
81
  if (isTruthy(r.title) && !acc.title.includes(r.title)) {
81
82
  acc.title.push(r.title)
82
83
  }
83
- if (isTruthy(r.description) && !acc.description.includes(r.description)) {
84
+ if (isTruthy(r.description) && !acc.description.some(isDeepEqual(r.description))) {
84
85
  acc.description.push(r.description)
85
86
  }
86
87
  if (isTruthy(r.technology) && !acc.technology.includes(r.technology)) {
@@ -114,7 +115,7 @@ export function mergePropsFromRelationships<A extends AnyAux>(
114
115
  },
115
116
  {
116
117
  title: [] as string[],
117
- description: [] as string[],
118
+ description: [] as scalar.MarkdownOrString[],
118
119
  technology: [] as string[],
119
120
  kind: [] as aux.RelationKind<A>[],
120
121
  head: [] as RelationshipArrowType[],
@@ -10,12 +10,33 @@ export function calcViewLayoutHash<V extends ComputedView>(view: SetOptional<V,
10
10
  autoLayout: view.autoLayout,
11
11
  nodes: pipe(
12
12
  view.nodes,
13
- map(pick(['id', 'title', 'description', 'technology', 'shape', 'icon', 'children'])),
14
- mapToObj(({ id, icon, ...node }) => [id, { ...node, icon: isTruthy(icon) ? 'Y' : 'N' }]),
13
+ map(n => ({
14
+ id: n.id,
15
+ icon: isTruthy(n.icon) ? 'Y' : 'N',
16
+ title: n.title,
17
+ description: n.description?.md ?? n.description?.txt ?? null,
18
+ technology: n.technology ?? null,
19
+ shape: n.shape,
20
+ size: n.style.size ?? null,
21
+ textSize: n.style.textSize ?? null,
22
+ padding: n.style.padding ?? null,
23
+ children: n.children,
24
+ })),
25
+ mapToObj(({ id, ...node }) => [id, node]),
15
26
  ),
16
27
  edges: pipe(
17
28
  view.edges,
18
- map(pick(['source', 'target', 'label', 'description', 'technology', 'dir', 'head', 'tail', 'line'])),
29
+ map(e => ({
30
+ source: e.source,
31
+ target: e.target,
32
+ label: e.label,
33
+ description: e.description?.md ?? e.description?.txt ?? null,
34
+ technology: e.technology ?? null,
35
+ dir: e.dir,
36
+ head: e.head,
37
+ tail: e.tail,
38
+ line: e.line,
39
+ })),
19
40
  mapToObj(({ source, target, ...edge }) => [`${source}:${target}`, edge]),
20
41
  ),
21
42
  }
@@ -1,4 +1,4 @@
1
- import { isTruthy, only, unique } from 'remeda'
1
+ import { isEmpty, isTruthy, only, unique } from 'remeda'
2
2
  import type { SetRequired } from 'type-fest'
3
3
  import {
4
4
  type Any,
@@ -12,11 +12,13 @@ import {
12
12
  type IteratorLike,
13
13
  type Link,
14
14
  type RelationshipLineType,
15
+ type RichTextOrEmpty,
15
16
  type scalar,
16
17
  DefaultElementShape,
17
18
  DefaultLineStyle,
18
19
  DefaultShapeSize,
19
20
  DefaultThemeColor,
21
+ RichText,
20
22
  } from '../types'
21
23
  import * as aux from '../types/aux'
22
24
  import { commonAncestor, hierarchyLevel, memoizeProp, nonNullable } from '../utils'
@@ -24,7 +26,7 @@ import { difference, intersection, union } from '../utils/set'
24
26
  import type { LikeC4DeploymentModel } from './DeploymentModel'
25
27
  import type { ElementModel } from './ElementModel'
26
28
  import type { AnyRelationshipModel, RelationshipModel, RelationshipsIterator } from './RelationModel'
27
- import type { IncomingFilter, OutgoingFilter } from './types'
29
+ import type { IncomingFilter, OutgoingFilter, WithMetadata, WithTags } from './types'
28
30
  import type { LikeC4ViewModel } from './view/LikeC4ViewModel'
29
31
 
30
32
  export type DeploymentElementsIterator<A extends Any> = IteratorLike<DeploymentElementModel<A>>
@@ -33,7 +35,7 @@ export type DeploymentNodesIterator<A extends Any> = IteratorLike<DeploymentNode
33
35
 
34
36
  export type DeploymentElementModel<A extends Any = Any> = DeploymentNodeModel<A> | DeployedInstanceModel<A>
35
37
 
36
- abstract class AbstractDeploymentElementModel<A extends Any> {
38
+ abstract class AbstractDeploymentElementModel<A extends Any> implements WithTags<A>, WithMetadata<A> {
37
39
  abstract readonly id: aux.DeploymentFqn<A>
38
40
  abstract readonly _literalId: aux.DeploymentId<A>
39
41
  abstract readonly parent: DeploymentNodeModel<A> | null
@@ -61,8 +63,8 @@ abstract class AbstractDeploymentElementModel<A extends Any> {
61
63
  return this.$node.style?.color as Color ?? DefaultThemeColor
62
64
  }
63
65
 
64
- get description(): string | null {
65
- return this.$node.description ?? null
66
+ get description(): RichTextOrEmpty {
67
+ return RichText.memoize(this, this.$node.description)
66
68
  }
67
69
 
68
70
  get technology(): string | null {
@@ -199,6 +201,10 @@ abstract class AbstractDeploymentElementModel<A extends Any> {
199
201
  ))
200
202
  }
201
203
 
204
+ public hasMetadata(): boolean {
205
+ return !!this.$node.metadata && !isEmpty(this.$node.metadata)
206
+ }
207
+
202
208
  public getMetadata(): aux.Metadata<A>
203
209
  public getMetadata(field: aux.MetadataKey<A>): string | undefined
204
210
  public getMetadata(field?: aux.MetadataKey<A>) {
@@ -402,12 +408,12 @@ export class DeployedInstanceModel<A extends Any = Any> extends AbstractDeployme
402
408
  return this.element.kind
403
409
  }
404
410
 
405
- override get description(): string | null {
406
- return this.$instance.description ?? this.element.description
411
+ override get description(): RichTextOrEmpty {
412
+ return RichText.memoize(this, this.$instance.description ?? this.element.$element.description)
407
413
  }
408
414
 
409
415
  override get technology(): string | null {
410
- return this.$instance.technology ?? this.element.technology
416
+ return this.$instance.technology ?? this.element.technology ?? null
411
417
  }
412
418
 
413
419
  override get links(): ReadonlyArray<Link> {
@@ -486,7 +492,7 @@ export class NestedElementOfDeployedInstanceModel<A extends Any = Any> {
486
492
  return this.element.title
487
493
  }
488
494
 
489
- get description(): string | null {
495
+ get description(): RichTextOrEmpty {
490
496
  return this.element.description
491
497
  }
492
498
 
@@ -536,17 +542,11 @@ export class DeploymentRelationModel<A extends Any = Any> implements AnyRelation
536
542
  }
537
543
 
538
544
  get technology(): string | null {
539
- if (!isTruthy(this.$relationship.technology)) {
540
- return null
541
- }
542
- return this.$relationship.technology
545
+ return this.$relationship.technology ?? null
543
546
  }
544
547
 
545
- get description(): string | null {
546
- if (!isTruthy(this.$relationship.description)) {
547
- return null
548
- }
549
- return this.$relationship.description
548
+ get description(): RichTextOrEmpty {
549
+ return RichText.memoize(this, this.$relationship.description)
550
550
  }
551
551
 
552
552
  get tags(): aux.Tags<A> {
@@ -590,6 +590,10 @@ export class DeploymentRelationModel<A extends Any = Any> implements AnyRelation
590
590
  return false
591
591
  }
592
592
 
593
+ public hasMetadata(): boolean {
594
+ return !!this.$relationship.metadata && !isEmpty(this.$relationship.metadata)
595
+ }
596
+
593
597
  public getMetadata(): aux.Metadata<A>
594
598
  public getMetadata(field: aux.MetadataKey<A>): string | undefined
595
599
  public getMetadata(field?: aux.MetadataKey<A>) {
@@ -1,4 +1,4 @@
1
- import { isTruthy, unique } from 'remeda'
1
+ import { isEmpty, isTruthy, unique } from 'remeda'
2
2
  import type { SetRequired } from 'type-fest'
3
3
  import type { Any, AnyAux, Color, IteratorLike } from '../types'
4
4
  import {
@@ -8,9 +8,11 @@ import {
8
8
  type IconUrl,
9
9
  type Link,
10
10
  type ProjectId,
11
+ type RichTextOrEmpty,
11
12
  DefaultElementShape,
12
13
  DefaultShapeSize,
13
14
  DefaultThemeColor,
15
+ RichText,
14
16
  splitGlobalFqn,
15
17
  } from '../types'
16
18
  import * as aux from '../types/aux'
@@ -18,12 +20,12 @@ import { commonAncestor, hierarchyLevel, ihead, isAncestor, memoizeProp, sortNat
18
20
  import { type DeployedInstancesIterator } from './DeploymentElementModel'
19
21
  import type { LikeC4Model } from './LikeC4Model'
20
22
  import type { RelationshipModel, RelationshipsIterator } from './RelationModel'
21
- import type { $ViewWithType, IncomingFilter, OutgoingFilter } from './types'
23
+ import type { IncomingFilter, OutgoingFilter, WithMetadata, WithTags } from './types'
22
24
  import type { LikeC4ViewModel } from './view/LikeC4ViewModel'
23
25
 
24
26
  export type ElementsIterator<M extends AnyAux> = IteratorLike<ElementModel<M>>
25
27
 
26
- export class ElementModel<A extends AnyAux = Any> {
28
+ export class ElementModel<A extends AnyAux = Any> implements WithTags<A>, WithMetadata<A> {
27
29
  readonly id: aux.Fqn<A>
28
30
  readonly _literalId: aux.ElementId<A>
29
31
  readonly hierarchyLevel: number
@@ -89,8 +91,8 @@ export class ElementModel<A extends AnyAux = Any> {
89
91
  return this.$element.title
90
92
  }
91
93
 
92
- get description(): string | null {
93
- return this.$element.description ?? null
94
+ get description(): RichTextOrEmpty {
95
+ return RichText.memoize(this, this.$element.description)
94
96
  }
95
97
 
96
98
  get technology(): string | null {
@@ -101,7 +103,7 @@ export class ElementModel<A extends AnyAux = Any> {
101
103
  return this.$element.links ?? []
102
104
  }
103
105
 
104
- get defaultView(): LikeC4ViewModel<A, $ViewWithType<A, 'element'> & { viewOf: aux.StrictFqn<A> }> | null {
106
+ get defaultView(): LikeC4ViewModel.ScopedElementView<A> | null {
105
107
  return memoizeProp(this, Symbol.for('defaultView'), () => ihead(this.scopedViews()) ?? null)
106
108
  }
107
109
 
@@ -242,11 +244,9 @@ export class ElementModel<A extends AnyAux = Any> {
242
244
  * Iterate over all views that scope this element.
243
245
  * It is possible that element is not included in the view.
244
246
  */
245
- public scopedViews(): ReadonlySet<
246
- LikeC4ViewModel<A, $ViewWithType<A, 'element'> & { viewOf: aux.StrictFqn<A> }>
247
- > {
247
+ public scopedViews(): ReadonlySet<LikeC4ViewModel.ScopedElementView<A>> {
248
248
  return memoizeProp(this, Symbol.for('scopedViews'), () => {
249
- const views = new Set<LikeC4ViewModel<A, $ViewWithType<A, 'element'> & { viewOf: aux.StrictFqn<A> }>>()
249
+ const views = new Set<LikeC4ViewModel.ScopedElementView<A>>()
250
250
  for (const vm of this.$model.views()) {
251
251
  if (vm.isScopedElementView() && vm.viewOf.id === this.id) {
252
252
  views.add(vm)
@@ -267,6 +267,10 @@ export class ElementModel<A extends AnyAux = Any> {
267
267
  return this.$model.deployment.instancesOf(this)
268
268
  }
269
269
 
270
+ public hasMetadata(): boolean {
271
+ return !!this.$element.metadata && !isEmpty(this.$element.metadata)
272
+ }
273
+
270
274
  public getMetadata(): aux.Metadata<A>
271
275
  public getMetadata(field: aux.MetadataKey<A>): string | undefined
272
276
  public getMetadata(field?: aux.MetadataKey<A>) {
@@ -1,11 +1,13 @@
1
- import { isTruthy } from 'remeda'
1
+ import { isEmpty, isTruthy } from 'remeda'
2
2
  import type { AnyAux, Color, IteratorLike, Link, scalar } from '../types'
3
3
  import {
4
4
  type Relationship,
5
5
  type RelationshipLineType,
6
+ type RichTextOrEmpty,
6
7
  DefaultLineStyle,
7
8
  DefaultRelationshipColor,
8
9
  FqnRef,
10
+ RichText,
9
11
  } from '../types'
10
12
  import type * as aux from '../types/aux'
11
13
  import { commonAncestor } from '../utils/fqn'
@@ -13,6 +15,7 @@ import type { DeploymentRelationModel } from './DeploymentElementModel'
13
15
  import type { ElementModel } from './ElementModel'
14
16
  import type { isDeploymentRelationModel } from './guards'
15
17
  import type { LikeC4Model } from './LikeC4Model'
18
+ import type { WithMetadata, WithTags } from './types'
16
19
  import type { LikeC4ViewModel, ViewsIterator } from './view/LikeC4ViewModel'
17
20
 
18
21
  export type RelationshipsIterator<A extends AnyAux> = IteratorLike<RelationshipModel<A>>
@@ -21,24 +24,20 @@ export type RelationshipsIterator<A extends AnyAux> = IteratorLike<RelationshipM
21
24
  * A relationship between two elements (in logical or deployment model)
22
25
  * use {@link isDeploymentRelationModel} guard to check if the relationship is a deployment relationship
23
26
  */
24
- export interface AnyRelationshipModel<A extends AnyAux> {
25
- get id(): scalar.RelationId
26
- get expression(): string
27
- get title(): string | null
28
- get technology(): string | null
29
- get description(): string | null
30
- get navigateTo(): LikeC4ViewModel<A> | null
31
- get tags(): aux.Tags<A>
32
- get kind(): aux.RelationKind<A> | null
33
- get links(): ReadonlyArray<Link>
34
- get color(): Color
35
- get line(): RelationshipLineType
27
+ export interface AnyRelationshipModel<A extends AnyAux> extends WithTags<A>, WithMetadata<A> {
28
+ readonly id: scalar.RelationId
29
+ readonly expression: string
30
+ readonly title: string | null
31
+ readonly technology: string | null
32
+ readonly description: RichTextOrEmpty
33
+ readonly navigateTo: LikeC4ViewModel<A> | null
34
+ readonly kind: aux.RelationKind<A> | null
35
+ readonly links: ReadonlyArray<Link>
36
+ readonly color: Color
37
+ readonly line: RelationshipLineType
36
38
  isDeploymentRelation(): this is DeploymentRelationModel<A>
37
39
  isModelRelation(): this is RelationshipModel<A>
38
- getMetadata(): aux.Metadata<A>
39
- getMetadata(field: aux.MetadataKey<A>): string | undefined
40
40
  views(): ViewsIterator<A>
41
- isTagged(tag: aux.LooseTag<A>): boolean
42
41
  }
43
42
 
44
43
  export class RelationshipModel<A extends AnyAux = AnyAux> implements AnyRelationshipModel<A> {
@@ -83,11 +82,8 @@ export class RelationshipModel<A extends AnyAux = AnyAux> implements AnyRelation
83
82
  return this.$relationship.technology
84
83
  }
85
84
 
86
- get description(): string | null {
87
- if (!isTruthy(this.$relationship.description)) {
88
- return null
89
- }
90
- return this.$relationship.description
85
+ get description(): RichTextOrEmpty {
86
+ return RichText.memoize(this, this.$relationship.description)
91
87
  }
92
88
 
93
89
  get navigateTo(): LikeC4ViewModel<A> | null {
@@ -134,6 +130,10 @@ export class RelationshipModel<A extends AnyAux = AnyAux> implements AnyRelation
134
130
  return true
135
131
  }
136
132
 
133
+ public hasMetadata(): boolean {
134
+ return !!this.$relationship.metadata && !isEmpty(this.$relationship.metadata)
135
+ }
136
+
137
137
  public getMetadata(): aux.Metadata<A>
138
138
  public getMetadata(field: aux.MetadataKey<A>): string | undefined
139
139
  public getMetadata(field?: aux.MetadataKey<A>) {
@@ -45,6 +45,8 @@ export type {
45
45
  IncomingFilter,
46
46
  NodeOrId,
47
47
  OutgoingFilter,
48
+ WithMetadata,
49
+ WithTags,
48
50
  } from './types'
49
51
 
50
52
  export * from './guards'
@@ -58,3 +60,5 @@ export type {
58
60
  AuxFromLikeC4ModelData,
59
61
  SpecAux,
60
62
  } from '../types'
63
+
64
+ export { RichText, type RichTextEmpty, type RichTextOrEmpty } from '../types'
@@ -61,3 +61,14 @@ export type $ModelData<A extends AnyAux> = {
61
61
  computed: ComputedLikeC4ModelData<A>
62
62
  layouted: LayoutedLikeC4ModelData<A>
63
63
  }[aux.Stage<A>]
64
+
65
+ export interface WithTags<A extends AnyAux> {
66
+ get tags(): aux.Tags<A>
67
+ isTagged(tag: aux.LooseTag<A>): boolean
68
+ }
69
+
70
+ export interface WithMetadata<A extends AnyAux> {
71
+ hasMetadata(): boolean
72
+ getMetadata(): aux.Metadata<A>
73
+ getMetadata(field: aux.MetadataKey<A>): string | undefined
74
+ }
@@ -11,15 +11,16 @@ import {
11
11
  extractStep,
12
12
  isStepEdgeId,
13
13
  } from '../../types'
14
+ import { type RichTextOrEmpty, RichText } from '../../types'
14
15
  import type { DeploymentRelationModel } from '../DeploymentElementModel'
15
16
  import type { RelationshipModel } from '../RelationModel'
16
- import type { $View } from '../types'
17
+ import type { $View, WithTags } from '../types'
17
18
  import type { LikeC4ViewModel } from './LikeC4ViewModel'
18
19
  import type { NodeModel } from './NodeModel'
19
20
 
20
21
  export type EdgesIterator<A extends Any, V extends $View<A>> = IteratorLike<EdgeModel<A, V>>
21
22
 
22
- export class EdgeModel<A extends Any = Any, View extends $View<A> = $View<A>> {
23
+ export class EdgeModel<A extends Any = Any, View extends $View<A> = $View<A>> implements WithTags<A> {
23
24
  #edge: ComputedEdge<A> | DiagramEdge<A>
24
25
  constructor(
25
26
  public readonly view: LikeC4ViewModel<A, View>,
@@ -39,11 +40,11 @@ export class EdgeModel<A extends Any = Any, View extends $View<A> = $View<A>> {
39
40
  }
40
41
 
41
42
  get label(): string | null {
42
- return this.#edge.label
43
+ return this.#edge.label ?? null
43
44
  }
44
45
 
45
- get description(): string | null {
46
- return this.#edge.description ?? null
46
+ get description(): RichTextOrEmpty {
47
+ return RichText.memoize(this, this.#edge.description)
47
48
  }
48
49
 
49
50
  get technology(): string | null {
@@ -8,6 +8,7 @@ import type {
8
8
  ViewWithType,
9
9
  } from '../../types'
10
10
  import { _stage, _type } from '../../types'
11
+ import { type RichTextOrEmpty, RichText } from '../../types'
11
12
  import type * as aux from '../../types/aux'
12
13
  import { DefaultMap, ifind, nonNullable } from '../../utils'
13
14
  import type { ElementModel } from '../ElementModel'
@@ -16,6 +17,7 @@ import {
16
17
  type $View,
17
18
  type EdgeOrId,
18
19
  type NodeOrId,
20
+ type WithTags,
19
21
  getId,
20
22
  } from '../types'
21
23
  import { type EdgesIterator, EdgeModel } from './EdgeModel'
@@ -25,7 +27,7 @@ export type ViewsIterator<A extends Any, V extends $View<A> = $View<A>> = Iterat
25
27
 
26
28
  export type InferViewType<V> = V extends AnyView<any> ? V[_type] : never
27
29
 
28
- export class LikeC4ViewModel<A extends Any = Any, V extends $View<A> = $View<A>> {
30
+ export class LikeC4ViewModel<A extends Any = Any, V extends $View<A> = $View<A>> implements WithTags<A> {
29
31
  /**
30
32
  * Don't use in runtime, only for type inference
31
33
  */
@@ -91,6 +93,10 @@ export class LikeC4ViewModel<A extends Any = Any, V extends $View<A> = $View<A>>
91
93
  return this.$view.title
92
94
  }
93
95
 
96
+ get description(): RichTextOrEmpty {
97
+ return RichText.memoize(this, this.$view.description)
98
+ }
99
+
94
100
  get tags(): aux.Tags<A> {
95
101
  return this.$view.tags ?? []
96
102
  }
@@ -245,7 +251,7 @@ export class LikeC4ViewModel<A extends Any = Any, V extends $View<A> = $View<A>>
245
251
 
246
252
  public isScopedElementView(
247
253
  this: LikeC4ViewModel<any, any>,
248
- ): this is LikeC4ViewModel.ScopedElementView<A, V> {
254
+ ): this is LikeC4ViewModel.ScopedElementView<A> {
249
255
  return this.$view[_type] === 'element' && isTruthy(this.$view.viewOf)
250
256
  }
251
257
 
@@ -264,8 +270,8 @@ export namespace LikeC4ViewModel {
264
270
  {
265
271
  }
266
272
 
267
- export interface ScopedElementView<A extends Any, V extends $View<A> = $View<A>>
268
- extends LikeC4ViewModel<A, ViewWithType<V, 'element'> & { viewOf: aux.StrictFqn<A> }>
273
+ export interface ScopedElementView<A extends Any>
274
+ extends LikeC4ViewModel<A, ViewWithType<$View<A>, 'element'> & { viewOf: aux.StrictFqn<A> }>
269
275
  {
270
276
  viewOf: ElementModel<A>
271
277
  }
@@ -10,21 +10,23 @@ import {
10
10
  type IconUrl,
11
11
  type IteratorLike,
12
12
  type Link,
13
+ type RichTextOrEmpty,
13
14
  type scalar,
14
15
  GroupElementKind,
15
16
  isGroupElementKind,
17
+ RichText,
16
18
  } from '../../types'
17
19
  import type * as aux from '../../types/aux'
18
20
  import { memoizeProp } from '../../utils'
19
21
  import type { DeployedInstanceModel, DeploymentElementModel } from '../DeploymentElementModel'
20
22
  import type { ElementModel } from '../ElementModel'
21
- import type { $View, IncomingFilter, OutgoingFilter } from '../types'
23
+ import type { $View, IncomingFilter, OutgoingFilter, WithTags } from '../types'
22
24
  import type { EdgesIterator } from './EdgeModel'
23
25
  import type { LikeC4ViewModel } from './LikeC4ViewModel'
24
26
 
25
27
  export type NodesIterator<M extends Any, V extends $View<M>> = IteratorLike<NodeModel<M, V>>
26
28
 
27
- export class NodeModel<A extends Any = Any, V extends $View<A> = $View<A>> {
29
+ export class NodeModel<A extends Any = Any, V extends $View<A> = $View<A>> implements WithTags<A> {
28
30
  #node: ComputedNode<A> | DiagramNode<A>
29
31
 
30
32
  constructor(
@@ -46,8 +48,8 @@ export class NodeModel<A extends Any = Any, V extends $View<A> = $View<A>> {
46
48
  return this.#node.kind as any
47
49
  }
48
50
 
49
- get description(): string | null {
50
- return this.#node.description ?? null
51
+ get description(): RichTextOrEmpty {
52
+ return RichText.memoize(this, this.#node.description)
51
53
  }
52
54
 
53
55
  get technology(): string | null {
@@ -37,12 +37,19 @@ export const defaultTheme: LikeC4Theme = {
37
37
  lg: 24, // 1.5rem = 16px + 8px
38
38
  xl: 32, // 2rem
39
39
  },
40
+ /**
41
+ * Text sizes for titles
42
+ * https://typescale.com/
43
+ *
44
+ * Scale: 1.2
45
+ * Base: 16px
46
+ */
40
47
  textSizes: {
41
- xs: 12,
42
- sm: 14,
43
- md: 19,
44
- lg: 24,
45
- xl: 32,
48
+ xs: 13.33,
49
+ sm: 16,
50
+ md: 19.2,
51
+ lg: 23.04,
52
+ xl: 27.65,
46
53
  },
47
54
  }
48
55