@likec4/core 1.27.2 → 1.28.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 (71) hide show
  1. package/dist/builder/index.d.mts +6 -6
  2. package/dist/builder/index.mjs +23 -13
  3. package/dist/compute-view/index.d.mts +3 -3
  4. package/dist/compute-view/index.mjs +3 -3
  5. package/dist/compute-view/relationships-view/index.d.mts +3 -3
  6. package/dist/index.d.mts +6 -6
  7. package/dist/index.mjs +2 -2
  8. package/dist/model/index.d.mts +6 -6
  9. package/dist/model/index.mjs +1 -1
  10. package/dist/shared/{core.RjpTh3vE.mjs → core.Bjl6Kmc8.mjs} +260 -168
  11. package/dist/shared/{core.C9JdFlAd.d.mts → core.CZR9J1Qx.d.mts} +2 -2
  12. package/dist/shared/{core.C7IN6igU.d.mts → core.CiFKryGW.d.mts} +7 -2
  13. package/dist/shared/{core.HKn0jlo_.d.mts → core.D2830qgg.d.mts} +1 -1
  14. package/dist/shared/{core.z6g06scf.d.mts → core.DJvmfnEi.d.mts} +484 -189
  15. package/dist/shared/{core.BMep80lJ.mjs → core.DgfwjBtu.mjs} +198 -87
  16. package/dist/shared/core.SjLhMA7a.d.mts +62 -0
  17. package/dist/types/index.d.mts +4 -4
  18. package/dist/types/index.mjs +1 -1
  19. package/dist/utils/index.d.mts +2 -2
  20. package/package.json +6 -6
  21. package/src/builder/Builder.ts +1 -0
  22. package/src/builder/Builder.view-common.ts +10 -10
  23. package/src/builder/Builder.view-element.ts +26 -14
  24. package/src/builder/__snapshots__/Builder-style1.spec.ts.snap +23 -8
  25. package/src/builder/__snapshots__/Builder-style2.spec.ts.snap +9 -4
  26. package/src/compute-view/deployment-view/predicates/deploymentRefs-where.ts +18 -0
  27. package/src/compute-view/deployment-view/predicates/relation-direct.ts +6 -0
  28. package/src/compute-view/deployment-view/predicates/relation-in-out.ts +3 -0
  29. package/src/compute-view/deployment-view/predicates/relation-incoming.ts +3 -0
  30. package/src/compute-view/deployment-view/predicates/relation-outgoing.ts +3 -0
  31. package/src/compute-view/deployment-view/predicates/utils.ts +10 -3
  32. package/src/compute-view/deployment-view/utils.ts +7 -4
  33. package/src/compute-view/element-view/__test__/fixture.ts +72 -65
  34. package/src/compute-view/element-view/_types.ts +13 -9
  35. package/src/compute-view/element-view/compute.ts +27 -27
  36. package/src/compute-view/element-view/memory/NodeGroup.ts +4 -3
  37. package/src/compute-view/element-view/memory/memory.ts +3 -2
  38. package/src/compute-view/element-view/memory/stage-include.ts +2 -2
  39. package/src/compute-view/element-view/predicates/_utils.ts +25 -24
  40. package/src/compute-view/element-view/predicates/element-expand.ts +7 -10
  41. package/src/compute-view/element-view/predicates/element-kind-tag.ts +5 -3
  42. package/src/compute-view/element-view/predicates/element-ref.ts +7 -19
  43. package/src/compute-view/element-view/predicates/relation-direct.ts +15 -13
  44. package/src/compute-view/element-view/predicates/relation-in-out.ts +5 -6
  45. package/src/compute-view/element-view/predicates/relation-in.ts +36 -32
  46. package/src/compute-view/element-view/predicates/relation-out.ts +37 -33
  47. package/src/compute-view/element-view/predicates/wildcard.ts +2 -2
  48. package/src/compute-view/utils/applyCustomElementProperties.ts +9 -4
  49. package/src/compute-view/utils/applyCustomRelationProperties.ts +10 -7
  50. package/src/compute-view/utils/elementExpressionToPredicate.ts +27 -26
  51. package/src/compute-view/utils/link-nodes-with-edges.ts +1 -1
  52. package/src/compute-view/utils/relationExpressionToPredicates.ts +10 -17
  53. package/src/compute-view/utils/uniqueTags.ts +3 -3
  54. package/src/index.ts +0 -2
  55. package/src/model/ElementModel.ts +25 -6
  56. package/src/model/LikeC4Model.ts +44 -6
  57. package/src/model/RelationModel.ts +4 -2
  58. package/src/model/view/LikeC4ViewModel.ts +3 -2
  59. package/src/types/_common.ts +0 -4
  60. package/src/types/deployments.ts +3 -2
  61. package/src/types/element.ts +2 -10
  62. package/src/types/expression-v2-model.ts +314 -0
  63. package/src/types/expression-v2.ts +238 -30
  64. package/src/types/index.ts +15 -46
  65. package/src/types/model-data.ts +4 -1
  66. package/src/types/relation.ts +2 -1
  67. package/src/types/scalars.ts +39 -0
  68. package/src/types/view-changes.ts +2 -1
  69. package/src/types/view.ts +10 -11
  70. package/dist/shared/core.30yYKXcZ.d.mts +0 -186
  71. package/src/types/expression.ts +0 -207
@@ -1,20 +1,20 @@
1
1
  import { anyPass, unique } from 'remeda'
2
2
  import { nonexhaustive } from '../../../errors'
3
- import type { LikeC4Model } from '../../../model'
4
- import { Connection, type ConnectionModel, findConnectionsBetween } from '../../../model/connection/model'
3
+ import { type ConnectionModel, Connection, findConnectionsBetween } from '../../../model/connection/model'
4
+ import type { LikeC4Model } from '../../../model/LikeC4Model'
5
5
  import type { RelationshipModel } from '../../../model/RelationModel'
6
6
  import type { AnyAux } from '../../../model/types'
7
- import * as Expr from '../../../types/expression'
7
+ import { ModelLayer } from '../../../types/expression-v2-model'
8
8
  import { toSet } from '../../../utils/iterable/to'
9
9
  import { elementExprToPredicate } from '../../utils/elementExpressionToPredicate'
10
10
  import type { ConnectionWhere, PredicateExecutor } from '../_types'
11
11
  import { resolveAndIncludeFromMemory, resolveElements } from './_utils'
12
12
 
13
- export const OutgoingExprPredicate: PredicateExecutor<Expr.OutgoingExpr> = {
13
+ export const OutgoingExprPredicate: PredicateExecutor<ModelLayer.RelationExpr.Outgoing> = {
14
14
  include: ({ expr, scope, model, memory, stage, filterWhere }) => {
15
15
  const target = expr.outgoing
16
16
  const connections = [] as ConnectionModel<AnyAux>[]
17
- if (Expr.isWildcard(target)) {
17
+ if (ModelLayer.FqnExpr.isWildcard(target)) {
18
18
  if (!scope) {
19
19
  return
20
20
  }
@@ -23,8 +23,8 @@ export const OutgoingExprPredicate: PredicateExecutor<Expr.OutgoingExpr> = {
23
23
  ...findConnectionsBetween(
24
24
  scope,
25
25
  scope.ascendingSiblings(),
26
- 'directed'
27
- )
26
+ 'directed',
27
+ ),
28
28
  )
29
29
  } else {
30
30
  const elements = resolveAndIncludeFromMemory(target, { memory, model })
@@ -32,8 +32,8 @@ export const OutgoingExprPredicate: PredicateExecutor<Expr.OutgoingExpr> = {
32
32
  if (visibleElements.length === 0) {
33
33
  visibleElements.push(
34
34
  ...unique(
35
- elements.flatMap(el => [...el.ascendingSiblings()])
36
- )
35
+ elements.flatMap(el => [...el.ascendingSiblings()]),
36
+ ),
37
37
  )
38
38
  }
39
39
  const ensureOutgoing = outgoingConnectionPredicate(model, target)
@@ -43,21 +43,21 @@ export const OutgoingExprPredicate: PredicateExecutor<Expr.OutgoingExpr> = {
43
43
  ...findConnectionsBetween(
44
44
  source,
45
45
  visibleElements,
46
- 'directed'
47
- ).filter(ensureOutgoing)
46
+ 'directed',
47
+ ).filter(ensureOutgoing),
48
48
  )
49
49
  }
50
50
  }
51
51
 
52
52
  stage.addConnections(
53
- filterWhere(connections)
53
+ filterWhere(connections),
54
54
  )
55
55
 
56
56
  return stage
57
57
  },
58
58
  exclude: ({ expr: { outgoing }, model, scope, stage, where }) => {
59
59
  const excluded = [] as RelationshipModel[]
60
- if (Expr.isWildcard(outgoing)) {
60
+ if (ModelLayer.FqnExpr.isWildcard(outgoing)) {
61
61
  if (!scope) {
62
62
  return
63
63
  }
@@ -65,50 +65,54 @@ export const OutgoingExprPredicate: PredicateExecutor<Expr.OutgoingExpr> = {
65
65
  } else {
66
66
  const elements = resolveElements(model, outgoing)
67
67
  excluded.push(
68
- ...elements.flatMap(e => [...e.allOutgoing])
68
+ ...elements.flatMap(e => [...e.allOutgoing]),
69
69
  )
70
70
  }
71
71
  stage.excludeRelations(
72
- toSet(excluded.filter(where))
72
+ toSet(excluded.filter(where)),
73
73
  )
74
74
 
75
75
  return stage
76
- }
76
+ },
77
77
  }
78
78
 
79
79
  export function outgoingConnectionPredicate(
80
80
  model: LikeC4Model,
81
- expr: Exclude<Expr.ElementExpression, Expr.WildcardExpr>
81
+ expr: ModelLayer.FqnExpr.NonWildcard,
82
82
  ): ConnectionWhere {
83
83
  switch (true) {
84
- case Expr.isElementKindExpr(expr):
85
- case Expr.isElementTagExpr(expr): {
84
+ case ModelLayer.FqnExpr.isElementKindExpr(expr):
85
+ case ModelLayer.FqnExpr.isElementTagExpr(expr): {
86
86
  const isElement = elementExprToPredicate(expr)
87
87
  return (connection) => isElement(connection.source)
88
88
  }
89
- case Expr.isElementRef(expr) && expr.isChildren: {
89
+ case ModelLayer.FqnExpr.isModelRef(expr) && expr.selector === 'children': {
90
+ const fqn = ModelLayer.FqnRef.toFqn(expr.ref)
90
91
  return anyPass(
91
- [...model.children(expr.element)].map(
92
- el => Connection.isOutgoing(el.id)
93
- )
92
+ [...model.children(fqn)].map(
93
+ el => Connection.isOutgoing(el.id),
94
+ ),
94
95
  )
95
96
  }
96
- case Expr.isElementRef(expr) && expr.isDescendants: {
97
+ case ModelLayer.FqnExpr.isModelRef(expr) && expr.selector === 'descendants': {
98
+ const fqn = ModelLayer.FqnRef.toFqn(expr.ref)
97
99
  return anyPass([
98
- Connection.isInside(expr.element),
99
- ...[...model.children(expr.element)].map(
100
- el => Connection.isOutgoing(el.id)
101
- )
100
+ Connection.isInside(fqn),
101
+ ...[...model.children(fqn)].map(
102
+ el => Connection.isOutgoing(el.id),
103
+ ),
102
104
  ])
103
105
  }
104
- case Expr.isExpandedElementExpr(expr): {
106
+ case ModelLayer.FqnExpr.isModelRef(expr) && expr.selector === 'expanded': {
107
+ const fqn = ModelLayer.FqnRef.toFqn(expr.ref)
105
108
  return anyPass([
106
- Connection.isOutgoing(expr.expanded),
107
- Connection.isInside(expr.expanded)
109
+ Connection.isOutgoing(fqn),
110
+ Connection.isInside(fqn),
108
111
  ])
109
112
  }
110
- case Expr.isElementRef(expr): {
111
- return Connection.isOutgoing(expr.element)
113
+ case ModelLayer.FqnExpr.isModelRef(expr): {
114
+ const fqn = ModelLayer.FqnRef.toFqn(expr.ref)
115
+ return Connection.isOutgoing(fqn)
112
116
  }
113
117
  default:
114
118
  nonexhaustive(expr)
@@ -1,12 +1,12 @@
1
1
  import { filter } from 'remeda'
2
2
  import { findConnectionsBetween, findConnectionsWithin } from '../../../model/connection/model'
3
- import type { WildcardExpr } from '../../../types/expression'
3
+ import type { ModelLayer } from '../../../types/expression-v2-model'
4
4
  import { ifilter, toArray } from '../../../utils/iterable'
5
5
  import { toSet } from '../../../utils/iterable/to'
6
6
  import { type PredicateExecutor, Memory } from '../_types'
7
7
  import { NoWhere } from '../utils'
8
8
 
9
- export const WildcardPredicate: PredicateExecutor<WildcardExpr> = {
9
+ export const WildcardPredicate: PredicateExecutor<ModelLayer.FqnExpr.Wildcard> = {
10
10
  include: ({ scope, model, stage, memory, where }) => {
11
11
  if (!scope) {
12
12
  const rootElements = [...model.roots()].filter(where)
@@ -1,9 +1,14 @@
1
1
  import { isEmpty, isNullish, omitBy } from 'remeda'
2
- import { type ViewRule, ComputedNode, isCustomElement, isViewRuleGroup, isViewRulePredicate } from '../../types'
3
- import type { Expression } from '../../types/expression'
2
+ import {
3
+ type ViewRule,
4
+ ComputedNode,
5
+ isViewRuleGroup,
6
+ isViewRulePredicate,
7
+ ModelLayer,
8
+ } from '../../types'
4
9
  import { elementExprToPredicate } from './elementExpressionToPredicate'
5
10
 
6
- export function flattenGroupRules<T extends Expression>(guard: (expr: Expression) => expr is T) {
11
+ export function flattenGroupRules<T extends ModelLayer.Expression>(guard: (expr: ModelLayer.Expression) => expr is T) {
7
12
  return (rule: ViewRule): Array<T> => {
8
13
  if (isViewRuleGroup(rule)) {
9
14
  return rule.groupRules.flatMap(flattenGroupRules(guard))
@@ -17,7 +22,7 @@ export function flattenGroupRules<T extends Expression>(guard: (expr: Expression
17
22
  }
18
23
 
19
24
  export function applyCustomElementProperties(_rules: ViewRule[], _nodes: ComputedNode[]) {
20
- const rules = _rules.flatMap(flattenGroupRules(isCustomElement))
25
+ const rules = _rules.flatMap(flattenGroupRules(ModelLayer.FqnExpr.isCustom))
21
26
  if (rules.length === 0) {
22
27
  return _nodes
23
28
  }
@@ -1,26 +1,29 @@
1
1
  import { isNullish, omitBy, pick } from 'remeda'
2
- import type { ComputedEdge, ComputedNode, ViewRule } from '../../types'
3
- import * as Expr from '../../types/expression'
2
+ import { type ComputedEdge, type ComputedNode, type ViewRule, ModelLayer } from '../../types'
4
3
  import { flattenGroupRules } from './applyCustomElementProperties'
5
4
  import { relationExpressionToPredicates } from './relationExpressionToPredicates'
6
5
 
7
6
  export function applyCustomRelationProperties(
8
7
  _rules: ViewRule[],
9
8
  nodes: ComputedNode[],
10
- _edges: Iterable<ComputedEdge>
9
+ _edges: Iterable<ComputedEdge>,
11
10
  ): ComputedEdge[] {
12
- const rules = _rules.flatMap(flattenGroupRules(Expr.isCustomRelationExpr))
11
+ const rules = _rules.flatMap(flattenGroupRules(ModelLayer.RelationExpr.isCustom))
13
12
  const edges = Array.from(_edges)
14
13
  if (rules.length === 0 || edges.length === 0) {
15
14
  return edges
16
15
  }
17
16
  for (
18
17
  const {
19
- customRelation: { relation, title, ...customprops }
18
+ customRelation: {
19
+ expr,
20
+ title,
21
+ ...customprops
22
+ },
20
23
  } of rules
21
24
  ) {
22
25
  const props = omitBy(customprops, isNullish)
23
- const satisfies = relationExpressionToPredicates(relation)
26
+ const satisfies = relationExpressionToPredicates(expr)
24
27
  edges.forEach((edge, i) => {
25
28
  const source = nodes.find(n => n.id === edge.source)
26
29
  const target = nodes.find(n => n.id === edge.target)
@@ -32,7 +35,7 @@ export function applyCustomRelationProperties(
32
35
  ...edge,
33
36
  ...props,
34
37
  label: title ?? edge.label,
35
- isCustomized: true
38
+ isCustomized: true,
36
39
  }
37
40
  }
38
41
  })
@@ -1,50 +1,51 @@
1
1
  import { isNullish } from 'remeda'
2
2
  import { nonexhaustive } from '../../errors'
3
3
  import {
4
- type ElementPredicateExpression,
5
- isCustomElement,
6
- isElementKindExpr,
7
- isElementRef,
8
- isElementTagExpr,
9
- isElementWhere,
10
- isExpandedElementExpr,
11
- isWildcard,
12
- whereOperatorAsPredicate
4
+ ModelLayer,
5
+ whereOperatorAsPredicate,
13
6
  } from '../../types'
14
7
  import { parentFqn } from '../../utils'
15
8
 
16
9
  type Predicate<T> = (x: T) => boolean
17
10
 
18
11
  export function elementExprToPredicate<T extends { id: string; tags?: readonly string[] | null; kind: string }>(
19
- target: ElementPredicateExpression
12
+ target: ModelLayer.AnyFqnExpr,
20
13
  ): Predicate<T> {
21
- if (isElementWhere(target)) {
14
+ if (ModelLayer.FqnExpr.isCustom(target)) {
15
+ return elementExprToPredicate(target.custom.expr)
16
+ }
17
+ if (ModelLayer.FqnExpr.isWhere(target)) {
22
18
  const predicate = elementExprToPredicate(target.where.expr)
23
19
  const where = whereOperatorAsPredicate(target.where.condition)
24
20
  return n => predicate(n) && where(n)
25
21
  }
26
- if (isWildcard(target)) {
27
- return () => true
28
- }
29
- if (isElementKindExpr(target)) {
22
+ if (ModelLayer.FqnExpr.isElementKindExpr(target)) {
30
23
  return target.isEqual ? n => n.kind === target.elementKind : n => n.kind !== target.elementKind
31
24
  }
32
- if (isElementTagExpr(target)) {
25
+ if (ModelLayer.FqnExpr.isElementTagExpr(target)) {
33
26
  return target.isEqual
34
27
  ? ({ tags }) => !!tags && tags.includes(target.elementTag)
35
28
  : ({ tags }) => isNullish(tags) || !tags.includes(target.elementTag)
36
29
  }
37
- if (isExpandedElementExpr(target)) {
38
- return n => n.id === target.expanded || parentFqn(n.id) === target.expanded
39
- }
40
- if (isElementRef(target)) {
41
- const { element, isChildren, isDescendants } = target
42
- return isChildren || isDescendants
43
- ? n => n.id.startsWith(element + '.')
44
- : n => (n.id as string) === element
30
+ if (ModelLayer.FqnExpr.isWildcard(target)) {
31
+ return () => true
45
32
  }
46
- if (isCustomElement(target)) {
47
- return elementExprToPredicate(target.custom.expr)
33
+ if (ModelLayer.FqnExpr.isModelRef(target)) {
34
+ const fqn = ModelLayer.FqnRef.toFqn(target.ref)
35
+ if (target.selector === 'expanded') {
36
+ return (n) => {
37
+ return n.id === fqn || parentFqn(n.id) === fqn
38
+ }
39
+ }
40
+ if (target.selector === 'descendants' || target.selector === 'children') {
41
+ const fqnWithDot = fqn + '.'
42
+ return (n) => {
43
+ return n.id.startsWith(fqnWithDot)
44
+ }
45
+ }
46
+ return (n) => {
47
+ return n.id === fqn
48
+ }
48
49
  }
49
50
  nonexhaustive(target)
50
51
  }
@@ -1,6 +1,6 @@
1
1
  import { last, reverse } from 'remeda'
2
2
  import { invariant } from '../../errors'
3
- import type { Fqn } from '../../types/element'
3
+ import type { Fqn } from '../../types'
4
4
  import type { ComputedEdge, ComputedNode } from '../../types/view'
5
5
  import { commonHead } from '../../utils/common-head'
6
6
  import { ancestorsOfNode } from './ancestorsOfNode'
@@ -1,15 +1,5 @@
1
- import { pick } from 'remeda'
2
1
  import { nonexhaustive } from '../../errors'
3
- import type { Element } from '../../types/element'
4
- import {
5
- type RelationExpression,
6
- type RelationWhereExpr,
7
- isIncoming,
8
- isInOut,
9
- isOutgoing,
10
- isRelation,
11
- isRelationWhere,
12
- } from '../../types/expression'
2
+ import { ModelLayer } from '../../types'
13
3
  import { whereOperatorAsPredicate } from '../../types/operators'
14
4
  import type { ModelRelation } from '../../types/relation'
15
5
  import type { ComputedNode } from '../../types/view'
@@ -22,10 +12,13 @@ export type FilterableEdge = Pick<ModelRelation, 'kind' | 'tags'> & {
22
12
  }
23
13
 
24
14
  export function relationExpressionToPredicates<T extends FilterableEdge>(
25
- expr: RelationExpression | RelationWhereExpr,
15
+ expr: ModelLayer.AnyRelationExpr,
26
16
  ): Predicate<T> {
27
17
  switch (true) {
28
- case isRelationWhere(expr): {
18
+ case ModelLayer.RelationExpr.isCustom(expr): {
19
+ return relationExpressionToPredicates(expr.customRelation.expr)
20
+ }
21
+ case ModelLayer.RelationExpr.isWhere(expr): {
29
22
  const predicate = relationExpressionToPredicates(expr.where.expr)
30
23
  const where = whereOperatorAsPredicate(expr.where.condition)
31
24
  return e =>
@@ -36,7 +29,7 @@ export function relationExpressionToPredicates<T extends FilterableEdge>(
36
29
  ...(e.kind && { kind: e.kind }),
37
30
  })
38
31
  }
39
- case isRelation(expr): {
32
+ case ModelLayer.RelationExpr.isDirect(expr): {
40
33
  const isSource = elementExprToPredicate(expr.source)
41
34
  const isTarget = elementExprToPredicate(expr.target)
42
35
  return edge => {
@@ -44,15 +37,15 @@ export function relationExpressionToPredicates<T extends FilterableEdge>(
44
37
  || (!!expr.isBidirectional && isSource(edge.target) && isTarget(edge.source))
45
38
  }
46
39
  }
47
- case isInOut(expr): {
40
+ case ModelLayer.RelationExpr.isInOut(expr): {
48
41
  const isInOut = elementExprToPredicate(expr.inout)
49
42
  return edge => isInOut(edge.source) || isInOut(edge.target)
50
43
  }
51
- case isIncoming(expr): {
44
+ case ModelLayer.RelationExpr.isIncoming(expr): {
52
45
  const isTarget = elementExprToPredicate(expr.incoming)
53
46
  return edge => isTarget(edge.target)
54
47
  }
55
- case isOutgoing(expr): {
48
+ case ModelLayer.RelationExpr.isOutgoing(expr): {
56
49
  const isSource = elementExprToPredicate(expr.outgoing)
57
50
  return edge => isSource(edge.source)
58
51
  }
@@ -1,7 +1,7 @@
1
1
  import { flatMap, hasAtLeast, pipe, sort, unique } from 'remeda'
2
2
  import type { LiteralUnion } from 'type-fest'
3
3
  import type { NonEmptyReadonlyArray } from '../../types/_common'
4
- import type { Tag } from '../../types/element'
4
+ import type { Tag } from '../../types/scalars'
5
5
  import { compareNatural } from '../../utils/compare-natural'
6
6
 
7
7
  /**
@@ -9,13 +9,13 @@ import { compareNatural } from '../../utils/compare-natural'
9
9
  * and sort in natural order; returns null if no tags are present.
10
10
  */
11
11
  export function uniqueTags<T extends { tags?: NonEmptyReadonlyArray<LiteralUnion<Tag, string>> | null }>(
12
- elements: ReadonlyArray<T>
12
+ elements: ReadonlyArray<T>,
13
13
  ) {
14
14
  const tags = pipe(
15
15
  elements,
16
16
  flatMap(e => e.tags ?? []),
17
17
  unique(),
18
- sort(compareNatural)
18
+ sort(compareNatural),
19
19
  )
20
20
  return hasAtLeast(tags, 1) ? tags as NonEmptyReadonlyArray<Tag> : null
21
21
  }
package/src/index.ts CHANGED
@@ -33,8 +33,6 @@ export * from './types'
33
33
 
34
34
  export * from './utils'
35
35
 
36
- export * as Expr from './types/expression'
37
-
38
36
  export {
39
37
  computeView,
40
38
  computeViews,
@@ -1,19 +1,23 @@
1
1
  import { isTruthy } from 'remeda'
2
2
  import type { SetRequired } from 'type-fest'
3
+ import type { IteratorLike } from '../types/_common'
3
4
  import {
4
5
  type Element as C4Element,
5
6
  type ElementKind as C4ElementKind,
6
7
  type ElementShape as C4ElementShape,
7
8
  type ElementStyle,
8
- type IconUrl,
9
- type IteratorLike,
10
9
  type Link,
11
- type Tag as C4Tag,
12
- type ThemeColor,
13
10
  DefaultElementShape,
14
11
  DefaultThemeColor,
15
- } from '../types'
12
+ } from '../types/element'
16
13
  import { DefaultShapeSize } from '../types/element'
14
+ import {
15
+ type IconUrl,
16
+ type ProjectId,
17
+ type Tag as C4Tag,
18
+ splitGlobalFqn,
19
+ } from '../types/scalars'
20
+ import type { ThemeColor } from '../types/theme'
17
21
  import { commonAncestor, hierarchyLevel, isAncestor, sortNaturalByFqn } from '../utils'
18
22
  import { type DeployedInstancesIterator } from './DeploymentElementModel'
19
23
  import type { LikeC4Model } from './LikeC4Model'
@@ -28,13 +32,28 @@ export class ElementModel<M extends AnyAux = AnyAux> {
28
32
  readonly _literalId: M['Element']
29
33
  readonly hierarchyLevel: number
30
34
 
35
+ readonly imported: null | {
36
+ from: ProjectId
37
+ fqn: string
38
+ }
39
+
31
40
  constructor(
32
41
  public readonly $model: LikeC4Model<M>,
33
42
  public readonly $element: C4Element,
34
43
  ) {
35
44
  this.id = this.$element.id
36
45
  this._literalId = this.$element.id
37
- this.hierarchyLevel = hierarchyLevel(this.id)
46
+ const [projectId, fqn] = splitGlobalFqn(this.id)
47
+ if (projectId) {
48
+ this.imported = {
49
+ from: projectId,
50
+ fqn,
51
+ }
52
+ this.hierarchyLevel = hierarchyLevel(fqn)
53
+ } else {
54
+ this.imported = null
55
+ this.hierarchyLevel = hierarchyLevel(this.id)
56
+ }
38
57
  }
39
58
 
40
59
  get parent(): ElementModel<M> | null {
@@ -1,15 +1,17 @@
1
- import { mapValues, pipe, sort, values } from 'remeda'
1
+ import { entries, flat, mapValues, pipe, sort, values } from 'remeda'
2
2
  import type { LiteralUnion } from 'type-fest'
3
3
  import { computeView, unsafeComputeView } from '../compute-view'
4
4
  import type { ComputeViewResult } from '../compute-view/compute-view'
5
5
  import { invariant, nonNullable } from '../errors'
6
- import type { ComputedView, DiagramView, IteratorLike, LikeC4View, ModelGlobals } from '../types'
6
+ import type { IteratorLike } from '../types/_common'
7
7
  import type { Element } from '../types/element'
8
- import { type Tag as C4Tag } from '../types/element'
8
+ import type { ModelGlobals } from '../types/global'
9
9
  import type { AnyParsedLikeC4ModelData, GenericLikeC4ModelData, LikeC4ModelDump } from '../types/model-data'
10
10
  import type { ModelRelation } from '../types/relation'
11
+ import { type ProjectId, type Tag as C4Tag, GlobalFqn, isGlobalFqn } from '../types/scalars'
12
+ import type { ComputedView, DiagramView, LikeC4View } from '../types/view'
11
13
  import { compareNatural } from '../utils'
12
- import { ancestorsFqn, commonAncestor, parentFqn } from '../utils/fqn'
14
+ import { ancestorsFqn, commonAncestor, parentFqn, sortParentsFirst } from '../utils/fqn'
13
15
  import { DefaultMap } from '../utils/mnemonist'
14
16
  import type {
15
17
  DeployedInstanceModel,
@@ -121,6 +123,11 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
121
123
  this.#allTags.get(tag).add(el)
122
124
  }
123
125
  }
126
+ for (const [projectId, elements] of entries($model.imports)) {
127
+ for (const element of sortParentsFirst(elements)) {
128
+ this.addImportedElement(projectId as ProjectId, element)
129
+ }
130
+ }
124
131
  for (const relation of values($model.relations)) {
125
132
  const el = this.addRelation(relation)
126
133
  for (const tag of el.tags) {
@@ -365,7 +372,7 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
365
372
  if (this.#elements.has(element.id)) {
366
373
  throw new Error(`Element ${element.id} already exists`)
367
374
  }
368
- const el = new ElementModel(this, Object.freeze(structuredClone(element)))
375
+ const el = new ElementModel(this, Object.freeze(element))
369
376
  this.#elements.set(el.id, el)
370
377
  const parentId = parentFqn(el.id)
371
378
  if (parentId) {
@@ -378,13 +385,42 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
378
385
  return el
379
386
  }
380
387
 
388
+ private addImportedElement(projectId: ProjectId, element: Element) {
389
+ invariant(!isGlobalFqn(element.id), `Imported element already has global FQN`)
390
+ const id = GlobalFqn(projectId, element.id)
391
+ if (this.#elements.has(id)) {
392
+ throw new Error(`Element ${id} already exists`)
393
+ }
394
+ const el = new ElementModel(
395
+ this,
396
+ Object.freeze({
397
+ ...element,
398
+ id,
399
+ }),
400
+ )
401
+ this.#elements.set(el.id, el)
402
+ let parentId = parentFqn(el.id)
403
+ while (parentId) {
404
+ // For imported elements - id has format `@projectId.fqn`
405
+ // We need to exclude `@projectId` from the parentId
406
+ if (parentId.includes('.') && this.#elements.has(parentId)) {
407
+ this.#parents.set(el.id, this.element(parentId))
408
+ this.#children.get(parentId).add(el)
409
+ return el
410
+ }
411
+ parentId = parentFqn(parentId)
412
+ }
413
+ this.#rootElements.add(el)
414
+ return el
415
+ }
416
+
381
417
  private addRelation(relation: ModelRelation) {
382
418
  if (this.#relations.has(relation.id)) {
383
419
  throw new Error(`Relation ${relation.id} already exists`)
384
420
  }
385
421
  const rel = new RelationshipModel(
386
422
  this,
387
- Object.freeze(structuredClone(relation)),
423
+ Object.freeze(relation),
388
424
  )
389
425
  const { source, target } = rel
390
426
  this.#relations.set(rel.id, rel)
@@ -418,6 +454,7 @@ export class LikeC4Model<M extends AnyAux = LikeC4Model.Any> {
418
454
 
419
455
  export namespace LikeC4Model {
420
456
  export const EMPTY = LikeC4Model.create({
457
+ projectId: 'default' as ProjectId,
421
458
  specification: {
422
459
  elements: {},
423
460
  relationships: {},
@@ -436,6 +473,7 @@ export namespace LikeC4Model {
436
473
  elements: {},
437
474
  relations: {},
438
475
  views: {},
476
+ imports: {},
439
477
  }) as LikeC4Model<AnyAux>
440
478
 
441
479
  export type Any = Aux<
@@ -1,12 +1,14 @@
1
1
  import { isEmpty } from 'remeda'
2
- import type { Color, IteratorLike } from '../types'
3
- import type { Link, Tag } from '../types/element'
2
+ import type { IteratorLike } from '../types/_common'
3
+ import type { Link } from '../types/element'
4
4
  import {
5
5
  type ModelRelation,
6
6
  type RelationshipLineType,
7
7
  DefaultLineStyle,
8
8
  DefaultRelationshipColor,
9
9
  } from '../types/relation'
10
+ import type { Tag } from '../types/scalars'
11
+ import type { Color } from '../types/theme'
10
12
  import { commonAncestor } from '../utils/fqn'
11
13
  import type { DeploymentRelationModel } from './DeploymentElementModel'
12
14
  import type { ElementModel } from './ElementModel'
@@ -1,6 +1,7 @@
1
1
  import { nonNullable } from '../../errors'
2
- import type { IteratorLike } from '../../types'
3
- import type { Link, Tag } from '../../types/element'
2
+ import type { IteratorLike } from '../../types/_common'
3
+ import type { Link } from '../../types/element'
4
+ import type { Tag } from '../../types/scalars'
4
5
  import {
5
6
  type ComputedDeploymentView,
6
7
  type ComputedDynamicView,
@@ -1,13 +1,9 @@
1
1
  import type { Simplify, Tagged, UnionToIntersection } from 'type-fest'
2
2
 
3
- export type ProjectId = Tagged<string, 'ProjectID'>
4
-
5
3
  export type NonEmptyArray<T> = [T, ...T[]]
6
4
 
7
5
  export type NonEmptyReadonlyArray<T> = readonly [T, ...T[]]
8
6
 
9
- export type IconUrl = Tagged<string, 'IconUrl'> | 'none'
10
-
11
7
  export type CustomColor = string
12
8
 
13
9
  export type Point = readonly [x: number, y: number]
@@ -1,7 +1,8 @@
1
1
  import type { MergeExclusive, Simplify, Tagged } from 'type-fest'
2
- import type { IconUrl, NonEmptyArray } from './_common'
3
- import type { ElementShape, ElementStyle, Fqn, Link, Tag } from './element'
2
+ import type { NonEmptyArray } from './_common'
3
+ import type { ElementShape, ElementStyle, Link } from './element'
4
4
  import type { AbstractRelation, RelationId } from './relation'
5
+ import type { Fqn, IconUrl, Tag } from './scalars'
5
6
  import type { Color } from './theme'
6
7
 
7
8
  export type DeploymentNodeKind<Kinds extends string = string> = Tagged<Kinds, 'DeploymentNodeKind'>
@@ -1,14 +1,8 @@
1
1
  import type { Tagged, TupleToUnion } from 'type-fest'
2
- import type { IconUrl, NonEmptyArray } from './_common'
2
+ import type { NonEmptyArray } from './_common'
3
+ import type { Fqn, IconUrl, Tag } from './scalars'
3
4
  import type { Color, ShapeSize, SpacingSize, TextSize, ThemeColor } from './theme'
4
5
 
5
- // Full-qualified-name
6
- export type Fqn<Id extends string = string> = Tagged<Id, 'Fqn'>
7
-
8
- export function AsFqn(name: string, parent?: Fqn | null) {
9
- return (parent ? parent + '.' + name : name) as Fqn
10
- }
11
-
12
6
  export const BorderStyles = ['solid', 'dashed', 'dotted', 'none'] as const
13
7
 
14
8
  export type BorderStyle = TupleToUnion<typeof BorderStyles>
@@ -60,8 +54,6 @@ export interface ElementStyle {
60
54
  readonly textSize?: TextSize
61
55
  }
62
56
 
63
- export type Tag<Tags extends string = string> = Tagged<Tags, 'Tag'>
64
-
65
57
  export interface TagSpec {
66
58
  readonly id: Tag
67
59
  readonly style: ElementStyle