@likec4/core 1.17.1 → 1.18.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 (112) hide show
  1. package/dist/compute-view/index.d.mts +91 -0
  2. package/dist/compute-view/index.d.ts +91 -0
  3. package/dist/compute-view/index.mjs +79 -0
  4. package/dist/index.d.mts +246 -434
  5. package/dist/index.d.ts +246 -434
  6. package/dist/index.mjs +418 -1322
  7. package/dist/model/index.d.mts +642 -0
  8. package/dist/model/index.d.ts +642 -0
  9. package/dist/model/index.mjs +2 -0
  10. package/dist/shared/core.BBgiAAv1.mjs +144 -0
  11. package/dist/shared/core.BPyJvuMU.mjs +2420 -0
  12. package/dist/shared/core.BQ4AbfvB.mjs +9228 -0
  13. package/dist/shared/{core.CoLQkcYL.d.mts → core.DMYY7C8s.d.mts} +398 -249
  14. package/dist/shared/{core.CoLQkcYL.d.ts → core.DMYY7C8s.d.ts} +398 -249
  15. package/dist/shared/core.k12s_keD.mjs +203 -0
  16. package/dist/types/index.d.mts +69 -1
  17. package/dist/types/index.d.ts +69 -1
  18. package/dist/types/index.mjs +21 -1
  19. package/package.json +38 -40
  20. package/src/builder/Builder.deployment.ts +219 -0
  21. package/src/builder/Builder.deploymentModel.ts +360 -0
  22. package/src/builder/Builder.element.ts +17 -19
  23. package/src/builder/Builder.model.ts +13 -2
  24. package/src/builder/Builder.test-d.ts +59 -4
  25. package/src/builder/Builder.ts +238 -53
  26. package/src/builder/Builder.view.ts +238 -53
  27. package/src/builder/_types.ts +87 -15
  28. package/src/compute-view/LikeC4ModelGraph.ts +330 -0
  29. package/src/compute-view/compute-view.ts +92 -0
  30. package/src/compute-view/deployment-view/Memory.ts +108 -0
  31. package/src/compute-view/deployment-view/Stage.ts +145 -0
  32. package/src/compute-view/deployment-view/__test__/fixture.ts +210 -0
  33. package/src/compute-view/deployment-view/_types.ts +29 -0
  34. package/src/compute-view/deployment-view/clean-connections.ts +229 -0
  35. package/src/compute-view/deployment-view/compute.ts +111 -0
  36. package/src/compute-view/deployment-view/predicates/deployment-ref.ts +146 -0
  37. package/src/compute-view/deployment-view/predicates/relation-direct.ts +130 -0
  38. package/src/compute-view/deployment-view/predicates/relation-in-out.ts +43 -0
  39. package/src/compute-view/deployment-view/predicates/relation-incoming.ts +46 -0
  40. package/src/compute-view/deployment-view/predicates/relation-outgoing.ts +49 -0
  41. package/src/compute-view/deployment-view/predicates/wildcard.ts +29 -0
  42. package/src/compute-view/deployment-view/utils.ts +212 -0
  43. package/src/compute-view/dynamic-view/__test__/fixture.ts +58 -0
  44. package/src/compute-view/dynamic-view/compute.ts +310 -0
  45. package/src/compute-view/element-view/__test__/__snapshots__/legacy.spec.ts.snap +456 -0
  46. package/src/compute-view/element-view/__test__/fixture.ts +645 -0
  47. package/src/compute-view/element-view/compute.ts +734 -0
  48. package/src/compute-view/element-view/predicates.ts +517 -0
  49. package/src/compute-view/index.ts +5 -0
  50. package/src/compute-view/utils/__snapshots__/sortNodes.spec.ts.snap +113 -0
  51. package/src/compute-view/utils/ancestorsOfNode.ts +19 -0
  52. package/src/compute-view/utils/applyCustomElementProperties.ts +66 -0
  53. package/src/compute-view/utils/applyCustomRelationProperties.ts +41 -0
  54. package/src/compute-view/utils/applyViewRuleStyles.ts +53 -0
  55. package/src/compute-view/utils/buildComputedNodes.ts +148 -0
  56. package/src/compute-view/utils/buildElementNotations.ts +63 -0
  57. package/src/compute-view/utils/derive-edge-props-from-relationships.ts +97 -0
  58. package/src/compute-view/utils/elementExpressionToPredicate.ts +52 -0
  59. package/src/compute-view/utils/link-nodes-with-edges.ts +58 -0
  60. package/src/compute-view/utils/relationExpressionToPredicates.ts +55 -0
  61. package/src/compute-view/utils/resolve-extended-views.ts +65 -0
  62. package/src/compute-view/utils/resolve-global-rules.ts +83 -0
  63. package/src/compute-view/utils/sortNodes.ts +101 -0
  64. package/src/compute-view/utils/topological-sort.ts +160 -0
  65. package/src/compute-view/utils/uniqueTags.test.ts +42 -0
  66. package/src/compute-view/utils/uniqueTags.ts +21 -0
  67. package/src/compute-view/utils/view-hash.ts +27 -0
  68. package/src/compute-view/utils/with-readable-edges.ts +31 -0
  69. package/src/index.ts +5 -1
  70. package/src/model/DeploymentElementModel.ts +534 -0
  71. package/src/model/DeploymentModel.ts +364 -0
  72. package/src/model/ElementModel.ts +195 -0
  73. package/src/model/LikeC4Model.test-d.ts +151 -0
  74. package/src/model/LikeC4Model.ts +280 -457
  75. package/src/model/RelationModel.ts +84 -0
  76. package/src/model/__test__/fixture.ts +153 -338
  77. package/src/model/connection/ConnectionModel.ts +94 -0
  78. package/src/model/connection/DeploymentConnectionModel.ts +99 -0
  79. package/src/model/connection/deployment.ts +124 -0
  80. package/src/model/connection/model.ts +123 -0
  81. package/src/model/index.ts +4 -2
  82. package/src/model/types.ts +95 -15
  83. package/src/model/view/EdgeModel.ts +103 -0
  84. package/src/model/view/LikeC4ViewModel.ts +220 -0
  85. package/src/model/view/NodeModel.ts +228 -0
  86. package/src/types/_common.ts +2 -0
  87. package/src/types/deployments.ts +237 -0
  88. package/src/types/element.ts +7 -3
  89. package/src/types/expression.ts +8 -6
  90. package/src/types/index.ts +1 -0
  91. package/src/types/model.ts +68 -9
  92. package/src/types/overview-graph.ts +2 -2
  93. package/src/types/relation.ts +4 -4
  94. package/src/types/view-notation.ts +2 -2
  95. package/src/types/view.ts +104 -30
  96. package/src/utils/{commonHead.ts → common-head.ts} +2 -0
  97. package/src/utils/fqn.ts +83 -27
  98. package/src/utils/getOrCreate.ts +42 -0
  99. package/src/utils/graphlib.ts +10 -0
  100. package/src/utils/index.ts +4 -1
  101. package/src/utils/object-hash.ts +3 -0
  102. package/src/utils/set.ts +76 -0
  103. package/src/utils/string-hash.ts +16 -0
  104. package/dist/index.cjs +0 -5452
  105. package/dist/index.d.cts +0 -1053
  106. package/dist/shared/core.BXTs-8n7.mjs +0 -293
  107. package/dist/shared/core.CoLQkcYL.d.cts +0 -734
  108. package/dist/shared/core.DIYZvpVg.cjs +0 -353
  109. package/dist/types/index.cjs +0 -70
  110. package/dist/types/index.d.cts +0 -2
  111. package/src/model/LikeC4DiagramModel.ts +0 -338
  112. package/src/model/LikeC4ViewModel.ts +0 -320
@@ -1,4 +1,5 @@
1
1
  import { expectTypeOf, test } from 'vitest'
2
+ import type { LikeC4Model } from '../model/LikeC4Model'
2
3
  import type { ParsedLikeC4Model } from '../types'
3
4
  import { Builder } from './Builder'
4
5
 
@@ -11,10 +12,18 @@ test('should have types', () => {
11
12
  component,
12
13
  relTo
13
14
  },
15
+ deployment: {
16
+ deployment,
17
+ instanceOf,
18
+ env,
19
+ vm
20
+ },
14
21
  views: {
22
+ deploymentView,
15
23
  views,
16
24
  view,
17
25
  viewOf,
26
+ $rules,
18
27
  $include
19
28
  },
20
29
  builder
@@ -28,6 +37,10 @@ test('should have types', () => {
28
37
  system: {},
29
38
  component: {}
30
39
  },
40
+ deployments: {
41
+ env: {},
42
+ vm: {}
43
+ },
31
44
  relationships: {
32
45
  like: {},
33
46
  dislike: {}
@@ -50,20 +63,62 @@ test('should have types', () => {
50
63
  )
51
64
  )
52
65
  ),
66
+ deployment(
67
+ env('prod').with(
68
+ vm('vm1'),
69
+ vm('vm2')
70
+ ),
71
+ env('dev').with(
72
+ vm('vm1'),
73
+ instanceOf('api', 'cloud.backend.api'),
74
+ vm('vm2')
75
+ )
76
+ ),
53
77
  views(
54
78
  view('index', $include('*')),
55
- viewOf('cloud', 'cloud', $include('*'))
79
+ viewOf(
80
+ 'cloud',
81
+ 'cloud',
82
+ $rules(
83
+ $include('*'),
84
+ $include('cloud.backend.*')
85
+ )
86
+ ),
87
+ deploymentView(
88
+ 'prod',
89
+ $rules(
90
+ $include('prod.*')
91
+ )
92
+ )
56
93
  )
57
94
  )
58
- .build()
59
95
 
60
- expectTypeOf(m).toEqualTypeOf(
96
+ expectTypeOf(m.Types.Fqn).toEqualTypeOf(
97
+ '' as 'alice' | 'bob' | 'cloud' | 'cloud.backend' | 'cloud.backend.api' | 'cloud.backend.db' | 'cloud.frontend'
98
+ )
99
+ expectTypeOf(m.Types.ViewId).toEqualTypeOf(
100
+ '' as 'index' | 'cloud' | 'prod'
101
+ )
102
+ expectTypeOf(m.Types.DeploymentFqn).toEqualTypeOf(
103
+ '' as 'prod' | 'dev' | 'prod.vm1' | 'prod.vm2' | 'dev.vm1' | 'dev.vm2' | 'dev.api'
104
+ )
105
+
106
+ expectTypeOf(m.build()).toEqualTypeOf(
61
107
  {} as ParsedLikeC4Model<
62
108
  'actor' | 'system' | 'component',
63
109
  'like' | 'dislike',
64
110
  'tag1' | 'tag2',
65
111
  'alice' | 'bob' | 'cloud' | 'cloud.backend' | 'cloud.backend.api' | 'cloud.backend.db' | 'cloud.frontend',
66
- 'index' | 'cloud'
112
+ 'index' | 'cloud' | 'prod',
113
+ 'prod' | 'dev' | 'prod.vm1' | 'prod.vm2' | 'dev.vm1' | 'dev.vm2' | 'dev.api'
114
+ >
115
+ )
116
+
117
+ expectTypeOf(m.buildComputedModel()).toEqualTypeOf(
118
+ {} as LikeC4Model.Computed<
119
+ 'alice' | 'bob' | 'cloud' | 'cloud.backend' | 'cloud.backend.api' | 'cloud.backend.db' | 'cloud.frontend',
120
+ 'prod' | 'dev' | 'prod.vm1' | 'prod.vm2' | 'dev.vm1' | 'dev.vm2' | 'dev.api',
121
+ 'index' | 'cloud' | 'prod'
67
122
  >
68
123
  )
69
124
  })
@@ -1,11 +1,16 @@
1
1
  import defu from 'defu'
2
2
  import { fromEntries, hasAtLeast, isFunction, isNonNullish, map, mapToObj, mapValues, pickBy } from 'remeda'
3
3
  import type { Writable } from 'type-fest'
4
+ import { computeViews } from '../compute-view'
4
5
  import { invariant } from '../errors'
6
+ import { LikeC4Model } from '../model/LikeC4Model'
5
7
  import {
6
8
  type Color,
7
9
  DefaultElementShape,
8
10
  DefaultThemeColor,
11
+ DeploymentElement,
12
+ type DeploymentRelation,
13
+ type DeploymentView,
9
14
  type Element,
10
15
  type ElementShape,
11
16
  type ElementView,
@@ -17,22 +22,24 @@ import {
17
22
  type ModelGlobals,
18
23
  type NonEmptyArray,
19
24
  type Relation,
20
- type RelationID,
25
+ type RelationId,
21
26
  type Tag
22
27
  } from '../types'
23
28
  import type { ParsedLikeC4Model } from '../types/model'
24
29
  import { isSameHierarchy, nameFromFqn, parentFqn } from '../utils/fqn'
25
30
  import type { AnyTypes, BuilderSpecification, Types } from './_types'
26
- import type { AddElement, AddElementHelpers } from './Builder.element'
27
- import { type ModelBuilder, type ModelHelpers } from './Builder.model'
31
+ import type { AddDeploymentNode } from './Builder.deployment'
32
+ import type { AddDeploymentNodeHelpers, DeloymentModelHelpers, DeploymentModelBuilder } from './Builder.deploymentModel'
33
+ import type { AddElement } from './Builder.element'
34
+ import type { AddElementHelpers, ModelBuilder, ModelHelpers } from './Builder.model'
28
35
  import {
29
36
  $autoLayout,
30
37
  $exclude,
31
- $expr,
32
38
  $include,
33
39
  $rules,
34
40
  $style,
35
- type ViewBuilder,
41
+ type DeploymentViewRuleBuilderOp,
42
+ mkViewBuilder,
36
43
  type ViewHelpers,
37
44
  type ViewRuleBuilderOp
38
45
  } from './Builder.view'
@@ -52,16 +59,32 @@ export interface Builder<T extends AnyTypes> {
52
59
  helpers(): {
53
60
  model: ModelHelpers<T>
54
61
  views: ViewHelpers<T['NewViewProps']>
62
+ deployment: DeloymentModelHelpers<T>
55
63
  }
56
64
 
57
65
  __model(): ModelBuilder<T>
58
66
  __views(): ViewsBuilder<T>
67
+ __deployment(): DeploymentModelBuilder<T>
59
68
 
69
+ /**
70
+ * Returns model as result of parsing only
71
+ * Model is not computed or layouted
72
+ */
60
73
  build(): ParsedLikeC4Model<
61
74
  T['ElementKind'],
62
75
  T['RelationshipKind'],
63
76
  T['Tag'],
64
77
  T['Fqn'],
78
+ T['ViewId'],
79
+ T['DeploymentFqn']
80
+ >
81
+
82
+ /**
83
+ * Returns model with computed views
84
+ */
85
+ buildComputedModel(): LikeC4Model.Computed<
86
+ T['Fqn'],
87
+ T['DeploymentFqn'],
65
88
  T['ViewId']
66
89
  >
67
90
 
@@ -225,16 +248,21 @@ function builder<Spec extends BuilderSpecification, T extends AnyTypes>(
225
248
  predicates: {},
226
249
  dynamicPredicates: {},
227
250
  styles: {}
228
- } as ModelGlobals
251
+ } as ModelGlobals,
252
+ _deployments = new Map<string, DeploymentElement>(),
253
+ _deploymentRelations = [] as DeploymentRelation[]
229
254
  ): Builder<T> {
230
255
  const toLikeC4Specification = (): Types.ToParsedLikeC4Model<T>['specification'] => ({
231
- tags: (spec.tags ?? []) as Tag<T['Tag']>[],
232
256
  elements: {
233
257
  ...spec.elements as any
234
258
  },
259
+ deployments: {
260
+ ...spec.deployments as any
261
+ },
235
262
  relationships: {
236
263
  ...spec.relationships
237
- }
264
+ },
265
+ tags: (spec.tags ?? []) as Tag<T['Tag']>[]
238
266
  })
239
267
 
240
268
  const mapLinks = (links?: Array<string | { title?: string; url: string }>): NonEmptyArray<Link> | null => {
@@ -244,54 +272,21 @@ function builder<Spec extends BuilderSpecification, T extends AnyTypes>(
244
272
  return map(links, l => (typeof l === 'string' ? { url: l } : l))
245
273
  }
246
274
 
247
- const mkViewBuilder = (view: Writable<ElementView>): ViewBuilder<T> => {
248
- const viewBuilder: ViewBuilder<T> = {
249
- autoLayout(autoLayout) {
250
- view.rules.push({
251
- direction: autoLayout
252
- })
253
- return viewBuilder
254
- },
255
- exclude(expr) {
256
- view.rules.push({
257
- exclude: [expr]
258
- })
259
- return viewBuilder
260
- },
261
- include(expr) {
262
- view.rules.push({
263
- include: [expr]
264
- })
265
- return viewBuilder
266
- },
267
- style(rule) {
268
- view.rules.push(rule)
269
- return viewBuilder
270
- }
271
- // title(title: string) {
272
- // view.title = title
273
- // return viewBuilder
274
- // },
275
- // description(description: string) {
276
- // view.description = description
277
- // return viewBuilder
278
- // }
279
- }
280
- return viewBuilder
281
- }
282
-
283
275
  const self: Builder<T> = {
284
276
  get Types(): T {
285
277
  throw new Error('Types are not available in runtime')
286
278
  },
287
- clone: () =>
288
- builder(
279
+ clone: () => {
280
+ return builder(
289
281
  structuredClone(spec),
290
282
  structuredClone(_elements),
291
283
  structuredClone(_relations),
292
284
  structuredClone(_views),
293
- structuredClone(_globals)
294
- ),
285
+ structuredClone(_globals),
286
+ structuredClone(_deployments),
287
+ structuredClone(_deploymentRelations)
288
+ )
289
+ },
295
290
  __model: () => ({
296
291
  addElement: (element) => {
297
292
  const parent = parentFqn(element.id)
@@ -317,7 +312,7 @@ function builder<Spec extends BuilderSpecification, T extends AnyTypes>(
317
312
  'Cannot create relationship between elements in the same hierarchy'
318
313
  )
319
314
  _relations.push({
320
- id: `rel${_relations.length + 1}` as RelationID,
315
+ id: `rel${_relations.length + 1}` as RelationId,
321
316
  ...relation
322
317
  })
323
318
  return self
@@ -341,13 +336,53 @@ function builder<Spec extends BuilderSpecification, T extends AnyTypes>(
341
336
  return self
342
337
  }
343
338
  }),
339
+ __deployment: () => ({
340
+ addDeployment: (node: DeploymentElement) => {
341
+ if (_deployments.has(node.id)) {
342
+ throw new Error(`Deployment with id "${node.id}" already exists`)
343
+ }
344
+ const parent = parentFqn(node.id)
345
+ if (parent) {
346
+ invariant(
347
+ _deployments.get(parent),
348
+ `Parent element with id "${parent}" not found for node with id "${node.id}"`
349
+ )
350
+ }
351
+ if (DeploymentElement.isInstance(node)) {
352
+ invariant(parent, 'Instance must have parent')
353
+ }
354
+ _deployments.set(node.id, node)
355
+ return self
356
+ },
357
+ addDeploymentRelation: (relation) => {
358
+ invariant(
359
+ !isSameHierarchy(relation.source.id, relation.target.id),
360
+ 'Cannot create relationship between elements in the same hierarchy'
361
+ )
362
+ _deploymentRelations.push({
363
+ id: `deploy_rel${_deploymentRelations.length + 1}` as RelationId,
364
+ ...relation
365
+ })
366
+ return self
367
+ },
368
+ fqn: (id) => id as Fqn
369
+ }),
344
370
  build: () => ({
345
371
  specification: toLikeC4Specification(),
346
372
  elements: fromEntries(Array.from(_elements.entries())) as any,
347
373
  relations: mapToObj(_relations, r => [r.id, r]),
348
374
  globals: structuredClone(_globals),
375
+ deployments: {
376
+ elements: fromEntries(Array.from(_deployments.entries())) as any,
377
+ relations: mapToObj(_deploymentRelations, r => [r.id, r])
378
+ },
349
379
  views: fromEntries(Array.from(_views.entries())) as any
350
380
  }),
381
+ buildComputedModel: () => {
382
+ const parsed = self.build()
383
+ const computed = computeViews(parsed)
384
+ return LikeC4Model.create(computed)
385
+ },
351
386
  helpers: () => ({
352
387
  model: {
353
388
  model: (...ops: ((b: ModelBuilder<T>) => ModelBuilder<T>)[]) => {
@@ -475,7 +510,7 @@ function builder<Spec extends BuilderSpecification, T extends AnyTypes>(
475
510
  return b
476
511
  }
477
512
  },
478
- view: (id, _props, builder?: ViewRuleBuilderOp<T>) => {
513
+ view: (id, _props, builder?: ViewRuleBuilderOp<any>) => {
479
514
  if (isFunction(_props)) {
480
515
  builder = _props
481
516
  _props = {}
@@ -512,8 +547,8 @@ function builder<Spec extends BuilderSpecification, T extends AnyTypes>(
512
547
  viewOf: (
513
548
  id,
514
549
  viewOf,
515
- _props?: T['NewViewProps'] | string | ViewRuleBuilderOp<T>,
516
- builder?: ViewRuleBuilderOp<T>
550
+ _props?: T['NewViewProps'] | string | ViewRuleBuilderOp<any>,
551
+ builder?: ViewRuleBuilderOp<any>
517
552
  ) => {
518
553
  if (isFunction(_props)) {
519
554
  builder = _props
@@ -549,12 +584,161 @@ function builder<Spec extends BuilderSpecification, T extends AnyTypes>(
549
584
  return b
550
585
  }
551
586
  },
587
+ deploymentView: (
588
+ id,
589
+ _props?: T['NewViewProps'] | string | DeploymentViewRuleBuilderOp<any>,
590
+ builder?: DeploymentViewRuleBuilderOp<any>
591
+ ) => {
592
+ if (isFunction(_props)) {
593
+ builder = _props as any
594
+ _props = {}
595
+ }
596
+ return <T extends AnyTypes>(b: ViewsBuilder<T>): ViewsBuilder<T> => {
597
+ const {
598
+ links: _links = [],
599
+ title = null,
600
+ ...props
601
+ } = typeof _props === 'string' ? { title: _props } : { ..._props }
602
+
603
+ const links = mapLinks(_links)
604
+
605
+ const view: Writable<DeploymentView> = {
606
+ id: id as any,
607
+ __: 'deployment',
608
+ title,
609
+ description: null,
610
+ tags: null,
611
+ rules: [],
612
+ links,
613
+ customColorDefinitions: {},
614
+ ...props
615
+ }
616
+ b.addView(view)
617
+
618
+ if (builder) {
619
+ builder(mkViewBuilder(view))
620
+ }
621
+
622
+ return b
623
+ }
624
+ },
552
625
  $autoLayout,
553
626
  $exclude,
554
- $expr,
555
627
  $include,
556
628
  $rules,
557
629
  $style
630
+ },
631
+ deployment: {
632
+ deployment: (...ops: ((b: DeploymentModelBuilder<T>) => DeploymentModelBuilder<T>)[]) => {
633
+ return (b: Builder<T>) => {
634
+ const v = b.__deployment()
635
+ for (const op of ops) {
636
+ op(v)
637
+ }
638
+ return b
639
+ }
640
+ },
641
+ instanceOf: (id, to, _props?: string | T['NewDeploymentNodeProps']) => {
642
+ return <T extends AnyTypes>(
643
+ b: DeploymentModelBuilder<T>
644
+ ): DeploymentModelBuilder<Types.AddDeploymentFqn<T, any>> => {
645
+ const {
646
+ links,
647
+ title,
648
+ ...props
649
+ } = typeof _props === 'string' ? { title: _props } : { ..._props }
650
+ const _id = b.fqn(id)
651
+ invariant(_elements.has(to), `Target element with id "${to}" not found`)
652
+ b.addDeployment({
653
+ id: _id,
654
+ element: to as any,
655
+ ...title && { title },
656
+ ...links && { links: mapLinks(links) },
657
+ ...props
658
+ })
659
+ return b as any
660
+ }
661
+ },
662
+ rel: (source: string, target: string, _props?: T['NewRelationshipProps'] | string) => {
663
+ return <T extends AnyTypes>(b: DeploymentModelBuilder<T>) => {
664
+ const {
665
+ title,
666
+ links,
667
+ ...props
668
+ } = typeof _props === 'string' ? { title: _props } : { ..._props }
669
+
670
+ b.addDeploymentRelation({
671
+ source: {
672
+ id: source as any
673
+ },
674
+ target: {
675
+ id: target as any
676
+ },
677
+ ...title && { title },
678
+ ...links && { links: mapLinks(links) },
679
+ ...props
680
+ })
681
+ return b
682
+ }
683
+ },
684
+ ...mapValues(
685
+ spec.deployments ?? {},
686
+ ({ style: specStyle, ...spec }, kind) =>
687
+ <Id extends string>(
688
+ id: Id,
689
+ _props?: T['NewDeploymentNodeProps'] | string
690
+ ): AddDeploymentNode<Id> => {
691
+ const add = (<T extends AnyTypes>(b: DeploymentModelBuilder<T>) => {
692
+ const {
693
+ links,
694
+ icon: _icon,
695
+ style,
696
+ title,
697
+ ...props
698
+ } = typeof _props === 'string' ? { title: _props } : { ..._props }
699
+
700
+ const icon = _icon ?? specStyle?.icon
701
+
702
+ const _id = b.fqn(id)
703
+
704
+ b.addDeployment({
705
+ id: _id,
706
+ kind: kind as any,
707
+ title: title ?? nameFromFqn(_id),
708
+ description: null,
709
+ technology: null,
710
+ tags: null,
711
+ color: specStyle?.color ?? DefaultThemeColor as Color,
712
+ shape: specStyle?.shape ?? DefaultElementShape as ElementShape,
713
+ style: pickBy({
714
+ border: specStyle?.border,
715
+ opacity: specStyle?.opacity,
716
+ ...style
717
+ }, isNonNullish),
718
+ ...links && { links: mapLinks(links) },
719
+ ...icon && { icon: icon as IconUrl },
720
+ ...spec,
721
+ ...props
722
+ })
723
+ return b
724
+ }) as AddDeploymentNode<Id>
725
+
726
+ add.with =
727
+ (...ops: Array<(input: DeploymentModelBuilder<any>) => DeploymentModelBuilder<any>>) =>
728
+ (b: DeploymentModelBuilder<any>) => {
729
+ add(b)
730
+ const nestedBuilder: DeploymentModelBuilder<any> = {
731
+ ...b,
732
+ fqn: (child) => `${b.fqn(id)}.${child}` as Fqn
733
+ }
734
+ for (const op of ops) {
735
+ op(nestedBuilder)
736
+ }
737
+ return b
738
+ }
739
+ return add
740
+ }
741
+ ) as AddDeploymentNodeHelpers<T>
558
742
  }
559
743
  }),
560
744
  with: (...ops: ((b: Builder<T>) => Builder<T>)[]) => {
@@ -571,6 +755,7 @@ export namespace Builder {
571
755
  ): {
572
756
  builder: Builder<Types.FromSpecification<Spec>>
573
757
  model: ModelHelpers<Types.FromSpecification<Spec>>
758
+ deployment: DeloymentModelHelpers<Types.FromSpecification<Spec>>
574
759
  views: ViewHelpers<Types.FromSpecification<Spec>['NewViewProps']>
575
760
  } {
576
761
  const b = builder<Spec, Types.FromSpecification<Spec>>(spec)