Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow restricting what types can be passed in as parameters #448

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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 @@
],
};

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 @@
],
];

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 @@

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 @@

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 @@
};

const customizedDeepmerge = deepmergeCustom<
unknown,
{
DeepMergeOthersURI: "KeyPathBasedMerge";
},
Expand Down Expand Up @@ -485,7 +501,7 @@
metaMeta.key ?? metaMeta.id,
],
mergeArrays: (values, utils, meta = []) => {
const idPath = idsPaths.find((idPath) => {

Check warning on line 504 in tests/deepmerge-custom.test.ts

View workflow job for this annotation

GitHub Actions / lint_js

'idPath' is already declared in the upper scope on line 504 column 17
const parentPath = idPath.slice(0, -1);
return (
parentPath.length === meta.length &&
Expand Down Expand Up @@ -519,7 +535,7 @@
);

return [...valuesById.entries()].reduce<unknown[]>(
(carry, [id, values]) => {

Check warning on line 538 in tests/deepmerge-custom.test.ts

View workflow job for this annotation

GitHub Actions / lint_js

'id' is already declared in the upper scope on line 515 column 17

Check warning on line 538 in tests/deepmerge-custom.test.ts

View workflow job for this annotation

GitHub Actions / lint_js

'values' is already declared in the upper scope on line 503 column 23
const childMeta = utils.metaDataUpdater(meta, { id });
return [
...carry,
Expand Down Expand Up @@ -717,9 +733,12 @@
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
Loading