type-fest 4.25.0 → 4.26.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.
package/index.d.ts CHANGED
@@ -37,7 +37,7 @@ export type {ReadonlyDeep} from './source/readonly-deep';
37
37
  export type {LiteralUnion} from './source/literal-union';
38
38
  export type {Promisable} from './source/promisable';
39
39
  export type {Arrayable} from './source/arrayable';
40
- export type {Opaque, UnwrapOpaque, Tagged, GetTagMetadata, UnwrapTagged} from './source/opaque';
40
+ export type {Opaque, UnwrapOpaque, Tagged, GetTagMetadata, UnwrapTagged} from './source/tagged';
41
41
  export type {InvariantOf} from './source/invariant-of';
42
42
  export type {SetOptional} from './source/set-optional';
43
43
  export type {SetReadonly} from './source/set-readonly';
@@ -104,6 +104,7 @@ export type {Spread} from './source/spread';
104
104
  export type {IsInteger} from './source/is-integer';
105
105
  export type {IsFloat} from './source/is-float';
106
106
  export type {TupleToUnion} from './source/tuple-to-union';
107
+ export type {UnionToTuple} from './source/union-to-tuple';
107
108
  export type {IntRange} from './source/int-range';
108
109
  export type {IsEqual} from './source/is-equal';
109
110
  export type {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "type-fest",
3
- "version": "4.25.0",
3
+ "version": "4.26.1",
4
4
  "description": "A collection of essential TypeScript types",
5
5
  "license": "(MIT OR CC0-1.0)",
6
6
  "repository": "sindresorhus/type-fest",
package/readme.md CHANGED
@@ -151,8 +151,8 @@ Click the type names for complete docs.
151
151
  - [`UndefinedOnPartialDeep`](source/undefined-on-partial-deep.d.ts) - Create a deep version of another type where all optional keys are set to also accept `undefined`.
152
152
  - [`ReadonlyDeep`](source/readonly-deep.d.ts) - Create a deeply immutable version of an `object`/`Map`/`Set`/`Array` type. Use [`Readonly<T>`](https://www.typescriptlang.org/docs/handbook/utility-types.html#readonlytype) if you only need one level deep.
153
153
  - [`LiteralUnion`](source/literal-union.d.ts) - Create a union type by combining primitive types and literal types without sacrificing auto-completion in IDEs for the literal type part of the union. Workaround for [Microsoft/TypeScript#29729](https://github.com/Microsoft/TypeScript/issues/29729).
154
- - [`Tagged`](source/opaque.d.ts) - Create a [tagged type](https://medium.com/@KevinBGreene/surviving-the-typescript-ecosystem-branding-and-type-tagging-6cf6e516523d) that can support [multiple tags](https://github.com/sindresorhus/type-fest/issues/665) and [per-tag metadata](https://medium.com/@ethanresnick/advanced-typescript-tagged-types-improved-with-type-level-metadata-5072fc125fcf). (This replaces the previous [`Opaque`](source/opaque.d.ts) type, which is now deprecated.)
155
- - [`UnwrapTagged`](source/opaque.d.ts) - Get the untagged portion of a tagged type created with `Tagged`. (This replaces the previous [`UnwrapOpaque`](source/opaque.d.ts) type, which is now deprecated.)
154
+ - [`Tagged`](source/tagged.d.ts) - Create a [tagged type](https://medium.com/@KevinBGreene/surviving-the-typescript-ecosystem-branding-and-type-tagging-6cf6e516523d) that can support [multiple tags](https://github.com/sindresorhus/type-fest/issues/665) and [per-tag metadata](https://medium.com/@ethanresnick/advanced-typescript-tagged-types-improved-with-type-level-metadata-5072fc125fcf). (This replaces the previous [`Opaque`](source/tagged.d.ts) type, which is now deprecated.)
155
+ - [`UnwrapTagged`](source/tagged.d.ts) - Get the untagged portion of a tagged type created with `Tagged`. (This replaces the previous [`UnwrapOpaque`](source/tagged.d.ts) type, which is now deprecated.)
156
156
  - [`InvariantOf`](source/invariant-of.d.ts) - Create an [invariant type](https://basarat.gitbook.io/typescript/type-system/type-compatibility#footnote-invariance), which is a type that does not accept supertypes and subtypes.
157
157
  - [`SetOptional`](source/set-optional.d.ts) - Create a type that makes the given keys optional.
158
158
  - [`SetReadonly`](source/set-readonly.d.ts) - Create a type that makes the given keys readonly.
@@ -283,6 +283,7 @@ type ShouldBeNever = IfAny<'not any', 'not never', 'never'>;
283
283
  - [`MultidimensionalReadonlyArray`](source/multidimensional-readonly-array.d.ts) - Create a type that represents a multidimensional readonly array of the given type and dimensions.
284
284
  - [`ReadonlyTuple`](source/readonly-tuple.d.ts) - Create a type that represents a read-only tuple of the given type and length.
285
285
  - [`TupleToUnion`](source/tuple-to-union.d.ts) - Convert a tuple/array into a union type of its elements.
286
+ - [`UnionToTuple`](source/union-to-tuple.d.ts) - Convert a union type into an unordered tuple type of its elements.
286
287
 
287
288
  ### Numeric
288
289
 
@@ -356,8 +357,8 @@ type ShouldBeNever = IfAny<'not any', 'not never', 'never'>;
356
357
  - `RequireOnlyOne`, `OneOf` - See [`RequireExactlyOne`](source/require-exactly-one.d.ts)
357
358
  - `AtMostOne` - See [`RequireOneOrNone`](source/require-one-or-none.d.ts)
358
359
  - `AllKeys` - See [`KeysOfUnion`](source/keys-of-union.d.ts)
359
- - `Branded` - See [`Tagged`](source/opaque.d.ts)
360
- - `Opaque` - See [`Tagged`](source/opaque.d.ts)
360
+ - `Branded` - See [`Tagged`](source/tagged.d.ts)
361
+ - `Opaque` - See [`Tagged`](source/tagged.d.ts)
361
362
  - `SetElement` - See [`IterableElement`](source/iterable-element.d.ts)
362
363
  - `SetEntry` - See [`IterableElement`](source/iterable-element.d.ts)
363
364
  - `SetValues` - See [`IterableElement`](source/iterable-element.d.ts)
@@ -391,6 +392,53 @@ type ShouldBeNever = IfAny<'not any', 'not never', 'never'>;
391
392
 
392
393
  There are many advanced types most users don't know about.
393
394
 
395
+
396
+ - [`Awaited<T>`](https://www.typescriptlang.org/docs/handbook/utility-types.html#awaitedtype) - Extract the type of a value that a `Promise` resolves to.
397
+ <details>
398
+ <summary>
399
+ Example
400
+ </summary>
401
+
402
+ [Playground](https://www.typescriptlang.org/play/?#code/JYOwLgpgTgZghgYwgAgKoGdrIN4FgBQyyAkMACYBcyIArgLYBG0A3AUcSHHRFemFKADmrQiTiCe1ekygiiAXwJtkCADZx06NJigBBAA7AAytABuwJDmXENATxAJkMCGAQALDNAAUNHQElKKUZoAEoqAAUoAHs6YEwAHk8oAD4rUWJiAHpM5AAxF3dkMDcUXywyODA4J2i6IpLkCqqGDQgAOmssnIAVBsQwGjhVZGA6fVUIbnBK4CiQZFjBNzBkVSiogGtV4A2UYriKTuyVOb5kKAh0fVOUAF5kOAB3OGAV51c3LwAiTLhDTLKUEyABJsICAvIQnISF0TiAzk1qvcLlcbm0AFboOZeKFHHIXAZQeaI6EZAk0Ik4EaBACMABpqFxJF8AFJRNzzAAiUQgXwZ4kkAGYAAzIeSkxSiSXKMC2fQofIfCBkJLIe66Z6vZXxABKLgpIG6cogiR0BmMZgsEAA2l93u4kl8ALrJZIiZR2BxOGgOMCzeZuOAgMgTJKcypwLx-C1QcxIKhJc0mWNWhngwK0YJQEJpdj8Wy5mEIU4rQFURXuZWq+5PF4raPJuPte0eHQ+fxkXHpWG6GCQKBOApuITIQGNCMM2xRGgqIPIeWwKJQOqmOACadafr+rToGiFDSj-RNEfFUo6EbgaDwJB0vGz9wnhqImpRb2Es8QBlLhZwDYjuBkGQrz+kMyC6OEfjnBAACONCXGAm5aCAEDKsqHTpPIs4fMgXjQNE2aFhkxx4d+gbBqoQjWJKChKKIxbwqWZqGI2VpqtQECPNo0BJpaSA4tCZEhhAYYRu23HMbxn7IDSUJAA)
403
+
404
+ ```ts
405
+ interface User {
406
+ id: number;
407
+ name: string;
408
+ age: number;
409
+ }
410
+
411
+ class UserApiService {
412
+ async fetchUser(userId: number): Promise<User> {
413
+ // Fetch the user data from the database.
414
+ // The actual implementation might look like this:
415
+ // const response = await fetch('/api/user/${userId}');
416
+ // const data = response.json();
417
+ // return data;
418
+ return {
419
+ id: 1,
420
+ name: 'John Doe',
421
+ age: 30
422
+ };
423
+ }
424
+ }
425
+
426
+ type FetchedUser = Awaited<ReturnType<UserApiService['fetchUser']>>;
427
+
428
+ async function handleUserData(apiService: UserApiService, userId: number) {
429
+ try {
430
+ const user: FetchedUser = await apiService.fetchUser(userId);
431
+ // After fetching user data, you can perform various actions such as updating the user interface,
432
+ // caching the data for future use, or making additional API requests as needed.
433
+ } catch (error) {
434
+ // Error handling
435
+ }
436
+ }
437
+
438
+ const userApiService = new UserApiService();
439
+ handleUserData(userApiService, 1);
440
+ ```
441
+
394
442
  - [`Partial<T>`](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype) - Make all properties in `T` optional.
395
443
  <details>
396
444
  <summary>
package/source/exact.d.ts CHANGED
@@ -53,13 +53,14 @@ onlyAcceptNameImproved(invalidInput); // Compilation error
53
53
  @category Utilities
54
54
  */
55
55
  export type Exact<ParameterType, InputType> =
56
- // If the parameter is a primitive, return it as is immediately to avoid it being converted to a complex type
57
- ParameterType extends Primitive ? ParameterType
58
- // If the parameter is an unknown, return it as is immediately to avoid it being converted to a complex type
59
- : IsUnknown<ParameterType> extends true ? unknown
60
- // If the parameter is a Function, return it as is because this type is not capable of handling function, leave it to TypeScript
61
- : ParameterType extends Function ? ParameterType
62
- : IsEqual<ParameterType, InputType> extends true ? ParameterType
56
+ // Before distributing, check if the two types are equal and if so, return the parameter type immediately
57
+ IsEqual<ParameterType, InputType> extends true ? ParameterType
58
+ // If the parameter is a primitive, return it as is immediately to avoid it being converted to a complex type
59
+ : ParameterType extends Primitive ? ParameterType
60
+ // If the parameter is an unknown, return it as is immediately to avoid it being converted to a complex type
61
+ : IsUnknown<ParameterType> extends true ? unknown
62
+ // If the parameter is a Function, return it as is because this type is not capable of handling function, leave it to TypeScript
63
+ : ParameterType extends Function ? ParameterType
63
64
  // Convert union of array to array of union: A[] & B[] => (A & B)[]
64
65
  : ParameterType extends unknown[] ? Array<Exact<ArrayElement<ParameterType>, ArrayElement<InputType>>>
65
66
  // In TypeScript, Array is a subtype of ReadonlyArray, so always test Array before ReadonlyArray.
package/source/get.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- import type {StringDigit} from './internal';
1
+ import type {StringDigit, ToString} from './internal';
2
+ import type {LiteralStringUnion} from './literal-union';
3
+ import type {Paths} from './paths';
2
4
  import type {Split} from './split';
3
5
  import type {StringKeyOf} from './string-key-of';
4
6
 
@@ -16,7 +18,7 @@ type GetOptions = {
16
18
  /**
17
19
  Like the `Get` type but receives an array of strings as a path parameter.
18
20
  */
19
- type GetWithPath<BaseType, Keys extends readonly string[], Options extends GetOptions = {}> =
21
+ type GetWithPath<BaseType, Keys, Options extends GetOptions = {}> =
20
22
  Keys extends readonly []
21
23
  ? BaseType
22
24
  : Keys extends readonly [infer Head, ...infer Tail]
@@ -187,5 +189,10 @@ Get<Record<string, string>, 'foo', {strict: true}> // => string
187
189
  @category Array
188
190
  @category Template literal
189
191
  */
190
- export type Get<BaseType, Path extends string | readonly string[], Options extends GetOptions = {}> =
191
- GetWithPath<BaseType, Path extends string ? ToPath<Path> : Path, Options>;
192
+ export type Get<
193
+ BaseType,
194
+ Path extends
195
+ | readonly string[]
196
+ | LiteralStringUnion<ToString<Paths<BaseType, {bracketNotation: false}> | Paths<BaseType, {bracketNotation: true}>>>,
197
+ Options extends GetOptions = {}> =
198
+ GetWithPath<BaseType, Path extends string ? ToPath<Path> : Path, Options>;
@@ -1,5 +1,7 @@
1
1
  import type {Primitive} from './primitive';
2
2
 
3
+ export type LiteralStringUnion<T> = LiteralUnion<T, string>;
4
+
3
5
  /**
4
6
  Allows creating a union type by combining primitive types and literal types without sacrificing auto-completion in IDEs for the literal type part of the union.
5
7
 
@@ -1,252 +1 @@
1
- declare const tag: unique symbol;
2
-
3
- export type TagContainer<Token> = {
4
- readonly [tag]: Token;
5
- };
6
-
7
- type Tag<Token extends PropertyKey, TagMetadata> = TagContainer<{[K in Token]: TagMetadata}>;
8
-
9
- /**
10
- Attach a "tag" to an arbitrary type. This allows you to create distinct types, that aren't assignable to one another, for runtime values that would otherwise have the same type. (See examples.)
11
-
12
- The generic type parameters can be anything.
13
-
14
- Note that `Opaque` is somewhat of a misnomer here, in that, unlike [some alternative implementations](https://github.com/microsoft/TypeScript/issues/4895#issuecomment-425132582), the original, untagged type is not actually hidden. (E.g., functions that accept the untagged type can still be called with the "opaque" version -- but not vice-versa.)
15
-
16
- Also note that this implementation is limited to a single tag. If you want to allow multiple tags, use `Tagged` instead.
17
-
18
- [Read more about tagged types.](https://medium.com/@KevinBGreene/surviving-the-typescript-ecosystem-branding-and-type-tagging-6cf6e516523d)
19
-
20
- There have been several discussions about adding similar features to TypeScript. Unfortunately, nothing has (yet) moved forward:
21
- - [Microsoft/TypeScript#202](https://github.com/microsoft/TypeScript/issues/202)
22
- - [Microsoft/TypeScript#15408](https://github.com/Microsoft/TypeScript/issues/15408)
23
- - [Microsoft/TypeScript#15807](https://github.com/Microsoft/TypeScript/issues/15807)
24
-
25
- @example
26
- ```
27
- import type {Opaque} from 'type-fest';
28
-
29
- type AccountNumber = Opaque<number, 'AccountNumber'>;
30
- type AccountBalance = Opaque<number, 'AccountBalance'>;
31
-
32
- // The `Token` parameter allows the compiler to differentiate between types, whereas "unknown" will not. For example, consider the following structures:
33
- type ThingOne = Opaque<string>;
34
- type ThingTwo = Opaque<string>;
35
-
36
- // 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 }`.
37
- // To avoid this behaviour, you would instead pass the "Token" parameter, like so.
38
- type NewThingOne = Opaque<string, 'ThingOne'>;
39
- type NewThingTwo = Opaque<string, 'ThingTwo'>;
40
-
41
- // Now they're completely separate types, so the following will fail to compile.
42
- function createNewThingOne (): NewThingOne {
43
- // As you can see, casting from a string is still allowed. However, you may not cast NewThingOne to NewThingTwo, and vice versa.
44
- return 'new thing one' as NewThingOne;
45
- }
46
-
47
- // This will fail to compile, as they are fundamentally different types.
48
- const thingTwo = createNewThingOne() as NewThingTwo;
49
-
50
- // Here's another example of opaque typing.
51
- function createAccountNumber(): AccountNumber {
52
- return 2 as AccountNumber;
53
- }
54
-
55
- function getMoneyForAccount(accountNumber: AccountNumber): AccountBalance {
56
- return 4 as AccountBalance;
57
- }
58
-
59
- // This will compile successfully.
60
- getMoneyForAccount(createAccountNumber());
61
-
62
- // But this won't, because it has to be explicitly passed as an `AccountNumber` type.
63
- getMoneyForAccount(2);
64
-
65
- // You can use opaque values like they aren't opaque too.
66
- const accountNumber = createAccountNumber();
67
-
68
- // This will compile successfully.
69
- const newAccountNumber = accountNumber + 2;
70
-
71
- // As a side note, you can (and should) use recursive types for your opaque types to make them stronger and hopefully easier to type.
72
- type Person = {
73
- id: Opaque<number, Person>;
74
- name: string;
75
- };
76
- ```
77
-
78
- @category Type
79
- @deprecated Use {@link Tagged} instead
80
- */
81
- export type Opaque<Type, Token = unknown> = Type & TagContainer<Token>;
82
-
83
- /**
84
- Revert an opaque or tagged type back to its original type by removing the readonly `[tag]`.
85
-
86
- Why is this necessary?
87
-
88
- 1. Use an `Opaque` type as object keys
89
- 2. Prevent TS4058 error: "Return type of exported function has or is using name X from external module Y but cannot be named"
90
-
91
- @example
92
- ```
93
- import type {Opaque, UnwrapOpaque} from 'type-fest';
94
-
95
- type AccountType = Opaque<'SAVINGS' | 'CHECKING', 'AccountType'>;
96
-
97
- const moneyByAccountType: Record<UnwrapOpaque<AccountType>, number> = {
98
- SAVINGS: 99,
99
- CHECKING: 0.1
100
- };
101
-
102
- // Without UnwrapOpaque, the following expression would throw a type error.
103
- const money = moneyByAccountType.SAVINGS; // TS error: Property 'SAVINGS' does not exist
104
-
105
- // Attempting to pass an non-Opaque type to UnwrapOpaque will raise a type error.
106
- type WontWork = UnwrapOpaque<string>;
107
-
108
- // Using a Tagged type will work too.
109
- type WillWork = UnwrapOpaque<Tagged<number, 'AccountNumber'>>; // number
110
- ```
111
-
112
- @category Type
113
- @deprecated Use {@link UnwrapTagged} instead
114
- */
115
- export type UnwrapOpaque<OpaqueType extends TagContainer<unknown>> =
116
- OpaqueType extends Tag<PropertyKey, any>
117
- ? RemoveAllTags<OpaqueType>
118
- : OpaqueType extends Opaque<infer Type, OpaqueType[typeof tag]>
119
- ? Type
120
- : OpaqueType;
121
-
122
- /**
123
- Attach a "tag" to an arbitrary type. This allows you to create distinct types, that aren't assignable to one another, for distinct concepts in your program that should not be interchangeable, even if their runtime values have the same type. (See examples.)
124
-
125
- A type returned by `Tagged` can be passed to `Tagged` again, to create a type with multiple tags.
126
-
127
- [Read more about tagged types.](https://medium.com/@KevinBGreene/surviving-the-typescript-ecosystem-branding-and-type-tagging-6cf6e516523d)
128
-
129
- A tag's name is usually a string (and must be a string, number, or symbol), but each application of a tag can also contain an arbitrary type as its "metadata". See {@link GetTagMetadata} for examples and explanation.
130
-
131
- A type `A` returned by `Tagged` is assignable to another type `B` returned by `Tagged` if and only if:
132
- - the underlying (untagged) type of `A` is assignable to the underlying type of `B`;
133
- - `A` contains at least all the tags `B` has;
134
- - and the metadata type for each of `A`'s tags is assignable to the metadata type of `B`'s corresponding tag.
135
-
136
- There have been several discussions about adding similar features to TypeScript. Unfortunately, nothing has (yet) moved forward:
137
- - [Microsoft/TypeScript#202](https://github.com/microsoft/TypeScript/issues/202)
138
- - [Microsoft/TypeScript#4895](https://github.com/microsoft/TypeScript/issues/4895)
139
- - [Microsoft/TypeScript#33290](https://github.com/microsoft/TypeScript/pull/33290)
140
-
141
- @example
142
- ```
143
- import type {Tagged} from 'type-fest';
144
-
145
- type AccountNumber = Tagged<number, 'AccountNumber'>;
146
- type AccountBalance = Tagged<number, 'AccountBalance'>;
147
-
148
- function createAccountNumber(): AccountNumber {
149
- // As you can see, casting from a `number` (the underlying type being tagged) is allowed.
150
- return 2 as AccountNumber;
151
- }
152
-
153
- function getMoneyForAccount(accountNumber: AccountNumber): AccountBalance {
154
- return 4 as AccountBalance;
155
- }
156
-
157
- // This will compile successfully.
158
- getMoneyForAccount(createAccountNumber());
159
-
160
- // But this won't, because it has to be explicitly passed as an `AccountNumber` type!
161
- // Critically, you could not accidentally use an `AccountBalance` as an `AccountNumber`.
162
- getMoneyForAccount(2);
163
-
164
- // You can also use tagged values like their underlying, untagged type.
165
- // I.e., this will compile successfully because an `AccountNumber` can be used as a regular `number`.
166
- // In this sense, the underlying base type is not hidden, which differentiates tagged types from opaque types in other languages.
167
- const accountNumber = createAccountNumber() + 2;
168
- ```
169
-
170
- @example
171
- ```
172
- import type {Tagged} from 'type-fest';
173
-
174
- // You can apply multiple tags to a type by using `Tagged` repeatedly.
175
- type Url = Tagged<string, 'URL'>;
176
- type SpecialCacheKey = Tagged<Url, 'SpecialCacheKey'>;
177
-
178
- // You can also pass a union of tag names, so this is equivalent to the above, although it doesn't give you the ability to assign distinct metadata to each tag.
179
- type SpecialCacheKey2 = Tagged<string, 'URL' | 'SpecialCacheKey'>;
180
- ```
181
-
182
- @category Type
183
- */
184
- export type Tagged<Type, TagName extends PropertyKey, TagMetadata = never> = Type & Tag<TagName, TagMetadata>;
185
-
186
- /**
187
- Given a type and a tag name, returns the metadata associated with that tag on that type.
188
-
189
- In the example below, one could use `Tagged<string, 'JSON'>` to represent "a string that is valid JSON". That type might be useful -- for instance, it communicates that the value can be safely passed to `JSON.parse` without it throwing an exception. However, it doesn't indicate what type of value will be produced on parse (which is sometimes known). `JsonOf<T>` solves this; it represents "a string that is valid JSON and that, if parsed, would produce a value of type T". The type T is held in the metadata associated with the `'JSON'` tag.
190
-
191
- This article explains more about [how tag metadata works and when it can be useful](https://medium.com/@ethanresnick/advanced-typescript-tagged-types-improved-with-type-level-metadata-5072fc125fcf).
192
-
193
- @example
194
- ```
195
- import type {Tagged} from 'type-fest';
196
-
197
- type JsonOf<T> = Tagged<string, 'JSON', T>;
198
-
199
- function stringify<T>(it: T) {
200
- return JSON.stringify(it) as JsonOf<T>;
201
- }
202
-
203
- function parse<T extends JsonOf<unknown>>(it: T) {
204
- return JSON.parse(it) as GetTagMetadata<T, 'JSON'>;
205
- }
206
-
207
- const x = stringify({ hello: 'world' });
208
- const parsed = parse(x); // The type of `parsed` is { hello: string }
209
- ```
210
-
211
- @category Type
212
- */
213
- export type GetTagMetadata<Type extends Tag<TagName, unknown>, TagName extends PropertyKey> = Type[typeof tag][TagName];
214
-
215
- /**
216
- Revert a tagged type back to its original type by removing all tags.
217
-
218
- Why is this necessary?
219
-
220
- 1. Use a `Tagged` type as object keys
221
- 2. Prevent TS4058 error: "Return type of exported function has or is using name X from external module Y but cannot be named"
222
-
223
- @example
224
- ```
225
- import type {Tagged, UnwrapTagged} from 'type-fest';
226
-
227
- type AccountType = Tagged<'SAVINGS' | 'CHECKING', 'AccountType'>;
228
-
229
- const moneyByAccountType: Record<UnwrapTagged<AccountType>, number> = {
230
- SAVINGS: 99,
231
- CHECKING: 0.1
232
- };
233
-
234
- // Without UnwrapTagged, the following expression would throw a type error.
235
- const money = moneyByAccountType.SAVINGS; // TS error: Property 'SAVINGS' does not exist
236
-
237
- // Attempting to pass an non-Tagged type to UnwrapTagged will raise a type error.
238
- type WontWork = UnwrapTagged<string>;
239
- ```
240
-
241
- @category Type
242
- */
243
- export type UnwrapTagged<TaggedType extends Tag<PropertyKey, any>> =
244
- RemoveAllTags<TaggedType>;
245
-
246
- type RemoveAllTags<T> = T extends Tag<PropertyKey, any>
247
- ? {
248
- [ThisTag in keyof T[typeof tag]]: T extends Tagged<infer Type, ThisTag, T[typeof tag][ThisTag]>
249
- ? RemoveAllTags<Type>
250
- : never
251
- }[keyof T[typeof tag]]
252
- : T;
1
+ export * from './tagged';
@@ -0,0 +1,256 @@
1
+ declare const tag: unique symbol;
2
+
3
+ export type TagContainer<Token> = {
4
+ readonly [tag]: Token;
5
+ };
6
+
7
+ type Tag<Token extends PropertyKey, TagMetadata> = TagContainer<{[K in Token]: TagMetadata}>;
8
+
9
+ /**
10
+ Attach a "tag" to an arbitrary type. This allows you to create distinct types, that aren't assignable to one another, for distinct concepts in your program that should not be interchangeable, even if their runtime values have the same type. (See examples.)
11
+
12
+ A type returned by `Tagged` can be passed to `Tagged` again, to create a type with multiple tags.
13
+
14
+ [Read more about tagged types.](https://medium.com/@KevinBGreene/surviving-the-typescript-ecosystem-branding-and-type-tagging-6cf6e516523d)
15
+
16
+ A tag's name is usually a string (and must be a string, number, or symbol), but each application of a tag can also contain an arbitrary type as its "metadata". See {@link GetTagMetadata} for examples and explanation.
17
+
18
+ A type `A` returned by `Tagged` is assignable to another type `B` returned by `Tagged` if and only if:
19
+ - the underlying (untagged) type of `A` is assignable to the underlying type of `B`;
20
+ - `A` contains at least all the tags `B` has;
21
+ - and the metadata type for each of `A`'s tags is assignable to the metadata type of `B`'s corresponding tag.
22
+
23
+ There have been several discussions about adding similar features to TypeScript. Unfortunately, nothing has (yet) moved forward:
24
+ - [Microsoft/TypeScript#202](https://github.com/microsoft/TypeScript/issues/202)
25
+ - [Microsoft/TypeScript#4895](https://github.com/microsoft/TypeScript/issues/4895)
26
+ - [Microsoft/TypeScript#33290](https://github.com/microsoft/TypeScript/pull/33290)
27
+
28
+ @example
29
+ ```
30
+ import type {Tagged} from 'type-fest';
31
+
32
+ type AccountNumber = Tagged<number, 'AccountNumber'>;
33
+ type AccountBalance = Tagged<number, 'AccountBalance'>;
34
+
35
+ function createAccountNumber(): AccountNumber {
36
+ // As you can see, casting from a `number` (the underlying type being tagged) is allowed.
37
+ return 2 as AccountNumber;
38
+ }
39
+
40
+ function getMoneyForAccount(accountNumber: AccountNumber): AccountBalance {
41
+ return 4 as AccountBalance;
42
+ }
43
+
44
+ // This will compile successfully.
45
+ getMoneyForAccount(createAccountNumber());
46
+
47
+ // But this won't, because it has to be explicitly passed as an `AccountNumber` type!
48
+ // Critically, you could not accidentally use an `AccountBalance` as an `AccountNumber`.
49
+ getMoneyForAccount(2);
50
+
51
+ // You can also use tagged values like their underlying, untagged type.
52
+ // I.e., this will compile successfully because an `AccountNumber` can be used as a regular `number`.
53
+ // In this sense, the underlying base type is not hidden, which differentiates tagged types from opaque types in other languages.
54
+ const accountNumber = createAccountNumber() + 2;
55
+ ```
56
+
57
+ @example
58
+ ```
59
+ import type {Tagged} from 'type-fest';
60
+
61
+ // You can apply multiple tags to a type by using `Tagged` repeatedly.
62
+ type Url = Tagged<string, 'URL'>;
63
+ type SpecialCacheKey = Tagged<Url, 'SpecialCacheKey'>;
64
+
65
+ // You can also pass a union of tag names, so this is equivalent to the above, although it doesn't give you the ability to assign distinct metadata to each tag.
66
+ type SpecialCacheKey2 = Tagged<string, 'URL' | 'SpecialCacheKey'>;
67
+ ```
68
+
69
+ @category Type
70
+ */
71
+ export type Tagged<Type, TagName extends PropertyKey, TagMetadata = never> = Type & Tag<TagName, TagMetadata>;
72
+
73
+ /**
74
+ Given a type and a tag name, returns the metadata associated with that tag on that type.
75
+
76
+ In the example below, one could use `Tagged<string, 'JSON'>` to represent "a string that is valid JSON". That type might be useful -- for instance, it communicates that the value can be safely passed to `JSON.parse` without it throwing an exception. However, it doesn't indicate what type of value will be produced on parse (which is sometimes known). `JsonOf<T>` solves this; it represents "a string that is valid JSON and that, if parsed, would produce a value of type T". The type T is held in the metadata associated with the `'JSON'` tag.
77
+
78
+ This article explains more about [how tag metadata works and when it can be useful](https://medium.com/@ethanresnick/advanced-typescript-tagged-types-improved-with-type-level-metadata-5072fc125fcf).
79
+
80
+ @example
81
+ ```
82
+ import type {Tagged} from 'type-fest';
83
+
84
+ type JsonOf<T> = Tagged<string, 'JSON', T>;
85
+
86
+ function stringify<T>(it: T) {
87
+ return JSON.stringify(it) as JsonOf<T>;
88
+ }
89
+
90
+ function parse<T extends JsonOf<unknown>>(it: T) {
91
+ return JSON.parse(it) as GetTagMetadata<T, 'JSON'>;
92
+ }
93
+
94
+ const x = stringify({ hello: 'world' });
95
+ const parsed = parse(x); // The type of `parsed` is { hello: string }
96
+ ```
97
+
98
+ @category Type
99
+ */
100
+ export type GetTagMetadata<Type extends Tag<TagName, unknown>, TagName extends PropertyKey> = Type[typeof tag][TagName];
101
+
102
+ /**
103
+ Revert a tagged type back to its original type by removing all tags.
104
+
105
+ Why is this necessary?
106
+
107
+ 1. Use a `Tagged` type as object keys
108
+ 2. Prevent TS4058 error: "Return type of exported function has or is using name X from external module Y but cannot be named"
109
+
110
+ @example
111
+ ```
112
+ import type {Tagged, UnwrapTagged} from 'type-fest';
113
+
114
+ type AccountType = Tagged<'SAVINGS' | 'CHECKING', 'AccountType'>;
115
+
116
+ const moneyByAccountType: Record<UnwrapTagged<AccountType>, number> = {
117
+ SAVINGS: 99,
118
+ CHECKING: 0.1
119
+ };
120
+
121
+ // Without UnwrapTagged, the following expression would throw a type error.
122
+ const money = moneyByAccountType.SAVINGS; // TS error: Property 'SAVINGS' does not exist
123
+
124
+ // Attempting to pass an non-Tagged type to UnwrapTagged will raise a type error.
125
+ type WontWork = UnwrapTagged<string>;
126
+ ```
127
+
128
+ @category Type
129
+ */
130
+ export type UnwrapTagged<TaggedType extends Tag<PropertyKey, any>> =
131
+ RemoveAllTags<TaggedType>;
132
+
133
+ type RemoveAllTags<T> = T extends Tag<PropertyKey, any>
134
+ ? {
135
+ [ThisTag in keyof T[typeof tag]]: T extends Tagged<infer Type, ThisTag, T[typeof tag][ThisTag]>
136
+ ? RemoveAllTags<Type>
137
+ : never
138
+ }[keyof T[typeof tag]]
139
+ : T;
140
+
141
+ /**
142
+ Note: The `Opaque` type is deprecated in favor of `Tagged`.
143
+
144
+ Attach a "tag" to an arbitrary type. This allows you to create distinct types, that aren't assignable to one another, for runtime values that would otherwise have the same type. (See examples.)
145
+
146
+ The generic type parameters can be anything.
147
+
148
+ Note that `Opaque` is somewhat of a misnomer here, in that, unlike [some alternative implementations](https://github.com/microsoft/TypeScript/issues/4895#issuecomment-425132582), the original, untagged type is not actually hidden. (E.g., functions that accept the untagged type can still be called with the "opaque" version -- but not vice-versa.)
149
+
150
+ Also note that this implementation is limited to a single tag. If you want to allow multiple tags, use `Tagged` instead.
151
+
152
+ [Read more about tagged types.](https://medium.com/@KevinBGreene/surviving-the-typescript-ecosystem-branding-and-type-tagging-6cf6e516523d)
153
+
154
+ There have been several discussions about adding similar features to TypeScript. Unfortunately, nothing has (yet) moved forward:
155
+ - [Microsoft/TypeScript#202](https://github.com/microsoft/TypeScript/issues/202)
156
+ - [Microsoft/TypeScript#15408](https://github.com/Microsoft/TypeScript/issues/15408)
157
+ - [Microsoft/TypeScript#15807](https://github.com/Microsoft/TypeScript/issues/15807)
158
+
159
+ @example
160
+ ```
161
+ import type {Opaque} from 'type-fest';
162
+
163
+ type AccountNumber = Opaque<number, 'AccountNumber'>;
164
+ type AccountBalance = Opaque<number, 'AccountBalance'>;
165
+
166
+ // The `Token` parameter allows the compiler to differentiate between types, whereas "unknown" will not. For example, consider the following structures:
167
+ type ThingOne = Opaque<string>;
168
+ type ThingTwo = Opaque<string>;
169
+
170
+ // 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 }`.
171
+ // To avoid this behaviour, you would instead pass the "Token" parameter, like so.
172
+ type NewThingOne = Opaque<string, 'ThingOne'>;
173
+ type NewThingTwo = Opaque<string, 'ThingTwo'>;
174
+
175
+ // Now they're completely separate types, so the following will fail to compile.
176
+ function createNewThingOne (): NewThingOne {
177
+ // As you can see, casting from a string is still allowed. However, you may not cast NewThingOne to NewThingTwo, and vice versa.
178
+ return 'new thing one' as NewThingOne;
179
+ }
180
+
181
+ // This will fail to compile, as they are fundamentally different types.
182
+ const thingTwo = createNewThingOne() as NewThingTwo;
183
+
184
+ // Here's another example of opaque typing.
185
+ function createAccountNumber(): AccountNumber {
186
+ return 2 as AccountNumber;
187
+ }
188
+
189
+ function getMoneyForAccount(accountNumber: AccountNumber): AccountBalance {
190
+ return 4 as AccountBalance;
191
+ }
192
+
193
+ // This will compile successfully.
194
+ getMoneyForAccount(createAccountNumber());
195
+
196
+ // But this won't, because it has to be explicitly passed as an `AccountNumber` type.
197
+ getMoneyForAccount(2);
198
+
199
+ // You can use opaque values like they aren't opaque too.
200
+ const accountNumber = createAccountNumber();
201
+
202
+ // This will compile successfully.
203
+ const newAccountNumber = accountNumber + 2;
204
+
205
+ // As a side note, you can (and should) use recursive types for your opaque types to make them stronger and hopefully easier to type.
206
+ type Person = {
207
+ id: Opaque<number, Person>;
208
+ name: string;
209
+ };
210
+ ```
211
+
212
+ @category Type
213
+ @deprecated Use {@link Tagged} instead
214
+ */
215
+ export type Opaque<Type, Token = unknown> = Type & TagContainer<Token>;
216
+
217
+ /**
218
+ Note: The `UnwrapOpaque` type is deprecated in favor of `UnwrapTagged`.
219
+
220
+ Revert an opaque or tagged type back to its original type by removing the readonly `[tag]`.
221
+
222
+ Why is this necessary?
223
+
224
+ 1. Use an `Opaque` type as object keys
225
+ 2. Prevent TS4058 error: "Return type of exported function has or is using name X from external module Y but cannot be named"
226
+
227
+ @example
228
+ ```
229
+ import type {Opaque, UnwrapOpaque} from 'type-fest';
230
+
231
+ type AccountType = Opaque<'SAVINGS' | 'CHECKING', 'AccountType'>;
232
+
233
+ const moneyByAccountType: Record<UnwrapOpaque<AccountType>, number> = {
234
+ SAVINGS: 99,
235
+ CHECKING: 0.1
236
+ };
237
+
238
+ // Without UnwrapOpaque, the following expression would throw a type error.
239
+ const money = moneyByAccountType.SAVINGS; // TS error: Property 'SAVINGS' does not exist
240
+
241
+ // Attempting to pass an non-Opaque type to UnwrapOpaque will raise a type error.
242
+ type WontWork = UnwrapOpaque<string>;
243
+
244
+ // Using a Tagged type will work too.
245
+ type WillWork = UnwrapOpaque<Tagged<number, 'AccountNumber'>>; // number
246
+ ```
247
+
248
+ @category Type
249
+ @deprecated Use {@link UnwrapTagged} instead
250
+ */
251
+ export type UnwrapOpaque<OpaqueType extends TagContainer<unknown>> =
252
+ OpaqueType extends Tag<PropertyKey, any>
253
+ ? RemoveAllTags<OpaqueType>
254
+ : OpaqueType extends Opaque<infer Type, OpaqueType[typeof tag]>
255
+ ? Type
256
+ : OpaqueType;
@@ -0,0 +1,54 @@
1
+ import type {IsNever} from './is-never';
2
+ import type {UnionToIntersection} from './union-to-intersection';
3
+
4
+ /**
5
+ Returns the last element of a union type.
6
+
7
+ @example
8
+ ```
9
+ type Last = LastOfUnion<1 | 2 | 3>;
10
+ //=> 3
11
+ ```
12
+ */
13
+ type LastOfUnion<T> =
14
+ UnionToIntersection<T extends any ? () => T : never> extends () => (infer R)
15
+ ? R
16
+ : never;
17
+
18
+ /**
19
+ Convert a union type into an unordered tuple type of its elements.
20
+
21
+ This can be useful when you have objects with a finite set of keys and want a type defining only the allowed keys, but do not want to repeat yourself.
22
+
23
+ @example
24
+ ```
25
+ import type {UnionToTuple} from 'type-fest';
26
+
27
+ type Numbers = 1 | 2 | 3;
28
+ type NumbersTuple = UnionToTuple<Numbers>;
29
+ //=> [1, 2, 3]
30
+ ```
31
+
32
+ @example
33
+ ```
34
+ import type {UnionToTuple} from 'type-fest';
35
+
36
+ const pets = {
37
+ dog: '🐶',
38
+ cat: '🐱',
39
+ snake: '🐍',
40
+ };
41
+
42
+ type Pet = keyof typeof pets;
43
+ //=> 'dog' | 'cat' | 'snake'
44
+
45
+ const petList = Object.keys(pets) as UnionToTuple<Pet>;
46
+ //=> ['dog', 'cat', 'snake']
47
+ ```
48
+
49
+ @category Array
50
+ */
51
+ export type UnionToTuple<T, L = LastOfUnion<T>> =
52
+ IsNever<T> extends false
53
+ ? [...UnionToTuple<Exclude<T, L>>, L]
54
+ : [];