From f54235e12c46bccc9d60975616ebc0803027dae2 Mon Sep 17 00:00:00 2001 From: Rebecca Stevens Date: Fri, 10 May 2024 18:18:32 +1200 Subject: [PATCH] feat: allow restricting what types can be passed in as parameters BREAKING CHANGE: The order of the generics of `deepmergeCustom` and `deepmergeIntoCustom` have changed. If you are passing generics to these functions you need to update them. fix #305 --- docs/deepmergeCustom.md | 52 ++++++++++++++++++++++++++----- src/deepmerge-into.ts | 12 ++++--- src/deepmerge.ts | 15 +++++---- tests/deepmerge-custom.test.ts | 57 ++++++++++++++++++++++------------ 4 files changed, 99 insertions(+), 37 deletions(-) diff --git a/docs/deepmergeCustom.md b/docs/deepmergeCustom.md index 6c443a036..e24aec029 100644 --- a/docs/deepmergeCustom.md +++ b/docs/deepmergeCustom.md @@ -93,6 +93,36 @@ const customizedDeepmerge = deepmergeCustom({ }); ``` +## Restricting the Parameter Types + +By default, anything can be passed into a deepmerge function. +If your custom version relies on certain input types, you can restrict the parameters that can be passed. +This is done with the first generic that can be passed into `deepmergeCustom`. + +For example: + +```ts +import { deepmergeCustom } from "deepmerge-ts"; + +type Foo = { + foo: { + bar: string; + baz?: number; + }; +}; + +const customDeepmerge = deepmergeCustom({}); // <-- Only parameters of type Foo to be passed into the function. + +const x = { foo: { bar: "bar-1", baz: 3 } }; +const y = { foo: { bar: "bar-2" } }; + +customDeepmerge(x, y); // => { foo: { bar: "bar-2", baz: 3 } } + +const z = { bar: "bar" }; + +customDeepmerge(x, z); // Argument of type '{ bar: string; }' is not assignable to parameter of type 'Foo'. +``` + ## Customizing the Return Type If you want to customize the deepmerge function, you probably also want the return type of the result to be correct too.\ @@ -107,9 +137,12 @@ Here's a simple example that creates a custom deepmerge function that does not m ```ts import { type DeepMergeLeafURI, deepmergeCustom } from "deepmerge-ts"; -const customDeepmerge = deepmergeCustom<{ - DeepMergeArraysURI: DeepMergeLeafURI; // <-- Needed for correct output type. -}>({ +const customDeepmerge = deepmergeCustom< + unknown, // <-- Types that can be passed into the function. + { + DeepMergeArraysURI: DeepMergeLeafURI; // <-- Needed for correct output type. + } +>({ mergeArrays: false, }); @@ -147,9 +180,12 @@ import { deepmergeCustom, } from "deepmerge-ts"; -const customizedDeepmerge = deepmergeCustom<{ - DeepMergeOthersURI: "MyDeepMergeDatesURI"; // <-- Needed for correct output type. -}>({ +const customizedDeepmerge = deepmergeCustom< + unknown, // <-- Types that can be passed into the function. + { + DeepMergeOthersURI: "MyDeepMergeDatesURI"; // <-- Needed for correct output type. + } +>({ mergeOthers: (values, utils, meta) => { // If every value is a date, the return the amalgamated array. if (values.every((value) => value instanceof Date)) { @@ -254,6 +290,8 @@ import { } from "deepmerge-ts"; const customizedDeepmerge = deepmergeCustom< + // Allow any value to be passed into the function. + unknown, // Change the return type of `mergeOthers`. { DeepMergeOthersURI: "KeyPathBasedMerge"; @@ -337,7 +375,7 @@ The signature of merging functions for `deepmergeIntoCustom` looks like this: values: Ts, utils: U, meta: M | undefined, -) => undefined | symbol; +) => symbol | undefined; ``` Instead of returning a value like with `deepmergeCustom`'s merge functions, mutations should be made to `target.value`.\ diff --git a/src/deepmerge-into.ts b/src/deepmerge-into.ts index 6c9c9d13c..f5a095a6e 100644 --- a/src/deepmerge-into.ts +++ b/src/deepmerge-into.ts @@ -66,12 +66,12 @@ export function deepmergeInto< * * @param options - The options on how to customize the merge function. */ -export function deepmergeIntoCustom( +export function deepmergeIntoCustom( options: DeepMergeIntoOptions< DeepMergeBuiltInMetaData, DeepMergeBuiltInMetaData >, -): >( +): >( target: Target, ...objects: Ts ) => void; @@ -83,23 +83,25 @@ export function deepmergeIntoCustom( * @param rootMetaData - The meta data passed to the root items' being merged. */ export function deepmergeIntoCustom< - MetaData, + BaseTs = unknown, + MetaData = DeepMergeBuiltInMetaData, MetaMetaData extends DeepMergeBuiltInMetaData = DeepMergeBuiltInMetaData, >( options: DeepMergeIntoOptions, rootMetaData?: MetaData, -): >( +): >( target: Target, ...objects: Ts ) => void; export function deepmergeIntoCustom< + BaseTs, MetaData, MetaMetaData extends DeepMergeBuiltInMetaData, >( options: DeepMergeIntoOptions, rootMetaData?: MetaData, -): >( +): >( target: Target, ...objects: Ts ) => void { diff --git a/src/deepmerge.ts b/src/deepmerge.ts index c4338c42f..1b9741f40 100644 --- a/src/deepmerge.ts +++ b/src/deepmerge.ts @@ -37,10 +37,11 @@ export function deepmerge>>( * @param options - The options on how to customize the merge function. */ export function deepmergeCustom< - PMF extends Partial, + BaseTs = unknown, + PMF extends Partial = {}, >( options: DeepMergeOptions, -): >( +): >( ...objects: Ts ) => DeepMergeHKT< Ts, @@ -55,24 +56,26 @@ export function deepmergeCustom< * @param rootMetaData - The meta data passed to the root items' being merged. */ export function deepmergeCustom< - PMF extends Partial, - MetaData, + BaseTs = unknown, + PMF extends Partial = {}, + MetaData = DeepMergeBuiltInMetaData, MetaMetaData extends DeepMergeBuiltInMetaData = DeepMergeBuiltInMetaData, >( options: DeepMergeOptions, rootMetaData?: MetaData, -): >( +): >( ...objects: Ts ) => DeepMergeHKT, MetaData>; export function deepmergeCustom< + BaseTs, PMF extends Partial, MetaData, MetaMetaData extends DeepMergeBuiltInMetaData, >( options: DeepMergeOptions, rootMetaData?: MetaData, -): >( +): >( ...objects: Ts ) => DeepMergeHKT, MetaData> { /** diff --git a/tests/deepmerge-custom.test.ts b/tests/deepmerge-custom.test.ts index da5a2d50f..584d8c460 100644 --- a/tests/deepmerge-custom.test.ts +++ b/tests/deepmerge-custom.test.ts @@ -163,9 +163,12 @@ describe("deepmergeCustom", () => { foo: { bar: { baz: { qux: ["1a", "2b", "3c"] } } }, }; - const customizedDeepmerge = deepmergeCustom<{ - DeepMergeArraysURI: "CustomArrays1"; - }>({ + const customizedDeepmerge = deepmergeCustom< + unknown, + { + DeepMergeArraysURI: "CustomArrays1"; + } + >({ mergeArrays: (arrays) => { const maxLength = Math.max(...arrays.map((array) => array.length)); @@ -216,10 +219,13 @@ describe("deepmergeCustom", () => { ], }; - const customizedDeepmerge = deepmergeCustom<{ - DeepMergeArraysURI: "CustomArrays2"; - DeepMergeOthersURI: "CustomOthers2"; - }>({ + const customizedDeepmerge = deepmergeCustom< + unknown, + { + DeepMergeArraysURI: "CustomArrays2"; + DeepMergeOthersURI: "CustomOthers2"; + } + >({ mergeArrays: (arrays, utils) => { const maxLength = Math.max(...arrays.map((array) => array.length)); const m_result: unknown[] = []; @@ -277,9 +283,12 @@ describe("deepmergeCustom", () => { ], ]; - const customizedDeepmerge = deepmergeCustom<{ - DeepMergeRecordsURI: "CustomRecords3"; - }>({ + const customizedDeepmerge = deepmergeCustom< + unknown, + { + DeepMergeRecordsURI: "CustomRecords3"; + } + >({ mergeRecords: (records, utils, meta) => Object.entries( utils.defaultMergeFunctions.mergeRecords(records, utils, meta), @@ -299,9 +308,12 @@ describe("deepmergeCustom", () => { const expected = { foo: [7, 8] } as const; - const customizedDeepmerge = deepmergeCustom<{ - DeepMergeArraysURI: DeepMergeLeafURI; - }>({ + const customizedDeepmerge = deepmergeCustom< + unknown, + { + DeepMergeArraysURI: DeepMergeLeafURI; + } + >({ mergeArrays: false, }); @@ -317,9 +329,12 @@ describe("deepmergeCustom", () => { const expected = { foo: [x.foo, y.foo, z.foo] } as const; - const customizedDeepmerge = deepmergeCustom<{ - DeepMergeOthersURI: "MergeDates1"; - }>({ + const customizedDeepmerge = deepmergeCustom< + unknown, + { + DeepMergeOthersURI: "MergeDates1"; + } + >({ mergeOthers: (values, utils) => { if (values.every((value) => value instanceof Date)) { return values; @@ -387,6 +402,7 @@ describe("deepmergeCustom", () => { }; const customizedDeepmerge = deepmergeCustom< + unknown, { DeepMergeOthersURI: "KeyPathBasedMerge"; }, @@ -717,9 +733,12 @@ describe("deepmergeCustom", () => { foo: false, }; - const customizedDeepmerge = deepmergeCustom<{ - DeepMergeOthersURI: "CustomOthers3"; - }>({ + const customizedDeepmerge = deepmergeCustom< + unknown, + { + DeepMergeOthersURI: "CustomOthers3"; + } + >({ mergeOthers: (values, utils, meta) => { let m_allRecords = true; const records = values.map((v) => {