@likec4/core 1.8.1 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +4467 -0
- package/dist/index.d.cts +793 -0
- package/dist/index.d.mts +793 -0
- package/dist/index.d.ts +793 -7
- package/dist/index.mjs +4390 -0
- package/dist/shared/core.CpR2fq5B.d.cts +833 -0
- package/dist/shared/core.CpR2fq5B.d.mts +833 -0
- package/dist/shared/core.CpR2fq5B.d.ts +833 -0
- package/dist/shared/core.Db3ntVII.cjs +318 -0
- package/dist/shared/core.DeifT5lC.mjs +263 -0
- package/dist/types/index.cjs +55 -0
- package/dist/types/index.d.cts +1 -0
- package/dist/types/index.d.mts +1 -0
- package/dist/types/index.d.ts +1 -14
- package/dist/types/index.mjs +1 -0
- package/package.json +43 -33
- package/src/colors/index.ts +27 -1
- package/src/errors/index.ts +3 -95
- package/src/index.ts +6 -6
- package/src/model/LikeC4Model.ts +480 -0
- package/src/model/LikeC4ViewModel.ts +318 -0
- package/src/model/__test__/LikeC4Model.spec.ts +79 -0
- package/src/model/__test__/fixture.ts +333 -0
- package/src/model/index.ts +2 -0
- package/src/model/types.ts +10 -0
- package/src/types/_common.ts +4 -2
- package/src/types/element.ts +11 -8
- package/src/types/expression.ts +7 -3
- package/src/types/index.ts +0 -1
- package/src/types/model.ts +16 -6
- package/src/types/relation.ts +18 -5
- package/src/types/theme.ts +28 -12
- package/src/types/view-notation.ts +3 -4
- package/src/types/view.ts +51 -16
- package/src/utils/compare-natural.spec.ts +37 -0
- package/src/utils/compare-natural.ts +14 -0
- package/src/utils/fqn.spec.ts +99 -2
- package/src/utils/fqn.ts +45 -5
- package/src/utils/index.ts +1 -0
- package/src/utils/relations.spec.ts +2 -4
- package/src/utils/relations.ts +14 -24
- package/dist/colors/element.d.ts +0 -69
- package/dist/colors/element.js +0 -75
- package/dist/colors/index.d.ts +0 -133
- package/dist/colors/index.js +0 -9
- package/dist/colors/relationships.d.ts +0 -58
- package/dist/colors/relationships.js +0 -49
- package/dist/errors/errors.spec.d.ts +0 -2
- package/dist/errors/errors.spec.js +0 -69
- package/dist/errors/index.d.ts +0 -39
- package/dist/errors/index.js +0 -104
- package/dist/index.js +0 -4
- package/dist/types/_common.d.ts +0 -9
- package/dist/types/_common.js +0 -1
- package/dist/types/element.d.ts +0 -55
- package/dist/types/element.js +0 -15
- package/dist/types/expression.d.ts +0 -119
- package/dist/types/expression.js +0 -67
- package/dist/types/index.js +0 -14
- package/dist/types/model.d.ts +0 -14
- package/dist/types/model.js +0 -1
- package/dist/types/opaque.d.ts +0 -103
- package/dist/types/opaque.js +0 -1
- package/dist/types/operators.d.ts +0 -44
- package/dist/types/operators.js +0 -59
- package/dist/types/overview-graph.d.ts +0 -41
- package/dist/types/overview-graph.js +0 -1
- package/dist/types/relation.d.ts +0 -30
- package/dist/types/relation.js +0 -3
- package/dist/types/theme.d.ts +0 -27
- package/dist/types/theme.js +0 -1
- package/dist/types/view-changes.d.ts +0 -26
- package/dist/types/view-changes.js +0 -1
- package/dist/types/view-notation.d.ts +0 -9
- package/dist/types/view-notation.js +0 -1
- package/dist/types/view.d.ts +0 -243
- package/dist/types/view.js +0 -50
- package/dist/utils/fqn.d.ts +0 -39
- package/dist/utils/fqn.js +0 -102
- package/dist/utils/fqn.spec.d.ts +0 -2
- package/dist/utils/fqn.spec.js +0 -82
- package/dist/utils/guards.d.ts +0 -5
- package/dist/utils/guards.js +0 -7
- package/dist/utils/index.d.ts +0 -5
- package/dist/utils/index.js +0 -4
- package/dist/utils/promises.d.ts +0 -2
- package/dist/utils/promises.js +0 -8
- package/dist/utils/relations.d.ts +0 -30
- package/dist/utils/relations.js +0 -79
- package/dist/utils/relations.spec.d.ts +0 -2
- package/dist/utils/relations.spec.js +0 -210
- package/src/errors/errors.spec.ts +0 -88
- package/src/reset.d.ts +0 -2
- package/src/types/opaque.ts +0 -108
|
@@ -1,210 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,88 +0,0 @@
|
|
|
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
|
-
})
|
package/src/reset.d.ts
DELETED
package/src/types/opaque.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
declare const tag: unique symbol
|
|
2
|
-
|
|
3
|
-
type Tagged<Token> = {
|
|
4
|
-
readonly [tag]: Token
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
Create an opaque type, which hides its internal details from the public, and can only be created by being used explicitly.
|
|
9
|
-
|
|
10
|
-
The generic type parameter can be anything. It doesn't have to be an object.
|
|
11
|
-
|
|
12
|
-
[Read more about opaque types.](https://codemix.com/opaque-types-in-javascript/)
|
|
13
|
-
|
|
14
|
-
There have been several discussions about adding this feature to TypeScript via the `opaque type` operator, similar to how Flow does it. Unfortunately, nothing has (yet) moved forward:
|
|
15
|
-
- [Microsoft/TypeScript#202](https://github.com/microsoft/TypeScript/issues/202)
|
|
16
|
-
- [Microsoft/TypeScript#15408](https://github.com/Microsoft/TypeScript/issues/15408)
|
|
17
|
-
- [Microsoft/TypeScript#15807](https://github.com/Microsoft/TypeScript/issues/15807)
|
|
18
|
-
|
|
19
|
-
@example
|
|
20
|
-
```
|
|
21
|
-
import type {Opaque} from 'type-fest';
|
|
22
|
-
|
|
23
|
-
type AccountNumber = Opaque<number, 'AccountNumber'>;
|
|
24
|
-
type AccountBalance = Opaque<number, 'AccountBalance'>;
|
|
25
|
-
|
|
26
|
-
// The `Token` parameter allows the compiler to differentiate between types, whereas "unknown" will not. For example, consider the following structures:
|
|
27
|
-
type ThingOne = Opaque<string>;
|
|
28
|
-
type ThingTwo = Opaque<string>;
|
|
29
|
-
|
|
30
|
-
// To the compiler, these types are allowed to be cast to each other as they have the same underlying type. They are both `string & { __opaque__: unknown }`.
|
|
31
|
-
// To avoid this behaviour, you would instead pass the "Token" parameter, like so.
|
|
32
|
-
type NewThingOne = Opaque<string, 'ThingOne'>;
|
|
33
|
-
type NewThingTwo = Opaque<string, 'ThingTwo'>;
|
|
34
|
-
|
|
35
|
-
// Now they're completely separate types, so the following will fail to compile.
|
|
36
|
-
function createNewThingOne (): NewThingOne {
|
|
37
|
-
// As you can see, casting from a string is still allowed. However, you may not cast NewThingOne to NewThingTwo, and vice versa.
|
|
38
|
-
return 'new thing one' as NewThingOne;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// This will fail to compile, as they are fundamentally different types.
|
|
42
|
-
const thingTwo = createNewThingOne() as NewThingTwo;
|
|
43
|
-
|
|
44
|
-
// Here's another example of opaque typing.
|
|
45
|
-
function createAccountNumber(): AccountNumber {
|
|
46
|
-
return 2 as AccountNumber;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function getMoneyForAccount(accountNumber: AccountNumber): AccountBalance {
|
|
50
|
-
return 4 as AccountBalance;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// This will compile successfully.
|
|
54
|
-
getMoneyForAccount(createAccountNumber());
|
|
55
|
-
|
|
56
|
-
// But this won't, because it has to be explicitly passed as an `AccountNumber` type.
|
|
57
|
-
getMoneyForAccount(2);
|
|
58
|
-
|
|
59
|
-
// You can use opaque values like they aren't opaque too.
|
|
60
|
-
const accountNumber = createAccountNumber();
|
|
61
|
-
|
|
62
|
-
// This will not compile successfully.
|
|
63
|
-
const newAccountNumber = accountNumber + 2;
|
|
64
|
-
|
|
65
|
-
// As a side note, you can (and should) use recursive types for your opaque types to make them stronger and hopefully easier to type.
|
|
66
|
-
type Person = {
|
|
67
|
-
id: Opaque<number, Person>;
|
|
68
|
-
name: string;
|
|
69
|
-
};
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
@category Type
|
|
73
|
-
*/
|
|
74
|
-
export type Opaque<Type, Token = unknown> = Type & Tagged<Token>
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
Revert an opaque type back to its original type by removing the readonly `[tag]`.
|
|
78
|
-
|
|
79
|
-
Why is this necessary?
|
|
80
|
-
|
|
81
|
-
1. Use an `Opaque` type as object keys
|
|
82
|
-
2. Prevent TS4058 error: "Return type of exported function has or is using name X from external module Y but cannot be named"
|
|
83
|
-
|
|
84
|
-
@example
|
|
85
|
-
```
|
|
86
|
-
import type {Opaque, UnwrapOpaque} from 'type-fest';
|
|
87
|
-
|
|
88
|
-
type AccountType = Opaque<'SAVINGS' | 'CHECKING', 'AccountType'>;
|
|
89
|
-
|
|
90
|
-
const moneyByAccountType: Record<UnwrapOpaque<AccountType>, number> = {
|
|
91
|
-
SAVINGS: 99,
|
|
92
|
-
CHECKING: 0.1
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
// Without UnwrapOpaque, the following expression would throw a type error.
|
|
96
|
-
const money = moneyByAccountType.SAVINGS; // TS error: Property 'SAVINGS' does not exist
|
|
97
|
-
|
|
98
|
-
// Attempting to pass an non-Opaque type to UnwrapOpaque will raise a type error.
|
|
99
|
-
type WontWork = UnwrapOpaque<string>;
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
@category Type
|
|
103
|
-
*/
|
|
104
|
-
export type UnwrapOpaque<OpaqueType extends Tagged<unknown>> = OpaqueType extends Opaque<
|
|
105
|
-
infer Type,
|
|
106
|
-
OpaqueType[typeof tag]
|
|
107
|
-
> ? Type
|
|
108
|
-
: OpaqueType
|