@likec4/core 1.34.2 → 1.36.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 (79) hide show
  1. package/README.md +1 -1
  2. package/dist/builder/index.d.mts +15 -4
  3. package/dist/builder/index.mjs +19 -15
  4. package/dist/compute-view/index.d.mts +3 -3
  5. package/dist/compute-view/index.mjs +16 -13
  6. package/dist/compute-view/relationships-view/index.d.mts +3 -3
  7. package/dist/compute-view/relationships-view/index.mjs +2 -2
  8. package/dist/index.d.mts +3 -3
  9. package/dist/index.mjs +14 -9
  10. package/dist/model/connection/deployment/index.d.mts +5 -5
  11. package/dist/model/connection/deployment/index.mjs +5 -4
  12. package/dist/model/connection/index.d.mts +6 -6
  13. package/dist/model/connection/index.mjs +6 -5
  14. package/dist/model/connection/model/index.d.mts +5 -5
  15. package/dist/model/connection/model/index.mjs +3 -3
  16. package/dist/model/index.d.mts +33 -9
  17. package/dist/model/index.mjs +12 -10
  18. package/dist/shared/{core.DAj3LuFH.d.mts → core.BFb3p5d-.d.mts} +4 -2
  19. package/dist/shared/{core.PqNe5WxQ.mjs → core.BlwZ4tBa.mjs} +98 -92
  20. package/dist/shared/core.BpJu1RrF.mjs +5 -0
  21. package/dist/shared/{core.CJSFKrYf.d.mts → core.Bt3kuGUN.d.mts} +3 -3
  22. package/dist/shared/{core.D5qVzkws.mjs → core.Bus32P0S.mjs} +4 -3
  23. package/dist/shared/{core.4SpVCM9m.mjs → core.BvIGRozf.mjs} +364 -28
  24. package/dist/shared/{core.C80Z37Eu.mjs → core.C1GV0WWV.mjs} +1 -1
  25. package/dist/shared/{core.DXRAwwDB.mjs → core.CaJp8Amg.mjs} +3 -2
  26. package/dist/shared/{core.D56nCp7e.mjs → core.CaaYNQDM.mjs} +1 -3
  27. package/dist/shared/core.CnIyafWn.mjs +3 -0
  28. package/dist/shared/{core.D3xEWj5B.d.mts → core.D8bTI5KC.d.mts} +2 -2
  29. package/dist/shared/{core.Di7__sMg.d.mts → core.DHYd2-VU.d.mts} +3 -3
  30. package/dist/shared/{core.C_C-3c1S.mjs → core.DQj9iv_y.mjs} +3 -5
  31. package/dist/shared/{core.DrS4Bf4y.d.mts → core.DoNP3Yl-.d.mts} +187 -11
  32. package/dist/shared/{core.IWLie-Ma.d.mts → core.DxDn5nB5.d.mts} +24 -6
  33. package/dist/shared/{core.CtKXPqN_.mjs → core.HUONfQfd.mjs} +1 -1
  34. package/dist/shared/{core.BTnf14gD.mjs → core.Y3j3s-oq.mjs} +2 -2
  35. package/dist/shared/{core.Br7Fab9l.mjs → core._jnYNUQ-.mjs} +8 -70
  36. package/dist/shared/{core.XptQjhMA.mjs → core.b2BzqGz0.mjs} +2 -2
  37. package/dist/shared/{core.BMCb0wJI.mjs → core.yaRvRnMW.mjs} +1 -1
  38. package/dist/theme/index.mjs +34 -3510
  39. package/dist/types/_aux.d.mts +1 -1
  40. package/dist/types/index.d.mts +3 -3
  41. package/dist/types/index.mjs +6 -4
  42. package/dist/types/scalar.d.mts +2 -0
  43. package/dist/types/scalar.mjs +68 -0
  44. package/dist/utils/index.d.mts +6 -8
  45. package/dist/utils/index.mjs +5 -5
  46. package/dist/utils/iterable/index.d.mts +18 -1
  47. package/dist/utils/iterable/index.mjs +22 -2
  48. package/package.json +11 -16
  49. package/src/builder/Builder.ts +17 -6
  50. package/src/builder/__snapshots__/Builder-style1.spec.ts.snap +9 -0
  51. package/src/builder/__snapshots__/Builder-style2.compute-model.json5 +3 -0
  52. package/src/builder/__snapshots__/Builder-style2.spec.ts.snap +3 -0
  53. package/src/builder/_types.ts +10 -0
  54. package/src/compute-view/dynamic-view/compute.ts +2 -0
  55. package/src/compute-view/element-view/__test__/fixture.ts +1 -0
  56. package/src/compute-view/element-view/predicates/relation-direct.ts +8 -2
  57. package/src/compute-view/utils/applyCustomRelationProperties.ts +2 -0
  58. package/src/model/DeploymentModel.ts +1 -1
  59. package/src/model/LikeC4Model.ts +162 -13
  60. package/src/model/index.ts +8 -0
  61. package/src/model/types.ts +1 -8
  62. package/src/model/utils.ts +66 -0
  63. package/src/model/view/LikeC4ViewModel.ts +62 -7
  64. package/src/model/view/LikeC4ViewsFolder.ts +151 -0
  65. package/src/theme/__test__/theme-index.ts +29 -0
  66. package/src/theme/element.ts +0 -19
  67. package/src/theme/index.ts +49 -3
  68. package/src/types/_aux.ts +6 -0
  69. package/src/types/index.ts +1 -0
  70. package/src/types/model-data.ts +2 -0
  71. package/src/types/model-dump.ts +8 -1
  72. package/src/types/model-spec.ts +4 -0
  73. package/src/types/project.ts +9 -0
  74. package/src/types/view-computed.ts +1 -1
  75. package/src/types/view-parsed.dynamic.ts +2 -2
  76. package/src/utils/compare-natural.ts +14 -12
  77. package/src/utils/index.ts +1 -0
  78. package/src/utils/iterable/first.ts +48 -0
  79. package/src/utils/iterable/index.ts +1 -0
@@ -0,0 +1,29 @@
1
+ import type { HexColor } from "../../types";
2
+
3
+ export const lightValue = {
4
+ elements: {
5
+ fill: "#2ec2fb" as HexColor,
6
+ stroke: "#1fabe1" as HexColor,
7
+ hiContrast: "#00002d" as HexColor,
8
+ loContrast: "#00153d" as HexColor,
9
+ },
10
+ relationships: {
11
+ lineColor: "#4ccdfb" as HexColor,
12
+ labelColor: "#6fd8fc" as HexColor,
13
+ labelBgColor: "#0084b2" as HexColor,
14
+ },
15
+ }
16
+
17
+ export const darkValue = {
18
+ elements: {
19
+ fill: "#2c40dc" as HexColor,
20
+ stroke: "#1f32c4" as HexColor,
21
+ hiContrast: "#ffffff" as HexColor,
22
+ loContrast: "#fffeff" as HexColor,
23
+ },
24
+ relationships: {
25
+ lineColor: "#5362e1" as HexColor,
26
+ labelColor: "#7a87e9" as HexColor,
27
+ labelBgColor: "#0a259c" as HexColor,
28
+ },
29
+ }
@@ -29,30 +29,15 @@ export const ElementColors = {
29
29
  muted: slate,
30
30
  slate,
31
31
  gray: {
32
- // fill: colors.neutral[500],
33
- // stroke: colors.neutral[600],
34
- // hiContrast: colors.neutral[50],
35
- // loContrast: colors.neutral[200],
36
32
  fill: '#737373',
37
33
  stroke: '#525252',
38
34
  hiContrast: '#fafafa',
39
35
  loContrast: '#d4d4d4',
40
36
  },
41
37
  red: {
42
- // fill: colors.red[500],
43
- // stroke: colors.red[600],
44
- // hiContrast: colors.red[50],
45
- // loContrast: colors.red[200],
46
38
  fill: '#AC4D39',
47
- // fill: '#b54548',
48
39
  stroke: '#853A2D',
49
- // hiContrast: '#fef2f2',
50
- // loContrast: '#fecaca',
51
- // hiContrast: '#191111', // colors.gray[900],
52
- // loContrast: '#3b1219' // colors.gray[800],
53
40
  hiContrast: '#FBD3CB',
54
- // hiContrast: '#f8fafc',
55
- // loContrast: '#fdd8d8' // radix black red 12
56
41
  loContrast: '#f5b2a3',
57
42
  },
58
43
  green: {
@@ -68,10 +53,6 @@ export const ElementColors = {
68
53
  loContrast: '#f9b27c',
69
54
  },
70
55
  indigo: {
71
- // fill: colors.indigo[500],
72
- // stroke: colors.indigo[600],
73
- // hiContrast: colors.indigo[50],
74
- // loContrast: colors.indigo[200],
75
56
  fill: '#6366f1',
76
57
  stroke: '#4f46e5',
77
58
  hiContrast: '#eef2ff',
@@ -1,7 +1,13 @@
1
1
  import { generateColors } from '@mantine/colors-generator'
2
+ import chroma from 'chroma-js';
2
3
  import type { ColorLiteral, HexColor, LikeC4Theme, ThemeColorValues } from '../types'
3
4
  import { ElementColors } from './element'
4
5
  import { RelationshipColors } from './relationships'
6
+ import { isDeepEqual } from 'remeda'
7
+
8
+ const CONTRAST_MIN_WITH_FOREGROUND = 60
9
+ const CONTRAST_START_TONE_DIFFERENCE = 2
10
+ const CONTRAST_STEP_TONE_DIFFERENCE = 1
5
11
 
6
12
  export const defaultTheme: LikeC4Theme = {
7
13
  elements: ElementColors,
@@ -57,12 +63,15 @@ export function computeColorValues(color: ColorLiteral): ThemeColorValues {
57
63
  if (color.match(/^#([0-9a-f]{3}){1,2}$/i)) {
58
64
  const colors = generateColors(color)
59
65
 
66
+ const fillColor = colors[6]
67
+ const contrastedColors = getContrastedColorsAPCA(fillColor)
68
+
60
69
  return {
61
70
  elements: {
62
- fill: colors[6] as HexColor,
71
+ fill: fillColor as HexColor,
63
72
  stroke: colors[7] as HexColor,
64
- hiContrast: colors[0] as HexColor,
65
- loContrast: colors[1] as HexColor,
73
+ hiContrast: contrastedColors[0] as HexColor,
74
+ loContrast: contrastedColors[1] as HexColor,
66
75
  },
67
76
  relationships: {
68
77
  lineColor: colors[4] as HexColor,
@@ -78,4 +87,41 @@ export function computeColorValues(color: ColorLiteral): ThemeColorValues {
78
87
  }
79
88
  }
80
89
 
90
+ function getContrastedColorsAPCA(refColor: string): [string, string] {
91
+ const refColorChroma = chroma(refColor)
92
+
93
+ // Start with 2 steps tone difference in the CIELAB color space from reference
94
+ let lightColorRgb = refColorChroma.brighten(CONTRAST_START_TONE_DIFFERENCE);
95
+ let darkColorRgb = refColorChroma.darken(CONTRAST_START_TONE_DIFFERENCE);
96
+
97
+ let previousLight
98
+ let previousDark
99
+ let contrastWithLight
100
+ let contrastWithDark
101
+ do {
102
+ // Store previous colors for loop condition
103
+ previousLight = lightColorRgb
104
+ previousDark = darkColorRgb
105
+
106
+ // Change tone by 1 step each loop
107
+ lightColorRgb = lightColorRgb.brighten(CONTRAST_STEP_TONE_DIFFERENCE)
108
+ darkColorRgb = darkColorRgb.darken(CONTRAST_STEP_TONE_DIFFERENCE)
109
+
110
+ // Calculate contrast of each color with the reference color
111
+ contrastWithLight = chroma.contrastAPCA(refColorChroma, lightColorRgb);
112
+ contrastWithDark = chroma.contrastAPCA(refColorChroma, darkColorRgb);
113
+ }
114
+ // Stop if one of the contrast is high enough or if we reach max value for each (aka when they are equal between two rounds)
115
+ while (Math.abs(contrastWithLight) < CONTRAST_MIN_WITH_FOREGROUND
116
+ && Math.abs(contrastWithDark) < CONTRAST_MIN_WITH_FOREGROUND
117
+ && (!isDeepEqual(lightColorRgb, previousLight) || !isDeepEqual(darkColorRgb, previousDark)))
118
+
119
+ // Choose the max contrast between the two
120
+ if (Math.abs(contrastWithLight) > Math.abs(contrastWithDark)) {
121
+ return [lightColorRgb.brighten(0.4).hex(), lightColorRgb.hex()]
122
+ } else {
123
+ return [darkColorRgb.darken(0.4).hex(), darkColorRgb.hex()]
124
+ }
125
+ }
126
+
81
127
  export { ElementColors, RelationshipColors }
package/src/types/_aux.ts CHANGED
@@ -149,6 +149,12 @@ export type toLayouted<A> = A extends Aux<any, infer E, infer D, infer V, infer
149
149
  */
150
150
  export type ProjectId<A> = A extends infer T extends Any ? Coalesce<T['ProjectId']> : never
151
151
 
152
+ export type setProject<A, P extends string> =
153
+ // dprint-ignore
154
+ A extends Aux<infer S, infer E, infer D, infer V, any, infer Spec>
155
+ ? Aux<S, E, D, V, P, Spec>
156
+ : never
157
+
152
158
  /**
153
159
  * Element FQN from Aux as branded type
154
160
  */
@@ -25,6 +25,7 @@ export * from './model-dump'
25
25
  export * from './model-logical'
26
26
  export * from './model-spec'
27
27
  export * from './operators'
28
+ export * from './project'
28
29
  export * from './scalar'
29
30
  export * from './styles'
30
31
  export * from './view'
@@ -7,11 +7,13 @@ import type { ModelGlobals } from './global'
7
7
  import type { DeploymentElement, DeploymentRelationship } from './model-deployment'
8
8
  import type { Element, Relationship } from './model-logical'
9
9
  import type { Specification } from './model-spec'
10
+ import type { LikeC4Project } from './project'
10
11
  import type * as scalar from './scalar'
11
12
  import type { ComputedView, LayoutedView, ParsedView } from './view'
12
13
 
13
14
  interface BaseLikeC4ModelData<A extends aux.Any> {
14
15
  projectId: aux.ProjectId<A>
16
+ project: LikeC4Project
15
17
  specification: Specification<A>
16
18
  elements: Record<aux.ElementId<A>, Element<A>>
17
19
  deployments: {
@@ -39,6 +39,7 @@ export type SpecTypesFromDump<J> = J extends SpecificationDump ? aux.SpecAux<
39
39
  export type LikeC4ModelDump = {
40
40
  [_stage]?: 'computed' | 'layouted'
41
41
  projectId?: string
42
+ project?: ProjectDump
42
43
  specification: SpecificationDump
43
44
  elements?: {
44
45
  [kind: string]: object
@@ -61,12 +62,18 @@ export type LikeC4ModelDump = {
61
62
  imports?: {}
62
63
  }
63
64
 
65
+ export type ProjectDump = {
66
+ id: string
67
+ name?: string
68
+ title?: string | undefined
69
+ }
70
+
64
71
  export type AuxFromDump<D> = D extends LikeC4ModelDump ? aux.Aux<
65
72
  D[_stage] extends infer S extends string & 'computed' | 'layouted' ? S : 'computed' | 'layouted' | 'parsed',
66
73
  KeysOf<D['elements']>,
67
74
  KeysOf<D['deployments']['elements']>,
68
75
  KeysOf<D['views']>,
69
- D['projectId'] extends infer P extends string ? P : never,
76
+ D['projectId'] extends infer PID extends string ? PID : never,
70
77
  SpecTypesFromDump<D['specification']>
71
78
  >
72
79
  : aux.Never
@@ -1,6 +1,7 @@
1
1
  import type { IsNever } from 'type-fest'
2
2
  import type * as aux from './_aux'
3
3
  import type { Any } from './_aux'
4
+ import type { Link, NonEmptyArray } from './_common'
4
5
  import type * as scalar from './scalar'
5
6
  import type {
6
7
  Icon,
@@ -24,8 +25,11 @@ import type {
24
25
  */
25
26
  export interface ElementSpecification {
26
27
  tags?: scalar.Tag[]
28
+ title?: string
29
+ description?: scalar.MarkdownOrString
27
30
  technology?: string
28
31
  notation?: string
32
+ links?: NonEmptyArray<Link>
29
33
  style: {
30
34
  shape?: ElementShape
31
35
  icon?: Icon
@@ -0,0 +1,9 @@
1
+ import * as scalar from './scalar'
2
+
3
+ /**
4
+ * LikeC4 projects encapsulate model, and can import from each other
5
+ */
6
+ export interface LikeC4Project {
7
+ id: scalar.ProjectId
8
+ title?: string
9
+ }
@@ -66,7 +66,7 @@ export interface ComputedEdge<A extends Any = Any> extends aux.WithOptionalTags<
66
66
  kind?: aux.RelationKind<A> | typeof scalar.StepEdgeKind
67
67
  notation?: string
68
68
  // Notes for walkthrough
69
- notes?: string
69
+ notes?: scalar.MarkdownOrString
70
70
  color?: Color
71
71
  line?: RelationshipLineType
72
72
  head?: RelationshipArrowType
@@ -5,7 +5,7 @@ import type { AnyAux } from './_aux'
5
5
  import type { ExclusiveUnion } from './_common'
6
6
  import type { _type } from './const'
7
7
  import type { ModelFqnExpr } from './expression-model'
8
- import { isStepEdgeId } from './scalar'
8
+ import { type MarkdownOrString, isStepEdgeId } from './scalar'
9
9
  import type { Color, RelationshipArrowType, RelationshipLineType } from './styles'
10
10
  import type {
11
11
  BaseParsedViewProperties,
@@ -23,7 +23,7 @@ export interface DynamicViewStep<A extends AnyAux = AnyAux> {
23
23
  readonly technology?: string
24
24
  readonly notation?: string
25
25
  // Notes for walkthrough
26
- readonly notes?: string
26
+ readonly notes?: MarkdownOrString
27
27
  readonly color?: Color
28
28
  readonly line?: RelationshipLineType
29
29
  readonly head?: RelationshipArrowType
@@ -13,22 +13,20 @@ export function compareNatural(a: string | undefined, b: string | undefined): -1
13
13
  }
14
14
 
15
15
  /**
16
- * Compares two strings hierarchically based on their depth.
16
+ * Compares two strings lexicographically first, then hierarchically based on their depth.\
17
17
  * From parent nodes to leaves
18
18
  *
19
19
  * @example
20
20
  * const lines = [
21
+ * 'b.c',
22
+ * 'b',
21
23
  * 'a.b.c',
22
- * 'a',
23
- * 'a.b',
24
- * 'a.c.c',
25
24
  * ]
26
25
  * lines.sort(compareNaturalHierarchically('.'))
27
26
  * // [
28
- * // 'a',
29
- * // 'a.b',
30
27
  * // 'a.b.c',
31
- * // 'a.c.c',
28
+ * // 'b',
29
+ * // 'b.c',
32
30
  * // ]
33
31
  */
34
32
  export function compareNaturalHierarchically(
@@ -40,10 +38,12 @@ export function compareNaturalHierarchically(
40
38
  if (!b) return 1
41
39
  const aParts = a.split(separator)
42
40
  const bParts = b.split(separator)
43
- if (aParts.length !== bParts.length) {
44
- return aParts.length - bParts.length
45
- }
46
- for (let i = 0; i < aParts.length; i++) {
41
+
42
+ // Find the minimum length to avoid out-of-bounds access
43
+ const minLength = Math.min(aParts.length, bParts.length)
44
+
45
+ // Compare segments until we find a difference
46
+ for (let i = 0; i < minLength; i++) {
47
47
  const aPart = aParts[i]!
48
48
  const bPart = bParts[i]!
49
49
  const comparison = compare(aPart, bPart)
@@ -51,6 +51,8 @@ export function compareNaturalHierarchically(
51
51
  return comparison
52
52
  }
53
53
  }
54
- return 0
54
+
55
+ // If all common segments are equal, shorter paths come first
56
+ return aParts.length - bParts.length
55
57
  }
56
58
  }
@@ -41,6 +41,7 @@ export {
41
41
  export {
42
42
  ifilter,
43
43
  ifind,
44
+ ifirst,
44
45
  iflat,
45
46
  ihead,
46
47
  imap,
@@ -0,0 +1,48 @@
1
+ import type { IteratorLike } from '../../types'
2
+ import { invariant } from '../../utils/invariant'
3
+
4
+ /**
5
+ * Takes the first N elements from an iterable.
6
+ * Composable first version of `ifirst`.
7
+ * @signature
8
+ * ifirst(count)(data)
9
+ */
10
+ export function ifirst<T>(count: number): (iterable: Iterable<T>) => IteratorLike<T>
11
+
12
+ /**
13
+ * Takes the first N elements from an iterable.
14
+ * Data first version of `ifirst`.
15
+ * @signature
16
+ * ifirst(data, count)
17
+ * @example
18
+ * ifirst([1, 2, 3, 4, 5], 3) // => Iterable<[1, 2, 3]>
19
+ */
20
+ export function ifirst<T>(iterable: Iterable<T>, count: number): IteratorLike<T>
21
+
22
+ export function ifirst(
23
+ arg1: unknown,
24
+ arg2?: unknown,
25
+ ): IteratorLike<unknown> | ((it: Iterable<unknown>) => IteratorLike<unknown>) {
26
+ const count = (arg2 ?? arg1) as number
27
+ invariant(typeof count === 'number' && count >= 0, 'Count must be a non-negative number')
28
+
29
+ function* _first(iter: Iterable<unknown>): IteratorLike<unknown> {
30
+ let taken = 0
31
+ for (const value of iter) {
32
+ if (taken >= count) {
33
+ break
34
+ }
35
+ yield value
36
+ taken++
37
+ }
38
+ return
39
+ }
40
+
41
+ if (arg2 === undefined) {
42
+ // Composable version: ifirst(count)(iterable)
43
+ return (iterable: Iterable<unknown>) => _first(iterable)
44
+ }
45
+
46
+ // Data-first version: ifirst(iterable, count)
47
+ return _first(arg1 as Iterable<unknown>)
48
+ }
@@ -5,6 +5,7 @@ export { isIterable } from './_types'
5
5
  */
6
6
  export { ifilter } from './filter'
7
7
  export { ifind } from './find'
8
+ export { ifirst } from './first'
8
9
  export { iflat } from './flat'
9
10
  export { ihead } from './head'
10
11
  export { imap } from './map'