Skip to content

Commit

Permalink
fix: improve typings
Browse files Browse the repository at this point in the history
  • Loading branch information
Rebecca Stevens committed Nov 10, 2020
1 parent 8e70dd0 commit c456187
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 26 deletions.
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"rollup-plugin-node-resolve": "^5.2.0",
"tape": "^4.11.0",
"ts-node": "7.0.1",
"typescript": "^3.9.3",
"typescript": "^4.1.1-rc",
"uglify-js": "^3.6.1"
},
"license": "MIT",
Expand Down
179 changes: 157 additions & 22 deletions types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,167 @@
// Minimum TypeScript Version: 4.2
// TODO: Can be reduce to version 4.1 upon it's release.

type DeepMergeAll<
Ts extends readonly [any, ...any[]],
Options extends deepmerge.Options
> = Ts extends readonly [infer T1, ...any[]]
? Ts extends readonly [any, infer T2, ...infer TRest]
? TRest extends readonly never[]
? DeepMerge<T1, T2, Options>
: DeepMerge<T1, DeepMergeAll<[T2, ...TRest], Options>, Options>
: T1
: never;

type DeepMerge<T1, T2, Options extends deepmerge.Options> = IsSame<
T1,
T2
> extends true
? T1 | T2
: IsObjectOrArray<T1> extends true
? IsObjectOrArray<T2> extends true
? DeepMergeNonPrimitive<T1, T2, Options>
: MergeLeafs<T1, T2>
: MergeLeafs<T1, T2>;

type DeepMergeNonPrimitive<
T1,
T2,
Options extends deepmerge.Options
> = ShouldMergeArrays<T1, T2> extends true
? DeepMergeArrays<T1, T2, Options>
: DeepMergeObjects<T1, T2, Options>;

type DeepMergeObjects<
T1,
T2,
Options extends deepmerge.Options
> = ShouldMergeObjects<T1, T2> extends true
? DeepMergeObjectProps<T1, T2, Options>
: MergeLeafs<T1, T2>;

// @see https://github.com/microsoft/TypeScript/issues/41448
type DeepMergeObjectProps<
T1,
T2,
Options extends deepmerge.Options
> = FlatternAlias<
{
-readonly [K in keyof T1]: Options["customMerge"] extends undefined
? DeepMerge<ValueOfKey<T1, K>, ValueOfKey<T2, K>, Options>
: ReturnType<NonNullable<Options["customMerge"]>> extends undefined
? DeepMerge<ValueOfKey<T1, K>, ValueOfKey<T2, K>, Options>
: ReturnType<
NonNullable<ReturnType<NonNullable<Options["customMerge"]>>>
>;
} &
{
-readonly [K in keyof T2]: Options["customMerge"] extends undefined
? DeepMerge<ValueOfKey<T1, K>, ValueOfKey<T2, K>, Options>
: ReturnType<NonNullable<Options["customMerge"]>> extends undefined
? DeepMerge<ValueOfKey<T1, K>, ValueOfKey<T2, K>, Options>
: ReturnType<
NonNullable<ReturnType<NonNullable<Options["customMerge"]>>>
>;
}
>;

type DeepMergeArrays<
T1,
T2,
Options extends deepmerge.Options
> = T1 extends readonly [...infer E1]
? T2 extends readonly [...infer E2]
? Options["arrayMerge"] extends undefined
? [...E1, ...E2]
: ReturnType<NonNullable<Options["arrayMerge"]>>
: never
: never;

type MergeLeafs<T1, T2> = Is<T2, never> extends true
? T1
: Is<T1, never> extends true
? T2
: Is<T2, undefined> extends true
? T1
: T2;

type FlatternAlias<T> = T extends any
? {
[K in keyof T]: T[K];
}
: never;

type ValueOfKey<T, K> = K extends keyof T ? T[K] : never;
// Is object AND NOT ('never' or 'array')
type IsObject<T> = [T] extends [never] ? false : T extends unknown[] ? false : [T] extends [object] ? true : false;
type ShouldMergeProps<T1, T2> = IsObject<T1> extends false ? false : IsObject<T2> extends false ? false : true;
type DeepMerge<T1, T2> = ShouldMergeProps<T1, T2> extends true
? {
[K in keyof T1 | keyof T2]: DeepMerge<ValueOfKey<T1, K>, ValueOfKey<T2, K>>;
}
: T1 | T2;

type Is<T1, T2> = [T1] extends [T2] ? true : false;

type IsSame<T1, T2> = Is<T1, T2> extends true ? Is<T2, T1> : false;

type IsObjectOrArray<T> = Is<T, never> extends true ? false : Is<T, object>;

type IsObject<T> = Is<T, ReadonlyArray<any>> extends true
? false
: IsObjectOrArray<T>;

type ShouldMergeObjects<T1, T2> = IsObject<T1> extends true
? IsObject<T2>
: false;

type ShouldMergeArrays<T1, T2> = Is<T1, ReadonlyArray<any>> extends true
? Is<T2, ReadonlyArray<any>>
: false;

type ArrayMerge = (
target: Array<any>,
source: Array<any>,
options: Required<deepmerge.Options>
) => any;

type ObjectMerge = (
key: string,
options: Required<deepmerge.Options>
) =>
| ((target: any, source: any, options?: deepmerge.Options) => any)
| undefined;

type IsMergeable = (value: any) => boolean;

type DefaultOptions = {
arrayMerge: undefined;
clone: true;
customMerge: undefined;
isMergeableObject: undefined;
};

/**
* ## JS Object Deep Merge
* look more info in https://github.com/TehShrike/deepmerge
* @param x the first obj
* @param y the second obj
* @param options deepmerge option
*/
declare function deepmerge<T1 extends object, T2 extends object>(x: T1, y: T2, options?: deepmerge.Options): DeepMerge<T1, T2>
* Deeply merge two objects.
*
* @param target the first obj
* @param source the second obj
* @param options deepmerge option
*/
declare function deepmerge<
T1 extends object,
T2 extends object,
Options extends deepmerge.Options = DefaultOptions
>(target: T1, source: T2, options?: Options): DeepMerge<T1, T2, Options>;

declare namespace deepmerge {
export interface Options {
arrayMerge?(target: any[], source: any[], options?: Options): any[];
export type Options = {
arrayMerge?: ArrayMerge;
clone?: boolean;
customMerge?: (key: string, options?: Options) => ((x: any, y: any) => any) | undefined;
isMergeableObject?(value: object): boolean;
}
customMerge?: ObjectMerge;
isMergeableObject?: IsMergeable;
};

export function all (objects: object[], options?: Options): object;
export function all<T> (objects: Partial<T>[], options?: Options): T;
export function all<
Ts extends readonly [object, ...object[]],
O extends Options = DefaultOptions
>(objects: [...Ts], options?: O): DeepMergeAll<Ts, O>;
export function all(
objects: ReadonlyArray<object>,
options?: Options
): object;
}

export = deepmerge;

0 comments on commit c456187

Please sign in to comment.