UNPKG

5.88 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@category Object
81@category Union
82*/
83export type SharedUnionFieldsDeep<Union, Options extends SharedUnionFieldsDeepOptions = {recurseIntoArrays: false}> =
84// If `Union` is not a union type, return `Union` directly.
85IsUnion<Union> extends false
86 ? Union
87 // `Union extends` will convert `Union`
88 // to a [distributive conditionaltype](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types).
89 // But this is not what we want, so we need to wrap `Union` with `[]` to prevent it.
90 : [Union] extends [NonRecursiveType | ReadonlyMap<unknown, unknown> | ReadonlySet<unknown>]
91 ? Union
92 : [Union] extends [UnknownArray]
93 ? Options['recurseIntoArrays'] extends true
94 ? SetArrayAccess<SharedArrayUnionFieldsDeep<Union, Options>, IsArrayReadonly<Union>>
95 : Union
96 : [Union] extends [object]
97 ? SharedObjectUnionFieldsDeep<Union, Options>
98 : Union;
99
100/**
101Same as `SharedUnionFieldsDeep`, but accepts only `object`s and as inputs. Internal helper for `SharedUnionFieldsDeep`.
102*/
103type SharedObjectUnionFieldsDeep<Union, Options extends SharedUnionFieldsDeepOptions> =
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 : SharedUnionFieldsDeep<Union[Key], Options>
111 }
112 : {}
113 : Union;
114
115/**
116Same as `SharedUnionFieldsDeep`, but accepts only `UnknownArray`s and as inputs. Internal helper for `SharedUnionFieldsDeep`.
117*/
118type SharedArrayUnionFieldsDeep<Union extends UnknownArray, Options extends SharedUnionFieldsDeepOptions> =
119 // Restore the readonly modifier of the array.
120 SetArrayAccess<
121 InternalSharedArrayUnionFieldsDeep<Union, Options>,
122 IsArrayReadonly<Union>
123 >;
124
125/**
126Internal helper for `SharedArrayUnionFieldsDeep`. Needn't care the `readonly` modifier of arrays.
127*/
128type InternalSharedArrayUnionFieldsDeep<
129 Union extends UnknownArray,
130 Options extends SharedUnionFieldsDeepOptions,
131 ResultTuple extends UnknownArray = [],
132> =
133 // We should build a minimum possible length tuple where each element in the tuple exists in the union tuple.
134 IsNever<TupleLength<Union>> extends true
135 // Rule 1: If all the arrays in the union have non-fixed lengths,
136 // like `Array<string> | [number, ...string[]]`
137 // we should build a tuple that is [the_fixed_parts_of_union, ...the_rest_of_union[]].
138 // For example: `InternalSharedArrayUnionFieldsDeep<Array<string> | [number, ...string[]]>`
139 // => `[string | number, ...string[]]`.
140 ? ResultTuple['length'] extends UnionMax<StaticPartOfArray<Union>['length']>
141 ? [
142 // The fixed-length part of the tuple.
143 ...ResultTuple,
144 // The rest of the union.
145 // Due to `ResultTuple` is the maximum possible fixed-length part of the tuple,
146 // so we can use `StaticPartOfArray` to get the rest of the union.
147 ...Array<
148 SharedUnionFieldsDeep<VariablePartOfArray<Union>[number], Options>
149 >,
150 ]
151 // Build the fixed-length tuple recursively.
152 : InternalSharedArrayUnionFieldsDeep<
153 Union, Options,
154 [...ResultTuple, SharedUnionFieldsDeep<Union[ResultTuple['length']], Options>]
155 >
156 // Rule 2: If at least one of the arrays in the union have fixed lengths,
157 // like `Array<string> | [number, string]`,
158 // we should build a tuple of the smallest possible length to ensure any
159 // item in the result tuple exists in the union tuple.
160 // For example: `InternalSharedArrayUnionFieldsDeep<Array<string> | [number, string]>`
161 // => `[string | number, string]`.
162 : ResultTuple['length'] extends UnionMin<TupleLength<Union>>
163 ? ResultTuple
164 // As above, build tuple recursively.
165 : InternalSharedArrayUnionFieldsDeep<
166 Union, Options,
167 [...ResultTuple, SharedUnionFieldsDeep<Union[ResultTuple['length']], Options>]
168 >;