@likec4/core 1.17.1 → 1.19.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 (184) hide show
  1. package/LICENSE +1 -1
  2. package/dist/builder/index.d.mts +502 -0
  3. package/dist/builder/index.d.ts +502 -0
  4. package/dist/builder/index.js +871 -0
  5. package/dist/compute-view/index.d.mts +22 -0
  6. package/dist/compute-view/index.d.ts +22 -0
  7. package/dist/compute-view/index.js +78 -0
  8. package/dist/index.d.mts +10 -919
  9. package/dist/index.d.ts +10 -919
  10. package/dist/{index.mjs → index.js} +7 -1688
  11. package/dist/model/index.d.mts +254 -0
  12. package/dist/model/index.d.ts +254 -0
  13. package/dist/model/index.js +1 -0
  14. package/dist/shared/chunk-RAAYCPUM.M-JWF7SS.js +21 -0
  15. package/dist/shared/core.BIfgabEw.d.ts +719 -0
  16. package/dist/shared/core.BZ9Msq7w.d.mts +719 -0
  17. package/dist/shared/{core.CoLQkcYL.d.cts → core.C05RDLhi.d.ts} +460 -362
  18. package/dist/shared/{core.CoLQkcYL.d.mts → core.CmYMqgha.d.mts} +460 -362
  19. package/dist/shared/core.D6-fWbM3.d.ts +127 -0
  20. package/dist/shared/core.D74xkKkG.d.mts +148 -0
  21. package/dist/shared/core.D74xkKkG.d.ts +148 -0
  22. package/dist/shared/core.sLaWHBjR.d.mts +127 -0
  23. package/dist/shared/index.-BdzdI2C.js +7443 -0
  24. package/dist/shared/object_hash.CE_oF3I3.js +1222 -0
  25. package/dist/shared/{core.BXTs-8n7.mjs → view.CyZrG8BJ.js} +108 -47
  26. package/dist/types/index.d.mts +71 -1
  27. package/dist/types/index.d.ts +71 -1
  28. package/dist/types/index.js +20 -0
  29. package/dist/utils/index.d.mts +404 -0
  30. package/dist/utils/index.d.ts +404 -0
  31. package/dist/utils/index.js +330 -0
  32. package/package.json +59 -47
  33. package/src/builder/Builder-style1.test-d.ts +190 -0
  34. package/src/builder/Builder-style2.test-d.ts +175 -0
  35. package/src/builder/Builder.deployment.ts +224 -0
  36. package/src/builder/Builder.deploymentModel.ts +385 -0
  37. package/src/builder/Builder.element.ts +19 -19
  38. package/src/builder/Builder.model.ts +67 -42
  39. package/src/builder/Builder.ts +533 -367
  40. package/src/builder/Builder.view-common.ts +234 -0
  41. package/src/builder/Builder.view-deployment.ts +135 -0
  42. package/src/builder/Builder.view-element.ts +189 -0
  43. package/src/builder/Builder.views.ts +109 -33
  44. package/src/builder/Builder.with.ts +475 -0
  45. package/src/builder/__snapshots__/Builder-style1.spec.ts.snap +374 -0
  46. package/src/builder/__snapshots__/Builder-style2.spec.ts.snap +183 -0
  47. package/src/builder/__snapshots__/Builder.spec.ts.snap +374 -0
  48. package/src/builder/_types.ts +113 -18
  49. package/src/builder/index.ts +10 -4
  50. package/src/colors/index.ts +2 -2
  51. package/src/compute-view/compute-view.ts +83 -0
  52. package/src/compute-view/deployment-view/__test__/TestHelper.ts +236 -0
  53. package/src/compute-view/deployment-view/__test__/fixture.ts +198 -0
  54. package/src/compute-view/deployment-view/_types.ts +37 -0
  55. package/src/compute-view/deployment-view/clean-connections.ts +238 -0
  56. package/src/compute-view/deployment-view/compute.ts +125 -0
  57. package/src/compute-view/deployment-view/memory/index.ts +9 -0
  58. package/src/compute-view/deployment-view/memory/memory.ts +97 -0
  59. package/src/compute-view/deployment-view/predicates/elements.ts +144 -0
  60. package/src/compute-view/deployment-view/predicates/relation-direct.ts +237 -0
  61. package/src/compute-view/deployment-view/predicates/relation-in-out.ts +61 -0
  62. package/src/compute-view/deployment-view/predicates/relation-incoming.ts +95 -0
  63. package/src/compute-view/deployment-view/predicates/relation-outgoing.ts +88 -0
  64. package/src/compute-view/deployment-view/predicates/wildcard.ts +29 -0
  65. package/src/compute-view/deployment-view/stages/stage-exclude.ts +5 -0
  66. package/src/compute-view/deployment-view/stages/stage-final.ts +245 -0
  67. package/src/compute-view/deployment-view/stages/stage-include.ts +95 -0
  68. package/src/compute-view/deployment-view/utils.ts +276 -0
  69. package/src/compute-view/dynamic-view/__test__/fixture.ts +58 -0
  70. package/src/compute-view/dynamic-view/compute.ts +324 -0
  71. package/src/compute-view/element-view/__test__/TestHelper.ts +258 -0
  72. package/src/compute-view/element-view/__test__/__snapshots__/legacy.spec.ts.snap +458 -0
  73. package/src/compute-view/element-view/__test__/fixture.ts +675 -0
  74. package/src/compute-view/element-view/_types.ts +48 -0
  75. package/src/compute-view/element-view/clean-connections.ts +113 -0
  76. package/src/compute-view/element-view/compute.ts +315 -0
  77. package/src/compute-view/element-view/memory/NodeGroup.ts +31 -0
  78. package/src/compute-view/element-view/memory/index.ts +10 -0
  79. package/src/compute-view/element-view/memory/memory.ts +177 -0
  80. package/src/compute-view/element-view/memory/stage-exclude.ts +59 -0
  81. package/src/compute-view/element-view/memory/stage-final.ts +129 -0
  82. package/src/compute-view/element-view/memory/stage-include.ts +128 -0
  83. package/src/compute-view/element-view/predicates/_utils.ts +86 -0
  84. package/src/compute-view/element-view/predicates/element-expand.ts +33 -0
  85. package/src/compute-view/element-view/predicates/element-kind-tag.ts +26 -0
  86. package/src/compute-view/element-view/predicates/element-ref.ts +37 -0
  87. package/src/compute-view/element-view/predicates/relation-direct.ts +309 -0
  88. package/src/compute-view/element-view/predicates/relation-in-out.ts +66 -0
  89. package/src/compute-view/element-view/predicates/relation-in.ts +118 -0
  90. package/src/compute-view/element-view/predicates/relation-out.ts +116 -0
  91. package/src/compute-view/element-view/predicates/wildcard.ts +88 -0
  92. package/src/compute-view/element-view/utils.ts +59 -0
  93. package/src/compute-view/index.ts +4 -0
  94. package/src/compute-view/memory/AbstractMemory.ts +45 -0
  95. package/src/compute-view/memory/AbstractStageExclude.ts +216 -0
  96. package/src/compute-view/memory/AbstractStageInclude.ts +200 -0
  97. package/src/compute-view/memory/_types.ts +147 -0
  98. package/src/compute-view/memory/index.ts +5 -0
  99. package/src/compute-view/memory/ops.ts +43 -0
  100. package/src/compute-view/utils/__snapshots__/sortNodes.spec.ts.snap +113 -0
  101. package/src/compute-view/utils/ancestorsOfNode.ts +19 -0
  102. package/src/compute-view/utils/applyCustomElementProperties.ts +66 -0
  103. package/src/compute-view/utils/applyCustomRelationProperties.ts +41 -0
  104. package/src/compute-view/utils/applyViewRuleStyles.ts +53 -0
  105. package/src/compute-view/utils/buildComputedNodes.ts +148 -0
  106. package/src/compute-view/utils/buildElementNotations.ts +63 -0
  107. package/src/compute-view/utils/elementExpressionToPredicate.ts +50 -0
  108. package/src/compute-view/utils/link-nodes-with-edges.ts +54 -0
  109. package/src/compute-view/utils/merge-props-from-relationships.ts +128 -0
  110. package/src/compute-view/utils/relationExpressionToPredicates.ts +55 -0
  111. package/src/compute-view/utils/resolve-extended-views.ts +69 -0
  112. package/src/compute-view/utils/resolve-global-rules.ts +83 -0
  113. package/src/compute-view/utils/topological-sort.ts +256 -0
  114. package/src/compute-view/utils/uniqueTags.test.ts +42 -0
  115. package/src/compute-view/utils/uniqueTags.ts +21 -0
  116. package/src/compute-view/utils/view-hash.ts +27 -0
  117. package/src/compute-view/utils/with-readable-edges.ts +36 -0
  118. package/src/index.ts +22 -12
  119. package/src/model/DeploymentElementModel.ts +599 -0
  120. package/src/model/DeploymentModel.ts +370 -0
  121. package/src/model/ElementModel.ts +221 -0
  122. package/src/model/LikeC4Model.test-d.ts +152 -0
  123. package/src/model/LikeC4Model.ts +336 -457
  124. package/src/model/RelationModel.ts +85 -0
  125. package/src/model/__test__/fixture.ts +149 -338
  126. package/src/model/connection/Connection.ts +72 -0
  127. package/src/model/connection/deployment/DeploymentConnectionModel.ts +178 -0
  128. package/src/model/connection/deployment/find.ts +120 -0
  129. package/src/model/connection/deployment/index.ts +8 -0
  130. package/src/model/connection/index.ts +22 -0
  131. package/src/model/connection/model/ConnectionModel.ts +157 -0
  132. package/src/model/connection/model/find.ts +117 -0
  133. package/src/model/connection/model/index.ts +8 -0
  134. package/src/model/connection/ops.ts +274 -0
  135. package/src/model/guards.ts +9 -0
  136. package/src/model/index.ts +46 -2
  137. package/src/model/types.ts +112 -15
  138. package/src/model/view/EdgeModel.ts +103 -0
  139. package/src/model/view/LikeC4ViewModel.ts +226 -0
  140. package/src/model/view/NodeModel.ts +229 -0
  141. package/src/types/_common.ts +55 -1
  142. package/src/types/deployments.ts +169 -0
  143. package/src/types/element.ts +9 -5
  144. package/src/types/expression-v2.ts +172 -0
  145. package/src/types/expression.ts +17 -11
  146. package/src/types/global.ts +3 -3
  147. package/src/types/index.ts +220 -12
  148. package/src/types/model.ts +75 -12
  149. package/src/types/operators.ts +1 -1
  150. package/src/types/overview-graph.ts +2 -2
  151. package/src/types/relation.ts +18 -14
  152. package/src/types/theme.ts +0 -1
  153. package/src/types/view-notation.ts +2 -2
  154. package/src/types/view.ts +117 -41
  155. package/src/utils/{commonHead.ts → common-head.ts} +2 -0
  156. package/src/utils/const.ts +1 -0
  157. package/src/utils/fqn.ts +203 -56
  158. package/src/utils/getOrCreate.ts +42 -0
  159. package/src/utils/graphlib.ts +10 -0
  160. package/src/utils/index.ts +74 -6
  161. package/src/utils/iterable/_types.ts +5 -0
  162. package/src/utils/iterable/filter.ts +55 -0
  163. package/src/utils/iterable/flat.ts +17 -0
  164. package/src/utils/iterable/index.ts +12 -0
  165. package/src/utils/iterable/map.ts +40 -0
  166. package/src/utils/iterable/reduce.ts +24 -0
  167. package/src/utils/iterable/some.ts +36 -0
  168. package/src/utils/iterable/to.ts +19 -0
  169. package/src/utils/iterable/unique.ts +29 -0
  170. package/src/utils/object-hash.ts +3 -0
  171. package/src/utils/relations.ts +3 -64
  172. package/src/utils/set.ts +78 -0
  173. package/src/utils/string-hash.ts +16 -0
  174. package/dist/index.cjs +0 -5452
  175. package/dist/index.d.cts +0 -1053
  176. package/dist/shared/core.CoLQkcYL.d.ts +0 -734
  177. package/dist/shared/core.DIYZvpVg.cjs +0 -353
  178. package/dist/types/index.cjs +0 -70
  179. package/dist/types/index.d.cts +0 -2
  180. package/dist/types/index.mjs +0 -1
  181. package/src/builder/Builder.test-d.ts +0 -69
  182. package/src/builder/Builder.view.ts +0 -358
  183. package/src/model/LikeC4DiagramModel.ts +0 -338
  184. package/src/model/LikeC4ViewModel.ts +0 -320
@@ -1,305 +1,397 @@
1
- import { isString, isTruthy, map, pipe, sort, values } from 'remeda'
2
- import type { Class } from 'type-fest'
3
- import { nonNullable } from '../errors'
1
+ import DefaultMap from 'mnemonist/default-map'
2
+ import { mapValues, pipe, sort, values } from 'remeda'
3
+ import type { LiteralUnion } from 'type-fest'
4
+ import { computeView, unsafeComputeView } from '../compute-view'
5
+ import type { ComputeViewResult } from '../compute-view/compute-view'
6
+ import { invariant, nonNullable } from '../errors'
7
+ import type { ComputedView, DiagramView, IteratorLike, LikeC4View, ModelGlobals } from '../types'
8
+ import type { Element } from '../types/element'
9
+ import { type Tag as C4Tag } from '../types/element'
10
+ import type { AnyParsedLikeC4Model, GenericLikeC4Model, LikeC4ModelDump } from '../types/model'
11
+ import type { ModelRelation } from '../types/relation'
12
+ import { compareNatural } from '../utils'
13
+ import { ancestorsFqn, commonAncestor, parentFqn } from '../utils/fqn'
4
14
  import type {
5
- Element as C4Element,
6
- ElementKind as C4ElementKind,
7
- ElementShape as C4ElementShape,
8
- Tag as C4Tag
9
- } from '../types/element'
10
- import { DefaultElementShape, DefaultThemeColor } from '../types/element'
11
- import type {
12
- ComputedLikeC4Model as C4ComputedLikeC4Model,
13
- LayoutedLikeC4Model as C4LayoutedLikeC4Model
14
- } from '../types/model'
15
- import type { Relation as C4Relation } from '../types/relation'
16
- import type { Color as C4Color } from '../types/theme'
17
- import { compareNatural } from '../utils/compare-natural'
18
- import { ancestorsFqn, commonAncestor, parentFqn, sortNaturalByFqn } from '../utils/fqn'
19
- import { LikeC4DiagramModel } from './LikeC4DiagramModel'
20
- import { LikeC4ViewModel } from './LikeC4ViewModel'
21
- import type { ElementOrFqn, Fqn, IncomingFilter, OutgoingFilter, RelationID, ViewID } from './types'
22
- import { getId } from './types'
23
-
24
- type PickBySource<Source> = Source extends LikeC4Model.Layouted.SourceModel ? LikeC4Model.Layouted
25
- : Source extends LikeC4Model.Computed.SourceModel ? LikeC4Model.Computed
26
- : never
27
-
28
- export class LikeC4Model<M extends LikeC4Model.ViewModel = LikeC4Model.ViewModel> {
29
- private _elements = new Map<Fqn, LikeC4Model.ElementModel<M>>()
15
+ DeployedInstanceModel,
16
+ DeploymentElementModel,
17
+ DeploymentNodeModel,
18
+ DeploymentRelationModel,
19
+ } from './DeploymentElementModel'
20
+ import { LikeC4DeploymentModel } from './DeploymentModel'
21
+ import { type ElementsIterator, ElementModel } from './ElementModel'
22
+ import { type RelationshipsIterator, RelationshipModel } from './RelationModel'
23
+ import { type AnyAux, type Aux, type IncomingFilter, type OutgoingFilter, getId } from './types'
24
+ import { EdgeModel } from './view/EdgeModel'
25
+ import { LikeC4ViewModel } from './view/LikeC4ViewModel'
26
+ import { NodeModel } from './view/NodeModel'
27
+
28
+ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
29
+ /**
30
+ * Only available in compile tim
31
+ */
32
+ readonly Aux!: M
30
33
 
34
+ readonly #elements = new Map<M['Element'], ElementModel<M>>()
31
35
  // Parent element for given FQN
32
- private _parents = new Map<Fqn, LikeC4Model.ElementModel<M>>()
33
-
36
+ readonly #parents = new Map<M['Element'], ElementModel<M>>()
34
37
  // Children elements for given FQN
35
- private _children = new Map<Fqn, LikeC4Model.ElementModel<M>[]>()
38
+ readonly #children = new DefaultMap<M['Element'], Set<ElementModel<M>>>(() => new Set())
36
39
 
37
- private _rootElements = new Set<LikeC4Model.ElementModel<M>>()
40
+ readonly #rootElements = new Set<ElementModel<M>>()
38
41
 
39
- private _relations = new Map<RelationID, LikeC4Model.Relationship<M>>()
42
+ readonly #relations = new Map<M['RelationId'], RelationshipModel<M>>()
40
43
 
41
44
  // Incoming to an element or its descendants
42
- private _incoming = new Map<Fqn, Set<LikeC4Model.Relationship<M>>>()
45
+ readonly #incoming = new DefaultMap<M['Element'], Set<RelationshipModel<M>>>(() => new Set())
43
46
 
44
47
  // Outgoing from an element or its descendants
45
- private _outgoing = new Map<Fqn, Set<LikeC4Model.Relationship<M>>>()
48
+ readonly #outgoing = new DefaultMap<M['Element'], Set<RelationshipModel<M>>>(() => new Set())
46
49
 
47
50
  // Relationships inside the element, among descendants
48
- private _internal = new Map<Fqn, Set<LikeC4Model.Relationship<M>>>()
51
+ readonly #internal = new DefaultMap<M['Element'], Set<RelationshipModel<M>>>(() => new Set())
52
+
53
+ readonly #views = new Map<M['ViewId'], LikeC4ViewModel<M>>()
54
+
55
+ readonly #allTags = new DefaultMap<C4Tag, Set<ElementModel<M> | RelationshipModel<M> | LikeC4ViewModel<M>>>(() =>
56
+ new Set()
57
+ )
58
+
59
+ public readonly deployment: LikeC4DeploymentModel<M>
60
+
61
+ /**
62
+ * Computes views from the parsed model
63
+ * Creates a new LikeC4Model instance from a parsed model.
64
+ *
65
+ * May throw an error if the model is invalid.
66
+ *
67
+ * @typeParam M - The type of the parsed LikeC4 model, must extend ParsedLikeC4Model
68
+ * @param parsed - The parsed LikeC4 model to compute from
69
+ * @returns A new LikeC4Model instance with computed relationships and structure
70
+ */
71
+ static compute<const M extends AnyParsedLikeC4Model>(parsed: M): LikeC4Model<Aux.FromParsed<M>> {
72
+ let { views, ...rest } = parsed as Omit<M, '__'>
73
+ const model = new LikeC4Model({ ...rest, views: {} })
74
+ return new LikeC4Model({
75
+ ...rest,
76
+ views: mapValues(views, view => unsafeComputeView(view as LikeC4View, model)),
77
+ } as any)
78
+ }
49
79
 
50
- private _cacheAscendingSiblings = new Map<Fqn, LikeC4Model.ElementModel<M>[]>()
80
+ /**
81
+ * Creates a function that computes a view using the data from the model.
82
+ *
83
+ * @example
84
+ * const compute = LikeC4Model.makeCompute(parsedModel);
85
+ * const result = compute(viewSource);
86
+ */
87
+ static makeCompute<M extends AnyParsedLikeC4Model>(parsed: M): (viewsource: LikeC4View) => ComputeViewResult {
88
+ let { views, ...rest } = parsed as Omit<M, '__'>
89
+ const model = new LikeC4Model(structuredClone({ ...rest, views: {} }))
90
+ return (viewsource) => computeView(viewsource, model)
91
+ }
51
92
 
52
- private _views: Map<Fqn, M>
93
+ /**
94
+ * Creates a new LikeC4Model instance from the provided model data.
95
+ *
96
+ * @typeParam M - Type parameter constrained to AnyLikeC4Model
97
+ * @param model - The model data to create a LikeC4Model from
98
+ * @returns A new LikeC4Model instance with the type derived from the input model
99
+ */
100
+ static create<const M extends GenericLikeC4Model>(model: M): LikeC4Model<Aux.FromModel<M>> {
101
+ return new LikeC4Model(model as any)
102
+ }
53
103
 
54
- constructor(
55
- public readonly type: LikeC4Model.ModelType<M>,
56
- public readonly sourcemodel: LikeC4Model.SourceModel<M>,
57
- elements: C4Element[],
58
- relations: C4Relation[],
59
- views: LikeC4Model.SourceModel<M>['views'],
60
- ViewModelClass: Class<M>
104
+ /**
105
+ * Creates a new LikeC4Model instance from a model dump.
106
+ *
107
+ * @typeParam M - A constant type parameter extending LikeC4ModelDump
108
+ * @param dump - The model dump to create the instance from
109
+ * @returns A new LikeC4Model instance with types inferred from the dump
110
+ */
111
+ static fromDump<const M extends LikeC4ModelDump>(dump: M): LikeC4Model<Aux.FromDump<M>> {
112
+ return new LikeC4Model(dump as any)
113
+ }
114
+
115
+ private constructor(
116
+ public readonly $model: M['Model'],
61
117
  ) {
62
- for (const el of elements) {
63
- this.addElement(el)
118
+ for (const element of values($model.elements)) {
119
+ const el = this.addElement(element)
120
+ for (const tag of el.tags) {
121
+ this.#allTags.get(tag).add(el)
122
+ }
64
123
  }
65
- for (const rel of relations) {
66
- this.addRelation(rel)
124
+ for (const relation of values($model.relations)) {
125
+ const el = this.addRelation(relation)
126
+ for (const tag of el.tags) {
127
+ this.#allTags.get(tag).add(el)
128
+ }
67
129
  }
68
- this._views = new Map(
69
- pipe(
70
- values(views),
71
- sort((a, b) => compareNatural(a.title ?? 'untitled', b.title ?? 'untitled')),
72
- map(view => [view.id, new ViewModelClass(view, this)] as [Fqn, M])
73
- )
130
+ this.deployment = new LikeC4DeploymentModel(this, $model.deployments)
131
+ const views = pipe(
132
+ values($model.views),
133
+ sort((a, b) => compareNatural(a.title ?? 'untitled', b.title ?? 'untitled')),
74
134
  )
135
+ for (const view of views) {
136
+ const vm = new LikeC4ViewModel(this, Object.freeze(view as M['ViewType']))
137
+ this.#views.set(view.id, vm)
138
+ for (const tag of vm.tags) {
139
+ this.#allTags.get(tag).add(vm)
140
+ }
141
+ }
75
142
  }
76
143
 
77
- /**
78
- * Returns the root elements of the model.
79
- */
80
- public roots(): ReadonlyArray<LikeC4Model.ElementModel<M>> {
81
- return [...this._rootElements]
144
+ get type(): 'computed' | 'layouted' {
145
+ return this.$model.__ ?? 'computed'
146
+ }
147
+
148
+ public element(el: M['ElementOrFqn']): ElementModel<M> {
149
+ if (el instanceof ElementModel) {
150
+ return el
151
+ }
152
+ const id = getId(el)
153
+ return nonNullable(this.findElement(id), `Element ${getId(el)} not found`)
154
+ }
155
+ public findElement(el: LiteralUnion<M['Element'], string>): ElementModel<M> | null {
156
+ return this.#elements.get(el) ?? null
82
157
  }
83
158
 
84
159
  /**
85
- * Returns all elements in the model.
160
+ * Returns the root elements of the model.
86
161
  */
87
- public elements(): ReadonlyArray<LikeC4Model.ElementModel<M>> {
88
- return [...this._elements.values()]
162
+ public roots(): ElementsIterator<M> {
163
+ return this.#rootElements.values()
89
164
  }
90
165
 
91
166
  /**
92
- * Returns a specific element by its FQN.
167
+ * Returns all elements in the model.
93
168
  */
94
- public element(id: Fqn): LikeC4Model.ElementModel<M> {
95
- return nonNullable(this._elements.get(id), `Element ${id} not found`)
169
+ public elements(): ElementsIterator<M> {
170
+ return this.#elements.values()
96
171
  }
97
172
 
98
173
  /**
99
174
  * Returns all relationships in the model.
100
175
  */
101
- public relationships(): ReadonlyArray<LikeC4Model.Relationship<M>> {
102
- return [...this._relations.values()]
176
+ public relationships(): RelationshipsIterator<M> {
177
+ return this.#relations.values()
103
178
  }
104
179
 
105
180
  /**
106
181
  * Returns a specific relationship by its ID.
182
+ * If the relationship is not found in the model, it will be searched in the deployment model.
183
+ * Search can be limited to the model or deployment model only.
107
184
  */
108
- public relationship(id: RelationID) {
109
- return nonNullable(this._relations.get(id), `Relation ${id} not found`)
185
+ public relationship(id: M['RelationId'], type: 'model'): RelationshipModel<M>
186
+ public relationship(id: M['RelationId'], type: 'deployment'): DeploymentRelationModel<M>
187
+ public relationship(
188
+ id: M['RelationId'],
189
+ type?: 'model' | 'deployment',
190
+ ): RelationshipModel<M> | DeploymentRelationModel<M>
191
+ public relationship(
192
+ id: M['RelationId'],
193
+ type: 'model' | 'deployment' | undefined,
194
+ ): RelationshipModel<M> | DeploymentRelationModel<M> {
195
+ if (type === 'deployment') {
196
+ return this.deployment.relationship(id)
197
+ }
198
+ let model = this.#relations.get(id) ?? null
199
+ if (model || type === 'model') {
200
+ return nonNullable(model, `Model relation ${id} not found`)
201
+ }
202
+ // We did not find the relation in the model, let's search in the deployment
203
+ return nonNullable(this.deployment.findRelationship(id), `No model/deployment relation ${id} not found`)
204
+ }
205
+
206
+ public findRelationship(id: LiteralUnion<M['RelationId'], string>, type: 'model'): RelationshipModel<M> | null
207
+ public findRelationship(
208
+ id: LiteralUnion<M['RelationId'], string>,
209
+ type: 'deployment',
210
+ ): DeploymentRelationModel<M> | null
211
+ public findRelationship(
212
+ id: LiteralUnion<M['RelationId'], string>,
213
+ type?: 'model' | 'deployment',
214
+ ): RelationshipModel<M> | DeploymentRelationModel<M> | null
215
+ public findRelationship(
216
+ id: LiteralUnion<M['RelationId'], string>,
217
+ type: 'model' | 'deployment' | undefined,
218
+ ): RelationshipModel<M> | DeploymentRelationModel<M> | null {
219
+ if (type === 'deployment') {
220
+ return this.deployment.findRelationship(id)
221
+ }
222
+ let model = this.#relations.get(id as M['RelationId']) ?? null
223
+ if (model || type === 'model') {
224
+ return model
225
+ }
226
+ return this.deployment.findRelationship(id)
110
227
  }
111
228
 
112
229
  /**
113
230
  * Returns all views in the model.
114
231
  */
115
- public views(): ReadonlyArray<M> {
116
- return [...this._views.values()]
232
+ public views(): IteratorLike<LikeC4ViewModel<M>> {
233
+ return this.#views.values()
117
234
  }
118
235
 
119
236
  /**
120
237
  * Returns a specific view by its ID.
121
238
  */
122
- public view(viewId: ViewID): M {
123
- return nonNullable(this._views.get(viewId), `View ${viewId} not found`)
239
+ public view(viewId: M['View']): LikeC4ViewModel<M> {
240
+ return nonNullable(this.#views.get(viewId as any), `View ${viewId} not found`)
241
+ }
242
+ public findView(viewId: M['View']): LikeC4ViewModel<M> {
243
+ return nonNullable(this.#views.get(viewId as M['ViewId']), `View ${viewId} not found`)
124
244
  }
125
245
 
126
246
  /**
127
247
  * Returns the parent element of given element.
128
248
  * @see ancestors
129
249
  */
130
- public parent(element: ElementOrFqn): LikeC4Model.ElementModel<M> | null {
250
+ public parent(element: M['ElementOrFqn']): ElementModel<M> | null {
131
251
  const id = getId(element)
132
- return this._parents.get(id) || null
252
+ return this.#parents.get(id) || null
133
253
  }
134
254
 
135
255
  /**
136
256
  * Get all children of the element (only direct children),
137
257
  * @see descendants
138
258
  */
139
- public children(element: ElementOrFqn): ReadonlyArray<LikeC4Model.ElementModel<M>> {
140
- const id = getId(element)
141
- return this._childrenOf(id)
259
+ public children(element: M['ElementOrFqn']): ReadonlySet<ElementModel<M>> {
260
+ const id = getId(element) as M['Fqn']
261
+ return this.#children.get(id)
142
262
  }
143
263
 
144
264
  /**
145
265
  * Get all sibling (i.e. same parent)
146
266
  */
147
- public siblings(element: ElementOrFqn): ReadonlyArray<LikeC4Model.ElementModel<M>> {
148
- const id = getId(element)
149
- const parent = this._parents.get(id)
150
- const siblings = parent ? this._childrenOf(parent.id) : this.roots()
151
- return siblings.filter(e => e.id !== id)
267
+ public *siblings(element: M['ElementOrFqn']): ElementsIterator<M> {
268
+ const id = getId(element) as M['Fqn']
269
+ const parent = this.#parents.get(id)
270
+ const siblings = parent ? this.#children.get(parent.id).values() : this.roots()
271
+ for (const sibling of siblings) {
272
+ if (sibling.id !== id) {
273
+ yield sibling
274
+ }
275
+ }
276
+ return
152
277
  }
153
278
 
154
279
  /**
155
280
  * Get all ancestor elements (i.e. parent, parent’s parent, etc.)
156
281
  * (from closest to root)
157
282
  */
158
- public ancestors(element: ElementOrFqn): ReadonlyArray<LikeC4Model.ElementModel<M>> {
159
- let id = isString(element) ? element : element.id
160
- const result = [] as LikeC4Model.ElementModel<M>[]
283
+ public *ancestors(element: M['ElementOrFqn']): ElementsIterator<M> {
284
+ let id = getId(element) as M['Fqn']
161
285
  let parent
162
- while (parent = this._parents.get(id)) {
163
- result.push(parent)
286
+ while (parent = this.#parents.get(id)) {
287
+ yield parent
164
288
  id = parent.id
165
289
  }
166
- return result
290
+ return
167
291
  }
168
292
 
169
293
  /**
170
294
  * Get all descendant elements (i.e. children, children’s children, etc.)
171
295
  */
172
- public descendants(element: ElementOrFqn): ReadonlyArray<LikeC4Model.ElementModel<M>> {
173
- const id = getId(element)
174
- const children = this._childrenOf(id)
175
- return children.flatMap(c => [c, ...this.descendants(c.id)])
296
+ public *descendants(element: M['ElementOrFqn']): ElementsIterator<M> {
297
+ for (const child of this.children(element)) {
298
+ yield child
299
+ yield* this.descendants(child.id)
300
+ }
301
+ return
176
302
  }
177
303
 
178
304
  /**
179
305
  * Incoming relationships to the element and its descendants
180
306
  * @see incomers
181
307
  */
182
- public incoming(
183
- element: ElementOrFqn,
184
- filter: IncomingFilter = 'all'
185
- ): ReadonlyArray<LikeC4Model.Relationship<M>> {
308
+ public *incoming(
309
+ element: M['ElementOrFqn'],
310
+ filter: IncomingFilter = 'all',
311
+ ): RelationshipsIterator<M> {
186
312
  const id = getId(element)
187
- const incoming = Array.from(this._incomingTo(id))
188
- if (filter === 'all' || incoming.length === 0) {
189
- return incoming
190
- }
191
-
192
- return incoming.filter(rel => {
193
- if (filter === 'direct') {
194
- return rel.target.id === id
313
+ for (const rel of this.#incoming.get(id)) {
314
+ switch (true) {
315
+ case filter === 'all':
316
+ case filter === 'direct' && rel.target.id === id:
317
+ case filter === 'to-descendants' && rel.target.id !== id:
318
+ yield rel
319
+ break
195
320
  }
196
- return rel.target.id !== id
197
- })
198
- }
199
-
200
- /**
201
- * Source elements of incoming relationships
202
- */
203
- public incomers(
204
- element: ElementOrFqn,
205
- filter: IncomingFilter = 'all'
206
- ): ReadonlyArray<LikeC4Model.ElementModel<M>> {
207
- return [...new Set(this.incoming(element, filter).map(r => r.source))]
321
+ }
322
+ return
208
323
  }
209
324
 
210
325
  /**
211
326
  * Outgoing relationships from the element and its descendants
212
327
  * @see outgoers
213
328
  */
214
- public outgoing(
215
- element: ElementOrFqn,
216
- filter: OutgoingFilter = 'all'
217
- ): ReadonlyArray<LikeC4Model.Relationship<M>> {
329
+ public *outgoing(
330
+ element: M['ElementOrFqn'],
331
+ filter: OutgoingFilter = 'all',
332
+ ): RelationshipsIterator<M> {
218
333
  const id = getId(element)
219
- const outgoing = Array.from(this._outgoingFrom(id))
220
- if (filter === 'all' || outgoing.length === 0) {
221
- return outgoing
222
- }
223
- return outgoing.filter(rel => {
224
- if (filter === 'direct') {
225
- return rel.source.id === id
334
+ for (const rel of this.#outgoing.get(id)) {
335
+ switch (true) {
336
+ case filter === 'all':
337
+ case filter === 'direct' && rel.source.id === id:
338
+ case filter === 'from-descendants' && rel.source.id !== id:
339
+ yield rel
340
+ break
226
341
  }
227
- return rel.source.id !== id
228
- })
229
- }
230
-
231
- /**
232
- * Target elements of outgoing relationships
233
- */
234
- public outgoers(
235
- element: ElementOrFqn,
236
- filter: OutgoingFilter = 'all'
237
- ): ReadonlyArray<LikeC4Model.ElementModel<M>> {
238
- return [...new Set(this.outgoing(element, filter).map(r => r.target))]
239
- }
240
-
241
- /**
242
- * Relationships inside the element, among descendants
243
- */
244
- public internal(element: ElementOrFqn): ReadonlyArray<LikeC4Model.Relationship<M>> {
245
- const id = getId(element)
246
- return Array.from(this._internalOf(id))
247
- }
248
-
249
- /**
250
- * Resolve siblings of the element and siblings of ancestors
251
- * (from closest to root)
252
- */
253
- public ascendingSiblings(element: ElementOrFqn): ReadonlyArray<LikeC4Model.ElementModel<M>> {
254
- const id = getId(element)
255
- let siblings = this._cacheAscendingSiblings.get(id)
256
- if (!siblings) {
257
- siblings = [
258
- ...this.siblings(id),
259
- ...this.ancestors(id).flatMap(a => this.siblings(a.id))
260
- ]
261
- this._cacheAscendingSiblings.set(id, siblings)
262
342
  }
263
- return siblings.slice()
343
+ return
264
344
  }
265
345
 
266
- /**
267
- * Resolve all views that contain the element
268
- */
269
- public viewsWithElement(element: ElementOrFqn): ReadonlyArray<M> {
270
- const id = getId(element)
271
- return [...this._views.values()].filter(v => v.hasElement(id))
346
+ public globals(): ModelGlobals {
347
+ return {
348
+ predicates: {
349
+ ...this.$model.globals?.predicates,
350
+ },
351
+ dynamicPredicates: {
352
+ ...this.$model.globals?.dynamicPredicates,
353
+ },
354
+ styles: {
355
+ ...this.$model.globals?.styles,
356
+ },
357
+ }
272
358
  }
273
359
 
274
- private addElement(parsed: C4Element) {
275
- if (this._elements.has(parsed.id)) {
276
- throw new Error(`Element ${parsed.id} already exists`)
360
+ private addElement(element: Element) {
361
+ if (this.#elements.has(element.id)) {
362
+ throw new Error(`Element ${element.id} already exists`)
277
363
  }
278
- const el = new LikeC4Model.ElementModel(parsed, this)
279
- this._elements.set(el.id, el)
364
+ const el = new ElementModel(this, Object.freeze(structuredClone(element)))
365
+ this.#elements.set(el.id, el)
280
366
  const parentId = parentFqn(el.id)
281
367
  if (parentId) {
282
- this._parents.set(el.id, this.element(parentId))
283
- this._childrenOf(parentId).push(el)
368
+ invariant(this.#elements.has(parentId), `Parent ${parentId} of ${el.id} not found`)
369
+ this.#parents.set(el.id, this.element(parentId))
370
+ this.#children.get(parentId).add(el)
284
371
  } else {
285
- this._rootElements.add(el)
372
+ this.#rootElements.add(el)
286
373
  }
374
+ return el
287
375
  }
288
376
 
289
- private addRelation(relation: C4Relation) {
290
- if (this._relations.has(relation.id)) {
377
+ private addRelation(relation: ModelRelation) {
378
+ if (this.#relations.has(relation.id)) {
291
379
  throw new Error(`Relation ${relation.id} already exists`)
292
380
  }
293
- const rel = new LikeC4Model.Relationship(relation, this)
294
- this._relations.set(rel.id, rel)
295
- this._incomingTo(relation.target).add(rel)
296
- this._outgoingFrom(relation.source).add(rel)
381
+ const rel = new RelationshipModel(
382
+ this,
383
+ Object.freeze(structuredClone(relation)),
384
+ )
385
+ const { source, target } = rel
386
+ this.#relations.set(rel.id, rel)
387
+ this.#incoming.get(target.id).add(rel)
388
+ this.#outgoing.get(source.id).add(rel)
297
389
 
298
- const relParent = commonAncestor(relation.source, relation.target)
390
+ const relParent = commonAncestor(source.id, target.id)
299
391
  // Process internal relationships
300
392
  if (relParent) {
301
393
  for (const ancestor of [relParent, ...ancestorsFqn(relParent)]) {
302
- this._internalOf(ancestor).add(rel)
394
+ this.#internal.get(ancestor).add(rel)
303
395
  }
304
396
  }
305
397
  // Process source hierarchy
@@ -307,285 +399,72 @@ export class LikeC4Model<M extends LikeC4Model.ViewModel = LikeC4Model.ViewModel
307
399
  if (sourceAncestor === relParent) {
308
400
  break
309
401
  }
310
- this._outgoingFrom(sourceAncestor).add(rel)
402
+ this.#outgoing.get(sourceAncestor).add(rel)
311
403
  }
312
404
  // Process target hierarchy
313
405
  for (const targetAncestor of ancestorsFqn(relation.target)) {
314
406
  if (targetAncestor === relParent) {
315
407
  break
316
408
  }
317
- this._incomingTo(targetAncestor).add(rel)
318
- }
319
- }
320
-
321
- private _childrenOf(id: Fqn) {
322
- let children = this._children.get(id)
323
- if (!children) {
324
- children = []
325
- this._children.set(id, children)
409
+ this.#incoming.get(targetAncestor).add(rel)
326
410
  }
327
- return children
328
- }
329
-
330
- private _incomingTo(id: Fqn) {
331
- let incoming = this._incoming.get(id)
332
- if (!incoming) {
333
- incoming = new Set()
334
- this._incoming.set(id, incoming)
335
- }
336
- return incoming
337
- }
338
-
339
- private _outgoingFrom(id: Fqn) {
340
- let outgoing = this._outgoing.get(id)
341
- if (!outgoing) {
342
- outgoing = new Set()
343
- this._outgoing.set(id, outgoing)
344
- }
345
- return outgoing
346
- }
347
-
348
- private _internalOf(id: Fqn) {
349
- let internal = this._internal.get(id)
350
- if (!internal) {
351
- internal = new Set()
352
- this._internal.set(id, internal)
353
- }
354
- return internal
411
+ return rel
355
412
  }
356
413
  }
357
414
 
358
415
  export namespace LikeC4Model {
359
- export function create(source: LikeC4Model.SourceModel): PickBySource<typeof source> {
360
- // static create<MM extends LikeC4Model>(source: MM['s']): PickBySource<typeof source> {
361
- if (source.__ === 'layouted') {
362
- return LikeC4Model.layouted(source)
363
- }
364
- return LikeC4Model.computed(source)
365
- }
366
-
367
- export function computed(source: C4ComputedLikeC4Model): LikeC4Model<LikeC4ViewModel> {
368
- return new LikeC4Model(
369
- 'computed',
370
- source,
371
- values(source.elements),
372
- values(source.relations),
373
- source.views,
374
- LikeC4ViewModel
375
- )
376
- }
377
-
378
- export function layouted(source: C4LayoutedLikeC4Model): LikeC4Model<LikeC4DiagramModel> {
379
- return new LikeC4Model(
380
- 'layouted',
381
- source,
382
- values(source.elements),
383
- values(source.relations),
384
- source.views,
385
- LikeC4DiagramModel
386
- )
387
- }
388
-
389
- export function isModel(model: any): model is LikeC4Model {
390
- return model instanceof LikeC4Model
391
- }
392
-
393
- export type SourceModel<M extends ViewModel = ViewModel> = M extends LikeC4DiagramModel ? C4LayoutedLikeC4Model
394
- : C4ComputedLikeC4Model
395
-
396
- export type ViewModel = LikeC4ViewModel | LikeC4DiagramModel
397
- export namespace ViewModel {
398
- export type ElementModel = LikeC4ViewModel.Element | LikeC4DiagramModel.Element
399
- export type Relationship = LikeC4Model.Relationship<ViewModel>
400
-
401
- export function isLayouted(model: ViewModel): model is LikeC4DiagramModel {
402
- return model instanceof LikeC4DiagramModel
403
- }
404
- }
405
-
406
- export type Computed = LikeC4Model<LikeC4ViewModel>
407
- export namespace Computed {
408
- export type ViewModel = LikeC4ViewModel
409
- export type SourceModel = LikeC4Model.SourceModel<ViewModel>
410
- }
411
-
412
- export type Layouted = LikeC4Model<LikeC4DiagramModel>
413
- export namespace Layouted {
414
- export type ViewModel = LikeC4DiagramModel
415
- export type SourceModel = LikeC4Model.SourceModel<ViewModel>
416
- }
417
-
418
- export type ModelType<M extends ViewModel = ViewModel> = M extends LikeC4DiagramModel ? 'layouted'
419
- : M extends LikeC4ViewModel ? 'computed'
420
- : never
421
-
422
- export function isLayouted(model: LikeC4Model): model is Layouted {
423
- return model.type === 'layouted'
424
- }
425
-
426
- export class Relationship<M extends ViewModel = ViewModel> {
427
- constructor(
428
- public readonly relationship: C4Relation,
429
- private readonly model: LikeC4Model<M>
430
- ) {
431
- }
432
-
433
- get id() {
434
- return this.relationship.id
435
- }
436
-
437
- get title() {
438
- return this.relationship.title
439
- }
440
-
441
- get kind() {
442
- return this.relationship.kind ?? null
443
- }
444
-
445
- get tags(): C4Tag[] {
446
- return this.relationship.tags ?? []
447
- }
448
-
449
- get source() {
450
- return this.model.element(this.relationship.source)
451
- }
452
-
453
- get target() {
454
- return this.model.element(this.relationship.target)
455
- }
456
-
457
- public metadata(key: string): string | undefined
458
- public metadata(key: string, defaultValue: string): string
459
- public metadata(key: string, defaultValue?: string): string | undefined {
460
- return this.relationship.metadata?.[key] ?? defaultValue
461
- }
462
-
463
- public hasMetadata(key: string): boolean {
464
- return isTruthy(this.relationship.metadata?.[key])
465
- }
466
- }
467
-
468
- // Class renamed to ElementModel, otherwise generated DTS will be incorrect
469
- export class ElementModel<M extends ViewModel = ViewModel> {
470
- constructor(
471
- public readonly element: C4Element,
472
- private readonly model: LikeC4Model<M>
473
- ) {
474
- }
475
-
476
- get id() {
477
- return this.element.id
478
- }
479
-
480
- get title() {
481
- return this.element.title
482
- }
483
-
484
- get kind(): C4ElementKind {
485
- return this.element.kind
486
- }
487
-
488
- get isRoot(): boolean {
489
- return parentFqn(this.element.id) === null
490
- }
491
-
492
- get hasNested(): boolean {
493
- return this.model.children(this).length > 0
494
- }
495
-
496
- get shape(): C4ElementShape {
497
- return this.element.shape ?? DefaultElementShape
498
- }
499
-
500
- get color(): C4Color {
501
- return this.element.color ?? DefaultThemeColor
502
- }
503
-
504
- get tags(): C4Tag[] {
505
- return this.element.tags ?? []
506
- }
507
-
508
- public parent(): ElementModel<M> | null {
509
- return this.model.parent(this)
510
- }
511
-
512
- public metadata(key: string): string | undefined
513
- public metadata(key: string, defaultValue: string): string
514
- public metadata(key: string, defaultValue?: string): string | undefined {
515
- return this.element.metadata?.[key] ?? defaultValue
516
- }
517
-
518
- public hasMetadata(key: string): boolean {
519
- return isTruthy(this.element.metadata?.[key])
520
- }
521
-
522
- public ancestors() {
523
- return this.model.ancestors(this)
524
- }
525
-
526
- public siblings() {
527
- return this.model.siblings(this)
528
- }
529
-
530
- public descendants() {
531
- return this.model.descendants(this)
532
- }
533
-
534
- public children() {
535
- return this.model.children(this)
536
- }
537
-
538
- /**
539
- * First 'view of' current element
540
- */
541
- public viewOf() {
542
- return this.model.views().find(v => v.viewOf?.id === this.id) ?? null
543
- }
544
-
545
- /**
546
- * All views 'view of' current element
547
- */
548
- public viewsOf() {
549
- return this.model.views().filter(v => v.viewOf?.id === this.id)
550
- }
551
-
552
- /**
553
- * Views that contain this element
554
- */
555
- public views() {
556
- return this.model.viewsWithElement(this)
557
- }
558
-
559
- public incoming(filter?: IncomingFilter) {
560
- return this.model.incoming(this, filter)
561
- }
562
-
563
- public incomers(filter?: IncomingFilter) {
564
- return this.model.incomers(this, filter)
565
- }
566
-
567
- public outgoing(filter?: OutgoingFilter) {
568
- return this.model.outgoing(this, filter)
569
- }
570
-
571
- public outgoers(filter?: OutgoingFilter) {
572
- return this.model.outgoers(this, filter)
573
- }
574
-
575
- public internal() {
576
- return this.model.internal(this)
577
- }
578
-
579
- /**
580
- * Resolve siblings of the element and siblings of ancestors
581
- * (from closest to root)
582
- */
583
- public ascendingSiblings() {
584
- return this.model.ascendingSiblings(this)
585
- }
586
-
587
- // public *descendants(): IterableIterator<LikeC4Element> {
588
- // return
589
- // }
590
- }
416
+ export type Any = Aux<
417
+ string,
418
+ string,
419
+ string,
420
+ ComputedView | DiagramView
421
+ >
422
+
423
+ export type Element<M extends AnyAux = Any> = ElementModel<M>
424
+
425
+ // Relationship in logical model
426
+ export type Relation<M extends AnyAux = Any> = RelationshipModel<M>
427
+
428
+ // Relationship in logical or deployment model
429
+ export type AnyRelation<M extends AnyAux = Any> = RelationshipModel<M> | DeploymentRelationModel<M>
430
+
431
+ export type View<
432
+ M extends AnyAux = Any,
433
+ V extends ComputedView | DiagramView = M['ViewType'],
434
+ > = LikeC4ViewModel<M, V>
435
+
436
+ export type Node<
437
+ M extends AnyAux = Any,
438
+ V extends ComputedView | DiagramView = M['ViewType'],
439
+ > = NodeModel<M, V>
440
+
441
+ export type Edge<
442
+ M extends AnyAux = Any,
443
+ V extends ComputedView | DiagramView = M['ViewType'],
444
+ > = EdgeModel<M, V>
445
+
446
+ export type DeploymentModel<M extends AnyAux = AnyAux> = LikeC4DeploymentModel<M>
447
+
448
+ export type Deployment<M extends AnyAux = AnyAux> = DeploymentElementModel<M>
449
+ export type DeploymentNode<M extends AnyAux = AnyAux> = DeploymentNodeModel<M>
450
+ export type DeployedInstance<M extends AnyAux = AnyAux> = DeployedInstanceModel<M>
451
+
452
+ export type Typed<
453
+ Elements extends string = string,
454
+ Deployments extends string = string,
455
+ Views extends string = string,
456
+ ViewType = DiagramView<Views> | ComputedView<Views>,
457
+ > = Aux<Elements, Deployments, Views, ViewType>
458
+
459
+ export type Computed<
460
+ Elements extends string = string,
461
+ Deployments extends string = string,
462
+ Views extends string = string,
463
+ > = LikeC4Model<Typed<Elements, Deployments, Views, ComputedView<Views>>>
464
+
465
+ export type Layouted<
466
+ Elements extends string = string,
467
+ Deployments extends string = string,
468
+ Views extends string = string,
469
+ > = LikeC4Model<Typed<Elements, Deployments, Views, DiagramView<Views>>>
591
470
  }