type-fest 4.10.2 → 4.11.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/index.d.ts +3 -1
- package/package.json +1 -1
- package/readme.md +2 -0
- package/source/array-splice.d.ts +95 -0
- package/source/internal.d.ts +106 -0
- package/source/omit-deep.d.ts +136 -0
- package/source/opaque.d.ts +61 -16
- package/source/partial-on-undefined-deep.d.ts +4 -3
- package/source/shared-union-fields-deep.d.ts +1 -28
package/index.d.ts
CHANGED
|
@@ -26,12 +26,13 @@ export type {PickIndexSignature} from './source/pick-index-signature';
|
|
|
26
26
|
export type {PartialDeep, PartialDeepOptions} from './source/partial-deep';
|
|
27
27
|
export type {RequiredDeep} from './source/required-deep';
|
|
28
28
|
export type {PickDeep} from './source/pick-deep';
|
|
29
|
+
export type {OmitDeep} from './source/omit-deep';
|
|
29
30
|
export type {PartialOnUndefinedDeep, PartialOnUndefinedDeepOptions} from './source/partial-on-undefined-deep';
|
|
30
31
|
export type {UndefinedOnPartialDeep} from './source/undefined-on-partial-deep';
|
|
31
32
|
export type {ReadonlyDeep} from './source/readonly-deep';
|
|
32
33
|
export type {LiteralUnion} from './source/literal-union';
|
|
33
34
|
export type {Promisable} from './source/promisable';
|
|
34
|
-
export type {Opaque, UnwrapOpaque, Tagged, UnwrapTagged} from './source/opaque';
|
|
35
|
+
export type {Opaque, UnwrapOpaque, Tagged, GetTagMetadata, UnwrapTagged} from './source/opaque';
|
|
35
36
|
export type {InvariantOf} from './source/invariant-of';
|
|
36
37
|
export type {SetOptional} from './source/set-optional';
|
|
37
38
|
export type {SetReadonly} from './source/set-readonly';
|
|
@@ -103,6 +104,7 @@ export type {IsUnknown} from './source/is-unknown';
|
|
|
103
104
|
export type {IfUnknown} from './source/if-unknown';
|
|
104
105
|
export type {ArrayIndices} from './source/array-indices';
|
|
105
106
|
export type {ArrayValues} from './source/array-values';
|
|
107
|
+
export type {ArraySplice} from './source/array-splice';
|
|
106
108
|
export type {SetFieldType} from './source/set-field-type';
|
|
107
109
|
export type {Paths} from './source/paths';
|
|
108
110
|
export type {SharedUnionFieldsDeep} from './source/shared-union-fields-deep';
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -127,6 +127,7 @@ Click the type names for complete docs.
|
|
|
127
127
|
- [`RequireOneOrNone`](source/require-one-or-none.d.ts) - Create a type that requires exactly a single key of the given keys and disallows more, or none of the given keys.
|
|
128
128
|
- [`RequiredDeep`](source/required-deep.d.ts) - Create a deeply required version of another type. Use [`Required<T>`](https://www.typescriptlang.org/docs/handbook/utility-types.html#requiredtype) if you only need one level deep.
|
|
129
129
|
- [`PickDeep`](source/pick-deep.d.ts) - Pick properties from a deeply-nested object. Use [`Pick<T>`](https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-keys) if you only need one level deep.
|
|
130
|
+
- [`OmitDeep`](source/omit-deep.d.ts) - Omit properties from a deeply-nested object. Use [`Omit<T>`](https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys) if you only need one level deep.
|
|
130
131
|
- [`OmitIndexSignature`](source/omit-index-signature.d.ts) - Omit any index signatures from the given object type, leaving only explicitly defined properties.
|
|
131
132
|
- [`PickIndexSignature`](source/pick-index-signature.d.ts) - Pick only index signatures from the given object type, leaving out all explicitly defined properties.
|
|
132
133
|
- [`PartialDeep`](source/partial-deep.d.ts) - Create a deeply optional version of another type. Use [`Partial<T>`](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype) if you only need one level deep.
|
|
@@ -177,6 +178,7 @@ Click the type names for complete docs.
|
|
|
177
178
|
- [`IntRange`](source/int-range.d.ts) - Generate a union of numbers.
|
|
178
179
|
- [`ArrayIndices`](source/array-indices.d.ts) - Provides valid indices for a constant array or tuple.
|
|
179
180
|
- [`ArrayValues`](source/array-values.d.ts) - Provides all values for a constant array or tuple.
|
|
181
|
+
- [`ArraySplice`](source/array-splice.d.ts) - Creates a new array type by adding or removing elements at a specified index range in the original array.
|
|
180
182
|
- [`SetFieldType`](source/set-field-type.d.ts) - Create a type that changes the type of the given keys.
|
|
181
183
|
- [`Paths`](source/paths.d.ts) - Generate a union of all possible paths to properties in the given object.
|
|
182
184
|
- [`SharedUnionFieldsDeep`](source/shared-union-fields-deep.d.ts) - Create a type with shared fields from a union of object types, deeply traversing nested structures.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type {BuildTuple, Subtract, StaticPartOfArray, VariablePartOfArray, GTE} from './internal';
|
|
2
|
+
import type {UnknownArray} from './unknown-array';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
The implementation of `SplitArrayByIndex` for fixed length arrays.
|
|
6
|
+
*/
|
|
7
|
+
type SplitFixedArrayByIndex<T extends UnknownArray, SplitIndex extends number> =
|
|
8
|
+
SplitIndex extends 0
|
|
9
|
+
? [[], T]
|
|
10
|
+
: T extends readonly [...BuildTuple<SplitIndex>, ...infer V]
|
|
11
|
+
? T extends readonly [...infer U, ...V]
|
|
12
|
+
? [U, V]
|
|
13
|
+
: [never, never]
|
|
14
|
+
: [never, never];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
The implementation of `SplitArrayByIndex` for variable length arrays.
|
|
18
|
+
*/
|
|
19
|
+
type SplitVariableArrayByIndex<T extends UnknownArray,
|
|
20
|
+
SplitIndex extends number,
|
|
21
|
+
T1 = Subtract<SplitIndex, StaticPartOfArray<T>['length']>,
|
|
22
|
+
T2 = T1 extends number ? BuildTuple<T1, VariablePartOfArray<T>[number]> : [],
|
|
23
|
+
> =
|
|
24
|
+
SplitIndex extends 0
|
|
25
|
+
? [[], T]
|
|
26
|
+
: GTE<StaticPartOfArray<T>['length'], SplitIndex> extends true
|
|
27
|
+
? [
|
|
28
|
+
SplitFixedArrayByIndex<StaticPartOfArray<T>, SplitIndex>[0],
|
|
29
|
+
[
|
|
30
|
+
...SplitFixedArrayByIndex<StaticPartOfArray<T>, SplitIndex>[1],
|
|
31
|
+
...VariablePartOfArray<T>,
|
|
32
|
+
],
|
|
33
|
+
]
|
|
34
|
+
: [
|
|
35
|
+
[
|
|
36
|
+
...StaticPartOfArray<T>,
|
|
37
|
+
...(T2 extends UnknownArray ? T2 : []),
|
|
38
|
+
],
|
|
39
|
+
VariablePartOfArray<T>,
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
Split the given array `T` by the given `SplitIndex`.
|
|
44
|
+
|
|
45
|
+
@example
|
|
46
|
+
```
|
|
47
|
+
type A = SplitArrayByIndex<[1, 2, 3, 4], 2>;
|
|
48
|
+
// type A = [[1, 2], [3, 4]];
|
|
49
|
+
|
|
50
|
+
type B = SplitArrayByIndex<[1, 2, 3, 4], 0>;
|
|
51
|
+
// type B = [[], [1, 2, 3, 4]];
|
|
52
|
+
```
|
|
53
|
+
*/
|
|
54
|
+
type SplitArrayByIndex<T extends UnknownArray, SplitIndex extends number> =
|
|
55
|
+
SplitIndex extends 0
|
|
56
|
+
? [[], T]
|
|
57
|
+
: number extends T['length']
|
|
58
|
+
? SplitVariableArrayByIndex<T, SplitIndex>
|
|
59
|
+
: SplitFixedArrayByIndex<T, SplitIndex>;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
Creates a new array type by adding or removing elements at a specified index range in the original array.
|
|
63
|
+
|
|
64
|
+
Use-case: Replace or insert items in an array type.
|
|
65
|
+
|
|
66
|
+
Like [`Array#splice()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) but for types.
|
|
67
|
+
|
|
68
|
+
@example
|
|
69
|
+
```
|
|
70
|
+
type SomeMonths0 = ['January', 'April', 'June'];
|
|
71
|
+
type Mouths0 = ArraySplice<SomeMonths0, 1, 0, ['Feb', 'March']>;
|
|
72
|
+
//=> type Mouths0 = ['January', 'Feb', 'March', 'April', 'June'];
|
|
73
|
+
|
|
74
|
+
type SomeMonths1 = ['January', 'April', 'June'];
|
|
75
|
+
type Mouths1 = ArraySplice<SomeMonths1, 1, 1>;
|
|
76
|
+
//=> type Mouths1 = ['January', 'June'];
|
|
77
|
+
|
|
78
|
+
type SomeMonths2 = ['January', 'Foo', 'April'];
|
|
79
|
+
type Mouths2 = ArraySplice<SomeMonths2, 1, 1, ['Feb', 'March']>;
|
|
80
|
+
//=> type Mouths2 = ['January', 'Feb', 'March', 'April'];
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
@category Array
|
|
84
|
+
*/
|
|
85
|
+
export type ArraySplice<
|
|
86
|
+
T extends UnknownArray,
|
|
87
|
+
Start extends number,
|
|
88
|
+
DeleteCount extends number,
|
|
89
|
+
Items extends UnknownArray = [],
|
|
90
|
+
> =
|
|
91
|
+
SplitArrayByIndex<T, Start> extends [infer U extends UnknownArray, infer V extends UnknownArray]
|
|
92
|
+
? SplitArrayByIndex<V, DeleteCount> extends [infer _Deleted extends UnknownArray, infer X extends UnknownArray]
|
|
93
|
+
? [...U, ...Items, ...X]
|
|
94
|
+
: never // Should never happen
|
|
95
|
+
: never; // Should never happen
|
package/source/internal.d.ts
CHANGED
|
@@ -2,9 +2,11 @@ import type {Primitive} from './primitive';
|
|
|
2
2
|
import type {Simplify} from './simplify';
|
|
3
3
|
import type {Trim} from './trim';
|
|
4
4
|
import type {IsAny} from './is-any';
|
|
5
|
+
import type {IsLiteral} from './is-literal';
|
|
5
6
|
import type {UnknownRecord} from './unknown-record';
|
|
6
7
|
import type {IsNever} from './is-never';
|
|
7
8
|
import type {UnknownArray} from './unknown-array';
|
|
9
|
+
import type {IsEqual} from './is-equal';
|
|
8
10
|
|
|
9
11
|
// TODO: Remove for v5.
|
|
10
12
|
export type {UnknownRecord} from './unknown-record';
|
|
@@ -357,6 +359,11 @@ IsPrimitive<Object>
|
|
|
357
359
|
*/
|
|
358
360
|
export type IsPrimitive<T> = [T] extends [Primitive] ? true : false;
|
|
359
361
|
|
|
362
|
+
/**
|
|
363
|
+
Utility type to retrieve only literal keys from type.
|
|
364
|
+
*/
|
|
365
|
+
export type LiteralKeyOf<T> = keyof {[K in keyof T as IsLiteral<K> extends true ? K : never]-?: never};
|
|
366
|
+
|
|
360
367
|
/**
|
|
361
368
|
Returns the static, fixed-length portion of the given array, excluding variable-length parts.
|
|
362
369
|
|
|
@@ -471,3 +478,102 @@ type InternalIsUnion<T, U = T> =
|
|
|
471
478
|
? boolean extends Result ? true
|
|
472
479
|
: Result
|
|
473
480
|
: never; // Should never happen
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
Set the given array to readonly if `IsReadonly` is `true`, otherwise set the given array to normal, then return the result.
|
|
484
|
+
|
|
485
|
+
@example
|
|
486
|
+
```
|
|
487
|
+
type ReadonlyArray = readonly string[];
|
|
488
|
+
type NormalArray = string[];
|
|
489
|
+
|
|
490
|
+
type ReadonlyResult = SetArrayAccess<NormalArray, true>;
|
|
491
|
+
//=> readonly string[]
|
|
492
|
+
|
|
493
|
+
type NormalResult = SetArrayAccess<ReadonlyArray, false>;
|
|
494
|
+
//=> string[]
|
|
495
|
+
```
|
|
496
|
+
*/
|
|
497
|
+
export type SetArrayAccess<T extends UnknownArray, IsReadonly extends boolean> =
|
|
498
|
+
T extends readonly [...infer U] ?
|
|
499
|
+
IsReadonly extends true
|
|
500
|
+
? readonly [...U]
|
|
501
|
+
: [...U]
|
|
502
|
+
: T;
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
Returns whether the given array `T` is readonly.
|
|
506
|
+
*/
|
|
507
|
+
export type IsArrayReadonly<T extends UnknownArray> = T extends unknown[] ? false : true;
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
Returns the result of `A >= B`.
|
|
511
|
+
|
|
512
|
+
@example
|
|
513
|
+
```
|
|
514
|
+
type A = GTE<15, 10>;
|
|
515
|
+
//=> true
|
|
516
|
+
|
|
517
|
+
type B = GTE<10, 15>;
|
|
518
|
+
//=> false
|
|
519
|
+
|
|
520
|
+
type C = GTE<10, 10>;
|
|
521
|
+
//=> true
|
|
522
|
+
```
|
|
523
|
+
*/
|
|
524
|
+
export type GTE<A extends number, B extends number> =
|
|
525
|
+
BuildTuple<A> extends [...infer _, ...BuildTuple<B>]
|
|
526
|
+
? true
|
|
527
|
+
: false;
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
Returns the result of `A > B`
|
|
531
|
+
|
|
532
|
+
@example
|
|
533
|
+
```
|
|
534
|
+
type A = GT<15, 10>;
|
|
535
|
+
//=> true
|
|
536
|
+
|
|
537
|
+
type B = GT<10, 15>;
|
|
538
|
+
//=> false
|
|
539
|
+
*/
|
|
540
|
+
export type GT<A extends number, B extends number> =
|
|
541
|
+
IsEqual<A, B> extends true
|
|
542
|
+
? false
|
|
543
|
+
: GTE<A, B>;
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
Get the exact version of the given `Key` in the given object `T`.
|
|
547
|
+
|
|
548
|
+
Use-case: You known that a number key (e.g. 10) is in an object, but you don't know how it is defined in the object, as a string or as a number (e.g. 10 or '10'). You can use this type to get the exact version of the key. See the example.
|
|
549
|
+
|
|
550
|
+
@example
|
|
551
|
+
```
|
|
552
|
+
type Object = {
|
|
553
|
+
0: number;
|
|
554
|
+
'1': string;
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
type Key1 = ExactKey<Object, '0'>;
|
|
558
|
+
//=> 0
|
|
559
|
+
type Key2 = ExactKey<Object, 0>;
|
|
560
|
+
//=> 0
|
|
561
|
+
|
|
562
|
+
type Key3 = ExactKey<Object, '1'>;
|
|
563
|
+
//=> '1'
|
|
564
|
+
type Key4 = ExactKey<Object, 1>;
|
|
565
|
+
//=> '1'
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
@category Object
|
|
569
|
+
*/
|
|
570
|
+
export type ExactKey<T extends object, Key extends PropertyKey> =
|
|
571
|
+
Key extends keyof T
|
|
572
|
+
? Key
|
|
573
|
+
: ToString<Key> extends keyof T
|
|
574
|
+
? ToString<Key>
|
|
575
|
+
: Key extends `${infer NumberKey extends number}`
|
|
576
|
+
? NumberKey extends keyof T
|
|
577
|
+
? NumberKey
|
|
578
|
+
: never
|
|
579
|
+
: never;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type {ArraySplice} from './array-splice';
|
|
2
|
+
import type {ExactKey, IsArrayReadonly, NonRecursiveType, SetArrayAccess, ToString} from './internal';
|
|
3
|
+
import type {IsEqual} from './is-equal';
|
|
4
|
+
import type {SimplifyDeep} from './merge-deep';
|
|
5
|
+
import type {Paths} from './paths';
|
|
6
|
+
import type {SharedUnionFieldsDeep} from './shared-union-fields-deep';
|
|
7
|
+
import type {UnknownArray} from './unknown-array';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
Omit properties from a deeply-nested object.
|
|
11
|
+
|
|
12
|
+
It supports recursing into arrays.
|
|
13
|
+
|
|
14
|
+
It supports removing specific items from an array, replacing each removed item with unknown at the specified index.
|
|
15
|
+
|
|
16
|
+
Use-case: Remove unneeded parts of complex objects.
|
|
17
|
+
|
|
18
|
+
Use [`Omit`](https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys) if you only need one level deep.
|
|
19
|
+
|
|
20
|
+
@example
|
|
21
|
+
```
|
|
22
|
+
import type {OmitDeep} from 'type-fest';
|
|
23
|
+
|
|
24
|
+
type Info = {
|
|
25
|
+
userInfo: {
|
|
26
|
+
name: string;
|
|
27
|
+
uselessInfo: {
|
|
28
|
+
foo: string;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type UsefulInfo = OmitDeep<Info, 'userInfo.uselessInfo'>;
|
|
34
|
+
// type UsefulInfo = {
|
|
35
|
+
// userInfo: {
|
|
36
|
+
// name: string;
|
|
37
|
+
// };
|
|
38
|
+
|
|
39
|
+
// Supports array
|
|
40
|
+
type A = OmitDeep<[1, 'foo', 2], 1>;
|
|
41
|
+
// type A = [1, unknown, 2];
|
|
42
|
+
|
|
43
|
+
// Supports recursing into array
|
|
44
|
+
|
|
45
|
+
type Info1 = {
|
|
46
|
+
address: [
|
|
47
|
+
{
|
|
48
|
+
street: string
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
street2: string,
|
|
52
|
+
foo: string
|
|
53
|
+
};
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
type AddressInfo = OmitDeep<Info1, 'address.1.foo'>;
|
|
57
|
+
// type AddressInfo = {
|
|
58
|
+
// address: [
|
|
59
|
+
// {
|
|
60
|
+
// street: string;
|
|
61
|
+
// },
|
|
62
|
+
// {
|
|
63
|
+
// street2: string;
|
|
64
|
+
// };
|
|
65
|
+
// ];
|
|
66
|
+
// };
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
@category Object
|
|
70
|
+
@category Array
|
|
71
|
+
*/
|
|
72
|
+
export type OmitDeep<T, PathUnion extends Paths<T>> =
|
|
73
|
+
SimplifyDeep<
|
|
74
|
+
SharedUnionFieldsDeep<
|
|
75
|
+
{[P in PathUnion]: OmitDeepWithOnePath<T, P>}[PathUnion]
|
|
76
|
+
>
|
|
77
|
+
>;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
Omit one path from the given object/array.
|
|
81
|
+
*/
|
|
82
|
+
type OmitDeepWithOnePath<T, Path extends string | number> =
|
|
83
|
+
T extends NonRecursiveType
|
|
84
|
+
? T
|
|
85
|
+
: T extends UnknownArray ? SetArrayAccess<OmitDeepArrayWithOnePath<T, Path>, IsArrayReadonly<T>>
|
|
86
|
+
: T extends object ? OmitDeepObjectWithOnePath<T, Path>
|
|
87
|
+
: T;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
Omit one path from the given object.
|
|
91
|
+
*/
|
|
92
|
+
type OmitDeepObjectWithOnePath<ObjectT extends object, P extends string | number> =
|
|
93
|
+
P extends `${infer RecordKeyInPath}.${infer SubPath}`
|
|
94
|
+
? {
|
|
95
|
+
[Key in keyof ObjectT]:
|
|
96
|
+
IsEqual<RecordKeyInPath, ToString<Key>> extends true
|
|
97
|
+
? ExactKey<ObjectT, Key> extends infer RealKey
|
|
98
|
+
? RealKey extends keyof ObjectT
|
|
99
|
+
? OmitDeepWithOnePath<ObjectT[RealKey], SubPath>
|
|
100
|
+
: ObjectT[Key]
|
|
101
|
+
: ObjectT[Key]
|
|
102
|
+
: ObjectT[Key]
|
|
103
|
+
}
|
|
104
|
+
: ExactKey<ObjectT, P> extends infer Key
|
|
105
|
+
? Key extends PropertyKey
|
|
106
|
+
? Omit<ObjectT, Key>
|
|
107
|
+
: ObjectT
|
|
108
|
+
: ObjectT;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
Omit one path from from the given array.
|
|
112
|
+
|
|
113
|
+
It replaces the item to `unknown` at the given index.
|
|
114
|
+
|
|
115
|
+
@example
|
|
116
|
+
```
|
|
117
|
+
type A = OmitDeepArrayWithOnePath<[10, 20, 30, 40], 2>;
|
|
118
|
+
//=> type A = [10, 20, unknown, 40];
|
|
119
|
+
```
|
|
120
|
+
*/
|
|
121
|
+
type OmitDeepArrayWithOnePath<ArrayType extends UnknownArray, P extends string | number> =
|
|
122
|
+
// Handle paths that are `${number}.${string}`
|
|
123
|
+
P extends `${infer ArrayIndex extends number}.${infer SubPath}`
|
|
124
|
+
// If `ArrayIndex` is equal to `number`
|
|
125
|
+
? number extends ArrayIndex
|
|
126
|
+
? Array<OmitDeepWithOnePath<NonNullable<ArrayType[number]>, SubPath>>
|
|
127
|
+
// If `ArrayIndex` is a number literal
|
|
128
|
+
: ArraySplice<ArrayType, ArrayIndex, 1, [OmitDeepWithOnePath<NonNullable<ArrayType[ArrayIndex]>, SubPath>]>
|
|
129
|
+
// If the path is equal to `number`
|
|
130
|
+
: P extends `${infer ArrayIndex extends number}`
|
|
131
|
+
// If `ArrayIndex` is `number`
|
|
132
|
+
? number extends ArrayIndex
|
|
133
|
+
? []
|
|
134
|
+
// If `ArrayIndex` is a number literal
|
|
135
|
+
: ArraySplice<ArrayType, ArrayIndex, 1, [unknown]>
|
|
136
|
+
: never;
|
package/source/opaque.d.ts
CHANGED
|
@@ -4,9 +4,7 @@ export type TagContainer<Token> = {
|
|
|
4
4
|
readonly [tag]: Token;
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
-
type
|
|
8
|
-
readonly [tag]: {[K in Token]: void};
|
|
9
|
-
};
|
|
7
|
+
type Tag<Token extends PropertyKey, TagMetadata> = TagContainer<{[K in Token]: TagMetadata}>;
|
|
10
8
|
|
|
11
9
|
/**
|
|
12
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.)
|
|
@@ -113,19 +111,26 @@ type WillWork = UnwrapOpaque<Tagged<number, 'AccountNumber'>>; // number
|
|
|
113
111
|
@category Type
|
|
114
112
|
*/
|
|
115
113
|
export type UnwrapOpaque<OpaqueType extends TagContainer<unknown>> =
|
|
116
|
-
OpaqueType extends
|
|
114
|
+
OpaqueType extends Tag<PropertyKey, any>
|
|
117
115
|
? RemoveAllTags<OpaqueType>
|
|
118
116
|
: OpaqueType extends Opaque<infer Type, OpaqueType[typeof tag]>
|
|
119
117
|
? Type
|
|
120
118
|
: OpaqueType;
|
|
121
119
|
|
|
122
120
|
/**
|
|
123
|
-
Attach a "tag" to an arbitrary type. This allows you to create distinct types, that aren't assignable to one another, for
|
|
121
|
+
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
122
|
|
|
125
123
|
A type returned by `Tagged` can be passed to `Tagged` again, to create a type with multiple tags.
|
|
126
124
|
|
|
127
125
|
[Read more about tagged types.](https://medium.com/@KevinBGreene/surviving-the-typescript-ecosystem-branding-and-type-tagging-6cf6e516523d)
|
|
128
126
|
|
|
127
|
+
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.
|
|
128
|
+
|
|
129
|
+
A type `A` returned by `Tagged` is assignable to another type `B` returned by `Tagged` if and only if:
|
|
130
|
+
- the underlying (untagged) type of `A` is assignable to the underlying type of `B`;
|
|
131
|
+
- `A` contains at least all the tags `B` has;
|
|
132
|
+
- and the metadata type for each of `A`'s tags is assignable to the metadata type of `B`'s corresponding tag.
|
|
133
|
+
|
|
129
134
|
There have been several discussions about adding similar features to TypeScript. Unfortunately, nothing has (yet) moved forward:
|
|
130
135
|
- [Microsoft/TypeScript#202](https://github.com/microsoft/TypeScript/issues/202)
|
|
131
136
|
- [Microsoft/TypeScript#4895](https://github.com/microsoft/TypeScript/issues/4895)
|
|
@@ -151,21 +156,62 @@ function getMoneyForAccount(accountNumber: AccountNumber): AccountBalance {
|
|
|
151
156
|
getMoneyForAccount(createAccountNumber());
|
|
152
157
|
|
|
153
158
|
// But this won't, because it has to be explicitly passed as an `AccountNumber` type!
|
|
159
|
+
// Critically, you could not accidentally use an `AccountBalance` as an `AccountNumber`.
|
|
154
160
|
getMoneyForAccount(2);
|
|
155
161
|
|
|
156
|
-
// You can use
|
|
157
|
-
|
|
162
|
+
// You can also use tagged values like their underlying, untagged type.
|
|
163
|
+
// I.e., this will compile successfully because an `AccountNumber` can be used as a regular `number`.
|
|
164
|
+
// In this sense, the underlying base type is not hidden, which differentiates tagged types from opaque types in other languages.
|
|
165
|
+
const accountNumber = createAccountNumber() + 2;
|
|
166
|
+
```
|
|
158
167
|
|
|
159
|
-
|
|
160
|
-
|
|
168
|
+
@example
|
|
169
|
+
```
|
|
170
|
+
import type {Tagged} from 'type-fest';
|
|
171
|
+
|
|
172
|
+
// You can apply multiple tags to a type by using `Tagged` repeatedly.
|
|
173
|
+
type Url = Tagged<string, 'URL'>;
|
|
174
|
+
type SpecialCacheKey = Tagged<Url, 'SpecialCacheKey'>;
|
|
175
|
+
|
|
176
|
+
// 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.
|
|
177
|
+
type SpecialCacheKey2 = Tagged<string, 'URL' | 'SpecialCacheKey'>;
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
@category Type
|
|
181
|
+
*/
|
|
182
|
+
export type Tagged<Type, TagName extends PropertyKey, TagMetadata = never> = Type & Tag<TagName, TagMetadata>;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
Given a type and a tag name, returns the metadata associated with that tag on that type.
|
|
186
|
+
|
|
187
|
+
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.
|
|
188
|
+
|
|
189
|
+
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).
|
|
190
|
+
|
|
191
|
+
@example
|
|
192
|
+
```
|
|
193
|
+
import type {Tagged} from 'type-fest';
|
|
194
|
+
|
|
195
|
+
type JsonOf<T> = Tagged<string, 'JSON', T>;
|
|
196
|
+
|
|
197
|
+
function stringify<T>(it: T) {
|
|
198
|
+
return JSON.stringify(it) as JsonOf<T>;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function parse<T extends JsonOf<unknown>>(it: T) {
|
|
202
|
+
return JSON.parse(it) as GetTagMetadata<T, 'JSON'>;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const x = stringify({ hello: 'world' });
|
|
206
|
+
const parsed = parse(x); // The type of `parsed` is { hello: string }
|
|
161
207
|
```
|
|
162
208
|
|
|
163
209
|
@category Type
|
|
164
210
|
*/
|
|
165
|
-
export type
|
|
211
|
+
export type GetTagMetadata<Type extends Tag<TagName, unknown>, TagName extends PropertyKey> = Type[typeof tag][TagName];
|
|
166
212
|
|
|
167
213
|
/**
|
|
168
|
-
Revert a tagged type back to its original type by removing
|
|
214
|
+
Revert a tagged type back to its original type by removing all tags.
|
|
169
215
|
|
|
170
216
|
Why is this necessary?
|
|
171
217
|
|
|
@@ -192,14 +238,13 @@ type WontWork = UnwrapTagged<string>;
|
|
|
192
238
|
|
|
193
239
|
@category Type
|
|
194
240
|
*/
|
|
195
|
-
export type UnwrapTagged<TaggedType extends
|
|
241
|
+
export type UnwrapTagged<TaggedType extends Tag<PropertyKey, any>> =
|
|
196
242
|
RemoveAllTags<TaggedType>;
|
|
197
243
|
|
|
198
|
-
type RemoveAllTags<T> = T extends
|
|
244
|
+
type RemoveAllTags<T> = T extends Tag<PropertyKey, any>
|
|
199
245
|
? {
|
|
200
|
-
[ThisTag in
|
|
201
|
-
T extends Tagged<infer Type, ThisTag>
|
|
246
|
+
[ThisTag in keyof T[typeof tag]]: T extends Tagged<infer Type, ThisTag, T[typeof tag][ThisTag]>
|
|
202
247
|
? RemoveAllTags<Type>
|
|
203
248
|
: never
|
|
204
|
-
}[
|
|
249
|
+
}[keyof T[typeof tag]]
|
|
205
250
|
: T;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {IfUnknown} from './if-unknown';
|
|
2
|
+
import type {BuiltIns, LiteralKeyOf} from './internal';
|
|
2
3
|
import type {Merge} from './merge';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -47,8 +48,8 @@ const testSettings: PartialOnUndefinedDeep<Settings> = {
|
|
|
47
48
|
@category Object
|
|
48
49
|
*/
|
|
49
50
|
export type PartialOnUndefinedDeep<T, Options extends PartialOnUndefinedDeepOptions = {}> = T extends Record<any, any> | undefined
|
|
50
|
-
? {[KeyType in keyof T as undefined extends T[KeyType] ? KeyType : never]?: PartialOnUndefinedDeepValue<T[KeyType], Options>} extends infer U // Make a partial type with all value types accepting undefined (and set them optional)
|
|
51
|
-
? Merge<{[KeyType in keyof T as KeyType extends
|
|
51
|
+
? {[KeyType in keyof T as undefined extends T[KeyType] ? IfUnknown<T[KeyType], never, KeyType> : never]?: PartialOnUndefinedDeepValue<T[KeyType], Options>} extends infer U // Make a partial type with all value types accepting undefined (and set them optional)
|
|
52
|
+
? Merge<{[KeyType in keyof T as KeyType extends LiteralKeyOf<U> ? never : KeyType]: PartialOnUndefinedDeepValue<T[KeyType], Options>}, U> // Join all remaining keys not treated in U
|
|
52
53
|
: never // Should not happen
|
|
53
54
|
: T;
|
|
54
55
|
|
|
@@ -1,34 +1,7 @@
|
|
|
1
|
-
import type {NonRecursiveType, UnionMin, UnionMax, TupleLength, StaticPartOfArray, VariablePartOfArray, IsUnion} from './internal';
|
|
1
|
+
import type {NonRecursiveType, UnionMin, UnionMax, TupleLength, StaticPartOfArray, VariablePartOfArray, IsUnion, IsArrayReadonly, SetArrayAccess} from './internal';
|
|
2
2
|
import type {IsNever} from './is-never';
|
|
3
3
|
import type {UnknownArray} from './unknown-array';
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
Set the given array to readonly if `IsReadonly` is `true`, otherwise set the given array to normal, then return the result.
|
|
7
|
-
|
|
8
|
-
@example
|
|
9
|
-
```
|
|
10
|
-
type ReadonlyArray = readonly string[];
|
|
11
|
-
type NormalArray = string[];
|
|
12
|
-
|
|
13
|
-
type ReadonlyResult = SetArrayAccess<NormalArray, true>;
|
|
14
|
-
//=> readonly string[]
|
|
15
|
-
|
|
16
|
-
type NormalResult = SetArrayAccess<ReadonlyArray, false>;
|
|
17
|
-
//=> string[]
|
|
18
|
-
```
|
|
19
|
-
*/
|
|
20
|
-
type SetArrayAccess<T extends UnknownArray, IsReadonly extends boolean> =
|
|
21
|
-
T extends readonly [...infer U] ?
|
|
22
|
-
IsReadonly extends true
|
|
23
|
-
? readonly [...U]
|
|
24
|
-
: [...U]
|
|
25
|
-
: T;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
Returns whether the given array `T` is readonly.
|
|
29
|
-
*/
|
|
30
|
-
type IsArrayReadonly<T extends UnknownArray> = T extends unknown[] ? false : true;
|
|
31
|
-
|
|
32
5
|
/**
|
|
33
6
|
SharedUnionFieldsDeep options.
|
|
34
7
|
|