@likec4/core 1.18.0 → 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.
- package/LICENSE +1 -1
- package/dist/builder/index.d.mts +502 -0
- package/dist/builder/index.d.ts +502 -0
- package/dist/builder/index.js +871 -0
- package/dist/compute-view/index.d.mts +9 -78
- package/dist/compute-view/index.d.ts +9 -78
- package/dist/compute-view/index.js +78 -0
- package/dist/index.d.mts +9 -730
- package/dist/index.d.ts +9 -730
- package/dist/{index.mjs → index.js} +7 -784
- package/dist/model/index.d.mts +218 -606
- package/dist/model/index.d.ts +218 -606
- package/dist/model/index.js +1 -0
- package/dist/shared/chunk-RAAYCPUM.M-JWF7SS.js +21 -0
- package/dist/shared/core.BIfgabEw.d.ts +719 -0
- package/dist/shared/core.BZ9Msq7w.d.mts +719 -0
- package/dist/shared/{core.DMYY7C8s.d.mts → core.C05RDLhi.d.ts} +179 -230
- package/dist/shared/{core.DMYY7C8s.d.ts → core.CmYMqgha.d.mts} +179 -230
- package/dist/shared/core.D6-fWbM3.d.ts +127 -0
- package/dist/shared/core.D74xkKkG.d.mts +148 -0
- package/dist/shared/core.D74xkKkG.d.ts +148 -0
- package/dist/shared/core.sLaWHBjR.d.mts +127 -0
- package/dist/shared/index.-BdzdI2C.js +7443 -0
- package/dist/shared/object_hash.CE_oF3I3.js +1222 -0
- package/dist/shared/view.CyZrG8BJ.js +354 -0
- package/dist/types/index.d.mts +4 -2
- package/dist/types/index.d.ts +4 -2
- package/dist/types/index.js +20 -0
- package/dist/utils/index.d.mts +404 -0
- package/dist/utils/index.d.ts +404 -0
- package/dist/utils/index.js +330 -0
- package/package.json +47 -33
- package/src/builder/Builder-style1.test-d.ts +190 -0
- package/src/builder/Builder-style2.test-d.ts +175 -0
- package/src/builder/Builder.deployment.ts +25 -20
- package/src/builder/Builder.deploymentModel.ts +80 -55
- package/src/builder/Builder.element.ts +2 -0
- package/src/builder/Builder.model.ts +57 -43
- package/src/builder/Builder.ts +412 -431
- package/src/builder/Builder.view-common.ts +234 -0
- package/src/builder/Builder.view-deployment.ts +135 -0
- package/src/builder/Builder.view-element.ts +189 -0
- package/src/builder/Builder.views.ts +109 -33
- package/src/builder/Builder.with.ts +475 -0
- package/src/builder/__snapshots__/Builder-style1.spec.ts.snap +374 -0
- package/src/builder/__snapshots__/Builder-style2.spec.ts.snap +183 -0
- package/src/builder/__snapshots__/Builder.spec.ts.snap +374 -0
- package/src/builder/_types.ts +28 -5
- package/src/builder/index.ts +10 -4
- package/src/colors/index.ts +2 -2
- package/src/compute-view/compute-view.ts +45 -54
- package/src/compute-view/deployment-view/__test__/TestHelper.ts +236 -0
- package/src/compute-view/deployment-view/__test__/fixture.ts +50 -62
- package/src/compute-view/deployment-view/_types.ts +26 -18
- package/src/compute-view/deployment-view/clean-connections.ts +173 -164
- package/src/compute-view/deployment-view/compute.ts +51 -37
- package/src/compute-view/deployment-view/memory/index.ts +9 -0
- package/src/compute-view/deployment-view/memory/memory.ts +97 -0
- package/src/compute-view/deployment-view/predicates/elements.ts +144 -0
- package/src/compute-view/deployment-view/predicates/relation-direct.ts +183 -76
- package/src/compute-view/deployment-view/predicates/relation-in-out.ts +38 -20
- package/src/compute-view/deployment-view/predicates/relation-incoming.ts +72 -23
- package/src/compute-view/deployment-view/predicates/relation-outgoing.ts +61 -22
- package/src/compute-view/deployment-view/predicates/wildcard.ts +15 -15
- package/src/compute-view/deployment-view/stages/stage-exclude.ts +5 -0
- package/src/compute-view/deployment-view/stages/stage-final.ts +245 -0
- package/src/compute-view/deployment-view/stages/stage-include.ts +95 -0
- package/src/compute-view/deployment-view/utils.ts +95 -31
- package/src/compute-view/dynamic-view/__test__/fixture.ts +4 -4
- package/src/compute-view/dynamic-view/compute.ts +59 -45
- package/src/compute-view/element-view/__test__/TestHelper.ts +258 -0
- package/src/compute-view/element-view/__test__/__snapshots__/legacy.spec.ts.snap +64 -62
- package/src/compute-view/element-view/__test__/fixture.ts +147 -117
- package/src/compute-view/element-view/_types.ts +48 -0
- package/src/compute-view/element-view/clean-connections.ts +113 -0
- package/src/compute-view/element-view/compute.ts +264 -683
- package/src/compute-view/element-view/memory/NodeGroup.ts +31 -0
- package/src/compute-view/element-view/memory/index.ts +10 -0
- package/src/compute-view/element-view/memory/memory.ts +177 -0
- package/src/compute-view/element-view/memory/stage-exclude.ts +59 -0
- package/src/compute-view/element-view/memory/stage-final.ts +129 -0
- package/src/compute-view/element-view/memory/stage-include.ts +128 -0
- package/src/compute-view/element-view/predicates/_utils.ts +86 -0
- package/src/compute-view/element-view/predicates/element-expand.ts +33 -0
- package/src/compute-view/element-view/predicates/element-kind-tag.ts +26 -0
- package/src/compute-view/element-view/predicates/element-ref.ts +37 -0
- package/src/compute-view/element-view/predicates/relation-direct.ts +309 -0
- package/src/compute-view/element-view/predicates/relation-in-out.ts +66 -0
- package/src/compute-view/element-view/predicates/relation-in.ts +118 -0
- package/src/compute-view/element-view/predicates/relation-out.ts +116 -0
- package/src/compute-view/element-view/predicates/wildcard.ts +88 -0
- package/src/compute-view/element-view/utils.ts +59 -0
- package/src/compute-view/index.ts +3 -4
- package/src/compute-view/memory/AbstractMemory.ts +45 -0
- package/src/compute-view/memory/AbstractStageExclude.ts +216 -0
- package/src/compute-view/memory/AbstractStageInclude.ts +200 -0
- package/src/compute-view/memory/_types.ts +147 -0
- package/src/compute-view/memory/index.ts +5 -0
- package/src/compute-view/memory/ops.ts +43 -0
- package/src/compute-view/utils/buildComputedNodes.ts +4 -4
- package/src/compute-view/utils/elementExpressionToPredicate.ts +1 -3
- package/src/compute-view/utils/link-nodes-with-edges.ts +3 -7
- package/src/compute-view/utils/{derive-edge-props-from-relationships.ts → merge-props-from-relationships.ts} +54 -23
- package/src/compute-view/utils/relationExpressionToPredicates.ts +2 -2
- package/src/compute-view/utils/resolve-extended-views.ts +44 -40
- package/src/compute-view/utils/resolve-global-rules.ts +6 -6
- package/src/compute-view/utils/topological-sort.ts +167 -71
- package/src/compute-view/utils/with-readable-edges.ts +14 -9
- package/src/index.ts +22 -16
- package/src/model/DeploymentElementModel.ts +150 -85
- package/src/model/DeploymentModel.ts +27 -21
- package/src/model/ElementModel.ts +51 -25
- package/src/model/LikeC4Model.test-d.ts +4 -3
- package/src/model/LikeC4Model.ts +98 -42
- package/src/model/RelationModel.ts +5 -4
- package/src/model/__test__/fixture.ts +5 -9
- package/src/model/connection/Connection.ts +72 -0
- package/src/model/connection/deployment/DeploymentConnectionModel.ts +178 -0
- package/src/model/connection/{deployment.ts → deployment/find.ts} +14 -18
- package/src/model/connection/deployment/index.ts +8 -0
- package/src/model/connection/index.ts +22 -0
- package/src/model/connection/model/ConnectionModel.ts +157 -0
- package/src/model/connection/{model.ts → model/find.ts} +43 -49
- package/src/model/connection/model/index.ts +8 -0
- package/src/model/connection/ops.ts +274 -0
- package/src/model/guards.ts +9 -0
- package/src/model/index.ts +45 -3
- package/src/model/types.ts +35 -18
- package/src/model/view/EdgeModel.ts +6 -6
- package/src/model/view/LikeC4ViewModel.ts +14 -8
- package/src/model/view/NodeModel.ts +6 -5
- package/src/types/_common.ts +53 -1
- package/src/types/deployments.ts +88 -156
- package/src/types/element.ts +2 -2
- package/src/types/expression-v2.ts +172 -0
- package/src/types/expression.ts +9 -5
- package/src/types/global.ts +3 -3
- package/src/types/index.ts +220 -13
- package/src/types/model.ts +28 -24
- package/src/types/operators.ts +1 -1
- package/src/types/relation.ts +14 -10
- package/src/types/theme.ts +0 -1
- package/src/types/view.ts +20 -18
- package/src/utils/const.ts +1 -0
- package/src/utils/fqn.ts +140 -49
- package/src/utils/getOrCreate.ts +1 -1
- package/src/utils/index.ts +74 -9
- package/src/utils/iterable/_types.ts +5 -0
- package/src/utils/iterable/filter.ts +55 -0
- package/src/utils/iterable/flat.ts +17 -0
- package/src/utils/iterable/index.ts +12 -0
- package/src/utils/iterable/map.ts +40 -0
- package/src/utils/iterable/reduce.ts +24 -0
- package/src/utils/iterable/some.ts +36 -0
- package/src/utils/iterable/to.ts +19 -0
- package/src/utils/iterable/unique.ts +29 -0
- package/src/utils/relations.ts +3 -64
- package/src/utils/set.ts +11 -9
- package/src/utils/string-hash.ts +1 -1
- package/dist/compute-view/index.mjs +0 -79
- package/dist/model/index.mjs +0 -2
- package/dist/shared/core.BBgiAAv1.mjs +0 -144
- package/dist/shared/core.BPyJvuMU.mjs +0 -2420
- package/dist/shared/core.BQ4AbfvB.mjs +0 -9228
- package/dist/shared/core.k12s_keD.mjs +0 -203
- package/dist/types/index.mjs +0 -21
- package/src/builder/Builder.test-d.ts +0 -124
- package/src/builder/Builder.view.ts +0 -543
- package/src/compute-view/LikeC4ModelGraph.ts +0 -330
- package/src/compute-view/deployment-view/Memory.ts +0 -108
- package/src/compute-view/deployment-view/Stage.ts +0 -145
- package/src/compute-view/deployment-view/predicates/deployment-ref.ts +0 -146
- package/src/compute-view/element-view/predicates.ts +0 -517
- package/src/compute-view/utils/sortNodes.ts +0 -101
- package/src/model/connection/ConnectionModel.ts +0 -94
- package/src/model/connection/DeploymentConnectionModel.ts +0 -99
|
@@ -1,32 +1,38 @@
|
|
|
1
1
|
import { isTruthy } from 'remeda'
|
|
2
2
|
import {
|
|
3
|
-
DefaultElementShape,
|
|
4
|
-
DefaultThemeColor,
|
|
5
3
|
type Element as C4Element,
|
|
6
4
|
type ElementKind as C4ElementKind,
|
|
7
5
|
type ElementShape as C4ElementShape,
|
|
6
|
+
type IteratorLike,
|
|
8
7
|
type Link,
|
|
9
8
|
type Tag as C4Tag,
|
|
10
|
-
type ThemeColor
|
|
9
|
+
type ThemeColor,
|
|
10
|
+
DefaultElementShape,
|
|
11
|
+
DefaultThemeColor,
|
|
11
12
|
} from '../types'
|
|
12
|
-
import { commonAncestor } from '../utils'
|
|
13
|
+
import { commonAncestor, hierarchyLevel, isAncestor, sortNaturalByFqn } from '../utils'
|
|
13
14
|
import { type DeployedInstancesIterator } from './DeploymentElementModel'
|
|
14
15
|
import type { LikeC4Model } from './LikeC4Model'
|
|
15
16
|
import type { RelationshipModel, RelationshipsIterator } from './RelationModel'
|
|
16
|
-
import type { AnyAux, IncomingFilter,
|
|
17
|
+
import type { AnyAux, IncomingFilter, OutgoingFilter } from './types'
|
|
17
18
|
import type { LikeC4ViewModel, ViewsIterator } from './view/LikeC4ViewModel'
|
|
18
19
|
|
|
19
20
|
export type ElementsIterator<M extends AnyAux> = IteratorLike<ElementModel<M>>
|
|
20
21
|
|
|
21
|
-
export class ElementModel<M extends AnyAux> {
|
|
22
|
+
export class ElementModel<M extends AnyAux = AnyAux> {
|
|
23
|
+
readonly id: M['Fqn']
|
|
24
|
+
readonly hierarchyLevel: number
|
|
25
|
+
|
|
22
26
|
constructor(
|
|
23
|
-
public readonly model: LikeC4Model<M>,
|
|
24
|
-
public readonly $element: C4Element
|
|
27
|
+
public readonly $model: LikeC4Model<M>,
|
|
28
|
+
public readonly $element: C4Element,
|
|
25
29
|
) {
|
|
30
|
+
this.id = this.$element.id
|
|
31
|
+
this.hierarchyLevel = hierarchyLevel(this.id)
|
|
26
32
|
}
|
|
27
33
|
|
|
28
|
-
get
|
|
29
|
-
return this.$
|
|
34
|
+
get parent(): ElementModel<M> | null {
|
|
35
|
+
return this.$model.parent(this)
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
get kind(): C4ElementKind {
|
|
@@ -61,20 +67,24 @@ export class ElementModel<M extends AnyAux> {
|
|
|
61
67
|
return this.$element.links ?? []
|
|
62
68
|
}
|
|
63
69
|
|
|
64
|
-
get parent(): ElementModel<M> | null {
|
|
65
|
-
return this.model.parent(this)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
70
|
get defaultView(): LikeC4ViewModel<M> | null {
|
|
69
71
|
return this.scopedViews().next().value ?? null
|
|
70
72
|
}
|
|
71
73
|
|
|
74
|
+
public isAncestorOf(another: ElementModel<M>): boolean {
|
|
75
|
+
return isAncestor(this, another)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public isDescendantOf(another: ElementModel<M>): boolean {
|
|
79
|
+
return isAncestor(another, this)
|
|
80
|
+
}
|
|
81
|
+
|
|
72
82
|
/**
|
|
73
83
|
* Get all ancestor elements (i.e. parent, parent’s parent, etc.)
|
|
74
84
|
* (from closest to root)
|
|
75
85
|
*/
|
|
76
86
|
public ancestors(): ElementsIterator<M> {
|
|
77
|
-
return this
|
|
87
|
+
return this.$model.ancestors(this)
|
|
78
88
|
}
|
|
79
89
|
|
|
80
90
|
/**
|
|
@@ -82,25 +92,29 @@ export class ElementModel<M extends AnyAux> {
|
|
|
82
92
|
*/
|
|
83
93
|
public commonAncestor(another: ElementModel<M>): ElementModel<M> | null {
|
|
84
94
|
const common = commonAncestor(this.id, another.id)
|
|
85
|
-
return common ? this
|
|
95
|
+
return common ? this.$model.element(common) : null
|
|
86
96
|
}
|
|
87
97
|
|
|
88
98
|
public children(): ReadonlySet<ElementModel<M>> {
|
|
89
|
-
return this
|
|
99
|
+
return this.$model.children(this)
|
|
90
100
|
}
|
|
91
101
|
|
|
92
102
|
/**
|
|
93
103
|
* Get all descendant elements (i.e. children, children’s children, etc.)
|
|
94
104
|
*/
|
|
95
|
-
public descendants(): ElementsIterator<M> {
|
|
96
|
-
|
|
105
|
+
public descendants(sort?: 'asc' | 'desc'): ElementsIterator<M> {
|
|
106
|
+
if (sort) {
|
|
107
|
+
const sorted = sortNaturalByFqn([...this.$model.descendants(this)], sort)
|
|
108
|
+
return sorted[Symbol.iterator]()
|
|
109
|
+
}
|
|
110
|
+
return this.$model.descendants(this)
|
|
97
111
|
}
|
|
98
112
|
|
|
99
113
|
/**
|
|
100
114
|
* Get all sibling (i.e. same parent)
|
|
101
115
|
*/
|
|
102
116
|
public siblings(): ElementsIterator<M> {
|
|
103
|
-
return this
|
|
117
|
+
return this.$model.siblings(this)
|
|
104
118
|
}
|
|
105
119
|
|
|
106
120
|
/**
|
|
@@ -115,8 +129,20 @@ export class ElementModel<M extends AnyAux> {
|
|
|
115
129
|
return
|
|
116
130
|
}
|
|
117
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Resolve siblings of the element and its ancestors
|
|
134
|
+
* (from root to closest)
|
|
135
|
+
*/
|
|
136
|
+
public *descendingSiblings(): ElementsIterator<M> {
|
|
137
|
+
for (const ancestor of [...this.ancestors()].reverse()) {
|
|
138
|
+
yield* ancestor.siblings()
|
|
139
|
+
}
|
|
140
|
+
yield* this.siblings()
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
|
|
118
144
|
public incoming(filter: IncomingFilter = 'all'): RelationshipsIterator<M> {
|
|
119
|
-
return this
|
|
145
|
+
return this.$model.incoming(this, filter)
|
|
120
146
|
}
|
|
121
147
|
public *incomers(filter: IncomingFilter = 'all'): ElementsIterator<M> {
|
|
122
148
|
const unique = new Set<M['Fqn']>()
|
|
@@ -130,7 +156,7 @@ export class ElementModel<M extends AnyAux> {
|
|
|
130
156
|
return
|
|
131
157
|
}
|
|
132
158
|
public outgoing(filter: OutgoingFilter = 'all'): RelationshipsIterator<M> {
|
|
133
|
-
return this
|
|
159
|
+
return this.$model.outgoing(this, filter)
|
|
134
160
|
}
|
|
135
161
|
public *outgoers(filter: OutgoingFilter = 'all'): ElementsIterator<M> {
|
|
136
162
|
const unique = new Set<M['Fqn']>()
|
|
@@ -161,7 +187,7 @@ export class ElementModel<M extends AnyAux> {
|
|
|
161
187
|
* Iterate over all views that include this element.
|
|
162
188
|
*/
|
|
163
189
|
public *views(): ViewsIterator<M> {
|
|
164
|
-
for (const view of this
|
|
190
|
+
for (const view of this.$model.views()) {
|
|
165
191
|
if (view.includesElement(this.id)) {
|
|
166
192
|
yield view
|
|
167
193
|
}
|
|
@@ -174,7 +200,7 @@ export class ElementModel<M extends AnyAux> {
|
|
|
174
200
|
* It is possible that element is not included in the view.
|
|
175
201
|
*/
|
|
176
202
|
public *scopedViews(): ViewsIterator<M> {
|
|
177
|
-
for (const vm of this
|
|
203
|
+
for (const vm of this.$model.views()) {
|
|
178
204
|
if (vm.isElementView() && vm.$view.viewOf === this.id) {
|
|
179
205
|
yield vm
|
|
180
206
|
}
|
|
@@ -190,6 +216,6 @@ export class ElementModel<M extends AnyAux> {
|
|
|
190
216
|
}
|
|
191
217
|
|
|
192
218
|
public deployments(): DeployedInstancesIterator<M> {
|
|
193
|
-
return this
|
|
219
|
+
return this.$model.deployment.instancesOf(this)
|
|
194
220
|
}
|
|
195
221
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { expectTypeOf, test } from 'vitest'
|
|
2
2
|
import { Builder } from '../builder'
|
|
3
|
-
import { computeViews } from '../compute-view/compute-view'
|
|
4
3
|
import type { ComputedView, Fqn, ViewId } from '../types'
|
|
5
4
|
import { LikeC4Model } from './LikeC4Model'
|
|
6
5
|
|
|
@@ -80,8 +79,10 @@ test('LikeC4Model.create: should have types', () => {
|
|
|
80
79
|
)
|
|
81
80
|
.build()
|
|
82
81
|
|
|
83
|
-
const
|
|
84
|
-
|
|
82
|
+
const m = LikeC4Model.compute(source)
|
|
83
|
+
|
|
84
|
+
// @ts-expect-error
|
|
85
|
+
m.element('wrong')
|
|
85
86
|
|
|
86
87
|
expectTypeOf(m.Aux.Element).toEqualTypeOf(
|
|
87
88
|
'' as 'alice' | 'bob' | 'cloud' | 'cloud.backend' | 'cloud.backend.api' | 'cloud.backend.db' | 'cloud.frontend'
|
package/src/model/LikeC4Model.ts
CHANGED
|
@@ -1,31 +1,33 @@
|
|
|
1
1
|
import DefaultMap from 'mnemonist/default-map'
|
|
2
|
-
import { pipe, sort, values } from 'remeda'
|
|
2
|
+
import { mapValues, pipe, sort, values } from 'remeda'
|
|
3
3
|
import type { LiteralUnion } from 'type-fest'
|
|
4
|
+
import { computeView, unsafeComputeView } from '../compute-view'
|
|
5
|
+
import type { ComputeViewResult } from '../compute-view/compute-view'
|
|
4
6
|
import { invariant, nonNullable } from '../errors'
|
|
5
|
-
import type { ComputedView, DiagramView } from '../types'
|
|
7
|
+
import type { ComputedView, DiagramView, IteratorLike, LikeC4View, ModelGlobals } from '../types'
|
|
6
8
|
import type { Element } from '../types/element'
|
|
7
9
|
import { type Tag as C4Tag } from '../types/element'
|
|
8
|
-
import type {
|
|
9
|
-
import type {
|
|
10
|
+
import type { AnyParsedLikeC4Model, GenericLikeC4Model, LikeC4ModelDump } from '../types/model'
|
|
11
|
+
import type { ModelRelation } from '../types/relation'
|
|
10
12
|
import { compareNatural } from '../utils'
|
|
11
13
|
import { ancestorsFqn, commonAncestor, parentFqn } from '../utils/fqn'
|
|
12
14
|
import type {
|
|
13
15
|
DeployedInstanceModel,
|
|
14
16
|
DeploymentElementModel,
|
|
15
17
|
DeploymentNodeModel,
|
|
16
|
-
DeploymentRelationModel
|
|
18
|
+
DeploymentRelationModel,
|
|
17
19
|
} from './DeploymentElementModel'
|
|
18
20
|
import { LikeC4DeploymentModel } from './DeploymentModel'
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import { type AnyAux, type Aux,
|
|
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'
|
|
22
24
|
import { EdgeModel } from './view/EdgeModel'
|
|
23
25
|
import { LikeC4ViewModel } from './view/LikeC4ViewModel'
|
|
24
26
|
import { NodeModel } from './view/NodeModel'
|
|
25
27
|
|
|
26
28
|
export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
|
|
27
29
|
/**
|
|
28
|
-
* Only available in compile
|
|
30
|
+
* Only available in compile tim
|
|
29
31
|
*/
|
|
30
32
|
readonly Aux!: M
|
|
31
33
|
|
|
@@ -48,7 +50,7 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
|
|
|
48
50
|
// Relationships inside the element, among descendants
|
|
49
51
|
readonly #internal = new DefaultMap<M['Element'], Set<RelationshipModel<M>>>(() => new Set())
|
|
50
52
|
|
|
51
|
-
readonly #views = new Map<M['
|
|
53
|
+
readonly #views = new Map<M['ViewId'], LikeC4ViewModel<M>>()
|
|
52
54
|
|
|
53
55
|
readonly #allTags = new DefaultMap<C4Tag, Set<ElementModel<M> | RelationshipModel<M> | LikeC4ViewModel<M>>>(() =>
|
|
54
56
|
new Set()
|
|
@@ -56,21 +58,62 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
|
|
|
56
58
|
|
|
57
59
|
public readonly deployment: LikeC4DeploymentModel<M>
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
+
}
|
|
79
|
+
|
|
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)
|
|
62
91
|
}
|
|
63
92
|
|
|
64
93
|
/**
|
|
65
|
-
*
|
|
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
|
|
66
99
|
*/
|
|
67
|
-
static
|
|
68
|
-
|
|
69
|
-
|
|
100
|
+
static create<const M extends GenericLikeC4Model>(model: M): LikeC4Model<Aux.FromModel<M>> {
|
|
101
|
+
return new LikeC4Model(model as any)
|
|
102
|
+
}
|
|
103
|
+
|
|
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)
|
|
70
113
|
}
|
|
71
114
|
|
|
72
115
|
private constructor(
|
|
73
|
-
public readonly $model: M['Model']
|
|
116
|
+
public readonly $model: M['Model'],
|
|
74
117
|
) {
|
|
75
118
|
for (const element of values($model.elements)) {
|
|
76
119
|
const el = this.addElement(element)
|
|
@@ -87,10 +130,10 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
|
|
|
87
130
|
this.deployment = new LikeC4DeploymentModel(this, $model.deployments)
|
|
88
131
|
const views = pipe(
|
|
89
132
|
values($model.views),
|
|
90
|
-
sort((a, b) => compareNatural(a.title ?? 'untitled', b.title ?? 'untitled'))
|
|
133
|
+
sort((a, b) => compareNatural(a.title ?? 'untitled', b.title ?? 'untitled')),
|
|
91
134
|
)
|
|
92
135
|
for (const view of views) {
|
|
93
|
-
const vm = new LikeC4ViewModel(this, view as M['ViewType'])
|
|
136
|
+
const vm = new LikeC4ViewModel(this, Object.freeze(view as M['ViewType']))
|
|
94
137
|
this.#views.set(view.id, vm)
|
|
95
138
|
for (const tag of vm.tags) {
|
|
96
139
|
this.#allTags.get(tag).add(vm)
|
|
@@ -110,7 +153,6 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
|
|
|
110
153
|
return nonNullable(this.findElement(id), `Element ${getId(el)} not found`)
|
|
111
154
|
}
|
|
112
155
|
public findElement(el: LiteralUnion<M['Element'], string>): ElementModel<M> | null {
|
|
113
|
-
// const id = getId(el) as C4Fqn
|
|
114
156
|
return this.#elements.get(el) ?? null
|
|
115
157
|
}
|
|
116
158
|
|
|
@@ -144,11 +186,11 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
|
|
|
144
186
|
public relationship(id: M['RelationId'], type: 'deployment'): DeploymentRelationModel<M>
|
|
145
187
|
public relationship(
|
|
146
188
|
id: M['RelationId'],
|
|
147
|
-
type?: 'model' | 'deployment'
|
|
189
|
+
type?: 'model' | 'deployment',
|
|
148
190
|
): RelationshipModel<M> | DeploymentRelationModel<M>
|
|
149
191
|
public relationship(
|
|
150
192
|
id: M['RelationId'],
|
|
151
|
-
type: 'model' | 'deployment' | undefined
|
|
193
|
+
type: 'model' | 'deployment' | undefined,
|
|
152
194
|
): RelationshipModel<M> | DeploymentRelationModel<M> {
|
|
153
195
|
if (type === 'deployment') {
|
|
154
196
|
return this.deployment.relationship(id)
|
|
@@ -164,15 +206,15 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
|
|
|
164
206
|
public findRelationship(id: LiteralUnion<M['RelationId'], string>, type: 'model'): RelationshipModel<M> | null
|
|
165
207
|
public findRelationship(
|
|
166
208
|
id: LiteralUnion<M['RelationId'], string>,
|
|
167
|
-
type: 'deployment'
|
|
209
|
+
type: 'deployment',
|
|
168
210
|
): DeploymentRelationModel<M> | null
|
|
169
211
|
public findRelationship(
|
|
170
212
|
id: LiteralUnion<M['RelationId'], string>,
|
|
171
|
-
type?: 'model' | 'deployment'
|
|
213
|
+
type?: 'model' | 'deployment',
|
|
172
214
|
): RelationshipModel<M> | DeploymentRelationModel<M> | null
|
|
173
215
|
public findRelationship(
|
|
174
216
|
id: LiteralUnion<M['RelationId'], string>,
|
|
175
|
-
type: 'model' | 'deployment' | undefined
|
|
217
|
+
type: 'model' | 'deployment' | undefined,
|
|
176
218
|
): RelationshipModel<M> | DeploymentRelationModel<M> | null {
|
|
177
219
|
if (type === 'deployment') {
|
|
178
220
|
return this.deployment.findRelationship(id)
|
|
@@ -187,18 +229,18 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
|
|
|
187
229
|
/**
|
|
188
230
|
* Returns all views in the model.
|
|
189
231
|
*/
|
|
190
|
-
public views() {
|
|
232
|
+
public views(): IteratorLike<LikeC4ViewModel<M>> {
|
|
191
233
|
return this.#views.values()
|
|
192
234
|
}
|
|
193
235
|
|
|
194
236
|
/**
|
|
195
237
|
* Returns a specific view by its ID.
|
|
196
238
|
*/
|
|
197
|
-
public view(viewId: M['
|
|
198
|
-
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`)
|
|
199
241
|
}
|
|
200
|
-
public findView(viewId:
|
|
201
|
-
return nonNullable(this.#views.get(viewId), `View ${viewId} not found`)
|
|
242
|
+
public findView(viewId: M['View']): LikeC4ViewModel<M> {
|
|
243
|
+
return nonNullable(this.#views.get(viewId as M['ViewId']), `View ${viewId} not found`)
|
|
202
244
|
}
|
|
203
245
|
|
|
204
246
|
/**
|
|
@@ -265,7 +307,7 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
|
|
|
265
307
|
*/
|
|
266
308
|
public *incoming(
|
|
267
309
|
element: M['ElementOrFqn'],
|
|
268
|
-
filter: IncomingFilter = 'all'
|
|
310
|
+
filter: IncomingFilter = 'all',
|
|
269
311
|
): RelationshipsIterator<M> {
|
|
270
312
|
const id = getId(element)
|
|
271
313
|
for (const rel of this.#incoming.get(id)) {
|
|
@@ -286,7 +328,7 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
|
|
|
286
328
|
*/
|
|
287
329
|
public *outgoing(
|
|
288
330
|
element: M['ElementOrFqn'],
|
|
289
|
-
filter: OutgoingFilter = 'all'
|
|
331
|
+
filter: OutgoingFilter = 'all',
|
|
290
332
|
): RelationshipsIterator<M> {
|
|
291
333
|
const id = getId(element)
|
|
292
334
|
for (const rel of this.#outgoing.get(id)) {
|
|
@@ -301,11 +343,25 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
|
|
|
301
343
|
return
|
|
302
344
|
}
|
|
303
345
|
|
|
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
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
304
360
|
private addElement(element: Element) {
|
|
305
361
|
if (this.#elements.has(element.id)) {
|
|
306
362
|
throw new Error(`Element ${element.id} already exists`)
|
|
307
363
|
}
|
|
308
|
-
const el = new ElementModel(this, element)
|
|
364
|
+
const el = new ElementModel(this, Object.freeze(structuredClone(element)))
|
|
309
365
|
this.#elements.set(el.id, el)
|
|
310
366
|
const parentId = parentFqn(el.id)
|
|
311
367
|
if (parentId) {
|
|
@@ -318,13 +374,13 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
|
|
|
318
374
|
return el
|
|
319
375
|
}
|
|
320
376
|
|
|
321
|
-
private addRelation(relation:
|
|
377
|
+
private addRelation(relation: ModelRelation) {
|
|
322
378
|
if (this.#relations.has(relation.id)) {
|
|
323
379
|
throw new Error(`Relation ${relation.id} already exists`)
|
|
324
380
|
}
|
|
325
381
|
const rel = new RelationshipModel(
|
|
326
382
|
this,
|
|
327
|
-
relation
|
|
383
|
+
Object.freeze(structuredClone(relation)),
|
|
328
384
|
)
|
|
329
385
|
const { source, target } = rel
|
|
330
386
|
this.#relations.set(rel.id, rel)
|
|
@@ -374,17 +430,17 @@ export namespace LikeC4Model {
|
|
|
374
430
|
|
|
375
431
|
export type View<
|
|
376
432
|
M extends AnyAux = Any,
|
|
377
|
-
V extends ComputedView | DiagramView = M['ViewType']
|
|
433
|
+
V extends ComputedView | DiagramView = M['ViewType'],
|
|
378
434
|
> = LikeC4ViewModel<M, V>
|
|
379
435
|
|
|
380
436
|
export type Node<
|
|
381
437
|
M extends AnyAux = Any,
|
|
382
|
-
V extends ComputedView | DiagramView = M['ViewType']
|
|
438
|
+
V extends ComputedView | DiagramView = M['ViewType'],
|
|
383
439
|
> = NodeModel<M, V>
|
|
384
440
|
|
|
385
441
|
export type Edge<
|
|
386
442
|
M extends AnyAux = Any,
|
|
387
|
-
V extends ComputedView | DiagramView = M['ViewType']
|
|
443
|
+
V extends ComputedView | DiagramView = M['ViewType'],
|
|
388
444
|
> = EdgeModel<M, V>
|
|
389
445
|
|
|
390
446
|
export type DeploymentModel<M extends AnyAux = AnyAux> = LikeC4DeploymentModel<M>
|
|
@@ -397,18 +453,18 @@ export namespace LikeC4Model {
|
|
|
397
453
|
Elements extends string = string,
|
|
398
454
|
Deployments extends string = string,
|
|
399
455
|
Views extends string = string,
|
|
400
|
-
ViewType = DiagramView<Views> | ComputedView<Views
|
|
456
|
+
ViewType = DiagramView<Views> | ComputedView<Views>,
|
|
401
457
|
> = Aux<Elements, Deployments, Views, ViewType>
|
|
402
458
|
|
|
403
459
|
export type Computed<
|
|
404
460
|
Elements extends string = string,
|
|
405
461
|
Deployments extends string = string,
|
|
406
|
-
Views extends string = string
|
|
462
|
+
Views extends string = string,
|
|
407
463
|
> = LikeC4Model<Typed<Elements, Deployments, Views, ComputedView<Views>>>
|
|
408
464
|
|
|
409
465
|
export type Layouted<
|
|
410
466
|
Elements extends string = string,
|
|
411
467
|
Deployments extends string = string,
|
|
412
|
-
Views extends string = string
|
|
468
|
+
Views extends string = string,
|
|
413
469
|
> = LikeC4Model<Typed<Elements, Deployments, Views, DiagramView<Views>>>
|
|
414
470
|
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { isEmpty } from 'remeda'
|
|
2
|
+
import type { IteratorLike } from '../types'
|
|
2
3
|
import type { Link, Tag } from '../types/element'
|
|
3
|
-
import type {
|
|
4
|
+
import type { ModelRelation } from '../types/relation'
|
|
4
5
|
import { commonAncestor } from '../utils/fqn'
|
|
5
6
|
import type { ElementModel } from './ElementModel'
|
|
6
7
|
import type { LikeC4Model } from './LikeC4Model'
|
|
7
|
-
import type { AnyAux
|
|
8
|
+
import type { AnyAux } from './types'
|
|
8
9
|
import type { LikeC4ViewModel, ViewsIterator } from './view/LikeC4ViewModel'
|
|
9
10
|
|
|
10
11
|
export type RelationshipsIterator<M extends AnyAux> = IteratorLike<RelationshipModel<M>>
|
|
11
12
|
|
|
12
|
-
export class RelationshipModel<M extends AnyAux> {
|
|
13
|
+
export class RelationshipModel<M extends AnyAux = AnyAux> {
|
|
13
14
|
public readonly source: ElementModel<M>
|
|
14
15
|
public readonly target: ElementModel<M>
|
|
15
16
|
|
|
@@ -21,7 +22,7 @@ export class RelationshipModel<M extends AnyAux> {
|
|
|
21
22
|
|
|
22
23
|
constructor(
|
|
23
24
|
public readonly model: LikeC4Model<M>,
|
|
24
|
-
public readonly $relationship:
|
|
25
|
+
public readonly $relationship: ModelRelation,
|
|
25
26
|
) {
|
|
26
27
|
this.source = model.element($relationship.source)
|
|
27
28
|
this.target = model.element($relationship.target)
|
|
@@ -26,9 +26,7 @@ const {
|
|
|
26
26
|
viewOf,
|
|
27
27
|
deploymentView,
|
|
28
28
|
$rules,
|
|
29
|
-
$include
|
|
30
|
-
$exclude,
|
|
31
|
-
$style
|
|
29
|
+
$include
|
|
32
30
|
}
|
|
33
31
|
} = Builder.forSpecification({
|
|
34
32
|
elements: {
|
|
@@ -102,6 +100,7 @@ const local = b
|
|
|
102
100
|
$m.rel('cloud.backend.api', 'cloud.media', 'uploads media'),
|
|
103
101
|
$m.rel('cloud.backend.api', 'aws.rds', 'reads/writes'),
|
|
104
102
|
$m.rel('cloud.backend.api', 'email', 'sends emails'),
|
|
103
|
+
$m.rel('cloud', 'email', 'uses'),
|
|
105
104
|
$m.rel('cloud.media', 'aws.s3', 'uploads'),
|
|
106
105
|
$m.rel('email', 'customer', 'sends emails')
|
|
107
106
|
),
|
|
@@ -138,12 +137,9 @@ const local = b
|
|
|
138
137
|
$include('cloud.frontend.dashboard')
|
|
139
138
|
)
|
|
140
139
|
),
|
|
141
|
-
deploymentView(
|
|
142
|
-
'
|
|
143
|
-
$
|
|
144
|
-
$include('customer.instance'),
|
|
145
|
-
$include('prod.eu.zone1.ui')
|
|
146
|
-
)
|
|
140
|
+
deploymentView('prod').with(
|
|
141
|
+
$include('customer.instance'),
|
|
142
|
+
$include('prod.eu.zone1.ui')
|
|
147
143
|
)
|
|
148
144
|
)
|
|
149
145
|
)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Fqn } from '../../types'
|
|
2
|
+
import { isAncestor } from '../../utils'
|
|
3
|
+
import type { DeploymentElementModel } from '../DeploymentElementModel'
|
|
4
|
+
import type { ElementModel } from '../ElementModel'
|
|
5
|
+
|
|
6
|
+
export interface Connection<Elem = ElementModel | DeploymentElementModel, Id = string> {
|
|
7
|
+
readonly id: Id
|
|
8
|
+
readonly source: Elem
|
|
9
|
+
readonly target: Elem
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Common ancestor of the source and target elements.
|
|
13
|
+
* Represents the boundary of the connection.
|
|
14
|
+
*/
|
|
15
|
+
readonly boundary: Elem | null
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Human readable expression of the connection
|
|
19
|
+
* Mostly used for testing and debugging
|
|
20
|
+
*/
|
|
21
|
+
readonly expression: string
|
|
22
|
+
|
|
23
|
+
mergeWith(this: Connection<Elem, Id>, other: typeof this): typeof this
|
|
24
|
+
|
|
25
|
+
nonEmpty(): boolean
|
|
26
|
+
|
|
27
|
+
difference(this: Connection<Elem, Id>, other: typeof this): typeof this
|
|
28
|
+
|
|
29
|
+
intersect(this: Connection<Elem, Id>, other: typeof this): typeof this
|
|
30
|
+
|
|
31
|
+
equals(other: Connection): boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export namespace Connection {
|
|
35
|
+
type ConnectionPredicate = <C extends Connection<{ id: string }, any>>(connection: C) => boolean
|
|
36
|
+
|
|
37
|
+
type ElementId = Fqn | string
|
|
38
|
+
|
|
39
|
+
export const isInside = (fqn: ElementId): ConnectionPredicate => {
|
|
40
|
+
return (connection) => isAncestor(fqn, connection.source.id) && isAncestor(fqn, connection.target.id)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const isDirectedBetween = (source: ElementId, target: ElementId): ConnectionPredicate => {
|
|
44
|
+
return (connection) =>
|
|
45
|
+
(connection.source.id === source || isAncestor(source, connection.source.id))
|
|
46
|
+
&& (connection.target.id === target || isAncestor(target, connection.target.id))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const isAnyBetween = (source: ElementId, target: ElementId): ConnectionPredicate => {
|
|
50
|
+
const forward = isDirectedBetween(source, target),
|
|
51
|
+
backward = isDirectedBetween(target, source)
|
|
52
|
+
return (connection) => forward(connection) || backward(connection)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const isIncoming = (target: ElementId): ConnectionPredicate => {
|
|
56
|
+
return (connection) =>
|
|
57
|
+
(connection.target.id === target || isAncestor(target, connection.target.id))
|
|
58
|
+
&& !isAncestor(target, connection.source.id)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const isOutgoing = (source: ElementId): ConnectionPredicate => {
|
|
62
|
+
return (connection) =>
|
|
63
|
+
(connection.source.id === source || isAncestor(source, connection.source.id))
|
|
64
|
+
&& !isAncestor(source, connection.target.id)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const isAnyInOut = (source: ElementId): ConnectionPredicate => {
|
|
68
|
+
const isIn = isIncoming(source),
|
|
69
|
+
isOut = isOutgoing(source)
|
|
70
|
+
return (connection) => isIn(connection) || isOut(connection)
|
|
71
|
+
}
|
|
72
|
+
}
|