forked from bryan-hunter/deepmerge
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(types): add typedefs from TehShrike#211
closes TehShrike#211
- Loading branch information
Rebecca Stevens
committed
Nov 12, 2020
1 parent
33bc7a7
commit 04e2bf4
Showing
2 changed files
with
304 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,261 @@ | ||
/** | ||
* Deep merge config options. | ||
*/ | ||
export type Options = { | ||
arrayMerge?: ArrayMerge; | ||
clone?: boolean; | ||
customMerge?: ObjectMerge; | ||
isMergeable?: IsMergeable; | ||
}; | ||
|
||
/** | ||
* The default config options. | ||
*/ | ||
export type DefaultOptions = { | ||
arrayMerge: undefined; | ||
clone: true; | ||
customMerge: undefined; | ||
isMergeable: undefined; | ||
}; | ||
|
||
/** | ||
* Deep merge 1 or more types given in an array. | ||
*/ | ||
export type DeepMergeAll< | ||
Ts extends readonly [any, ...any[]], | ||
O extends Options | ||
> = Ts extends readonly [infer T1, ...any[]] | ||
? Ts extends readonly [T1, infer T2, ...infer TRest] | ||
? TRest extends readonly never[] | ||
? DeepMerge<T1, T2, O> | ||
: DeepMerge<T1, DeepMergeAll<[T2, ...TRest], O>, O> | ||
: T1 | ||
: never; | ||
|
||
/** | ||
* Deep merge 2 types. | ||
*/ | ||
export type DeepMerge<T1, T2, O extends Options> = IsSame< | ||
T1, | ||
T2 | ||
> extends true | ||
? T1 | T2 | ||
: And<IsObjectOrArray<T1>, IsObjectOrArray<T2>> extends true | ||
? DeepMergeNonPrimitive<T1, T2, O> | ||
: Leaf<T1, T2>; | ||
|
||
/** | ||
* Deep merge 2 objects (they may be arrays). | ||
*/ | ||
type DeepMergeNonPrimitive<T1, T2, O extends Options> = And< | ||
IsArray<T1>, | ||
IsArray<T2> | ||
> extends true | ||
? DeepMergeArrays<T1, T2, O> | ||
: And<IsObject<T1>, IsObject<T2>> extends true | ||
? DeepMergeObjects<T1, T2, O> | ||
: Leaf<T1, T2>; | ||
|
||
/** | ||
* Deep merge 2 non-array objects. | ||
*/ | ||
type DeepMergeObjects< | ||
T1, | ||
T2, | ||
O extends Options | ||
> = FlatternAlias< | ||
// @see https://github.com/microsoft/TypeScript/issues/41448 | ||
{ | ||
-readonly [K in keyof T1]: DeepMergeObjectProps< | ||
ValueOfKey<T1, K>, | ||
ValueOfKey<T2, K>, | ||
O | ||
>; | ||
} & | ||
{ | ||
-readonly [K in keyof T2]: DeepMergeObjectProps< | ||
ValueOfKey<T1, K>, | ||
ValueOfKey<T2, K>, | ||
O | ||
>; | ||
} | ||
>; | ||
|
||
/** | ||
* Deep merge 2 types that are known to be properties of an object being deeply | ||
* merged. | ||
*/ | ||
type DeepMergeObjectProps<T1, T2, O extends Options> = Or< | ||
IsUndefinedOrNever<T1>, | ||
IsUndefinedOrNever<T2> | ||
> extends true | ||
? Leaf<T1, T2> | ||
: GetOption<O, "isMergeable"> extends undefined | ||
? GetOption<O, "customMerge"> extends undefined | ||
? DeepMerge<T1, T2, O> | ||
: DeepMergeObjectPropsCustom<T1, T2, O> | ||
: MaybeLeaf<T1, T2>; | ||
|
||
/** | ||
* Deep merge 2 types that are known to be properties of an object being deeply | ||
* merged and where a "customMerge" function has been provided. | ||
*/ | ||
type DeepMergeObjectPropsCustom< | ||
T1, | ||
T2, | ||
O extends Options | ||
> = ReturnType<NonNullable<GetOption<O, "customMerge">>> extends undefined | ||
? DeepMerge<T1, T2, O> | ||
: undefined extends ReturnType<NonNullable<GetOption<O, "customMerge">>> | ||
? Or<IsArray<T1>, IsArray<T2>> extends true | ||
? And<IsArray<T1>, IsArray<T2>> extends true | ||
? DeepMergeArrays<T1, T2, O> | ||
: Leaf<T1, T2> | ||
: | ||
| DeepMerge<T1, T2, O> | ||
| ReturnType< | ||
NonNullable< | ||
ReturnType<NonNullable<GetOption<O, "customMerge">>> | ||
> | ||
> | ||
: ReturnType< | ||
NonNullable<ReturnType<NonNullable<GetOption<O, "customMerge">>>> | ||
>; | ||
|
||
/** | ||
* Deep merge 2 arrays. | ||
*/ | ||
type DeepMergeArrays< | ||
T1, | ||
T2, | ||
O extends Options | ||
> = T1 extends readonly [...infer E1] | ||
? T2 extends readonly [...infer E2] | ||
? GetOption<O, "arrayMerge"> extends undefined | ||
? [...E1, ...E2] | ||
: ReturnType<NonNullable<GetOption<O, "arrayMerge">>> | ||
: never | ||
: never; | ||
|
||
/** | ||
* Get the leaf type from 2 types that can't be merged. | ||
*/ | ||
type Leaf<T1, T2> = IsNever<T2> extends true | ||
? T1 | ||
: IsNever<T1> extends true | ||
? T2 | ||
: IsUndefinedOrNever<T2> extends true | ||
? T1 | ||
: T2; | ||
|
||
/** | ||
* Get the leaf type from 2 types that might not be able to be merged. | ||
*/ | ||
type MaybeLeaf<T1, T2> = Or< | ||
Or<IsUndefinedOrNever<T1>, IsUndefinedOrNever<T2>>, | ||
Not<And<IsObjectOrArray<T1>, IsObjectOrArray<T2>>> | ||
> extends true | ||
? Leaf<T1, T2> | ||
// TODO: Handle case where return type of "isMergeable" is a typeguard. If it is we can do better than just "unknown". | ||
: unknown; | ||
|
||
/** | ||
* Flatten a complex type such as a union or intersection of objects into a | ||
* single object. | ||
*/ | ||
type FlatternAlias<T> = {} & { [P in keyof T]: T[P] }; | ||
|
||
/** | ||
* Get the value of the given key in the given object. | ||
*/ | ||
type ValueOfKey<T, K> = K extends keyof T ? T[K] : never; | ||
|
||
/** | ||
* Safely test whether or not the first given types extends the second. | ||
* | ||
* Needed in particular for testing if a type is "never". | ||
*/ | ||
type Is<T1, T2> = [T1] extends [T2] ? true : false; | ||
|
||
/** | ||
* Safely test whether or not the given type is "never". | ||
*/ | ||
type IsNever<T> = Is<T, never>; | ||
|
||
/** | ||
* Is the given type undefined or never? | ||
*/ | ||
type IsUndefinedOrNever<T> = Is<T, undefined>; | ||
|
||
/** | ||
* Returns whether or not the give two types are the same. | ||
*/ | ||
type IsSame<T1, T2> = Is<T1, T2> extends true ? Is<T2, T1> : false; | ||
|
||
/** | ||
* Returns whether or not the given type an object (arrays are objects). | ||
*/ | ||
type IsObjectOrArray<T> = And<Not<IsNever<T>>, T extends object ? true : false>; | ||
|
||
/** | ||
* Returns whether or not the given type a non-array object. | ||
*/ | ||
type IsObject<T> = And<IsObjectOrArray<T>, Not<IsArray<T>>>; | ||
|
||
/** | ||
* Returns whether or not the given type an array. | ||
*/ | ||
type IsArray<T> = And< | ||
Not<IsNever<T>>, | ||
T extends ReadonlyArray<any> ? true : false | ||
>; | ||
|
||
/** | ||
* And operator for types. | ||
*/ | ||
type And<T1 extends boolean, T2 extends boolean> = T1 extends false | ||
? false | ||
: T2; | ||
|
||
/** | ||
* Or operator for types. | ||
*/ | ||
type Or<T1 extends boolean, T2 extends boolean> = T1 extends true ? true : T2; | ||
|
||
/** | ||
* Not operator for types. | ||
*/ | ||
type Not<T extends boolean> = T extends true ? false : true; | ||
|
||
/** | ||
* A function that merges any 2 arrays. | ||
*/ | ||
type ArrayMerge = ( | ||
target: Array<any>, | ||
source: Array<any>, | ||
options: Required<Options> | ||
) => any; | ||
|
||
/** | ||
* A function that merges any 2 non-arrays objects. | ||
*/ | ||
type ObjectMerge = ( | ||
key: string, | ||
options: Required<Options> | ||
) => | ||
| ((target: any, source: any, options?: Options) => any) | ||
| undefined; | ||
|
||
/** | ||
* A function that determins if any non-array object is mergable. | ||
*/ | ||
type IsMergeable = (value: any) => boolean; | ||
|
||
/** | ||
* Get the type of a given config option, defaulting to the default type if it | ||
* wasn't given. | ||
*/ | ||
type GetOption< | ||
O extends Options, | ||
K extends keyof Options | ||
> = undefined extends O[K] ? DefaultOptions[K] : O[K]; |