@likec4/core 1.40.0 → 1.41.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 (61) hide show
  1. package/dist/builder/index.d.mts +75 -14
  2. package/dist/builder/index.mjs +111 -47
  3. package/dist/compute-view/index.d.mts +5 -5
  4. package/dist/compute-view/index.mjs +3 -3
  5. package/dist/compute-view/relationships-view/index.d.mts +5 -5
  6. package/dist/index.d.mts +5 -5
  7. package/dist/index.mjs +4 -4
  8. package/dist/model/connection/deployment/index.d.mts +7 -7
  9. package/dist/model/connection/deployment/index.mjs +1 -1
  10. package/dist/model/connection/index.d.mts +8 -8
  11. package/dist/model/connection/index.mjs +1 -1
  12. package/dist/model/connection/model/index.d.mts +7 -7
  13. package/dist/model/index.d.mts +10 -10
  14. package/dist/model/index.mjs +3 -3
  15. package/dist/shared/{core.DHQ32OHa.mjs → core.Ae86Y3tB.mjs} +2 -3
  16. package/dist/shared/{core.FPITT8Vx.mjs → core.Asc11Mjy.mjs} +28 -9
  17. package/dist/shared/{core.CySf-2gP.d.mts → core.BXsxn-hf.d.mts} +29 -4
  18. package/dist/shared/core.BazD6cot.mjs +14 -0
  19. package/dist/shared/{core.D_fI5Lf6.d.mts → core.BhpgpOep.d.mts} +24 -4
  20. package/dist/shared/{core.DuR3R-xJ.mjs → core.BvMRMo7Z.mjs} +68 -46
  21. package/dist/shared/{core.BbThmf6M.d.mts → core.CUG6vpyL.d.mts} +2 -2
  22. package/dist/shared/{core.yHe79zEl.mjs → core.CXy1NLUG.mjs} +2 -2
  23. package/dist/shared/{core.Bpou6c2f.d.mts → core.D-ZGWDDt.d.mts} +19 -38
  24. package/dist/shared/{core.BIAlzwKc.mjs → core.DA5vzwZ9.mjs} +30 -4
  25. package/dist/shared/{core.T-eLZTW_.mjs → core.DJp8fgok.mjs} +6 -2
  26. package/dist/shared/{core.DrvfVXkh.d.mts → core.DgesAwC8.d.mts} +11 -2
  27. package/dist/shared/{core.vWJTxpLV.d.mts → core.FUPplpae.d.mts} +1 -1
  28. package/dist/shared/{core.DrVOJuxX.d.mts → core.hfOE3RfK.d.mts} +4 -4
  29. package/dist/shared/{core.D704WZES.d.mts → core.xSQO0XYD.d.mts} +2 -2
  30. package/dist/shared/{core.kxeJe0OY.d.mts → core.zxTBF_PD.d.mts} +3 -3
  31. package/dist/types/_aux.d.mts +2 -2
  32. package/dist/types/_aux.mjs +1 -5
  33. package/dist/types/expression-model.d.mts +3 -3
  34. package/dist/types/expression.d.mts +3 -3
  35. package/dist/types/fqnRef.d.mts +2 -2
  36. package/dist/types/index.d.mts +6 -6
  37. package/dist/types/index.mjs +4 -4
  38. package/dist/utils/index.d.mts +2 -2
  39. package/dist/utils/iterable/index.d.mts +1 -1
  40. package/package.json +2 -2
  41. package/src/builder/Builder.ts +119 -43
  42. package/src/builder/_types.ts +12 -8
  43. package/src/compute-view/deployment-view/utils.ts +47 -36
  44. package/src/compute-view/relationships-view/layout.ts +7 -6
  45. package/src/compute-view/utils/buildComputedNodes.ts +23 -10
  46. package/src/compute-view/utils/buildElementNotations.ts +2 -1
  47. package/src/model/DeploymentElementModel.ts +33 -3
  48. package/src/model/ElementModel.ts +25 -2
  49. package/src/model/RelationModel.ts +1 -1
  50. package/src/model/view/EdgeModel.ts +3 -2
  51. package/src/model/view/LikeC4ViewModel.ts +1 -1
  52. package/src/model/view/NodeModel.ts +1 -1
  53. package/src/types/RichText.ts +2 -3
  54. package/src/types/_aux.ts +26 -0
  55. package/src/types/_common.ts +23 -1
  56. package/src/types/index.ts +2 -0
  57. package/src/types/model-deployment.ts +8 -18
  58. package/src/types/model-logical.ts +4 -13
  59. package/src/types/model-spec.ts +3 -0
  60. package/src/types/view-computed.ts +21 -13
  61. package/src/types/view-layouted.ts +0 -4
@@ -2,7 +2,9 @@ import defu from 'defu'
2
2
  import {
3
3
  entries,
4
4
  fromEntries,
5
+ fromKeys,
5
6
  hasAtLeast,
7
+ isArray,
6
8
  isFunction,
7
9
  isNonNullish,
8
10
  isNullish,
@@ -23,6 +25,7 @@ import type {
23
25
  ParsedElementView,
24
26
  ParsedLikeC4ModelData,
25
27
  Specification,
28
+ TagSpecification,
26
29
  } from '../types'
27
30
  import {
28
31
  type Color,
@@ -200,30 +203,51 @@ interface Internals<T extends AnyTypes> extends ViewsBuilder<T>, ModelBuilder<T>
200
203
 
201
204
  type Op<T> = (b: T) => T
202
205
 
203
- function validateSpec(specification: BuilderSpecification) {
204
- for (const [kind, spec] of entries(specification.elements)) {
206
+ function ensureObj<T>(value: string[] | Record<string, Partial<T>>): Record<string, Partial<T>> {
207
+ return isArray(value) ? fromKeys(value, v => ({})) : value
208
+ }
209
+
210
+ function validateSpec({ tags, elements, deployments, relationships, ...specification }: BuilderSpecification) {
211
+ const spectags = {} as Record<string, Partial<TagSpecification>>
212
+ if (tags) {
213
+ Object.assign(
214
+ spectags,
215
+ ensureObj(tags),
216
+ )
217
+ }
218
+ const _elements = ensureObj(elements)
219
+
220
+ for (const [kind, spec] of entries(_elements)) {
205
221
  if (spec.tags) {
206
222
  for (const tag of spec.tags) {
207
- invariant(specification.tags?.[tag], `Invalid specification for element kind "${kind}": tag "${tag}" not found`)
223
+ invariant(tag in spectags, `Invalid specification for element kind "${kind}": tag "${tag}" not found`)
208
224
  }
209
225
  }
210
226
  }
211
- if (specification.deployments) {
212
- for (const [kind, spec] of entries(specification.deployments)) {
213
- if (spec.tags) {
214
- for (const tag of spec.tags) {
215
- invariant(
216
- specification.tags?.[tag],
217
- `Invalid specification for deployment kind "${kind}": tag "${tag}" not found`,
218
- )
219
- }
227
+ const _deployments = ensureObj(deployments ?? {})
228
+
229
+ for (const [kind, spec] of entries(_deployments)) {
230
+ if (spec.tags) {
231
+ for (const tag of spec.tags) {
232
+ invariant(
233
+ tag in spectags,
234
+ `Invalid specification for deployment kind "${kind}": tag "${tag}" not found`,
235
+ )
220
236
  }
221
237
  }
222
238
  }
239
+
240
+ return {
241
+ ...specification,
242
+ tags: spectags,
243
+ elements: _elements,
244
+ deployments: _deployments,
245
+ relationships: ensureObj(relationships ?? {}),
246
+ }
223
247
  }
224
248
 
225
249
  function builder<Spec extends BuilderSpecification, T extends AnyTypes>(
226
- spec: Spec,
250
+ _spec: Spec,
227
251
  _elements = new Map<string, Element<Any>>(),
228
252
  _relations = [] as ModelRelation[],
229
253
  _views = new Map<string, LikeC4View>(),
@@ -235,22 +259,17 @@ function builder<Spec extends BuilderSpecification, T extends AnyTypes>(
235
259
  _deployments = new Map<string, DeploymentElement>(),
236
260
  _deploymentRelations = [] as DeploymentRelation[],
237
261
  ): Builder<T> {
262
+ const spec = validateSpec(_spec)
263
+
238
264
  const toLikeC4Specification = (): Specification<Types.ToAux<T>> => {
239
- validateSpec(spec)
240
265
  return ({
241
- elements: {
242
- ...structuredClone(spec.elements) as any,
243
- },
244
- deployments: {
245
- ...structuredClone(spec.deployments) as any,
246
- },
247
- relationships: {
248
- ...structuredClone(spec.relationships) as any,
249
- },
250
- tags: mapValues(spec.tags ?? {}, (tagSpec) => ({
266
+ elements: structuredClone(spec.elements),
267
+ deployments: structuredClone(spec.deployments),
268
+ relationships: structuredClone(spec.relationships),
269
+ tags: mapValues(spec.tags, (tagSpec) => ({
251
270
  color: DefaultThemeColor,
252
271
  ...tagSpec,
253
- })) as any,
272
+ })),
254
273
  ...(!!spec.metadataKeys ? { metadataKeys: spec.metadataKeys as any } : {}),
255
274
  customColors: {},
256
275
  } as Specification<Types.ToAux<T>>)
@@ -510,7 +529,8 @@ function builder<Spec extends BuilderSpecification, T extends AnyTypes>(
510
529
  icon: _icon,
511
530
  style,
512
531
  title,
513
- description = null,
532
+ description,
533
+ summary,
514
534
  ...props
515
535
  } = typeof _props === 'string' ? { title: _props } : { ..._props }
516
536
 
@@ -525,8 +545,7 @@ function builder<Spec extends BuilderSpecification, T extends AnyTypes>(
525
545
  kind: kind as any,
526
546
  title: title ?? nameFromFqn(_id),
527
547
  ...(description && { description: { txt: description } }),
528
- technology: null,
529
- tags: [],
548
+ ...(summary && { summary: { txt: summary } }),
530
549
  color: specStyle?.color ?? DefaultThemeColor as Color,
531
550
  shape: specStyle?.shape ?? DefaultElementShape as ElementShape,
532
551
  style: pickBy({
@@ -537,7 +556,7 @@ function builder<Spec extends BuilderSpecification, T extends AnyTypes>(
537
556
  textSize: specStyle?.textSize,
538
557
  ...style,
539
558
  }, isNonNullish),
540
- links,
559
+ ...(links && { links }),
541
560
  ...icon && { icon: icon as IconUrl },
542
561
  ...spec,
543
562
  ...props,
@@ -704,7 +723,8 @@ function builder<Spec extends BuilderSpecification, T extends AnyTypes>(
704
723
  const {
705
724
  links,
706
725
  title,
707
- description = null,
726
+ description,
727
+ summary,
708
728
  ...props
709
729
  } = typeof _props === 'string' ? { title: _props } : { ..._props }
710
730
  const _id = b.__deploymentFqn(id)
@@ -713,6 +733,7 @@ function builder<Spec extends BuilderSpecification, T extends AnyTypes>(
713
733
  id: _id,
714
734
  element: target as Fqn,
715
735
  ...title && { title },
736
+ ...(summary && { summary: { txt: summary } }),
716
737
  ...(description && { description: { txt: description } }),
717
738
  ...links && { links: mapLinks(links) },
718
739
  ...props,
@@ -757,7 +778,8 @@ function builder<Spec extends BuilderSpecification, T extends AnyTypes>(
757
778
  icon: _icon,
758
779
  style,
759
780
  title,
760
- description = null,
781
+ description,
782
+ summary,
761
783
  ...props
762
784
  } = typeof _props === 'string' ? { title: _props } : { ..._props }
763
785
 
@@ -770,8 +792,7 @@ function builder<Spec extends BuilderSpecification, T extends AnyTypes>(
770
792
  kind: kind as any,
771
793
  title: title ?? nameFromFqn(_id),
772
794
  ...(description && { description: { txt: description } }),
773
- technology: null,
774
- tags: null,
795
+ ...(summary && { summary: { txt: summary } }),
775
796
  style: {
776
797
  ...icon && { icon: icon as IconUrl },
777
798
  color: specStyle?.color ?? DefaultThemeColor as Color,
@@ -841,11 +862,40 @@ function builder<Spec extends BuilderSpecification, T extends AnyTypes>(
841
862
 
842
863
  return self
843
864
  }
844
-
845
- export namespace Builder {
846
- export type Any = Builder<AnyTypes>
847
-
848
- export function forSpecification<const Spec extends BuilderSpecification>(
865
+ export const Builder = {
866
+ /**
867
+ * Creates a builder with compositional methods
868
+ *
869
+ * @example
870
+ * ```ts
871
+ * const {
872
+ * model: { model, system, component, relTo },
873
+ * deployment: { env, vm},
874
+ * views: { view, $include },
875
+ * builder,
876
+ * } = Builder.forSpecification({
877
+ * elements: {
878
+ * system: {},
879
+ * component: {},
880
+ * },
881
+ * deployments: ['env', 'vm'],
882
+ * })
883
+ *
884
+ * const b = builder
885
+ * .with(
886
+ * model(
887
+ * system('cloud').with(
888
+ * component('backend'),
889
+ * component('backend.api'),
890
+ * component('frontend').with(
891
+ * relTo('cloud.backend.api'),
892
+ * ),
893
+ * ),
894
+ * ),
895
+ * )
896
+ * ```
897
+ */
898
+ forSpecification<const Spec extends BuilderSpecification>(
849
899
  spec: Spec,
850
900
  ): {
851
901
  builder: Builder<Types.FromSpecification<Spec>>
@@ -853,18 +903,44 @@ export namespace Builder {
853
903
  deployment: DeloymentModelHelpers<Types.FromSpecification<Spec>>
854
904
  views: ViewsHelpers
855
905
  } {
856
- validateSpec(spec)
857
906
  const b = builder<Spec, Types.FromSpecification<Spec>>(spec)
858
907
  return {
859
908
  ...b.helpers(),
860
909
  builder: b,
861
910
  }
862
- }
911
+ },
863
912
 
864
- export function specification<const Spec extends BuilderSpecification>(
913
+ /**
914
+ * Creates a builder with chainable methods
915
+ *
916
+ * @example
917
+ * ```ts
918
+ * const b = Builder
919
+ * .specification({
920
+ * elements: ['system', 'component'],
921
+ * deployments: ['env', 'vm'],
922
+ * })
923
+ * .model(({ system, component, relTo }, _) =>
924
+ * _(
925
+ * system('cloud').with(
926
+ * component('backend').with(
927
+ * component('api'),
928
+ * ),
929
+ * component('frontend').with(
930
+ * relTo('cloud.backend.api'),
931
+ * )
932
+ * )
933
+ * )
934
+ * )
935
+ * ```
936
+ */
937
+ specification<const Spec extends BuilderSpecification>(
865
938
  spec: Spec,
866
939
  ): Builder<Types.FromSpecification<Spec>> {
867
- validateSpec(spec)
868
940
  return builder<Spec, Types.FromSpecification<Spec>>(spec)
869
- }
941
+ },
942
+ }
943
+
944
+ export namespace Builder {
945
+ export type Any = Builder<AnyTypes>
870
946
  }
@@ -26,16 +26,16 @@ type ElementSpecification = Omit<ElementKindSpecification, 'tags'> & {
26
26
  }
27
27
 
28
28
  export type BuilderSpecification = {
29
- elements: {
29
+ elements: string[] | {
30
30
  [kind: string]: Partial<ElementSpecification>
31
31
  }
32
- relationships?: {
32
+ relationships?: string[] | {
33
33
  [kind: string]: Partial<RelationshipKindSpecification>
34
34
  }
35
- deployments?: {
35
+ deployments?: string[] | {
36
36
  [kind: string]: Partial<ElementSpecification>
37
37
  }
38
- tags?: {
38
+ tags?: string[] | {
39
39
  [kind: string]: Partial<TagSpecification>
40
40
  }
41
41
  metadataKeys?: string[]
@@ -59,6 +59,7 @@ export type Metadata<MetadataKey extends string> = IsNever<MetadataKey> extends
59
59
 
60
60
  export type NewElementProps<Tag, Metadata> = {
61
61
  title?: string
62
+ summary?: string
62
63
  description?: string
63
64
  technology?: string
64
65
  tags?: IfNever<Tag, never, [Tag, ...Tag[]]>
@@ -79,6 +80,7 @@ export type NewElementProps<Tag, Metadata> = {
79
80
 
80
81
  export type NewDeploymentNodeProps<Tag, Metadata> = {
81
82
  title?: string
83
+ summary?: string
82
84
  description?: string
83
85
  technology?: string
84
86
  tags?: IfNever<Tag, never, [Tag, ...Tag[]]>
@@ -211,15 +213,17 @@ export type AnyTypesNested = TypesNested<
211
213
  any
212
214
  >
213
215
 
216
+ type KeysOrTuple<T> = T extends readonly string[] ? TupleToUnion<T> : KeysOf<T>
217
+
214
218
  export namespace Types {
215
219
  export type FromSpecification<Spec> = Spec extends BuilderSpecification ? Types<
216
- KeysOf<Spec['elements']>,
220
+ KeysOrTuple<Spec['elements']>,
217
221
  never,
218
222
  never,
219
- KeysOf<Spec['relationships']>,
220
- KeysOf<Spec['tags']>,
223
+ KeysOrTuple<Spec['relationships']>,
224
+ KeysOrTuple<Spec['tags']>,
221
225
  TupleToUnion<Spec['metadataKeys']>,
222
- KeysOf<Spec['deployments']>,
226
+ KeysOrTuple<Spec['deployments']>,
223
227
  never
224
228
  >
225
229
  : never
@@ -1,4 +1,4 @@
1
- import { hasAtLeast, isEmpty, unique } from 'remeda'
1
+ import { hasAtLeast, unique } from 'remeda'
2
2
  import type {
3
3
  DeployedInstanceModel,
4
4
  DeploymentConnectionModel,
@@ -17,8 +17,12 @@ import {
17
17
  type scalar,
18
18
  type Unknown,
19
19
  DefaultArrowType,
20
+ DefaultElementShape,
21
+ DefaultThemeColor,
20
22
  FqnExpr,
21
23
  isViewRuleStyle,
24
+ omitUndefined,
25
+ preferSummary,
22
26
  } from '../../types'
23
27
  import { invariant, nameFromFqn, nonexhaustive, parentFqn } from '../../utils'
24
28
  import { applyViewRuleStyle } from '../utils/applyViewRuleStyles'
@@ -135,44 +139,56 @@ export function deploymentExpressionToPredicate<
135
139
  nonexhaustive(target)
136
140
  }
137
141
 
142
+ function instanceSummary(model: DeployedInstanceModel<any>) {
143
+ return preferSummary(model.$instance) ?? preferSummary(model.element.$element)
144
+ }
145
+
138
146
  export function toNodeSource<A extends AnyAux>(
139
147
  el: DeploymentNodeModel<A> | DeployedInstanceModel<A>,
140
148
  ): ComputedNodeSource<A> {
141
149
  if (el.isDeploymentNode()) {
142
150
  const onlyOneInstance = el.onlyOneInstance()
143
- let { title, kind, id, description, ...$node } = el.$node
144
- const { icon, color, shape, ...style } = el.$node.style ?? {}
151
+ let {
152
+ title,
153
+ id,
154
+ description,
155
+ summary,
156
+ metadata,
157
+ style,
158
+ element,
159
+ tags: _tags, // omit
160
+ ...$node
161
+ } = el.$node
162
+ let icon = style.icon
145
163
  let tags = [...el.tags]
146
164
  // let description
147
165
  // If there is only one instance
166
+ summary ??= description
148
167
  if (onlyOneInstance) {
149
168
  tags = unique([...tags, ...onlyOneInstance.tags])
150
169
  // If title was not overriden, take title from the instance
151
170
  if (title === nameFromFqn(el.id)) {
152
171
  title = onlyOneInstance.title
153
172
  }
154
- description = onlyOneInstance.description.$source ?? description
173
+ icon ??= onlyOneInstance.style.icon
174
+ summary ??= instanceSummary(onlyOneInstance)
155
175
  }
156
176
 
157
- return {
177
+ return omitUndefined({
178
+ id: id as scalar.NodeId,
179
+ deploymentRef: id,
180
+ title,
181
+ ...$node,
182
+ color: style.color ?? onlyOneInstance?.color ?? DefaultThemeColor,
183
+ shape: style.shape ?? DefaultElementShape,
158
184
  ...(onlyOneInstance && {
159
- ...toNodeSource(onlyOneInstance),
160
185
  modelRef: onlyOneInstance.element.id,
161
186
  }),
162
- title,
163
- ...$node,
164
- ...(icon && { icon }),
165
- ...(color && { color }),
166
- ...(shape && { shape }),
167
- ...(description && { description }),
187
+ icon,
188
+ description: summary,
168
189
  tags,
169
- style: {
170
- ...style,
171
- },
172
- deploymentRef: id,
173
- kind,
174
- id: id as scalar.NodeId,
175
- }
190
+ style,
191
+ })
176
192
  }
177
193
  invariant(el.isInstance(), 'Expected Instance')
178
194
  const instance = el.$instance
@@ -184,33 +200,28 @@ export function toNodeSource<A extends AnyAux>(
184
200
  ...(instance.links ?? []),
185
201
  ]
186
202
 
187
- const metadata = {
188
- ...element.getMetadata(),
189
- ...instance.metadata,
190
- }
191
-
192
203
  const notation = instance.notation ?? element.$element.notation
193
204
 
194
- let description = el.description.$source
195
- let technology = instance.technology ?? element.technology
205
+ const description = instanceSummary(el)
196
206
 
197
- return {
207
+ const technology = el.technology ?? undefined
208
+
209
+ return omitUndefined({
198
210
  id: el.id as scalar.NodeId,
199
211
  kind: 'instance' as unknown as aux.DeploymentKind<A>,
200
212
  title: el.title,
201
- ...(description && { description }),
202
- ...(technology && { technology }),
213
+ description,
214
+ technology,
203
215
  tags: [...el.tags],
204
- links: hasAtLeast(links, 1) ? links : null,
205
- ...icon && { icon },
206
- ...color && { color },
207
- ...shape && { shape },
216
+ links: hasAtLeast(links, 1) ? links : undefined,
217
+ icon,
218
+ color,
219
+ shape,
208
220
  style,
209
221
  deploymentRef: instance.id,
210
222
  modelRef: element.id,
211
- ...notation && { notation },
212
- ...!isEmpty(metadata) && ({ metadata }),
213
- }
223
+ notation,
224
+ })
214
225
  }
215
226
 
216
227
  export function toComputedEdges<A extends AnyAux>(
@@ -2,7 +2,7 @@ import dagre, { type EdgeConfig, type GraphLabel } from '@dagrejs/dagre'
2
2
  import { concat, filter, forEachObj, groupBy, map, mapToObj, pipe, prop, reduce, tap } from 'remeda'
3
3
  import type { ElementModel } from '../../model/ElementModel'
4
4
  import type { RelationshipModel } from '../../model/RelationModel'
5
- import type { DiagramNode, DiagramView, Fqn, NodeId } from '../../types'
5
+ import { type DiagramNode, type DiagramView, type Fqn, type NodeId, omitUndefined } from '../../types'
6
6
  import { invariant, sortParentsFirst } from '../../utils'
7
7
  import { toArray } from '../../utils/iterable'
8
8
  import { DefaultMap } from '../../utils/mnemonist'
@@ -311,13 +311,14 @@ export function layoutRelationshipsView(data: RelationshipsViewData): Pick<Diagr
311
311
  // }
312
312
  const children = (g.children(id) as NodeId[] | undefined ?? []).filter(c => !c.endsWith(PortSuffix))
313
313
 
314
- return {
314
+ return omitUndefined({
315
315
  id: id as NodeId,
316
316
  parent: parentId as NodeId ?? null,
317
317
  title: element.title,
318
- ...position,
318
+ x: position.x,
319
+ y: position.y,
319
320
  position: [position.x, position.y],
320
- description: element.description?.$source ?? null,
321
+ description: element.$element.summary ?? element.$element.description ?? null,
321
322
  technology: element.technology,
322
323
  tags: [],
323
324
  links: null,
@@ -337,11 +338,11 @@ export function layoutRelationshipsView(data: RelationshipsViewData): Pick<Diagr
337
338
  },
338
339
  inEdges: [],
339
340
  outEdges: [],
340
- ...(children.length > 0 && { depth: nodeDepth(id) }),
341
+ depth: children.length > 0 ? nodeDepth(id) : 0,
341
342
  children,
342
343
  width,
343
344
  height,
344
- }
345
+ })
345
346
  })
346
347
 
347
348
  return {
@@ -1,15 +1,16 @@
1
- import type { Simplify } from 'type-fest'
1
+ import type { Except } from 'type-fest'
2
2
  import type { ElementModel } from '../../model'
3
3
  import {
4
4
  type AnyAux,
5
5
  type aux,
6
6
  type ComputedNode,
7
- type Element,
8
7
  type scalar,
9
8
  type Unknown,
10
9
  DefaultElementShape,
11
10
  DefaultThemeColor,
12
11
  GroupElementKind,
12
+ omitUndefined,
13
+ preferSummary,
13
14
  } from '../../types'
14
15
  import { nonNullable } from '../../utils'
15
16
  import { compareByFqnHierarchically, parentFqn } from '../../utils/fqn'
@@ -28,18 +29,30 @@ function updateDepthOfAncestors(node: ComputedNode, nodes: ReadonlyMap<string, C
28
29
  }
29
30
  }
30
31
 
31
- export type ComputedNodeSource<A extends AnyAux = Unknown> = Simplify<
32
- & Pick<ComputedNode<A>, 'id' | 'title' | 'description' | 'kind' | 'deploymentRef' | 'modelRef'>
33
- & Partial<Omit<Element<A>, 'id' | 'title' | 'description' | 'kind'>>
32
+ export type ComputedNodeSource<A extends AnyAux = Unknown> = Except<
33
+ ComputedNode<A>,
34
+ 'parent' | 'children' | 'inEdges' | 'outEdges' | 'level' | 'depth',
35
+ { requireExactProps: true }
34
36
  >
35
37
 
36
38
  export function elementModelToNodeSource<A extends AnyAux>(el: ElementModel<A>): ComputedNodeSource<A> {
37
- return {
38
- ...el.$element,
39
+ const {
40
+ id,
41
+ summary, // omit
42
+ style,
43
+ metadata, // omit
44
+ ...rest
45
+ } = el.$element
46
+ return omitUndefined({
47
+ id: id as scalar.NodeId,
48
+ modelRef: id,
49
+ ...rest,
50
+ style: { ...style },
51
+ color: el.color,
52
+ shape: el.shape,
53
+ description: preferSummary(el.$element),
39
54
  tags: [...el.tags],
40
- id: el.id as scalar.NodeId,
41
- modelRef: el.id,
42
- }
55
+ })
43
56
  }
44
57
 
45
58
  export function buildComputedNodes<A extends AnyAux>(
@@ -1,4 +1,4 @@
1
- import { entries, flatMap, groupBy, map, mapValues, pipe, piped, prop, sortBy, unique } from 'remeda'
1
+ import { entries, filter, flatMap, groupBy, map, mapValues, pipe, piped, prop, sortBy, unique } from 'remeda'
2
2
  import type { ComputedNode, NodeNotation } from '../../types'
3
3
 
4
4
  /**
@@ -12,6 +12,7 @@ import type { ComputedNode, NodeNotation } from '../../types'
12
12
  export function buildElementNotations(nodes: ComputedNode<any>[]): NodeNotation[] {
13
13
  return pipe(
14
14
  nodes,
15
+ filter((n): n is ComputedNode<any> & { notation: string } => !!n.notation),
15
16
  groupBy(prop('notation')),
16
17
  mapValues(
17
18
  piped(
@@ -18,6 +18,8 @@ import {
18
18
  DefaultLineStyle,
19
19
  DefaultShapeSize,
20
20
  DefaultThemeColor,
21
+ preferDescription,
22
+ preferSummary,
21
23
  RichText,
22
24
  } from '../types'
23
25
  import * as aux from '../types/_aux'
@@ -67,8 +69,20 @@ abstract class AbstractDeploymentElementModel<A extends Any> implements WithTags
67
69
  return this.$node.style?.color as Color ?? DefaultThemeColor
68
70
  }
69
71
 
72
+ /**
73
+ * Short description of the element.
74
+ * Falls back to description if summary is not provided.
75
+ */
76
+ get summary(): RichTextOrEmpty {
77
+ return RichText.memoize(this, 'summary', preferSummary(this.$node))
78
+ }
79
+
80
+ /**
81
+ * Long description of the element.
82
+ * Falls back to summary if description is not provided.
83
+ */
70
84
  get description(): RichTextOrEmpty {
71
- return RichText.memoize(this, this.$node.description)
85
+ return RichText.memoize(this, 'description', preferDescription(this.$node))
72
86
  }
73
87
 
74
88
  get technology(): string | null {
@@ -412,8 +426,20 @@ export class DeployedInstanceModel<A extends Any = Any> extends AbstractDeployme
412
426
  return this.element.kind
413
427
  }
414
428
 
429
+ override get summary(): RichTextOrEmpty {
430
+ return RichText.memoize(
431
+ this,
432
+ 'summary',
433
+ preferSummary(this.$instance) ?? preferSummary(this.element.$element),
434
+ )
435
+ }
436
+
415
437
  override get description(): RichTextOrEmpty {
416
- return RichText.memoize(this, this.$instance.description ?? this.element.$element.description)
438
+ return RichText.memoize(
439
+ this,
440
+ 'description',
441
+ preferDescription(this.$instance) ?? preferDescription(this.element.$element),
442
+ )
417
443
  }
418
444
 
419
445
  override get technology(): string | null {
@@ -496,6 +522,10 @@ export class NestedElementOfDeployedInstanceModel<A extends Any = Any> {
496
522
  return this.element.title
497
523
  }
498
524
 
525
+ get summary(): RichTextOrEmpty {
526
+ return this.element.summary
527
+ }
528
+
499
529
  get description(): RichTextOrEmpty {
500
530
  return this.element.description
501
531
  }
@@ -550,7 +580,7 @@ export class DeploymentRelationModel<A extends Any = Any> implements AnyRelation
550
580
  }
551
581
 
552
582
  get description(): RichTextOrEmpty {
553
- return RichText.memoize(this, this.$relationship.description)
583
+ return RichText.memoize(this, 'description', this.$relationship.description)
554
584
  }
555
585
 
556
586
  get tags(): aux.Tags<A> {