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 #314

Closed
wants to merge 1 commit into from
Closed
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: 46 additions & 6 deletions docs/deepmergeCustom.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,38 @@ 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 too.\
Expand All @@ -108,9 +140,12 @@ Here's a simple example that creates a custom deepmerge function that does not m
import type { DeepMergeLeafURI } from "deepmerge-ts";
import { 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 @@ -140,9 +175,12 @@ Here's an example of creating a custom deepmerge function that amalgamates dates
import type { DeepMergeLeaf, DeepMergeMergeFunctionURItoKind, DeepMergeMergeFunctionsURIs } from "deepmerge-ts";
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 @@ -232,6 +270,8 @@ import type { DeepMergeLeaf, DeepMergeMergeFunctionURItoKind, DeepMergeMergeFunc
import { deepmergeCustom } 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
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 @@ -38,10 +38,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 @@ -56,24 +57,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 @@ -74,9 +74,12 @@ test("custom merge arrays", (t) => {
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 @@ -140,10 +143,13 @@ test("custom merge arrays of records", (t) => {
],
};

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 result: unknown[] = [];
Expand Down Expand Up @@ -214,9 +220,12 @@ test("custom merge records", (t) => {
],
];

const customizedDeepmerge = deepmergeCustom<{
DeepMergeRecordsURI: "CustomRecords3";
}>({
const customizedDeepmerge = deepmergeCustom<
unknown,
{
DeepMergeRecordsURI: "CustomRecords3";
}
>({
mergeRecords: (records, utils, meta) =>
Object.entries(
utils.defaultMergeFunctions.mergeRecords(records, utils, meta)
Expand Down Expand Up @@ -246,9 +255,12 @@ test("custom don't merge arrays", (t) => {

const expected = { foo: [7, 8] } as const;

const customizedDeepmerge = deepmergeCustom<{
DeepMergeArraysURI: DeepMergeLeafURI;
}>({
const customizedDeepmerge = deepmergeCustom<
unknown,
{
DeepMergeArraysURI: DeepMergeLeafURI;
}
>({
mergeArrays: false,
});

Expand Down Expand Up @@ -282,9 +294,12 @@ test("custom merge dates", (t) => {

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 @@ -364,6 +379,7 @@ test("key path based merging", (t) => {
};

const customizedDeepmerge = deepmergeCustom<
unknown,
{
DeepMergeOthersURI: "KeyPathBasedMerge";
},
Expand Down Expand Up @@ -711,9 +727,12 @@ test("merging class object as record", (t) => {
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
7 changes: 5 additions & 2 deletions tests/deepmerge-into-custom.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* eslint-disable @typescript-eslint/consistent-type-definitions, @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-unused-vars */

import test from "ava";
import _ from "lodash";
Expand Down Expand Up @@ -280,7 +280,10 @@ test("key path based merging", (t) => {
bar: { baz: "special merge", qux: 9 },
};

const customizedDeepmerge = deepmergeIntoCustom<ReadonlyArray<PropertyKey>>({
const customizedDeepmerge = deepmergeIntoCustom<
unknown,
ReadonlyArray<PropertyKey>
>({
metaDataUpdater: (previousMeta, metaMeta) => {
if (metaMeta.key === undefined) {
return previousMeta ?? [];
Expand Down
Loading
Loading