Skip to content

Commit

Permalink
feat: provide meta data to custom merge functions and allow it to be …
Browse files Browse the repository at this point in the history
…customized

fix #33

BREAKING CHANGE: custom function call signatures have changed
  • Loading branch information
RebeccaStevens committed Feb 14, 2022
1 parent 7ac7526 commit d53d085
Show file tree
Hide file tree
Showing 5 changed files with 271 additions and 69 deletions.
156 changes: 111 additions & 45 deletions src/deepmerge.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {
DeepMergeBuiltInMetaData,
DeepMergeHKT,
DeepMergeArraysDefaultHKT,
DeepMergeMergeFunctionsDefaultURIs,
Expand All @@ -18,18 +19,28 @@ import {
objectHasProperty,
} from "./utils";

const defaultOptions = {
const defaultMergeFunctions = {
mergeMaps,
mergeSets,
mergeArrays,
mergeRecords,
mergeOthers: leaf,
} as const;

/**
* The default function to update meta data.
*/
function defaultMetaDataUpdater<M>(
previousMeta: M,
metaMeta: DeepMergeBuiltInMetaData
): DeepMergeBuiltInMetaData {
return metaMeta;
}

/**
* The default merge functions.
*/
export type DeepMergeMergeFunctionsDefaults = typeof defaultOptions;
export type DeepMergeMergeFunctionsDefaults = typeof defaultMergeFunctions;

/**
* Deeply merge objects.
Expand All @@ -53,7 +64,35 @@ export function deepmerge<Ts extends Readonly<ReadonlyArray<unknown>>>(
export function deepmergeCustom<
PMF extends Partial<DeepMergeMergeFunctionsURIs>
>(
options: DeepMergeOptions
options: DeepMergeOptions<DeepMergeBuiltInMetaData>
): <Ts extends Readonly<ReadonlyArray<unknown>>>(
...objects: Ts
) => DeepMergeHKT<Ts, GetDeepMergeMergeFunctionsURIs<PMF>>;

/**
* Deeply merge two or more objects using the given options and meta data.
*
* @param options - The options on how to customize the merge function.
* @param rootMetaData - The meta data passed to the root items' being merged.
*/
export function deepmergeCustom<
PMF extends Partial<DeepMergeMergeFunctionsURIs>,
MetaData,
MetaMetaData extends Partial<DeepMergeBuiltInMetaData> = DeepMergeBuiltInMetaData
>(
options: DeepMergeOptions<MetaData>,
rootMetaData: MetaData
): <Ts extends Readonly<ReadonlyArray<unknown>>>(
...objects: Ts
) => DeepMergeHKT<Ts, GetDeepMergeMergeFunctionsURIs<PMF>>;

export function deepmergeCustom<
PMF extends Partial<DeepMergeMergeFunctionsURIs>,
MetaData,
MetaMetaData extends Partial<DeepMergeBuiltInMetaData>
>(
options: DeepMergeOptions<MetaData>,
rootMetaData?: MetaData
): <Ts extends Readonly<ReadonlyArray<unknown>>>(
...objects: Ts
) => DeepMergeHKT<Ts, GetDeepMergeMergeFunctionsURIs<PMF>> {
Expand All @@ -64,7 +103,10 @@ export function deepmergeCustom<
...objects: Ts
) => DeepMergeHKT<Ts, GetDeepMergeMergeFunctionsURIs<PMF>>;

const utils = getUtils(options, customizedDeepmerge as CustomizedDeepmerge);
const utils: DeepMergeMergeFunctionUtils<MetaData, MetaMetaData> = getUtils(
options,
customizedDeepmerge as CustomizedDeepmerge
);

/**
* The customized deepmerge function.
Expand All @@ -77,7 +119,13 @@ export function deepmergeCustom<
return objects[0];
}

return mergeUnknowns(objects, utils);
return mergeUnknowns<
ReadonlyArray<unknown>,
typeof utils,
GetDeepMergeMergeFunctionsURIs<PMF>,
MetaData,
MetaMetaData
>(objects, utils, rootMetaData as MetaData);
}

return customizedDeepmerge as CustomizedDeepmerge;
Expand All @@ -88,20 +136,29 @@ export function deepmergeCustom<
*
* @param options - The options the user specified
*/
function getUtils(
options: DeepMergeOptions,
customizedDeepmerge: DeepMergeMergeFunctionUtils["deepmerge"]
): DeepMergeMergeFunctionUtils {
function getUtils<M, MM extends Partial<DeepMergeBuiltInMetaData>>(
options: DeepMergeOptions<M>,
customizedDeepmerge: DeepMergeMergeFunctionUtils<M, MM>["deepmerge"]
): DeepMergeMergeFunctionUtils<M, MM> {
return {
defaultMergeFunctions: defaultOptions,
defaultMergeFunctions,
mergeFunctions: {
...defaultOptions,
...defaultMergeFunctions,
...Object.fromEntries(
Object.entries(options).map(([key, option]) =>
option === false ? [key, leaf] : [key, option]
)
Object.entries(options)
.filter(([key, option]) =>
Object.prototype.hasOwnProperty.call(defaultMergeFunctions, key)
)
.map(([key, option]) =>
option === false ? [key, leaf] : [key, option]
)
),
} as DeepMergeMergeFunctionUtils["mergeFunctions"],
} as DeepMergeMergeFunctionUtils<M, MM>["mergeFunctions"],
metaDataUpdater: (options.metaDataUpdater ??
defaultMetaDataUpdater) as DeepMergeMergeFunctionUtils<
M,
MM
>["metaDataUpdater"],
deepmerge: customizedDeepmerge,
};
}
Expand All @@ -113,9 +170,11 @@ function getUtils(
*/
function mergeUnknowns<
Ts extends Readonly<ReadonlyArray<unknown>>,
U extends DeepMergeMergeFunctionUtils,
MF extends DeepMergeMergeFunctionsURIs
>(values: Ts, utils: U): DeepMergeHKT<Ts, MF> {
U extends DeepMergeMergeFunctionUtils<M, MM>,
MF extends DeepMergeMergeFunctionsURIs,
M,
MM extends Partial<DeepMergeBuiltInMetaData>
>(values: Ts, utils: U, meta: M): DeepMergeHKT<Ts, MF> {
const type = getObjectType(values[0]);

// eslint-disable-next-line functional/no-conditional-statement -- add an early escape for better performance.
Expand All @@ -126,10 +185,11 @@ function mergeUnknowns<
continue;
}

return utils.mergeFunctions.mergeOthers(values, utils) as DeepMergeHKT<
Ts,
MF
>;
return utils.mergeFunctions.mergeOthers(
values,
utils,
meta
) as DeepMergeHKT<Ts, MF>;
}
}

Expand All @@ -139,34 +199,39 @@ function mergeUnknowns<
values as Readonly<
ReadonlyArray<Readonly<Record<PropertyKey, unknown>>>
>,
utils
utils,
meta
) as DeepMergeHKT<Ts, MF>;

case ObjectType.ARRAY:
return utils.mergeFunctions.mergeArrays(
values as Readonly<ReadonlyArray<Readonly<ReadonlyArray<unknown>>>>,
utils
utils,
meta
) as DeepMergeHKT<Ts, MF>;

case ObjectType.SET:
return utils.mergeFunctions.mergeSets(
values as Readonly<ReadonlyArray<Readonly<ReadonlySet<unknown>>>>,
utils
utils,
meta
) as DeepMergeHKT<Ts, MF>;

case ObjectType.MAP:
return utils.mergeFunctions.mergeMaps(
values as Readonly<
ReadonlyArray<Readonly<ReadonlyMap<unknown, unknown>>>
>,
utils
utils,
meta
) as DeepMergeHKT<Ts, MF>;

default:
return utils.mergeFunctions.mergeOthers(values, utils) as DeepMergeHKT<
Ts,
MF
>;
return utils.mergeFunctions.mergeOthers(
values,
utils,
meta
) as DeepMergeHKT<Ts, MF>;
}
}

Expand All @@ -177,9 +242,11 @@ function mergeUnknowns<
*/
function mergeRecords<
Ts extends Readonly<ReadonlyArray<Record<PropertyKey, unknown>>>,
U extends DeepMergeMergeFunctionUtils,
MF extends DeepMergeMergeFunctionsURIs
>(values: Ts, utils: U) {
U extends DeepMergeMergeFunctionUtils<M, MM>,
MF extends DeepMergeMergeFunctionsURIs,
M,
MM extends DeepMergeBuiltInMetaData
>(values: Ts, utils: U, meta: M) {
const result: Record<PropertyKey, unknown> = {};

/* eslint-disable functional/no-loop-statement, functional/no-conditional-statement -- using a loop here is more performant. */
Expand All @@ -195,10 +262,16 @@ function mergeRecords<

// assert(propValues.length > 0);

const updatedMeta = utils.metaDataUpdater(meta, { key } as MM);

result[key] =
propValues.length === 1
? propValues[0]
: mergeUnknowns(propValues, utils);
: mergeUnknowns<ReadonlyArray<unknown>, U, MF, M, MM>(
propValues,
utils,
updatedMeta
);
}

/* eslint-enable functional/no-loop-statement, functional/no-conditional-statement */
Expand All @@ -213,9 +286,8 @@ function mergeRecords<
*/
function mergeArrays<
Ts extends Readonly<ReadonlyArray<Readonly<ReadonlyArray<unknown>>>>,
U extends DeepMergeMergeFunctionUtils,
MF extends DeepMergeMergeFunctionsURIs
>(values: Ts, utils: U) {
>(values: Ts) {
return values.flat() as DeepMergeArraysDefaultHKT<Ts, MF>;
}

Expand All @@ -226,9 +298,8 @@ function mergeArrays<
*/
function mergeSets<
Ts extends Readonly<ReadonlyArray<Readonly<ReadonlySet<unknown>>>>,
U extends DeepMergeMergeFunctionUtils,
MF extends DeepMergeMergeFunctionsURIs
>(values: Ts, utils: U) {
>(values: Ts) {
return new Set(getIterableOfIterables(values)) as DeepMergeSetsDefaultHKT<
Ts,
MF
Expand All @@ -242,9 +313,8 @@ function mergeSets<
*/
function mergeMaps<
Ts extends Readonly<ReadonlyArray<Readonly<ReadonlyMap<unknown, unknown>>>>,
U extends DeepMergeMergeFunctionUtils,
MF extends DeepMergeMergeFunctionsURIs
>(values: Ts, utils: U) {
>(values: Ts) {
return new Map(getIterableOfIterables(values)) as DeepMergeMapsDefaultHKT<
Ts,
MF
Expand All @@ -256,10 +326,6 @@ function mergeMaps<
*
* @param values - The values.
*/
function leaf<
Ts extends Readonly<ReadonlyArray<unknown>>,
U extends DeepMergeMergeFunctionUtils,
MF extends DeepMergeMergeFunctionsURIs
>(values: Ts, utils: U) {
function leaf<Ts extends Readonly<ReadonlyArray<unknown>>>(values: Ts) {
return values[values.length - 1];
}
7 changes: 7 additions & 0 deletions src/types/merging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,10 @@ export type DeepMergeLeaf<Ts extends Readonly<ReadonlyArray<unknown>>> =
: never
: Tail
: never;

/**
* The meta data deepmerge is able to provide.
*/
export type DeepMergeBuiltInMetaData = Readonly<{
key: PropertyKey;
}>;
Loading

0 comments on commit d53d085

Please sign in to comment.