import type {ConditionalSimplifyDeep} from './conditional-simplify'; import type {OmitIndexSignature} from './omit-index-signature'; import type {PickIndexSignature} from './pick-index-signature'; import type {EnforceOptional} from './enforce-optional'; import type {Merge} from './merge'; import type { ArrayTail, FirstArrayElement, IsBothExtends, NonEmptyTuple, UnknownArrayOrTuple, UnknownRecord, } from './internal'; /** Deeply simplifies an object excluding iterables and functions. Used internally to improve the UX and accept both interfaces and type aliases as inputs. */ type SimplifyDeep = ConditionalSimplifyDeep, object>; /** Try to merge two record properties or return the source property value, preserving `undefined` properties values in both cases. */ type MergeDeepRecordProperty< Destination, Source, Options extends MergeDeepInternalOptions, > = undefined extends Source ? MergeDeepOrReturn, Exclude, Options> | undefined : MergeDeepOrReturn; /** Walk through the union of the keys of the two objects and test in which object the properties are defined. - If the source does not contain the key, the value of the destination is returned. - If the source contains the key and the destination does not contain the key, the value of the source is returned. - If both contain the key, try to merge according to the chosen {@link MergeDeepOptions options} or return the source if unable to merge. */ type DoMergeDeepRecord< Destination extends UnknownRecord, Source extends UnknownRecord, Options extends MergeDeepInternalOptions, > = EnforceOptional<{ [Key in keyof Destination | keyof Source]: Key extends keyof Source ? Key extends keyof Destination ? MergeDeepRecordProperty : Source[Key] : Key extends keyof Destination ? Destination[Key] : never; }>; /** Wrapper around {@link DoMergeDeepRecord} which preserves index signatures. */ type MergeDeepRecord< Destination extends UnknownRecord, Source extends UnknownRecord, Options extends MergeDeepInternalOptions, > = DoMergeDeepRecord, OmitIndexSignature, Options> & Merge, PickIndexSignature>; /** Pick the rest type. @example ``` type Rest1 = PickRestType<[]>; // => [] type Rest2 = PickRestType<[string]>; // => [] type Rest3 = PickRestType<[...number[]]>; // => number[] type Rest4 = PickRestType<[string, ...number[]]>; // => number[] type Rest5 = PickRestType; // => string[] ``` */ type PickRestType = number extends Type['length'] ? ArrayTail extends [] ? Type : PickRestType> : []; /** Omit the rest type. @example ``` type Tuple1 = OmitRestType<[]>; // => [] type Tuple2 = OmitRestType<[string]>; // => [string] type Tuple3 = OmitRestType<[...number[]]>; // => [] type Tuple4 = OmitRestType<[string, ...number[]]>; // => [string] type Tuple5 = OmitRestType<[string, boolean[], ...number[]]>; // => [string, boolean[]] type Tuple6 = OmitRestType; // => [] ``` */ type OmitRestType = number extends Type['length'] ? ArrayTail extends [] ? Result : OmitRestType, [...Result, FirstArrayElement]> : Type; // Utility to avoid picking two times the type. type TypeNumberOrType = Type[number] extends never ? Type : Type[number]; // Pick the rest type (array) and try to get the intrinsic type or return the provided type. type PickRestTypeFlat = TypeNumberOrType>; /** Try to merge two array/tuple elements or return the source element if the end of the destination is reached or vis-versa. */ type MergeDeepArrayOrTupleElements< Destination, Source, Options extends MergeDeepInternalOptions, > = Source extends [] ? Destination : Destination extends [] ? Source : MergeDeepOrReturn; /** Merge two tuples recursively. */ type DoMergeDeepTupleAndTupleRecursive< Destination extends UnknownArrayOrTuple, Source extends UnknownArrayOrTuple, DestinationRestType, SourceRestType, Options extends MergeDeepInternalOptions, > = Destination extends [] ? Source extends [] ? [] : MergeArrayTypeAndTuple : Source extends [] ? MergeTupleAndArrayType : [ MergeDeepArrayOrTupleElements, FirstArrayElement, Options>, ...DoMergeDeepTupleAndTupleRecursive, ArrayTail, DestinationRestType, SourceRestType, Options>, ]; /** Merge two tuples recursively taking into account a possible rest element. */ type MergeDeepTupleAndTupleRecursive< Destination extends UnknownArrayOrTuple, Source extends UnknownArrayOrTuple, Options extends MergeDeepInternalOptions, > = [ ...DoMergeDeepTupleAndTupleRecursive, OmitRestType, PickRestTypeFlat, PickRestTypeFlat, Options>, ...MergeDeepArrayOrTupleElements, PickRestType, Options>, ]; /** Merge an array type with a tuple recursively. */ type MergeTupleAndArrayType< Tuple extends UnknownArrayOrTuple, ArrayType, Options extends MergeDeepInternalOptions, > = Tuple extends [] ? Tuple : [ MergeDeepArrayOrTupleElements, ArrayType, Options>, ...MergeTupleAndArrayType, ArrayType, Options>, ]; /** Merge an array into a tuple recursively taking into account a possible rest element. */ type MergeDeepTupleAndArrayRecursive< Destination extends UnknownArrayOrTuple, Source extends UnknownArrayOrTuple, Options extends MergeDeepInternalOptions, > = [ ...MergeTupleAndArrayType, Source[number], Options>, ...MergeDeepArrayOrTupleElements, PickRestType, Options>, ]; /** Merge a tuple with an array type recursively. */ type MergeArrayTypeAndTuple< ArrayType, Tuple extends UnknownArrayOrTuple, Options extends MergeDeepInternalOptions, > = Tuple extends [] ? Tuple : [ MergeDeepArrayOrTupleElements, Options>, ...MergeArrayTypeAndTuple, Options>, ]; /** Merge a tuple into an array recursively taking into account a possible rest element. */ type MergeDeepArrayAndTupleRecursive< Destination extends UnknownArrayOrTuple, Source extends UnknownArrayOrTuple, Options extends MergeDeepInternalOptions, > = [ ...MergeArrayTypeAndTuple, Options>, ...MergeDeepArrayOrTupleElements, PickRestType, Options>, ]; /** Merge mode for array/tuple elements. */ type ArrayMergeMode = 'spread' | 'replace'; /** Test if it should spread top-level arrays. */ type ShouldSpread = Options['spreadTopLevelArrays'] extends false ? Options['arrayMergeMode'] extends 'spread' ? true : false : true; /** Merge two arrays/tuples according to the chosen {@link MergeDeepOptions.arrayMergeMode arrayMergeMode} option. */ type DoMergeArrayOrTuple< Destination extends UnknownArrayOrTuple, Source extends UnknownArrayOrTuple, Options extends MergeDeepInternalOptions, > = ShouldSpread extends true ? Array[number] | Exclude[number]> : Source; // 'replace' /** Merge two arrays recursively. If the two arrays are multi-level, we merge deeply, otherwise we merge the first level only. Note: The `[number]` accessor is used to test the type of the second level. */ type MergeDeepArrayRecursive< Destination extends UnknownArrayOrTuple, Source extends UnknownArrayOrTuple, Options extends MergeDeepInternalOptions, > = Destination[number] extends UnknownArrayOrTuple ? Source[number] extends UnknownArrayOrTuple ? Array> : DoMergeArrayOrTuple : Destination[number] extends UnknownRecord ? Source[number] extends UnknownRecord ? Array>> : DoMergeArrayOrTuple : DoMergeArrayOrTuple; /** Merge two array/tuple recursively by selecting one of the four strategies according to the type of inputs. - tuple/tuple - tuple/array - array/tuple - array/array */ type MergeDeepArrayOrTupleRecursive< Destination extends UnknownArrayOrTuple, Source extends UnknownArrayOrTuple, Options extends MergeDeepInternalOptions, > = IsBothExtends extends true ? MergeDeepTupleAndTupleRecursive : Destination extends NonEmptyTuple ? MergeDeepTupleAndArrayRecursive : Source extends NonEmptyTuple ? MergeDeepArrayAndTupleRecursive : MergeDeepArrayRecursive; /** Merge two array/tuple according to {@link MergeDeepOptions.recurseIntoArrays recurseIntoArrays} option. */ type MergeDeepArrayOrTuple< Destination extends UnknownArrayOrTuple, Source extends UnknownArrayOrTuple, Options extends MergeDeepInternalOptions, > = Options['recurseIntoArrays'] extends true ? MergeDeepArrayOrTupleRecursive : DoMergeArrayOrTuple; /** Try to merge two objects or two arrays/tuples recursively into a new type or return the default value. */ type MergeDeepOrReturn< DefaultType, Destination, Source, Options extends MergeDeepInternalOptions, > = SimplifyDeep<[undefined] extends [Destination | Source] ? DefaultType : Destination extends UnknownRecord ? Source extends UnknownRecord ? MergeDeepRecord : DefaultType : Destination extends UnknownArrayOrTuple ? Source extends UnknownArrayOrTuple ? MergeDeepArrayOrTuple> : DefaultType : DefaultType>; /** MergeDeep options. @see {@link MergeDeep} */ export type MergeDeepOptions = { /** Merge mode for array and tuple. 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: - `replace`: Replaces the destination value by the source value. This is the default mode. - `spread`: Spreads the destination and the source values. See {@link MergeDeep} for usages and examples. Note: Top-level arrays and tuples are always spread. @default 'spread' */ arrayMergeMode?: ArrayMergeMode; /** Whether to affect the individual elements of arrays and tuples. If this option is set to `true` the following rules are applied: - If the source does not contain the key, the value of the destination is returned. - If the source contains the key and the destination does not contain the key, the value of the source is returned. - If both contain the key, try to merge according to the chosen {@link MergeDeepOptions.arrayMergeMode arrayMergeMode} or return the source if unable to merge. @default false */ recurseIntoArrays?: boolean; }; /** Internal options. */ type MergeDeepInternalOptions = Merge; /** Merge default and internal options with user provided options. */ type DefaultMergeDeepOptions = Merge<{ arrayMergeMode: 'replace'; recurseIntoArrays: false; spreadTopLevelArrays: true; }, Options>; /** This utility selects the correct entry point with the corresponding default options. This avoids re-merging the options at each iteration. */ type MergeDeepWithDefaultOptions = SimplifyDeep< [undefined] extends [Destination | Source] ? never : Destination extends UnknownRecord ? Source extends UnknownRecord ? MergeDeepRecord> : never : Destination extends UnknownArrayOrTuple ? Source extends UnknownArrayOrTuple ? MergeDeepArrayOrTuple> : never : never >; /** Merge two objects or two arrays/tuples recursively into a new type. - Properties that only exist in one object are copied into the new object. - Properties that exist in both objects are merged if possible or replaced by the one of the source if not. - Top-level arrays and tuples are always spread. - By default, inner arrays and tuples are replaced. See {@link MergeDeepOptions.arrayMergeMode arrayMergeMode} option to change this behaviour. - By default, individual array/tuple elements are not affected. See {@link MergeDeepOptions.recurseIntoArrays recurseIntoArrays} option to change this behaviour. @example ``` import type {MergeDeep} from 'type-fest'; type Foo = { life: number; items: string[]; a: {b: string; c: boolean; d: number[]}; }; interface Bar { name: string; items: number[]; a: {b: number; d: boolean[]}; } type FooBar = MergeDeep; // { // life: number; // name: string; // items: number[]; // a: {b: number; c: boolean; d: boolean[]}; // } type FooBar = MergeDeep; // { // life: number; // name: string; // items: (string | number)[]; // a: {b: number; c: boolean; d: (number | boolean)[]}; // } ``` @example ``` import type {MergeDeep} from 'type-fest'; // Merge two arrays type ArrayMerge = MergeDeep; // => (string | number)[] // Merge two tuples type TupleMerge = MergeDeep<[1, 2, 3], ['a', 'b']>; // => (1 | 2 | 3 | 'a' | 'b')[] // Merge an array into a tuple type TupleArrayMerge = MergeDeep<[1, 2, 3], string[]>; // => (string | 1 | 2 | 3)[] // Merge a tuple into an array type ArrayTupleMerge = MergeDeep; // => (number | 'b' | 'a')[] ``` @example ``` import type {MergeDeep, MergeDeepOptions} from 'type-fest'; type Foo = {foo: 'foo'; fooBar: string[]}; type Bar = {bar: 'bar'; fooBar: number[]}; type FooBar = MergeDeep; // { foo: "foo"; bar: "bar"; fooBar: number[]} type FooBarSpread = MergeDeep; // { foo: "foo"; bar: "bar"; fooBar: (string | number)[]} type FooBarArray = MergeDeep; // (Foo | Bar)[] type FooBarArrayDeep = MergeDeep; // FooBar[] type FooBarArraySpreadDeep = MergeDeep; // FooBarSpread[] type FooBarTupleDeep = MergeDeep<[Foo, true, 42], [Bar, 'life'], {recurseIntoArrays: true}>; // [FooBar, 'life', 42] type FooBarTupleWithArrayDeep = MergeDeep<[Foo[], true], [Bar[], 'life', 42], {recurseIntoArrays: true}>; // [FooBar[], 'life', 42] ``` @example ``` import type {MergeDeep, MergeDeepOptions} from 'type-fest'; function mergeDeep( destination: Destination, source: Source, options?: Options, ): MergeDeep { // Make your implementation ... } ``` @experimental This type is marked as experimental because it depends on {@link ConditionalSimplifyDeep} which itself is experimental. @see {@link MergeDeepOptions} @category Array @category Object @category Utilities */ export type MergeDeep = MergeDeepWithDefaultOptions< SimplifyDeep, SimplifyDeep, Options >;