@likec4/language-server 1.8.1 → 1.10.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 (85) hide show
  1. package/contrib/likec4.tmLanguage.json +1 -1
  2. package/dist/browser.cjs +21 -0
  3. package/dist/browser.d.cts +22 -0
  4. package/dist/browser.d.mts +22 -0
  5. package/dist/browser.d.ts +22 -0
  6. package/dist/browser.mjs +19 -0
  7. package/dist/index.cjs +10 -0
  8. package/dist/index.d.cts +18 -0
  9. package/dist/index.d.mts +18 -0
  10. package/dist/index.d.ts +18 -0
  11. package/dist/index.mjs +1 -0
  12. package/dist/likec4lib.cjs +961 -0
  13. package/dist/likec4lib.d.cts +6 -0
  14. package/dist/likec4lib.d.mts +6 -0
  15. package/dist/likec4lib.d.ts +6 -0
  16. package/dist/likec4lib.mjs +957 -0
  17. package/dist/model-graph/index.cjs +10 -0
  18. package/dist/model-graph/index.d.cts +79 -0
  19. package/dist/model-graph/index.d.mts +79 -0
  20. package/dist/model-graph/index.d.ts +79 -0
  21. package/dist/model-graph/index.mjs +1 -0
  22. package/dist/node.cjs +18 -0
  23. package/dist/node.d.cts +20 -0
  24. package/dist/node.d.mts +20 -0
  25. package/dist/node.d.ts +20 -0
  26. package/dist/node.mjs +16 -0
  27. package/dist/protocol.cjs +25 -0
  28. package/dist/protocol.d.cts +43 -0
  29. package/dist/protocol.d.mts +43 -0
  30. package/dist/protocol.d.ts +43 -0
  31. package/dist/protocol.mjs +17 -0
  32. package/dist/shared/language-server.CjFzaJwI.d.cts +1223 -0
  33. package/dist/shared/language-server.CtKHXJDD.d.ts +1223 -0
  34. package/dist/shared/language-server.D-84I33F.d.mts +1223 -0
  35. package/dist/shared/language-server.DBJJUUgF.mjs +5737 -0
  36. package/dist/shared/language-server.DtBRb9os.mjs +1656 -0
  37. package/dist/shared/language-server.DwyCJvXm.cjs +1669 -0
  38. package/dist/shared/language-server.JWkqVjGv.cjs +5748 -0
  39. package/package.json +36 -20
  40. package/src/ast.ts +48 -36
  41. package/src/browser.ts +0 -3
  42. package/src/elementRef.ts +1 -1
  43. package/src/formatting/LikeC4Formatter.ts +388 -0
  44. package/src/formatting/utils.ts +26 -0
  45. package/src/generated/ast.ts +170 -12
  46. package/src/generated/grammar.ts +1 -1
  47. package/src/generated-lib/icons.ts +1 -1
  48. package/src/like-c4.langium +49 -8
  49. package/src/likec4lib.ts +2 -3
  50. package/src/logger.ts +9 -1
  51. package/src/lsp/DocumentLinkProvider.ts +27 -15
  52. package/src/lsp/RenameProvider.ts +8 -0
  53. package/src/lsp/SemanticTokenProvider.ts +20 -2
  54. package/src/lsp/index.ts +1 -0
  55. package/src/model/fqn-computation.ts +33 -23
  56. package/src/model/fqn-index.ts +5 -21
  57. package/src/model/model-builder.ts +180 -112
  58. package/src/model/model-locator.ts +1 -1
  59. package/src/model/model-parser-where.ts +3 -2
  60. package/src/model/model-parser.ts +99 -39
  61. package/src/model-graph/LikeC4ModelGraph.ts +42 -21
  62. package/src/model-graph/compute-view/__test__/fixture.ts +16 -14
  63. package/src/model-graph/compute-view/compute.ts +110 -81
  64. package/src/model-graph/compute-view/predicates.ts +6 -8
  65. package/src/model-graph/dynamic-view/__test__/fixture.ts +1 -0
  66. package/src/model-graph/dynamic-view/compute.ts +98 -61
  67. package/src/model-graph/utils/buildElementNotations.ts +1 -1
  68. package/src/model-graph/utils/elementExpressionToPredicate.ts +1 -1
  69. package/src/model-graph/utils/sortNodes.ts +2 -6
  70. package/src/module.ts +21 -4
  71. package/src/protocol.ts +4 -5
  72. package/src/references/scope-computation.ts +10 -1
  73. package/src/references/scope-provider.ts +2 -1
  74. package/src/shared/NodeKindProvider.ts +73 -34
  75. package/src/test/setup.ts +3 -8
  76. package/src/test/testServices.ts +27 -7
  77. package/src/utils/graphlib.ts +11 -0
  78. package/src/validation/index.ts +2 -1
  79. package/src/validation/property-checks.ts +13 -1
  80. package/src/validation/specification.ts +3 -3
  81. package/src/view-utils/manual-layout.ts +1 -1
  82. package/src/view-utils/resolve-extended-views.ts +19 -10
  83. package/src/view-utils/resolve-relative-paths.ts +19 -24
  84. package/src/view-utils/view-hash.ts +1 -1
  85. package/src/reset.d.ts +0 -2
@@ -1,7 +1,8 @@
1
- import { type c4, InvalidModelError, invariant, isNonEmptyArray, nonexhaustive } from '@likec4/core'
1
+ import { type HexColorLiteral, invariant, isNonEmptyArray, nonexhaustive } from '@likec4/core'
2
+ import type * as c4 from '@likec4/core'
2
3
  import type { AstNode, LangiumDocument } from 'langium'
3
4
  import { AstUtils, CstUtils } from 'langium'
4
- import { filter, flatMap, isDefined, isNonNullish, isTruthy, mapToObj, pipe } from 'remeda'
5
+ import { filter, first, flatMap, isDefined, isNonNullish, isTruthy, map, mapToObj, pipe } from 'remeda'
5
6
  import stripIndent from 'strip-indent'
6
7
  import type { Writable } from 'type-fest'
7
8
  import type {
@@ -23,6 +24,7 @@ import {
23
24
  resolveRelationPoints,
24
25
  streamModel,
25
26
  toAutoLayout,
27
+ toColor,
26
28
  toElementStyle,
27
29
  toRelationshipStyleExcludeDefaults,
28
30
  ViewOps
@@ -67,7 +69,7 @@ export class LikeC4ModelParser {
67
69
  try {
68
70
  result.push(this.parseLikeC4Document(doc))
69
71
  } catch (cause) {
70
- logError(new InvalidModelError(`Error parsing document ${doc.uri.toString()}`, { cause }))
72
+ logError(new Error(`Error parsing document ${doc.uri.toString()}`, { cause }))
71
73
  }
72
74
  }
73
75
  return result
@@ -89,20 +91,23 @@ export class LikeC4ModelParser {
89
91
  const element_specs = specifications.flatMap(s => s.elements.filter(isValid))
90
92
  for (const { kind, props } of element_specs) {
91
93
  try {
92
- const style = props.find(ast.isElementStyleProperty)
93
94
  const kindName = kind.name as c4.ElementKind
94
- if (kindName in c4Specification.kinds) {
95
+ if (!isTruthy(kindName)) {
96
+ continue
97
+ }
98
+ if (kindName in c4Specification.elements) {
95
99
  logger.warn(`Element kind "${kindName}" is already defined`)
96
100
  continue
97
101
  }
102
+ const style = props.find(ast.isElementStyleProperty)
98
103
  const bodyProps = mapToObj(
99
104
  props.filter(ast.isSpecificationElementStringProperty).filter(p => isNonNullish(p.value)) ?? [],
100
- p => [p.key, removeIndent(p.value)]
105
+ p => [p.key, removeIndent(p.value)] as const
101
106
  )
102
- c4Specification.kinds[kindName] = {
107
+ c4Specification.elements[kindName] = {
103
108
  ...bodyProps,
104
109
  style: {
105
- ...toElementStyle(style?.props)
110
+ ...toElementStyle(style?.props, isValid)
106
111
  }
107
112
  }
108
113
  } catch (e) {
@@ -114,13 +119,16 @@ export class LikeC4ModelParser {
114
119
  for (const { kind, props } of relations_specs) {
115
120
  try {
116
121
  const kindName = kind.name as c4.RelationshipKind
122
+ if (!isTruthy(kindName)) {
123
+ continue
124
+ }
117
125
  if (kindName in c4Specification.relationships) {
118
126
  logger.warn(`Relationship kind "${kindName}" is already defined`)
119
127
  continue
120
128
  }
121
129
  const bodyProps = mapToObj(
122
130
  props.filter(ast.isSpecificationRelationshipStringProperty).filter(p => isNonNullish(p.value)) ?? [],
123
- p => [p.key, p.value]
131
+ p => [p.key, removeIndent(p.value)]
124
132
  )
125
133
  c4Specification.relationships[kindName] = {
126
134
  ...bodyProps,
@@ -130,13 +138,38 @@ export class LikeC4ModelParser {
130
138
  logWarnError(e)
131
139
  }
132
140
  }
141
+
142
+ const tags_specs = specifications.flatMap(s => s.tags.filter(isValid))
143
+ for (const tagSpec of tags_specs) {
144
+ const tag = tagSpec.tag.name as c4.Tag
145
+ if (isTruthy(tag)) {
146
+ c4Specification.tags.add(tag)
147
+ }
148
+ }
149
+
150
+ const colors_specs = specifications.flatMap(s => s.colors.filter(isValid))
151
+ for (const { name, color } of colors_specs) {
152
+ try {
153
+ const colorName = name.name as c4.CustomColor
154
+ if (colorName in c4Specification.colors) {
155
+ logger.warn(`Custom color "${colorName}" is already defined`)
156
+ continue
157
+ }
158
+
159
+ c4Specification.colors[colorName] = {
160
+ color: color as HexColorLiteral
161
+ }
162
+ } catch (e) {
163
+ logWarnError(e)
164
+ }
165
+ }
133
166
  }
134
167
 
135
168
  private parseModel(doc: ParsedLikeC4LangiumDocument, isValid: IsValidFn) {
136
169
  for (const el of streamModel(doc, isValid)) {
137
170
  if (ast.isElement(el)) {
138
171
  try {
139
- doc.c4Elements.push(this.parseElement(el))
172
+ doc.c4Elements.push(this.parseElement(el, isValid))
140
173
  } catch (e) {
141
174
  logWarnError(e)
142
175
  }
@@ -154,12 +187,12 @@ export class LikeC4ModelParser {
154
187
  }
155
188
  }
156
189
 
157
- private parseElement(astNode: ast.Element): ParsedAstElement {
190
+ private parseElement(astNode: ast.Element, isValid: IsValidFn): ParsedAstElement {
158
191
  const id = this.resolveFqn(astNode)
159
192
  const kind = astNode.kind.$refText as c4.ElementKind
160
193
  const tags = this.convertTags(astNode.body)
161
194
  const stylePropsAst = astNode.body?.props.find(ast.isElementStyleProperty)?.props
162
- const style = toElementStyle(stylePropsAst)
195
+ const style = toElementStyle(stylePropsAst, isValid)
163
196
  const metadata = this.getMetadata(astNode.body?.props.find(ast.isMetadataProperty))
164
197
  const astPath = this.getAstNodePath(astNode)
165
198
 
@@ -214,6 +247,14 @@ export class LikeC4ModelParser {
214
247
  p => [p.key, p.value]
215
248
  )
216
249
 
250
+ const navigateTo = pipe(
251
+ astNode.body?.props ?? [],
252
+ filter(ast.isRelationNavigateToProperty),
253
+ map(p => p.value.view.ref?.name),
254
+ filter(isTruthy),
255
+ first()
256
+ )
257
+
217
258
  const title = removeIndent(astNode.title ?? bodyProps.title) ?? ''
218
259
  const description = removeIndent(bodyProps.description)
219
260
  const technology = removeIndent(astNode.technology) ?? toSingleLine(bodyProps.technology)
@@ -236,7 +277,8 @@ export class LikeC4ModelParser {
236
277
  ...(kind && { kind }),
237
278
  ...(tags && { tags }),
238
279
  ...(isNonEmptyArray(links) && { links }),
239
- ...toRelationshipStyleExcludeDefaults(styleProp?.props)
280
+ ...toRelationshipStyleExcludeDefaults(styleProp?.props),
281
+ ...(navigateTo && { navigateTo: navigateTo as c4.ViewID })
240
282
  }
241
283
  }
242
284
 
@@ -392,8 +434,9 @@ export class LikeC4ModelParser {
392
434
  return acc
393
435
  }
394
436
  if (ast.isColorProperty(prop)) {
395
- if (isDefined(prop.value)) {
396
- acc.custom[prop.key] = prop.value
437
+ const value = toColor(prop)
438
+ if (isDefined(value)) {
439
+ acc.custom[prop.key] = value
397
440
  }
398
441
  return acc
399
442
  }
@@ -480,7 +523,7 @@ export class LikeC4ModelParser {
480
523
  const props = astNode.custom?.props ?? []
481
524
  return props.reduce(
482
525
  (acc, prop) => {
483
- if (ast.isRelationStringProperty(prop)) {
526
+ if (ast.isRelationStringProperty(prop) || ast.isNotationProperty(prop) || ast.isNotesProperty(prop)) {
484
527
  if (isDefined(prop.value)) {
485
528
  acc.customRelation[prop.key] = removeIndent(prop.value) ?? ''
486
529
  }
@@ -493,8 +536,9 @@ export class LikeC4ModelParser {
493
536
  return acc
494
537
  }
495
538
  if (ast.isColorProperty(prop)) {
496
- if (isTruthy(prop.value)) {
497
- acc.customRelation[prop.key] = prop.value
539
+ const value = toColor(prop)
540
+ if (isTruthy(value)) {
541
+ acc.customRelation[prop.key] = value
498
542
  }
499
543
  return acc
500
544
  }
@@ -504,9 +548,10 @@ export class LikeC4ModelParser {
504
548
  }
505
549
  return acc
506
550
  }
507
- if (ast.isNotationProperty(prop)) {
508
- if (isTruthy(prop.value)) {
509
- acc.customRelation[prop.key] = removeIndent(prop.value)
551
+ if (ast.isRelationNavigateToProperty(prop)) {
552
+ const viewId = prop.value.view.ref?.name
553
+ if (isTruthy(viewId)) {
554
+ acc.customRelation.navigateTo = viewId as c4.ViewID
510
555
  }
511
556
  return acc
512
557
  }
@@ -551,7 +596,7 @@ export class LikeC4ModelParser {
551
596
  return this.parseViewRulePredicate(astRule, isValid)
552
597
  }
553
598
  if (ast.isViewRuleStyle(astRule)) {
554
- const styleProps = toElementStyle(astRule.props.filter(ast.isStyleProperty))
599
+ const styleProps = toElementStyle(astRule.props.filter(ast.isStyleProperty), isValid)
555
600
  const notation = removeIndent(astRule.props.find(ast.isNotationProperty)?.value)
556
601
  const targets = this.parseElementExpressionsIterator(astRule.target)
557
602
  return {
@@ -589,6 +634,12 @@ export class LikeC4ModelParser {
589
634
  }
590
635
  }
591
636
 
637
+ private parseDynamicParallelSteps(node: ast.DynamicViewParallelSteps): c4.DynamicViewParallelSteps {
638
+ return {
639
+ __parallel: node.steps.map(step => this.parseDynamicStep(step))
640
+ }
641
+ }
642
+
592
643
  private parseDynamicStep(node: ast.DynamicViewStep): c4.DynamicViewStep {
593
644
  const sourceEl = elementRef(node.source)
594
645
  if (!sourceEl) {
@@ -600,9 +651,7 @@ export class LikeC4ModelParser {
600
651
  }
601
652
  let source = this.resolveFqn(sourceEl)
602
653
  let target = this.resolveFqn(targetEl)
603
- const title = removeIndent(
604
- node.title ?? node.custom?.props.find((p): p is ast.RelationStringProperty => p.key === 'title')?.value
605
- ) ?? ''
654
+ const title = removeIndent(node.title) ?? null
606
655
 
607
656
  let step: Writable<c4.DynamicViewStep> = {
608
657
  source,
@@ -620,28 +669,35 @@ export class LikeC4ModelParser {
620
669
  if (Array.isArray(node.custom?.props)) {
621
670
  for (const prop of node.custom.props) {
622
671
  try {
623
- if (ast.isRelationStringProperty(prop)) {
624
- const value = removeIndent(prop.value)
625
- if (isTruthy(value) && prop.key !== 'title') {
626
- step[prop.key] = value
672
+ if (ast.isRelationStringProperty(prop) || ast.isNotationProperty(prop) || ast.isNotesProperty(prop)) {
673
+ if (isDefined(prop.value)) {
674
+ step[prop.key] = removeIndent(prop.value) ?? ''
627
675
  }
628
676
  continue
629
677
  }
630
678
  if (ast.isArrowProperty(prop)) {
631
- step[prop.key] = prop.value
679
+ if (isDefined(prop.value)) {
680
+ step[prop.key] = prop.value
681
+ }
632
682
  continue
633
683
  }
634
684
  if (ast.isColorProperty(prop)) {
635
- step[prop.key] = prop.value
685
+ const value = toColor(prop)
686
+ if (isDefined(value)) {
687
+ step[prop.key] = value
688
+ }
636
689
  continue
637
690
  }
638
691
  if (ast.isLineProperty(prop)) {
639
- step[prop.key] = prop.value
692
+ if (isDefined(prop.value)) {
693
+ step[prop.key] = prop.value
694
+ }
640
695
  continue
641
696
  }
642
- if (ast.isNotationProperty(prop)) {
643
- if (isTruthy(prop.value)) {
644
- step[prop.key] = prop.value
697
+ if (ast.isRelationNavigateToProperty(prop)) {
698
+ const viewId = prop.value.view.ref?.name
699
+ if (isTruthy(viewId)) {
700
+ step.navigateTo = viewId as c4.ViewID
645
701
  }
646
702
  continue
647
703
  }
@@ -778,7 +834,7 @@ export class LikeC4ModelParser {
778
834
  return acc
779
835
  }
780
836
  if (ast.isViewRuleStyle(n)) {
781
- const styleProps = toElementStyle(n.props.filter(ast.isStyleProperty))
837
+ const styleProps = toElementStyle(n.props.filter(ast.isStyleProperty), isValid)
782
838
  const notation = removeIndent(n.props.find(ast.isNotationProperty)?.value)
783
839
  const targets = this.parseElementExpressionsIterator(n.target)
784
840
  if (targets.length > 0) {
@@ -807,13 +863,17 @@ export class LikeC4ModelParser {
807
863
  steps: body.steps.reduce((acc, n) => {
808
864
  try {
809
865
  if (isValid(n)) {
810
- acc.push(this.parseDynamicStep(n))
866
+ if (ast.isDynamicViewParallelSteps(n)) {
867
+ acc.push(this.parseDynamicParallelSteps(n))
868
+ } else {
869
+ acc.push(this.parseDynamicStep(n))
870
+ }
811
871
  }
812
872
  } catch (e) {
813
873
  logWarnError(e)
814
874
  }
815
875
  return acc
816
- }, [] as c4.DynamicViewStep[]),
876
+ }, [] as c4.DynamicViewStepOrParallel[]),
817
877
  ...(manualLayout && { manualLayout })
818
878
  }
819
879
  }
@@ -845,7 +905,7 @@ export class LikeC4ModelParser {
845
905
  const tags = [] as c4.Tag[]
846
906
  while (iter) {
847
907
  try {
848
- const values = iter.values.map(t => t.ref?.name).filter(Boolean) as c4.Tag[]
908
+ const values = iter.values.map(t => t.ref?.name).filter(isTruthy) as c4.Tag[]
849
909
  if (values.length > 0) {
850
910
  tags.unshift(...values)
851
911
  }
@@ -3,14 +3,13 @@ import {
3
3
  commonAncestor,
4
4
  type Element,
5
5
  type Fqn,
6
- InvalidModelError,
7
6
  invariant,
8
7
  isSameHierarchy,
9
- isString,
10
8
  parentFqn,
11
9
  type Relation,
12
10
  type RelationID
13
11
  } from '@likec4/core'
12
+ import { isArray, isString } from 'remeda'
14
13
 
15
14
  type Params = {
16
15
  elements: Record<Fqn, Element>
@@ -25,7 +24,7 @@ type RelationEdge = {
25
24
  }
26
25
 
27
26
  type FqnOrElement = Fqn | Element
28
- type FqnsOrElements = Fqn[] | Element[]
27
+ type FqnsOrElements = ReadonlyArray<Fqn> | ReadonlyArray<Element>
29
28
 
30
29
  const RelationsSet = Set<Relation>
31
30
  const MapRelations = Map<Fqn, Set<Relation>>
@@ -37,9 +36,16 @@ function intersection<T>(a: Set<T>, b: Set<T>) {
37
36
  return new Set([...a].filter(value => b.has(value)))
38
37
  }
39
38
 
39
+ /**
40
+ * Used only for views calculations.
41
+ * Subject to change.
42
+ */
40
43
  export class LikeC4ModelGraph {
41
44
  #elements = new Map<Fqn, Element>()
42
- #children = new Map<Fqn, Fqn[]>()
45
+ // Parent element for given FQN
46
+ #parents = new Map<Fqn, Element>()
47
+ // Children elements for given FQN
48
+ #children = new Map<Fqn, Element[]>()
43
49
  #rootElements = new Set<Element>()
44
50
 
45
51
  #relations = new Map<RelationID, Relation>()
@@ -47,7 +53,7 @@ export class LikeC4ModelGraph {
47
53
  #incoming = new MapRelations()
48
54
  // Outgoing from an element or its descendants
49
55
  #outgoing = new MapRelations()
50
- // Relationships inside the element descendants
56
+ // Relationships inside the element, among descendants
51
57
  #internal = new MapRelations()
52
58
 
53
59
  #cacheAscendingSiblings = new Map<Fqn, Element[]>()
@@ -80,7 +86,7 @@ export class LikeC4ModelGraph {
80
86
  }
81
87
 
82
88
  public children(id: Fqn) {
83
- return this._childrenOf(id).flatMap(id => this.#elements.get(id) ?? [])
89
+ return this._childrenOf(id).slice()
84
90
  }
85
91
 
86
92
  // Get children or element itself if no children
@@ -93,19 +99,30 @@ export class LikeC4ModelGraph {
93
99
  public siblings(element: Fqn | Element) {
94
100
  const id = isString(element) ? element : element.id
95
101
  const parent = parentFqn(id)
96
- const fqns = parent ? this._childrenOf(parent) : [...this.#rootElements].map(e => e.id)
97
- return fqns.flatMap(fqn => (fqn !== id && this.#elements.get(fqn)) || [])
102
+ const siblings = parent ? this._childrenOf(parent) : this.rootElements
103
+ return siblings.filter(e => e.id !== id)
98
104
  }
99
105
 
100
- public ancestors(element: Fqn | Element) {
101
- const id = isString(element) ? element : element.id
102
- return ancestorsFqn(id).flatMap(id => this.#elements.get(id) ?? [])
106
+ /**
107
+ * Get all ancestor elements (i.e. parent, parent’s parent, etc.)
108
+ * (from closest to root)
109
+ */
110
+ public ancestors(element: Fqn | Element): Array<Element> {
111
+ let id = isString(element) ? element : element.id
112
+ const result = [] as Element[]
113
+ let parent
114
+ while (parent = this.#parents.get(id)) {
115
+ result.push(parent)
116
+ id = parent.id
117
+ }
118
+ return result as Array<Element>
103
119
  }
104
120
 
105
121
  /**
106
122
  * Resolve siblings of the element and its ancestors
123
+ * (from closest to root)
107
124
  */
108
- public ascendingSiblings(element: Fqn | Element) {
125
+ public ascendingSiblings(element: Fqn | Element): Array<Element> {
109
126
  const id = isString(element) ? element : element.id
110
127
  let siblings = this.#cacheAscendingSiblings.get(id)
111
128
  if (!siblings) {
@@ -121,7 +138,10 @@ export class LikeC4ModelGraph {
121
138
  /**
122
139
  * Resolve all RelationEdges between element and others (any direction)
123
140
  */
124
- public anyEdgesBetween(_element: Fqn | Element, others: Fqn[] | Element[]): RelationEdge[] {
141
+ public anyEdgesBetween(
142
+ _element: Fqn | Element,
143
+ others: ReadonlyArray<Fqn> | ReadonlyArray<Element>
144
+ ): Array<RelationEdge> {
125
145
  if (others.length === 0) {
126
146
  return []
127
147
  }
@@ -169,7 +189,7 @@ export class LikeC4ModelGraph {
169
189
  /**
170
190
  * Resolve all RelationEdges between elements (any direction)
171
191
  */
172
- public edgesWithin<T extends Fqn[] | Element[]>(elements: T): RelationEdge[] {
192
+ public edgesWithin<T extends Fqn[] | Element[]>(elements: T): Array<RelationEdge> {
173
193
  if (elements.length < 2) {
174
194
  return []
175
195
  }
@@ -190,8 +210,8 @@ export class LikeC4ModelGraph {
190
210
  _sources: FqnOrElement | FqnsOrElements,
191
211
  _targets: FqnOrElement | FqnsOrElements
192
212
  ) {
193
- const sources = Array.isArray(_sources) ? _sources : [_sources]
194
- const targets = Array.isArray(_targets) ? _targets : [_targets]
213
+ const sources = isArray(_sources) ? _sources : [_sources]
214
+ const targets = isArray(_targets) ? _targets : [_targets]
195
215
  if (sources.length === 0 || targets.length === 0) {
196
216
  return []
197
217
  }
@@ -226,12 +246,13 @@ export class LikeC4ModelGraph {
226
246
 
227
247
  private addElement(el: Element) {
228
248
  if (this.#elements.has(el.id)) {
229
- throw new InvalidModelError(`Element ${el.id} already exists`)
249
+ throw new Error(`Element ${el.id} already exists`)
230
250
  }
231
251
  this.#elements.set(el.id, el)
232
- const parent = parentFqn(el.id)
233
- if (parent) {
234
- this._childrenOf(parent).push(el.id)
252
+ const parentId = parentFqn(el.id)
253
+ if (parentId) {
254
+ this.#parents.set(el.id, this.element(parentId))
255
+ this._childrenOf(parentId).push(el)
235
256
  } else {
236
257
  this.#rootElements.add(el)
237
258
  }
@@ -239,7 +260,7 @@ export class LikeC4ModelGraph {
239
260
 
240
261
  private addRelation(rel: Relation) {
241
262
  if (this.#relations.has(rel.id)) {
242
- throw new InvalidModelError(`Relation ${rel.id} already exists`)
263
+ throw new Error(`Relation ${rel.id} already exists`)
243
264
  }
244
265
  this.#relations.set(rel.id, rel)
245
266
  this._incomingTo(rel.target).add(rel)
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  type BorderStyle,
3
+ type Color,
3
4
  type ComputedView,
4
5
  type CustomElementExpr as C4CustomElementExpr,
5
6
  type CustomRelationExpr as C4CustomRelationExpr,
@@ -17,7 +18,6 @@ import {
17
18
  type RelationshipLineType,
18
19
  type RelationWhereExpr,
19
20
  type Tag,
20
- type ThemeColor,
21
21
  type ViewID,
22
22
  type ViewRule,
23
23
  type ViewRulePredicate,
@@ -210,28 +210,29 @@ export const fakeElements = {
210
210
 
211
211
  export type FakeElementIds = keyof typeof fakeElements
212
212
 
213
- const rel = ({
213
+ const rel = <Source extends FakeElementIds, Target extends FakeElementIds>({
214
214
  source,
215
215
  target,
216
216
  title,
217
217
  ...props
218
218
  }: {
219
- source: FakeElementIds
220
- target: FakeElementIds
219
+ source: Source
220
+ target: Target
221
221
  title?: string
222
222
  kind?: string
223
- color?: ThemeColor
223
+ color?: Color
224
224
  line?: RelationshipLineType
225
225
  head?: RelationshipArrowType
226
226
  tail?: RelationshipArrowType
227
227
  tags?: NonEmptyArray<TestTag>
228
- }): Relation => ({
229
- id: `${source}:${target}` as RelationID,
230
- title: title ?? '',
231
- source: source as Fqn,
232
- target: target as Fqn,
233
- ...(props as any)
234
- })
228
+ }) =>
229
+ ({
230
+ id: `${source}:${target}` as RelationID,
231
+ title: title ?? '',
232
+ source: source as Fqn,
233
+ target: target as Fqn,
234
+ ...(props as any)
235
+ }) as Omit<Relation, 'id'> & { id: `${Source}:${Target}` }
235
236
 
236
237
  export const fakeRelations = [
237
238
  rel({
@@ -321,7 +322,7 @@ export const fakeRelations = [
321
322
  })
322
323
  ]
323
324
 
324
- export type FakeRelationIds = keyof typeof fakeRelations
325
+ export type FakeRelationIds = (typeof fakeRelations)[number]['id']
325
326
 
326
327
  export const fakeModel = new LikeC4ModelGraph({
327
328
  elements: fakeElements,
@@ -335,6 +336,7 @@ const emptyView = {
335
336
  description: null,
336
337
  tags: null,
337
338
  links: null,
339
+ customColorDefinitions: {},
338
340
  rules: []
339
341
  }
340
342
 
@@ -370,7 +372,7 @@ export function $custom(
370
372
  description?: string
371
373
  technology?: string
372
374
  shape?: ElementShape
373
- color?: ThemeColor
375
+ color?: Color
374
376
  border?: BorderStyle
375
377
  icon?: string
376
378
  opacity?: number