@likec4/core 1.1.1 → 1.2.1

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 (56) hide show
  1. package/dist/colors/element.d.ts +1 -0
  2. package/dist/colors/index.d.ts +1 -0
  3. package/dist/colors/relationships.d.ts +1 -0
  4. package/dist/errors/errors.spec.d.ts +2 -0
  5. package/dist/errors/errors.spec.js +69 -0
  6. package/dist/errors/index.d.ts +1 -0
  7. package/dist/errors/index.js +7 -1
  8. package/dist/index.d.ts +1 -0
  9. package/dist/types/_common.d.ts +1 -0
  10. package/dist/types/element.d.ts +1 -0
  11. package/dist/types/expression.d.ts +1 -0
  12. package/dist/types/index.d.ts +1 -2
  13. package/dist/types/index.js +2 -3
  14. package/dist/types/model.d.ts +3 -3
  15. package/dist/types/opaque.d.ts +1 -0
  16. package/dist/types/relation.d.ts +1 -0
  17. package/dist/types/theme.d.ts +1 -0
  18. package/dist/types/view.d.ts +133 -8
  19. package/dist/types/view.js +31 -3
  20. package/dist/utils/fqn.d.ts +1 -0
  21. package/dist/utils/fqn.spec.d.ts +2 -0
  22. package/dist/utils/fqn.spec.js +82 -0
  23. package/dist/utils/guards.d.ts +1 -0
  24. package/dist/utils/index.d.ts +1 -0
  25. package/dist/utils/promises.d.ts +1 -0
  26. package/dist/utils/relations.d.ts +1 -0
  27. package/dist/utils/relations.spec.d.ts +2 -0
  28. package/dist/utils/relations.spec.js +210 -0
  29. package/package.json +6 -6
  30. package/src/colors/element.ts +80 -0
  31. package/src/colors/index.ts +12 -0
  32. package/src/colors/relationships.ts +54 -0
  33. package/src/errors/errors.spec.ts +88 -0
  34. package/src/errors/index.ts +120 -0
  35. package/src/index.ts +9 -0
  36. package/src/reset.d.ts +2 -0
  37. package/src/types/_common.ts +5 -0
  38. package/src/types/element.ts +56 -0
  39. package/src/types/expression.ts +140 -0
  40. package/src/types/index.ts +10 -0
  41. package/src/types/model.ts +15 -0
  42. package/src/types/opaque.ts +108 -0
  43. package/src/types/relation.ts +38 -0
  44. package/src/types/theme.ts +46 -0
  45. package/src/types/view.ts +263 -0
  46. package/src/utils/fqn.spec.ts +105 -0
  47. package/src/utils/fqn.ts +112 -0
  48. package/src/utils/guards.ts +10 -0
  49. package/src/utils/index.ts +4 -0
  50. package/src/utils/promises.ts +9 -0
  51. package/src/utils/relations.spec.ts +268 -0
  52. package/src/utils/relations.ts +104 -0
  53. package/dist/types/computed-view.d.ts +0 -52
  54. package/dist/types/computed-view.js +0 -1
  55. package/dist/types/diagram.d.ts +0 -40
  56. package/dist/types/diagram.js +0 -1
@@ -0,0 +1,210 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { compareRelations, Relations } from './relations';
3
+ const { isAnyBetween, isAnyInOut, isBetween, isIncoming, isInside, isOutgoing } = Relations;
4
+ const relations = [
5
+ {
6
+ id: 'customer:cloud.frontend.dashboard',
7
+ source: 'customer',
8
+ target: 'cloud.frontend.dashboard',
9
+ title: ''
10
+ },
11
+ {
12
+ id: 'support:cloud.frontend.adminPanel',
13
+ source: 'support',
14
+ target: 'cloud.frontend.adminPanel',
15
+ title: ''
16
+ },
17
+ {
18
+ id: 'cloud.backend.storage:amazon.s3',
19
+ source: 'cloud.backend.storage',
20
+ target: 'amazon.s3',
21
+ title: ''
22
+ },
23
+ {
24
+ id: 'amazon.api:cloud.backend.graphql',
25
+ source: 'amazon.api',
26
+ target: 'cloud.backend.graphql',
27
+ title: ''
28
+ },
29
+ {
30
+ id: 'cloud.backend.graphql:cloud.backend.storage',
31
+ source: 'cloud.backend.graphql',
32
+ target: 'cloud.backend.storage',
33
+ title: ''
34
+ },
35
+ {
36
+ id: 'cloud.frontend.dashboard:cloud.backend.graphql',
37
+ source: 'cloud.frontend.dashboard',
38
+ target: 'cloud.backend.graphql',
39
+ title: ''
40
+ },
41
+ {
42
+ id: 'cloud.frontend.adminPanel:cloud.backend.graphql',
43
+ source: 'cloud.frontend.adminPanel',
44
+ target: 'cloud.backend.graphql',
45
+ title: ''
46
+ }
47
+ ];
48
+ describe('relation predicates', () => {
49
+ const expectRelations = (predicate) => expect(relations.filter(predicate).map(r => r.id));
50
+ const customer = 'customer';
51
+ const cloud = 'cloud';
52
+ const frontend = 'cloud.frontend';
53
+ const backend = 'cloud.backend';
54
+ it('isBetween: cloud.frontend -> cloud.backend', () => {
55
+ expectRelations(isBetween(frontend, backend)).toEqual([
56
+ 'cloud.frontend.dashboard:cloud.backend.graphql',
57
+ 'cloud.frontend.adminPanel:cloud.backend.graphql'
58
+ ]);
59
+ });
60
+ it('isBetween: cloud.frontend.dashboard -> cloud.backend', () => {
61
+ expectRelations(isBetween('cloud.frontend.dashboard', backend)).toEqual([
62
+ 'cloud.frontend.dashboard:cloud.backend.graphql'
63
+ ]);
64
+ });
65
+ it('isBetween: cloud.backend -> cloud.frontend', () => {
66
+ expectRelations(isBetween(backend, frontend)).toEqual([]);
67
+ });
68
+ it('isBetween: cloud.backend -> cloud.backend', () => {
69
+ expectRelations(isBetween(backend, backend)).toEqual([
70
+ 'cloud.backend.graphql:cloud.backend.storage'
71
+ ]);
72
+ });
73
+ it('isBetween: customer -> cloud', () => {
74
+ expectRelations(isBetween(customer, cloud)).toEqual(['customer:cloud.frontend.dashboard']);
75
+ });
76
+ it('isIncoming: customer', () => {
77
+ expectRelations(isIncoming(customer)).toEqual([]);
78
+ });
79
+ it('isIncoming: cloud', () => {
80
+ expectRelations(isIncoming(cloud)).toEqual([
81
+ 'customer:cloud.frontend.dashboard',
82
+ 'support:cloud.frontend.adminPanel',
83
+ 'amazon.api:cloud.backend.graphql'
84
+ ]);
85
+ });
86
+ it('isIncoming: cloud.frontend', () => {
87
+ expectRelations(isIncoming(frontend)).toEqual([
88
+ 'customer:cloud.frontend.dashboard',
89
+ 'support:cloud.frontend.adminPanel'
90
+ ]);
91
+ });
92
+ it('isIncoming: cloud.frontend.dashboard', () => {
93
+ expectRelations(isIncoming('cloud.frontend.dashboard')).toEqual([
94
+ 'customer:cloud.frontend.dashboard'
95
+ ]);
96
+ });
97
+ it('isOutgoing: cloud', () => {
98
+ expectRelations(isOutgoing(cloud)).toEqual(['cloud.backend.storage:amazon.s3']);
99
+ });
100
+ it('isOutgoing: cloud.frontend', () => {
101
+ expectRelations(isOutgoing(frontend)).toEqual([
102
+ 'cloud.frontend.dashboard:cloud.backend.graphql',
103
+ 'cloud.frontend.adminPanel:cloud.backend.graphql'
104
+ ]);
105
+ });
106
+ it('isOutgoing: cloud.backend.storage', () => {
107
+ expectRelations(isOutgoing('cloud.backend.storage')).toEqual([
108
+ 'cloud.backend.storage:amazon.s3'
109
+ ]);
110
+ });
111
+ it('isInside: cloud', () => {
112
+ expectRelations(isInside(cloud)).toEqual([
113
+ 'cloud.backend.graphql:cloud.backend.storage',
114
+ 'cloud.frontend.dashboard:cloud.backend.graphql',
115
+ 'cloud.frontend.adminPanel:cloud.backend.graphql'
116
+ ]);
117
+ });
118
+ it('isInside: cloud.frontend', () => {
119
+ expectRelations(isInside(frontend)).toEqual([]);
120
+ });
121
+ it('isInside: cloud.backend', () => {
122
+ expectRelations(isInside(backend)).toEqual(['cloud.backend.graphql:cloud.backend.storage']);
123
+ });
124
+ it('isAnyInOut: customer', () => {
125
+ expectRelations(isAnyInOut(customer)).toEqual(['customer:cloud.frontend.dashboard']);
126
+ });
127
+ it('isAnyInOut: cloud', () => {
128
+ expectRelations(isAnyInOut(cloud)).toEqual([
129
+ 'customer:cloud.frontend.dashboard',
130
+ 'support:cloud.frontend.adminPanel',
131
+ 'cloud.backend.storage:amazon.s3',
132
+ 'amazon.api:cloud.backend.graphql'
133
+ ]);
134
+ });
135
+ it('isAnyBetween: cloud and amazon', () => {
136
+ expectRelations(isAnyBetween(cloud, 'amazon')).toEqual([
137
+ 'cloud.backend.storage:amazon.s3',
138
+ 'amazon.api:cloud.backend.graphql'
139
+ ]);
140
+ });
141
+ });
142
+ describe('compareRelations', () => {
143
+ function rel(source, target) {
144
+ return {
145
+ source,
146
+ target
147
+ };
148
+ }
149
+ function sorted(...relations) {
150
+ return relations.sort(compareRelations).map(r => r.source + ' -> ' + r.target);
151
+ }
152
+ it('should sort by source and target', () => {
153
+ expect(sorted(rel('customer', 'cloud.ui'), rel('customer', 'cloud'))).toEqual([
154
+ 'customer -> cloud',
155
+ 'customer -> cloud.ui'
156
+ ]);
157
+ expect(sorted(rel('1', '2.1'), rel('1', '2'), rel('1.1.1', '2'), rel('1.1.1', '2.1'), rel('1.1', '2'))).toEqual(['1 -> 2', '1 -> 2.1', '1.1 -> 2', '1.1.1 -> 2', '1.1.1 -> 2.1']);
158
+ });
159
+ it('should sort by parent', () => {
160
+ expect(sorted(
161
+ // same parent
162
+ rel('cloud.1.1', 'cloud.2.1'), rel('cloud.1.1', 'cloud.2'), rel('cloud.1', 'cloud.2.1'), rel('cloud.1', 'cloud.2'),
163
+ // no same parent
164
+ rel('cloud.1.1', 'aws.1'), rel('cloud.1', 'aws.1'), rel('cloud', 'aws'))).toEqual([
165
+ 'cloud -> aws',
166
+ 'cloud.1 -> aws.1',
167
+ 'cloud.1.1 -> aws.1',
168
+ 'cloud.1 -> cloud.2',
169
+ 'cloud.1 -> cloud.2.1',
170
+ 'cloud.1.1 -> cloud.2',
171
+ 'cloud.1.1 -> cloud.2.1'
172
+ ]);
173
+ });
174
+ it('should sort by ancestors', () => {
175
+ expect(sorted(
176
+ // same parent 2nd level
177
+ rel('cloud.api.1', 'cloud.api.2'), rel('cloud.ui.1', 'cloud.ui.2'), rel('cloud.ui.2', 'cloud.ui.1'),
178
+ // same ancestor cloud
179
+ rel('cloud.ui.1', 'cloud.api.1'), rel('cloud.ui.2', 'cloud.api.2'), rel('cloud.ui', 'cloud.api.1'),
180
+ // same parent 1st level
181
+ rel('cloud.ui', 'cloud.api'),
182
+ // no same parent
183
+ rel('cloud.api.1', 'aws.1'), rel('cloud.api', 'aws.1'), rel('cloud.api', 'aws'), rel('cloud.ui.1', 'aws.1'), rel('cloud.ui', 'aws'), rel('cloud', 'aws'))).toEqual([
184
+ 'cloud -> aws',
185
+ 'cloud.api -> aws',
186
+ 'cloud.ui -> aws',
187
+ 'cloud.api -> aws.1',
188
+ 'cloud.api.1 -> aws.1',
189
+ 'cloud.ui.1 -> aws.1',
190
+ 'cloud.ui -> cloud.api',
191
+ 'cloud.ui -> cloud.api.1',
192
+ 'cloud.ui.1 -> cloud.api.1',
193
+ 'cloud.ui.2 -> cloud.api.2',
194
+ 'cloud.api.1 -> cloud.api.2',
195
+ 'cloud.ui.1 -> cloud.ui.2',
196
+ 'cloud.ui.2 -> cloud.ui.1'
197
+ ]);
198
+ });
199
+ it('should sort relations from example', () => {
200
+ expect(sorted(...relations)).toEqual([
201
+ 'customer -> cloud.frontend.dashboard',
202
+ 'support -> cloud.frontend.adminPanel',
203
+ 'amazon.api -> cloud.backend.graphql',
204
+ 'cloud.backend.storage -> amazon.s3',
205
+ 'cloud.frontend.dashboard -> cloud.backend.graphql',
206
+ 'cloud.frontend.adminPanel -> cloud.backend.graphql',
207
+ 'cloud.backend.graphql -> cloud.backend.storage'
208
+ ]);
209
+ });
210
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@likec4/core",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
4
4
  "license": "MIT",
5
5
  "homepage": "https://likec4.dev",
6
6
  "author": "Denis Davydkov <denis@davydkov.com>",
@@ -12,15 +12,14 @@
12
12
  "bugs": "https://github.com/likec4/likec4/issues",
13
13
  "scripts": {
14
14
  "typecheck": "tsc --noEmit",
15
- "build": "tsc",
16
- "prepack": "tsc -p tsconfig.build.json",
15
+ "prepack": "tsc",
17
16
  "lint": "run -T eslint src/ --fix",
18
17
  "test": "run -T vitest run",
19
18
  "clean": "run -T rimraf dist"
20
19
  },
21
20
  "files": [
22
21
  "dist",
23
- "!dist/src/**",
22
+ "src",
24
23
  "!**/*.map"
25
24
  ],
26
25
  "type": "module",
@@ -71,10 +70,11 @@
71
70
  "type-fest": "^4.18.2"
72
71
  },
73
72
  "devDependencies": {
73
+ "@likec4/tsconfig": "1.2.1",
74
74
  "@total-typescript/ts-reset": "^0.5.1",
75
75
  "execa": "^9.1.0",
76
76
  "typescript": "^5.4.5",
77
- "vitest": "~1.5.2"
77
+ "vitest": "~1.5.3"
78
78
  },
79
- "packageManager": "yarn@4.2.2"
79
+ "packageManager": "yarn@4.3.0"
80
80
  }
@@ -0,0 +1,80 @@
1
+ import type { ElementThemeColors, ElementThemeColorValues } from '../types/theme'
2
+
3
+ const blue = {
4
+ fill: '#3b82f6',
5
+ stroke: '#2563eb',
6
+ hiContrast: '#eff6ff',
7
+ loContrast: '#bfdbfe'
8
+ } satisfies ElementThemeColorValues
9
+
10
+ const sky = {
11
+ fill: '#0284c7',
12
+ stroke: '#0369a1',
13
+ hiContrast: '#f0f9ff',
14
+ loContrast: '#B6ECF7'
15
+ } satisfies ElementThemeColorValues
16
+
17
+ const slate = {
18
+ fill: '#64748b',
19
+ stroke: '#475569',
20
+ hiContrast: '#f8fafc',
21
+ loContrast: '#cbd5e1'
22
+ } satisfies ElementThemeColorValues
23
+
24
+ export const ElementColors = {
25
+ primary: blue,
26
+ blue,
27
+ secondary: sky,
28
+ sky,
29
+ muted: slate,
30
+ slate,
31
+ gray: {
32
+ // fill: colors.neutral[500],
33
+ // stroke: colors.neutral[600],
34
+ // hiContrast: colors.neutral[50],
35
+ // loContrast: colors.neutral[200],
36
+ fill: '#737373',
37
+ stroke: '#525252',
38
+ hiContrast: '#fafafa',
39
+ loContrast: '#d4d4d4'
40
+ },
41
+ red: {
42
+ // fill: colors.red[500],
43
+ // stroke: colors.red[600],
44
+ // hiContrast: colors.red[50],
45
+ // loContrast: colors.red[200],
46
+ fill: '#AC4D39',
47
+ // fill: '#b54548',
48
+ stroke: '#853A2D',
49
+ // hiContrast: '#fef2f2',
50
+ // loContrast: '#fecaca',
51
+ // hiContrast: '#191111', // colors.gray[900],
52
+ // loContrast: '#3b1219' // colors.gray[800],
53
+ hiContrast: '#FBD3CB',
54
+ // hiContrast: '#f8fafc',
55
+ // loContrast: '#fdd8d8' // radix black red 12
56
+ loContrast: '#FF977D'
57
+ },
58
+ green: {
59
+ fill: '#428a4f',
60
+ stroke: '#2d5d39',
61
+ hiContrast: '#f8fafc',
62
+ loContrast: '#c2f0c2'
63
+ },
64
+ amber: {
65
+ fill: '#A35829',
66
+ stroke: '#7E451D',
67
+ hiContrast: '#FFE0C2',
68
+ loContrast: '#FFA057'
69
+ },
70
+ indigo: {
71
+ // fill: colors.indigo[500],
72
+ // stroke: colors.indigo[600],
73
+ // hiContrast: colors.indigo[50],
74
+ // loContrast: colors.indigo[200],
75
+ fill: '#6366f1',
76
+ stroke: '#4f46e5',
77
+ hiContrast: '#eef2ff',
78
+ loContrast: '#c7d2fe'
79
+ }
80
+ } as const satisfies ElementThemeColors
@@ -0,0 +1,12 @@
1
+ import type { LikeC4Theme } from '../types/theme'
2
+ import { ElementColors } from './element'
3
+ import { RelationshipColors } from './relationships'
4
+
5
+ export const defaultTheme = {
6
+ elements: ElementColors,
7
+ relationships: RelationshipColors,
8
+ font: 'Helvetica',
9
+ shadow: '#0a0a0a'
10
+ } satisfies LikeC4Theme
11
+
12
+ export { ElementColors, RelationshipColors }
@@ -0,0 +1,54 @@
1
+ import type { RelationshipThemeColors, RelationshipThemeColorValues } from '../types/theme'
2
+
3
+ const gray = {
4
+ lineColor: '#6E6E6E',
5
+ labelBgColor: '#18191b',
6
+ labelColor: '#C6C6C6'
7
+ } satisfies RelationshipThemeColorValues
8
+ const slate = {
9
+ lineColor: '#64748b', // 500
10
+ labelBgColor: '#0f172a', // 900
11
+ labelColor: '#cbd5e1' // 300
12
+ } satisfies RelationshipThemeColorValues
13
+
14
+ const blue = {
15
+ lineColor: '#3b82f6', // 500
16
+ labelBgColor: '#172554', // 950
17
+ labelColor: '#60a5fa' // 400
18
+ } satisfies RelationshipThemeColorValues
19
+
20
+ const sky = {
21
+ lineColor: '#0ea5e9', // 500
22
+ labelBgColor: '#082f49', // 950
23
+ labelColor: '#38bdf8' // 400
24
+ } satisfies RelationshipThemeColorValues
25
+
26
+ export const RelationshipColors = {
27
+ amber: {
28
+ lineColor: '#b45309',
29
+ labelBgColor: '#78350f',
30
+ labelColor: '#FFE0C2'
31
+ },
32
+ blue,
33
+ gray,
34
+ green: {
35
+ lineColor: '#15803d', // 700
36
+ labelBgColor: '#052e16', // 950
37
+ labelColor: '#22c55e' // 500
38
+ },
39
+ indigo: {
40
+ lineColor: '#6366f1', // 500
41
+ labelBgColor: '#1e1b4b', // 950
42
+ labelColor: '#818cf8' // 400
43
+ },
44
+ muted: slate,
45
+ primary: blue,
46
+ red: {
47
+ lineColor: '#AC4D39',
48
+ labelBgColor: '#b91c1c',
49
+ labelColor: '#FF977D'
50
+ },
51
+ secondary: sky,
52
+ sky,
53
+ slate
54
+ } satisfies RelationshipThemeColors
@@ -0,0 +1,88 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ BaseError,
4
+ InvalidArgError,
5
+ InvalidModelError,
6
+ invariant,
7
+ InvariantError,
8
+ NonExhaustiveError,
9
+ normalizeError,
10
+ NullableError,
11
+ RelationRefError,
12
+ UnknownError
13
+ } from './index'
14
+
15
+ describe('errors', () => {
16
+ describe('normalizeError', () => {
17
+ it.each([
18
+ { error: new BaseError('BaseError') },
19
+ { error: new RelationRefError('RelationRefError') },
20
+ { error: new UnknownError('UnknownError') },
21
+ { error: new InvariantError('InvariantError') },
22
+ { error: new NonExhaustiveError('NonExhaustiveError') },
23
+ { error: new InvalidModelError('InvalidModelError') },
24
+ { error: new NullableError('NullableError') },
25
+ { error: new InvalidArgError('InvalidArgError') }
26
+ ])('should return $error.name as-is', ({ error }) => {
27
+ expect(normalizeError(error)).toBe(error)
28
+ })
29
+
30
+ it.skip('should wrap an error', () => {
31
+ const cause = new Error('original test')
32
+ const error = normalizeError(cause)
33
+ expect(error).toBeInstanceOf(UnknownError)
34
+ expect(error).to.include({
35
+ name: 'UnknownError',
36
+ message: 'original test'
37
+ })
38
+ expect(error).to.have.property('cause').that.eq(cause)
39
+ expect(error)
40
+ .to.have.property('stack')
41
+ .that.is.a('string')
42
+ .and.includes('UnknownError: original test')
43
+ })
44
+
45
+ it('should wrap a string', () => {
46
+ const cause = 'error string'
47
+ const error = normalizeError(cause)
48
+ expect(error).toBeInstanceOf(UnknownError)
49
+ expect(error).to.include({
50
+ name: 'UnknownError',
51
+ message: cause
52
+ })
53
+ expect(error).not.to.have.property('cause')
54
+ expect(error).to.have.property('stack').that.is.a('string')
55
+ })
56
+
57
+ // Fails on serializiation of AST
58
+ it.skip('should wrap an object', () => {
59
+ const cause = { foo: 'bar' }
60
+ const error = normalizeError(cause)
61
+ expect(error).toBeInstanceOf(UnknownError)
62
+ expect(error).to.include({
63
+ name: 'UnknownError',
64
+ message: '{"foo":"bar"}'
65
+ })
66
+ expect(error).not.to.have.property('cause')
67
+ expect(error).to.have.property('stack').that.is.a('string')
68
+ })
69
+ })
70
+ })
71
+
72
+ describe('invariant', () => {
73
+ it('should throw an error if the condition fails', () => {
74
+ expect(() => invariant(false)).toThrow('Invariant failed')
75
+ })
76
+
77
+ it('should throw an error if the condition is null', () => {
78
+ expect(() => invariant(null)).toThrow(InvariantError)
79
+ })
80
+
81
+ it('should not throw an error if the condition passes', () => {
82
+ expect(() => invariant(true)).not.toThrow()
83
+ })
84
+
85
+ it('should throw an error with the provided message if the condition fails', () => {
86
+ expect(() => invariant(false, 'test')).toThrow('test')
87
+ })
88
+ })
@@ -0,0 +1,120 @@
1
+ import { CustomError } from 'ts-custom-error'
2
+ import { isString } from '../utils/guards'
3
+
4
+ export interface BaseErrorOptions {
5
+ cause?: unknown
6
+ }
7
+
8
+ /**
9
+ * Base class for all errors in the LikeC4 library.
10
+ */
11
+ export class BaseError extends CustomError {
12
+ constructor(message: string, options?: BaseErrorOptions) {
13
+ super(message, options)
14
+ // Set name explicitly as minification can mangle class names
15
+ Object.defineProperty(this, 'name', { value: 'BaseError' })
16
+ }
17
+ }
18
+
19
+ export class InvalidArgError extends BaseError {
20
+ constructor(message: string, options?: BaseErrorOptions) {
21
+ super(message, options)
22
+ // Set name explicitly as minification can mangle class names
23
+ Object.defineProperty(this, 'name', { value: 'InvalidArgError' })
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Unknown error, mosly probably a bug, unhandled case or coming from a third-party library.
29
+ */
30
+ export class UnknownError extends BaseError {
31
+ constructor(message: string, options?: BaseErrorOptions) {
32
+ super(message, options)
33
+ // Set name explicitly as minification can mangle class names
34
+ Object.defineProperty(this, 'name', { value: 'UnknownError' })
35
+ }
36
+ }
37
+
38
+ export class NullableError extends BaseError {
39
+ public constructor(message: string, options?: BaseErrorOptions) {
40
+ super(message, options)
41
+ // Set name explicitly as minification can mangle class names
42
+ Object.defineProperty(this, 'name', { value: 'NullableError' })
43
+ }
44
+ }
45
+
46
+ export class InvariantError extends BaseError {
47
+ public constructor(message: string, options?: BaseErrorOptions) {
48
+ super(message, options)
49
+ // Set name explicitly as minification can mangle class names
50
+ Object.defineProperty(this, 'name', { value: 'InvariantError' })
51
+ }
52
+ }
53
+
54
+ // Ensure that the value is NonNullable
55
+ // Mostly as safer `value!`
56
+ // eslint-disable-next-line @typescript-eslint/ban-types
57
+ export function nonNullable<T>(value: T, message?: string): NonNullable<T> {
58
+ if (typeof value === 'undefined' || value == null) {
59
+ throw new NullableError(message ?? `Expected defined value, but received ${value}`)
60
+ }
61
+ return value
62
+ }
63
+
64
+ export class RelationRefError extends BaseError {
65
+ public constructor(message: string, options?: BaseErrorOptions) {
66
+ super(message, options)
67
+ // Set name explicitly as minification can mangle class names
68
+ Object.defineProperty(this, 'name', { value: 'RelationRefError' })
69
+ }
70
+ }
71
+
72
+ export function normalizeError(e: unknown): Error {
73
+ if (e instanceof BaseError || e instanceof Error) {
74
+ return e
75
+ }
76
+ const message = isString(e) ? e : String(e)
77
+ const error = new UnknownError(message)
78
+ try {
79
+ // @ts-ignore
80
+ Error.captureStackTrace(error, normalizeError)
81
+ } catch {
82
+ // Ignore
83
+ }
84
+ return error
85
+ }
86
+
87
+ // Throw an error if the condition fails
88
+ // > Not providing an inline default argument for message as the result is smaller
89
+ export function invariant(
90
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
91
+ condition: any,
92
+ // Can provide a string, or a function that returns a string for cases where
93
+ // the message takes a fair amount of effort to compute
94
+ message?: string
95
+ ): asserts condition {
96
+ if (condition) {
97
+ return
98
+ }
99
+ throw new InvariantError(message ?? 'Invariant failed')
100
+ }
101
+
102
+ export class NonExhaustiveError extends BaseError {
103
+ public constructor(message: string, options?: BaseErrorOptions) {
104
+ super(message, options)
105
+ // Set name explicitly as minification can mangle class names
106
+ Object.defineProperty(this, 'name', { value: 'NonExhaustiveError' })
107
+ }
108
+ }
109
+
110
+ export function nonexhaustive(value: never): never {
111
+ throw new NonExhaustiveError(`NonExhaustive value: ${value}`)
112
+ }
113
+
114
+ export class InvalidModelError extends BaseError {
115
+ public constructor(message: string, options?: BaseErrorOptions) {
116
+ super(message, options)
117
+ // Set name explicitly as minification can mangle class names
118
+ Object.defineProperty(this, 'name', { value: 'InvalidModelError' })
119
+ }
120
+ }
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ export * from './colors'
2
+ export * from './errors'
3
+ export * from './types'
4
+ export * from './utils'
5
+
6
+ // Type aliases 1
7
+ export type * as likec4 from './types'
8
+ // Type aliases 2
9
+ export type * as c4 from './types'
package/src/reset.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ // Do not add any other lines of code to this file!
2
+ import '@total-typescript/ts-reset'
@@ -0,0 +1,5 @@
1
+ import type { Opaque } from './opaque'
2
+
3
+ export type NonEmptyArray<T> = [T, ...T[]] | [...T[], T]
4
+
5
+ export type IconUrl = Opaque<string, 'IconUrl'>
@@ -0,0 +1,56 @@
1
+ import type { IconUrl, NonEmptyArray } from './_common'
2
+ import type { Opaque } from './opaque'
3
+ import type { ThemeColor } from './theme'
4
+
5
+ // Full-qualified-name
6
+ export type Fqn = Opaque<string, 'Fqn'>
7
+
8
+ export function AsFqn(name: string, parent?: Fqn | null) {
9
+ return (parent ? parent + '.' + name : name) as Fqn
10
+ }
11
+
12
+ export const BorderStyles = ['solid', 'dashed', 'dotted', 'none'] as const
13
+
14
+ export type BorderStyle = typeof BorderStyles[number]
15
+
16
+ export type ElementKind = Opaque<string, 'ElementKind'>
17
+ export const ElementShapes = [
18
+ 'rectangle',
19
+ 'person',
20
+ 'browser',
21
+ 'mobile',
22
+ 'cylinder',
23
+ 'storage',
24
+ 'queue'
25
+ ] as const
26
+
27
+ export type ElementShape = typeof ElementShapes[number]
28
+ export const DefaultThemeColor = 'primary' satisfies ThemeColor
29
+ export const DefaultElementShape = 'rectangle' satisfies ElementShape
30
+
31
+ export interface ElementStyle {
32
+ border?: BorderStyle
33
+ // 0-100
34
+ opacity?: number
35
+ }
36
+
37
+ export type Tag = Opaque<string, 'Tag'>
38
+
39
+ export interface TagSpec {
40
+ readonly id: Tag
41
+ readonly style: ElementStyle
42
+ }
43
+
44
+ export interface Element {
45
+ readonly id: Fqn
46
+ readonly kind: ElementKind
47
+ readonly title: string
48
+ readonly description: string | null
49
+ readonly technology: string | null
50
+ readonly tags: NonEmptyArray<Tag> | null
51
+ readonly links: NonEmptyArray<string> | null
52
+ readonly icon?: IconUrl
53
+ readonly shape?: ElementShape
54
+ readonly color?: ThemeColor
55
+ readonly style?: ElementStyle
56
+ }