UNPKG

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