UNPKG

16.5 kBTypeScriptView Raw
1import type {ConditionalSimplifyDeep} from './conditional-simplify';
2import type {OmitIndexSignature} from './omit-index-signature';
3import type {PickIndexSignature} from './pick-index-signature';
4import type {Merge} from './merge';
5import type {
6 FirstArrayElement,
7 IsBothExtends,
8 UnknownArrayOrTuple,
9} from './internal';
10import type {NonEmptyTuple} from './non-empty-tuple';
11import type {ArrayTail} from './array-tail';
12import type {UnknownRecord} from './unknown-record';
13import type {EnforceOptional} from './enforce-optional';
14import type {SimplifyDeep} from './simplify-deep';
15import type {UnknownArray} from './unknown-array';
16
17type SimplifyDeepExcludeArray<T> = SimplifyDeep<T, UnknownArray>;
18
19/**
20Try to merge two record properties or return the source property value, preserving `undefined` properties values in both cases.
21*/
22type MergeDeepRecordProperty<
23 Destination,
24 Source,
25 Options extends MergeDeepInternalOptions,
26> = undefined extends Source
27 ? MergeDeepOrReturn<Source, Exclude<Destination, undefined>, Exclude<Source, undefined>, Options> | undefined
28 : MergeDeepOrReturn<Source, Destination, Source, Options>;
29
30/**
31Walk through the union of the keys of the two objects and test in which object the properties are defined.
32Rules:
331. If the source does not contain the key, the value of the destination is returned.
342. If the source contains the key and the destination does not contain the key, the value of the source is returned.
353. If both contain the key, try to merge according to the chosen {@link MergeDeepOptions options} or return the source if unable to merge.
36*/
37type DoMergeDeepRecord<
38 Destination extends UnknownRecord,
39 Source extends UnknownRecord,
40 Options extends MergeDeepInternalOptions,
41> =
42// Case in rule 1: The destination contains the key but the source doesn't.
43{
44 [Key in keyof Destination as Key extends keyof Source ? never : Key]: Destination[Key];
45}
46// Case in rule 2: The source contains the key but the destination doesn't.
47& {
48 [Key in keyof Source as Key extends keyof Destination ? never : Key]: Source[Key];
49}
50// Case in rule 3: Both the source and the destination contain the key.
51& {
52 [Key in keyof Source as Key extends keyof Destination ? Key : never]: MergeDeepRecordProperty<Destination[Key], Source[Key], Options>;
53};
54
55/**
56Wrapper around {@link DoMergeDeepRecord} which preserves index signatures.
57*/
58type MergeDeepRecord<
59 Destination extends UnknownRecord,
60 Source extends UnknownRecord,
61 Options extends MergeDeepInternalOptions,
62> = DoMergeDeepRecord<OmitIndexSignature<Destination>, OmitIndexSignature<Source>, Options>
63& Merge<PickIndexSignature<Destination>, PickIndexSignature<Source>>;
64
65// Helper to avoid computing ArrayTail twice.
66type PickRestTypeHelper<Tail extends UnknownArrayOrTuple, Type> = Tail extends [] ? Type : PickRestType<Tail>;
67
68/**
69Pick the rest type.
70
71@example
72```
73type Rest1 = PickRestType<[]>; // => []
74type Rest2 = PickRestType<[string]>; // => []
75type Rest3 = PickRestType<[...number[]]>; // => number[]
76type Rest4 = PickRestType<[string, ...number[]]>; // => number[]
77type Rest5 = PickRestType<string[]>; // => string[]
78```
79*/
80type PickRestType<Type extends UnknownArrayOrTuple> = number extends Type['length']
81 ? PickRestTypeHelper<ArrayTail<Type>, Type>
82 : [];
83
84// Helper to avoid computing ArrayTail twice.
85type OmitRestTypeHelper<
86 Tail extends UnknownArrayOrTuple,
87 Type extends UnknownArrayOrTuple,
88 Result extends UnknownArrayOrTuple = [],
89> = Tail extends []
90 ? Result
91 : OmitRestType<Tail, [...Result, FirstArrayElement<Type>]>;
92
93/**
94Omit the rest type.
95
96@example
97```
98type Tuple1 = OmitRestType<[]>; // => []
99type Tuple2 = OmitRestType<[string]>; // => [string]
100type Tuple3 = OmitRestType<[...number[]]>; // => []
101type Tuple4 = OmitRestType<[string, ...number[]]>; // => [string]
102type Tuple5 = OmitRestType<[string, boolean[], ...number[]]>; // => [string, boolean[]]
103type Tuple6 = OmitRestType<string[]>; // => []
104```
105*/
106type OmitRestType<Type extends UnknownArrayOrTuple, Result extends UnknownArrayOrTuple = []> = number extends Type['length']
107 ? OmitRestTypeHelper<ArrayTail<Type>, Type, Result>
108 : Type;
109
110// Utility to avoid picking two times the type.
111type TypeNumberOrType<Type extends UnknownArrayOrTuple> = Type[number] extends never ? Type : Type[number];
112
113// Pick the rest type (array) and try to get the intrinsic type or return the provided type.
114type PickRestTypeFlat<Type extends UnknownArrayOrTuple> = TypeNumberOrType<PickRestType<Type>>;
115
116/**
117Try to merge two array/tuple elements or return the source element if the end of the destination is reached or vis-versa.
118*/
119type MergeDeepArrayOrTupleElements<
120 Destination,
121 Source,
122 Options extends MergeDeepInternalOptions,
123> = Source extends []
124 ? Destination
125 : Destination extends []
126 ? Source
127 : MergeDeepOrReturn<Source, Destination, Source, Options>;
128
129/**
130Merge two tuples recursively.
131*/
132type DoMergeDeepTupleAndTupleRecursive<
133 Destination extends UnknownArrayOrTuple,
134 Source extends UnknownArrayOrTuple,
135 DestinationRestType,
136 SourceRestType,
137 Options extends MergeDeepInternalOptions,
138> = Destination extends []
139 ? Source extends []
140 ? []
141 : MergeArrayTypeAndTuple<DestinationRestType, Source, Options>
142 : Source extends []
143 ? MergeTupleAndArrayType<Destination, SourceRestType, Options>
144 : [
145 MergeDeepArrayOrTupleElements<FirstArrayElement<Destination>, FirstArrayElement<Source>, Options>,
146 ...DoMergeDeepTupleAndTupleRecursive<ArrayTail<Destination>, ArrayTail<Source>, DestinationRestType, SourceRestType, Options>,
147 ];
148
149/**
150Merge two tuples recursively taking into account a possible rest element.
151*/
152type MergeDeepTupleAndTupleRecursive<
153 Destination extends UnknownArrayOrTuple,
154 Source extends UnknownArrayOrTuple,
155 Options extends MergeDeepInternalOptions,
156> = [
157 ...DoMergeDeepTupleAndTupleRecursive<OmitRestType<Destination>, OmitRestType<Source>, PickRestTypeFlat<Destination>, PickRestTypeFlat<Source>, Options>,
158 ...MergeDeepArrayOrTupleElements<PickRestType<Destination>, PickRestType<Source>, Options>,
159];
160
161/**
162Merge an array type with a tuple recursively.
163*/
164type MergeTupleAndArrayType<
165 Tuple extends UnknownArrayOrTuple,
166 ArrayType,
167 Options extends MergeDeepInternalOptions,
168> = Tuple extends []
169 ? Tuple
170 : [
171 MergeDeepArrayOrTupleElements<FirstArrayElement<Tuple>, ArrayType, Options>,
172 ...MergeTupleAndArrayType<ArrayTail<Tuple>, ArrayType, Options>,
173 ];
174
175/**
176Merge an array into a tuple recursively taking into account a possible rest element.
177*/
178type MergeDeepTupleAndArrayRecursive<
179 Destination extends UnknownArrayOrTuple,
180 Source extends UnknownArrayOrTuple,
181 Options extends MergeDeepInternalOptions,
182> = [
183 ...MergeTupleAndArrayType<OmitRestType<Destination>, Source[number], Options>,
184 ...MergeDeepArrayOrTupleElements<PickRestType<Destination>, PickRestType<Source>, Options>,
185];
186
187/**
188Merge a tuple with an array type recursively.
189*/
190type MergeArrayTypeAndTuple<
191 ArrayType,
192 Tuple extends UnknownArrayOrTuple,
193 Options extends MergeDeepInternalOptions,
194> = Tuple extends []
195 ? Tuple
196 : [
197 MergeDeepArrayOrTupleElements<ArrayType, FirstArrayElement<Tuple>, Options>,
198 ...MergeArrayTypeAndTuple<ArrayType, ArrayTail<Tuple>, Options>,
199 ];
200
201/**
202Merge a tuple into an array recursively taking into account a possible rest element.
203*/
204type MergeDeepArrayAndTupleRecursive<
205 Destination extends UnknownArrayOrTuple,
206 Source extends UnknownArrayOrTuple,
207 Options extends MergeDeepInternalOptions,
208> = [
209 ...MergeArrayTypeAndTuple<Destination[number], OmitRestType<Source>, Options>,
210 ...MergeDeepArrayOrTupleElements<PickRestType<Destination>, PickRestType<Source>, Options>,
211];
212
213/**
214Merge mode for array/tuple elements.
215*/
216type ArrayMergeMode = 'spread' | 'replace';
217
218/**
219Test if it should spread top-level arrays.
220*/
221type ShouldSpread<Options extends MergeDeepInternalOptions> = Options['spreadTopLevelArrays'] extends false
222 ? Options['arrayMergeMode'] extends 'spread' ? true : false
223 : true;
224
225/**
226Merge two arrays/tuples according to the chosen {@link MergeDeepOptions.arrayMergeMode arrayMergeMode} option.
227*/
228type DoMergeArrayOrTuple<
229 Destination extends UnknownArrayOrTuple,
230 Source extends UnknownArrayOrTuple,
231 Options extends MergeDeepInternalOptions,
232> = ShouldSpread<Options> extends true
233 ? Array<Exclude<Destination, undefined>[number] | Exclude<Source, undefined>[number]>
234 : Source; // 'replace'
235
236/**
237Merge two arrays recursively.
238
239If the two arrays are multi-level, we merge deeply, otherwise we merge the first level only.
240
241Note: The `[number]` accessor is used to test the type of the second level.
242*/
243type MergeDeepArrayRecursive<
244 Destination extends UnknownArrayOrTuple,
245 Source extends UnknownArrayOrTuple,
246 Options extends MergeDeepInternalOptions,
247> = Destination[number] extends UnknownArrayOrTuple
248 ? Source[number] extends UnknownArrayOrTuple
249 ? Array<MergeDeepArrayOrTupleRecursive<Destination[number], Source[number], Options>>
250 : DoMergeArrayOrTuple<Destination, Source, Options>
251 : Destination[number] extends UnknownRecord
252 ? Source[number] extends UnknownRecord
253 ? Array<SimplifyDeepExcludeArray<MergeDeepRecord<Destination[number], Source[number], Options>>>
254 : DoMergeArrayOrTuple<Destination, Source, Options>
255 : DoMergeArrayOrTuple<Destination, Source, Options>;
256
257/**
258Merge two array/tuple recursively by selecting one of the four strategies according to the type of inputs.
259
260- tuple/tuple
261- tuple/array
262- array/tuple
263- array/array
264*/
265type MergeDeepArrayOrTupleRecursive<
266 Destination extends UnknownArrayOrTuple,
267 Source extends UnknownArrayOrTuple,
268 Options extends MergeDeepInternalOptions,
269> = IsBothExtends<NonEmptyTuple, Destination, Source> extends true
270 ? MergeDeepTupleAndTupleRecursive<Destination, Source, Options>
271 : Destination extends NonEmptyTuple
272 ? MergeDeepTupleAndArrayRecursive<Destination, Source, Options>
273 : Source extends NonEmptyTuple
274 ? MergeDeepArrayAndTupleRecursive<Destination, Source, Options>
275 : MergeDeepArrayRecursive<Destination, Source, Options>;
276
277/**
278Merge two array/tuple according to {@link MergeDeepOptions.recurseIntoArrays recurseIntoArrays} option.
279*/
280type MergeDeepArrayOrTuple<
281 Destination extends UnknownArrayOrTuple,
282 Source extends UnknownArrayOrTuple,
283 Options extends MergeDeepInternalOptions,
284> = Options['recurseIntoArrays'] extends true
285 ? MergeDeepArrayOrTupleRecursive<Destination, Source, Options>
286 : DoMergeArrayOrTuple<Destination, Source, Options>;
287
288/**
289Try to merge two objects or two arrays/tuples recursively into a new type or return the default value.
290*/
291type MergeDeepOrReturn<
292 DefaultType,
293 Destination,
294 Source,
295 Options extends MergeDeepInternalOptions,
296> = SimplifyDeepExcludeArray<[undefined] extends [Destination | Source]
297 ? DefaultType
298 : Destination extends UnknownRecord
299 ? Source extends UnknownRecord
300 ? MergeDeepRecord<Destination, Source, Options>
301 : DefaultType
302 : Destination extends UnknownArrayOrTuple
303 ? Source extends UnknownArrayOrTuple
304 ? MergeDeepArrayOrTuple<Destination, Source, EnforceOptional<Merge<Options, {spreadTopLevelArrays: false}>>>
305 : DefaultType
306 : DefaultType>;
307
308/**
309MergeDeep options.
310
311@see {@link MergeDeep}
312*/
313export type MergeDeepOptions = {
314 /**
315 Merge mode for array and tuple.
316
317 When we walk through the properties of the objects and the same key is found and both are array or tuple, a merge mode must be chosen:
318 - `replace`: Replaces the destination value by the source value. This is the default mode.
319 - `spread`: Spreads the destination and the source values.
320
321 See {@link MergeDeep} for usages and examples.
322
323 Note: Top-level arrays and tuples are always spread.
324
325 @default 'replace'
326 */
327 arrayMergeMode?: ArrayMergeMode;
328
329 /**
330 Whether to affect the individual elements of arrays and tuples.
331
332 If this option is set to `true` the following rules are applied:
333 - If the source does not contain the key, the value of the destination is returned.
334 - If the source contains the key and the destination does not contain the key, the value of the source is returned.
335 - If both contain the key, try to merge according to the chosen {@link MergeDeepOptions.arrayMergeMode arrayMergeMode} or return the source if unable to merge.
336
337 @default false
338 */
339 recurseIntoArrays?: boolean;
340};
341
342/**
343Internal options.
344*/
345type MergeDeepInternalOptions = Merge<MergeDeepOptions, {spreadTopLevelArrays?: boolean}>;
346
347/**
348Merge default and internal options with user provided options.
349*/
350type DefaultMergeDeepOptions<Options extends MergeDeepOptions> = Merge<{
351 arrayMergeMode: 'replace';
352 recurseIntoArrays: false;
353 spreadTopLevelArrays: true;
354}, Options>;
355
356/**
357This utility selects the correct entry point with the corresponding default options. This avoids re-merging the options at each iteration.
358*/
359type MergeDeepWithDefaultOptions<Destination, Source, Options extends MergeDeepOptions> = SimplifyDeepExcludeArray<
360[undefined] extends [Destination | Source]
361 ? never
362 : Destination extends UnknownRecord
363 ? Source extends UnknownRecord
364 ? MergeDeepRecord<Destination, Source, DefaultMergeDeepOptions<Options>>
365 : never
366 : Destination extends UnknownArrayOrTuple
367 ? Source extends UnknownArrayOrTuple
368 ? MergeDeepArrayOrTuple<Destination, Source, DefaultMergeDeepOptions<Options>>
369 : never
370 : never
371>;
372
373/**
374Merge two objects or two arrays/tuples recursively into a new type.
375
376- Properties that only exist in one object are copied into the new object.
377- Properties that exist in both objects are merged if possible or replaced by the one of the source if not.
378- Top-level arrays and tuples are always spread.
379- By default, inner arrays and tuples are replaced. See {@link MergeDeepOptions.arrayMergeMode arrayMergeMode} option to change this behaviour.
380- By default, individual array/tuple elements are not affected. See {@link MergeDeepOptions.recurseIntoArrays recurseIntoArrays} option to change this behaviour.
381
382@example
383```
384import type {MergeDeep} from 'type-fest';
385
386type Foo = {
387 life: number;
388 items: string[];
389 a: {b: string; c: boolean; d: number[]};
390};
391
392interface Bar {
393 name: string;
394 items: number[];
395 a: {b: number; d: boolean[]};
396}
397
398type FooBar = MergeDeep<Foo, Bar>;
399// {
400// life: number;
401// name: string;
402// items: number[];
403// a: {b: number; c: boolean; d: boolean[]};
404// }
405
406type FooBar = MergeDeep<Foo, Bar, {arrayMergeMode: 'spread'}>;
407// {
408// life: number;
409// name: string;
410// items: (string | number)[];
411// a: {b: number; c: boolean; d: (number | boolean)[]};
412// }
413```
414
415@example
416```
417import type {MergeDeep} from 'type-fest';
418
419// Merge two arrays
420type ArrayMerge = MergeDeep<string[], number[]>; // => (string | number)[]
421
422// Merge two tuples
423type TupleMerge = MergeDeep<[1, 2, 3], ['a', 'b']>; // => (1 | 2 | 3 | 'a' | 'b')[]
424
425// Merge an array into a tuple
426type TupleArrayMerge = MergeDeep<[1, 2, 3], string[]>; // => (string | 1 | 2 | 3)[]
427
428// Merge a tuple into an array
429type ArrayTupleMerge = MergeDeep<number[], ['a', 'b']>; // => (number | 'b' | 'a')[]
430```
431
432@example
433```
434import type {MergeDeep, MergeDeepOptions} from 'type-fest';
435
436type Foo = {foo: 'foo'; fooBar: string[]};
437type Bar = {bar: 'bar'; fooBar: number[]};
438
439type FooBar = MergeDeep<Foo, Bar>;
440// { foo: "foo"; bar: "bar"; fooBar: number[]}
441
442type FooBarSpread = MergeDeep<Foo, Bar, {arrayMergeMode: 'spread'}>;
443// { foo: "foo"; bar: "bar"; fooBar: (string | number)[]}
444
445type FooBarArray = MergeDeep<Foo[], Bar[]>;
446// (Foo | Bar)[]
447
448type FooBarArrayDeep = MergeDeep<Foo[], Bar[], {recurseIntoArrays: true}>;
449// FooBar[]
450
451type FooBarArraySpreadDeep = MergeDeep<Foo[], Bar[], {recurseIntoArrays: true; arrayMergeMode: 'spread'}>;
452// FooBarSpread[]
453
454type FooBarTupleDeep = MergeDeep<[Foo, true, 42], [Bar, 'life'], {recurseIntoArrays: true}>;
455// [FooBar, 'life', 42]
456
457type FooBarTupleWithArrayDeep = MergeDeep<[Foo[], true], [Bar[], 'life', 42], {recurseIntoArrays: true}>;
458// [FooBar[], 'life', 42]
459```
460
461@example
462```
463import type {MergeDeep, MergeDeepOptions} from 'type-fest';
464
465function mergeDeep<Destination, Source, Options extends MergeDeepOptions = {}>(
466 destination: Destination,
467 source: Source,
468 options?: Options,
469): MergeDeep<Destination, Source, Options> {
470 // Make your implementation ...
471}
472```
473
474@experimental This type is marked as experimental because it depends on {@link ConditionalSimplifyDeep} which itself is experimental.
475
476@see {@link MergeDeepOptions}
477
478@category Array
479@category Object
480@category Utilities
481*/
482export type MergeDeep<Destination, Source, Options extends MergeDeepOptions = {}> = MergeDeepWithDefaultOptions<
483SimplifyDeepExcludeArray<Destination>,
484SimplifyDeepExcludeArray<Source>,
485Options
486>;