forked from sindresorhus/type-fest
-
Notifications
You must be signed in to change notification settings - Fork 0
/
shared-union-fields-deep.d.ts
172 lines (152 loc) · 5.96 KB
/
shared-union-fields-deep.d.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import type {NonRecursiveType, UnionMin, UnionMax, TupleLength, StaticPartOfArray, VariablePartOfArray, IsUnion, IsArrayReadonly, SetArrayAccess} from './internal';
import type {IsNever} from './is-never';
import type {UnknownArray} from './unknown-array';
/**
SharedUnionFieldsDeep options.
@see {@link SharedUnionFieldsDeep}
*/
export type SharedUnionFieldsDeepOptions = {
/**
When set to true, this option impacts each element within arrays or tuples. If all union values are arrays or tuples, it constructs an array of the shortest possible length, ensuring every element exists in the union array.
@default false
*/
recurseIntoArrays?: boolean;
};
/**
Create a type with shared fields from a union of object types, deeply traversing nested structures.
Use the {@link SharedUnionFieldsDeepOptions `Options`} to specify the behavior for arrays.
Use-cases:
- You want a safe object type where each key exists in the union object.
- You want to focus on the common fields of the union type and don't want to have to care about the other fields.
@example
```
import type {SharedUnionFieldsDeep} from 'type-fest';
type Cat = {
info: {
name: string;
type: 'cat';
catType: string;
};
};
type Dog = {
info: {
name: string;
type: 'dog';
dogType: string;
};
};
function displayPetInfo(petInfo: (Cat | Dog)['info']) {
// typeof petInfo =>
// {
// name: string;
// type: 'cat';
// catType: string; // Needn't care about this field, because it's not a common pet info field.
// } | {
// name: string;
// type: 'dog';
// dogType: string; // Needn't care about this field, because it's not a common pet info field.
// }
// petInfo type is complex and have some needless fields
console.log('name: ', petInfo.name);
console.log('type: ', petInfo.type);
}
function displayPetInfo(petInfo: SharedUnionFieldsDeep<Cat | Dog>['info']) {
// typeof petInfo =>
// {
// name: string;
// type: 'cat' | 'dog';
// }
// petInfo type is simple and clear
console.log('name: ', petInfo.name);
console.log('type: ', petInfo.type);
}
```
@see SharedUnionFields
@category Object
@category Union
*/
export type SharedUnionFieldsDeep<Union, Options extends SharedUnionFieldsDeepOptions = {recurseIntoArrays: false}> =
// `Union extends` will convert `Union`
// to a [distributive conditionaltype](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types).
// But this is not what we want, so we need to wrap `Union` with `[]` to prevent it.
[Union] extends [NonRecursiveType | ReadonlyMap<unknown, unknown> | ReadonlySet<unknown>]
? Union
: [Union] extends [UnknownArray]
? Options['recurseIntoArrays'] extends true
? SetArrayAccess<SharedArrayUnionFieldsDeep<Union, Options>, IsArrayReadonly<Union>>
: Union
: [Union] extends [object]
? SharedObjectUnionFieldsDeep<Union, Options>
: Union;
/**
Same as `SharedUnionFieldsDeep`, but accepts only `object`s and as inputs. Internal helper for `SharedUnionFieldsDeep`.
*/
type SharedObjectUnionFieldsDeep<Union, Options extends SharedUnionFieldsDeepOptions> =
// `keyof Union` can extract the same key in union type, if there is no same key, return never.
keyof Union extends infer Keys
? IsNever<Keys> extends false
? {
[Key in keyof Union]:
Union[Key] extends NonRecursiveType
? Union[Key]
// Remove `undefined` from the union to support optional
// fields, then recover `undefined` if union was already undefined.
: SharedUnionFieldsDeep<Exclude<Union[Key], undefined>, Options> | (
undefined extends Required<Union>[Key] ? undefined : never
)
}
: {}
: Union;
/**
Same as `SharedUnionFieldsDeep`, but accepts only `UnknownArray`s and as inputs. Internal helper for `SharedUnionFieldsDeep`.
*/
type SharedArrayUnionFieldsDeep<Union extends UnknownArray, Options extends SharedUnionFieldsDeepOptions> =
// Restore the readonly modifier of the array.
SetArrayAccess<
InternalSharedArrayUnionFieldsDeep<Union, Options>,
IsArrayReadonly<Union>
>;
/**
Internal helper for `SharedArrayUnionFieldsDeep`. Needn't care the `readonly` modifier of arrays.
*/
type InternalSharedArrayUnionFieldsDeep<
Union extends UnknownArray,
Options extends SharedUnionFieldsDeepOptions,
ResultTuple extends UnknownArray = [],
> =
// We should build a minimum possible length tuple where each element in the tuple exists in the union tuple.
IsNever<TupleLength<Union>> extends true
// Rule 1: If all the arrays in the union have non-fixed lengths,
// like `Array<string> | [number, ...string[]]`
// we should build a tuple that is [the_fixed_parts_of_union, ...the_rest_of_union[]].
// For example: `InternalSharedArrayUnionFieldsDeep<Array<string> | [number, ...string[]]>`
// => `[string | number, ...string[]]`.
? ResultTuple['length'] extends UnionMax<StaticPartOfArray<Union>['length']>
? [
// The fixed-length part of the tuple.
...ResultTuple,
// The rest of the union.
// Due to `ResultTuple` is the maximum possible fixed-length part of the tuple,
// so we can use `StaticPartOfArray` to get the rest of the union.
...Array<
SharedUnionFieldsDeep<VariablePartOfArray<Union>[number], Options>
>,
]
// Build the fixed-length tuple recursively.
: InternalSharedArrayUnionFieldsDeep<
Union, Options,
[...ResultTuple, SharedUnionFieldsDeep<Union[ResultTuple['length']], Options>]
>
// Rule 2: If at least one of the arrays in the union have fixed lengths,
// like `Array<string> | [number, string]`,
// we should build a tuple of the smallest possible length to ensure any
// item in the result tuple exists in the union tuple.
// For example: `InternalSharedArrayUnionFieldsDeep<Array<string> | [number, string]>`
// => `[string | number, string]`.
: ResultTuple['length'] extends UnionMin<TupleLength<Union>>
? ResultTuple
// As above, build tuple recursively.
: InternalSharedArrayUnionFieldsDeep<
Union, Options,
[...ResultTuple, SharedUnionFieldsDeep<Union[ResultTuple['length']], Options>]
>;