Skip to content

Commit

Permalink
feat: allow restricting what types can be passed in as parameters
Browse files Browse the repository at this point in the history
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
  • Loading branch information
RebeccaStevens committed May 19, 2024
1 parent f2f5956 commit 69e9ba3
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 37 deletions.
52 changes: 45 additions & 7 deletions docs/deepmergeCustom.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,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<Foo>({}); // <-- 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
Expand All @@ -114,9 +144,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,
});

Expand Down Expand Up @@ -157,9 +190,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)) {
Expand Down Expand Up @@ -269,6 +305,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";
Expand Down Expand Up @@ -354,7 +392,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`.\
Expand Down
12 changes: 7 additions & 5 deletions src/deepmerge-into.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,12 @@ export function deepmergeInto<
*
* @param options - The options on how to customize the merge function.
*/
export function deepmergeIntoCustom(
export function deepmergeIntoCustom<BaseTs = unknown>(
options: DeepMergeIntoOptions<
DeepMergeBuiltInMetaData,
DeepMergeBuiltInMetaData
>,
): <Target extends object, Ts extends ReadonlyArray<unknown>>(
): <Target extends object, Ts extends ReadonlyArray<BaseTs>>(
target: Target,
...objects: Ts
) => void;
Expand All @@ -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<MetaData, MetaMetaData>,
rootMetaData?: MetaData,
): <Target extends object, Ts extends ReadonlyArray<unknown>>(
): <Target extends object, Ts extends ReadonlyArray<BaseTs>>(
target: Target,
...objects: Ts
) => void;

export function deepmergeIntoCustom<
BaseTs,
MetaData,
MetaMetaData extends DeepMergeBuiltInMetaData,
>(
options: DeepMergeIntoOptions<MetaData, MetaMetaData>,
rootMetaData?: MetaData,
): <Target extends object, Ts extends ReadonlyArray<unknown>>(
): <Target extends object, Ts extends ReadonlyArray<BaseTs>>(
target: Target,
...objects: Ts
) => void {
Expand Down
15 changes: 9 additions & 6 deletions src/deepmerge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ export function deepmerge<Ts extends Readonly<ReadonlyArray<unknown>>>(
* @param options - The options on how to customize the merge function.
*/
export function deepmergeCustom<
PMF extends Partial<DeepMergeMergeFunctionsURIs>,
BaseTs = unknown,
PMF extends Partial<DeepMergeMergeFunctionsURIs> = {},
>(
options: DeepMergeOptions<DeepMergeBuiltInMetaData, DeepMergeBuiltInMetaData>,
): <Ts extends ReadonlyArray<unknown>>(
): <Ts extends ReadonlyArray<BaseTs>>(
...objects: Ts
) => DeepMergeHKT<
Ts,
Expand All @@ -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<DeepMergeMergeFunctionsURIs>,
MetaData,
BaseTs = unknown,
PMF extends Partial<DeepMergeMergeFunctionsURIs> = {},
MetaData = DeepMergeBuiltInMetaData,
MetaMetaData extends DeepMergeBuiltInMetaData = DeepMergeBuiltInMetaData,
>(
options: DeepMergeOptions<MetaData, MetaMetaData>,
rootMetaData?: MetaData,
): <Ts extends ReadonlyArray<unknown>>(
): <Ts extends ReadonlyArray<BaseTs>>(
...objects: Ts
) => DeepMergeHKT<Ts, GetDeepMergeMergeFunctionsURIs<PMF>, MetaData>;

export function deepmergeCustom<
BaseTs,
PMF extends Partial<DeepMergeMergeFunctionsURIs>,
MetaData,
MetaMetaData extends DeepMergeBuiltInMetaData,
>(
options: DeepMergeOptions<MetaData, MetaMetaData>,
rootMetaData?: MetaData,
): <Ts extends ReadonlyArray<unknown>>(
): <Ts extends ReadonlyArray<BaseTs>>(
...objects: Ts
) => DeepMergeHKT<Ts, GetDeepMergeMergeFunctionsURIs<PMF>, MetaData> {
/**
Expand Down
57 changes: 38 additions & 19 deletions tests/deepmerge-custom.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand Down Expand Up @@ -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[] = [];
Expand Down Expand Up @@ -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),
Expand All @@ -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,
});

Expand All @@ -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;
Expand Down Expand Up @@ -387,6 +402,7 @@ describe("deepmergeCustom", () => {
};

const customizedDeepmerge = deepmergeCustom<
unknown,
{
DeepMergeOthersURI: "KeyPathBasedMerge";
},
Expand Down Expand Up @@ -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) => {
Expand Down

0 comments on commit 69e9ba3

Please sign in to comment.