Skip to content

Commit

Permalink
docs: update docs for deepmergeCustom
Browse files Browse the repository at this point in the history
  • Loading branch information
RebeccaStevens committed Sep 13, 2021
1 parent 7c78218 commit 4a4e57b
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 110 deletions.
203 changes: 93 additions & 110 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ yarn add -D deepmerge-ts

## Usage

### Basic Example
### Example using default config

```js
import { deepmerge } from "deepmerge-ts";
Expand Down Expand Up @@ -68,118 +68,101 @@ const y = {
]),
};

const output = {
record: {
prop1: "changed",
prop2: "value2",
prop3: "value3",
},
array: [1, 2, 3, 2, 3, 4],
set: new Set([1, 2, 3, 4]),
map: new Map([
["key1", "value1"],
["key2", "changed"],
["key3", "value3"],
]),
};

deepmerge(x, y); // => output
const merged = deepmerge(x, y);

console.log(merged);

// Prettierfied output:
//
// {
// record: {
// prop1: "changed",
// prop2: "value2",
// prop3: "value3"
// }
// array: (6) [1, 2, 3, 2, 3, 4]
// set: Set(4) {1, 2, 3, 4}
// map: Map(3) {
// "key1" => "value1",
// "key2" => "changed",
// "key3" => "value3"
// }
// }
```

### Simple Customized Example
### Using customized config

```js
import type { DeepMergeLeafURI } from "deepmerge-ts";
import { deepmergeCustom } from "deepmerge-ts";

/**
* Create a custom deepmerge function that does not merge arrays, sets, or maps.
*/
const customDeepmerge = deepmergeCustom<{
DeepMergeArraysURI: DeepMergeLeafURI; // <-- Needed for correct typing information.
DeepMergeSetsURI: DeepMergeLeafURI;
DeepMergeMapsURI: DeepMergeLeafURI;
}>({
mergeArrays: false,
mergeSets: false,
mergeMaps: false,
});

const x = { foo: [1, 2], bar: [3, 4] };
const y = { foo: [5, 6] };

customDeepmerge(x, y); // => { foo: [5, 6], bar: [3, 4] }
```
[See deepmerge custom docs](./docs/deepmergeCustom.md).

### Advanced Customized Example

```ts
import type { DeepMergeMergeFunctionURItoKind, DeepMergeMergeFunctionsURIs } from "deepmerge-ts";
import { deepmergeCustom } from "deepmerge-ts";

declare module "deepmerge-ts" {
interface DeepMergeMergeFunctionURItoKind<
T1,
T2,
MF extends DeepMergeMergeFunctionsURIs
> {
/**
* Define how 2 "other" types are merged.
*/
readonly MyMergeOthersFn: T1 extends Date
? T2 extends Date
? [T1, T2]
: T2 extends Readonly<ReadonlyArray<Date>>
? [T1, ...T2]
: T2
: T1 extends Readonly<ReadonlyArray<Date>>
? T2 extends Date
? [...T1, T2]
: T2
: T2;
}
}

/**
* Create a custom deepmerge function that amalgamates dates into an array.
*/
const customDeepmerge = deepmergeCustom<{
DeepMergeOthersURI: "MyMergeOthersFn";
}>({
/**
* Define how 2 "other" values are merged.
*/
mergeOthers: (val1, val2) => {
// How 2 dates are merged.
if (val1 instanceof Date && val2 instanceof Date) {
return [val1, val2];
}
// How an array of dates and a date are merged.
// This is not needed if only merge 2 values.
if (
Array.isArray(val1) &&
val2 instanceof Date &&
val1.every((val) => val instanceof Date)
) {
return [...val1, val2];
}
// How a date and an array of dates are merged.
// This is not needed if only merge 2 values.
if (
val1 instanceof Date
Array.isArray(val2) &&
val2.every((val) => val instanceof Date)
) {
return [val1, ...val2];
}
// Something else? Return the 2nd value.
return val2;
},
});
## API

const x = { foo: new Date("2020-01-01") };
const y = { foo: new Date("2021-02-02") };
const z = { foo: new Date("2022-03-03") };
### deepmerge(x, y, ...)

customDeepmerge(x, y, z); // => { foo: [Date, Date, Date] }
```
Merges the given inputs together using the default configuration.

#### deepmerge(...inputs)

Merges the array of inputs together using the default configuration.

Note: If `inputs` isn't typed as a tuple then we cannot determin the output type. The output type will simply be `unknown`.

### deepmergeCustom(options)

Generate a customized deepmerge function using the given options. The returned function works just like `deepmerge` except uses the customized configuration.

#### options

The following options can be used to customize the deepmerge function. All these options are optional.

##### mergeRecords

Type: `false | (values: Record<any, unknown>[], utils: DeepMergeMergeFunctionUtils) => unknown`

If false, records won't be merged. If set to a function, that function will be used to merge records.

Note: Records are "vanilla" objects (e.g. `{ foo: "hello", bar: "world" }`).

##### mergeArrays

Type: `false | (values: unknown[][], utils: DeepMergeMergeFunctionUtils) => unknown`

If false, arrays won't be merged. If set to a function, that function will be used to merge arrays.

##### mergeMaps

Type: `false | (values: Map<unknown, unknown>[], utils: DeepMergeMergeFunctionUtils) => unknown`

If false, maps won't be merged. If set to a function, that function will be used to merge maps.

##### mergeSets

Type: `false | (values: Set<unknown>[], utils: DeepMergeMergeFunctionUtils) => unknown`

If false, sets won't be merged. If set to a function, that function will be used to merge sets.

##### mergeOthers

Type: `false | (values: Set<unknown>[], utils: DeepMergeMergeFunctionUtils) => unknown`

If set to a function, that function will be used to merge everything else.

Note: This includes mergining mixed types, such as mergeing a map with an array.

#### DeepMergeMergeFunctionUtils

This is a set of utility functions that are made available to your custom merge functions.

##### mergeFunctions

These are all the merge function being used to perform the deepmerge.\
These will be the custom merge functions you gave, or the default merge functions for options you didn't customize.

##### defaultMergeFunctions

These are all the merge functions that the default, non-customize deepmerge function uses.

##### deepmerge

This is your top level customized deepmerge function.

Note: Be careful when calling this as it is really easy to end up in an initiate loop.
94 changes: 94 additions & 0 deletions docs/deepmergeCustom.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Deepmerge Custom

`deepmergeCustom` allows you to customize the deepmerge function. It is a higher-order function; that is to say it returns a new customized deepmerge function.

## 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.\
Unfortunately however, due to TypeScript limitations, we can not automatically infer this.
In order to get the correct return type, you need to provide us with type information about how you have customized the function (we do the very same to define the default configuration).

We need to use HKTs (higher-kinded types) in order to generate the right output type. But again, unfortunately, TypeScript does not support HKTs. Luckily however, there is a workaround.
To use HKTs, we alias the type to a string type (a URI) and simply refer to that type by its alias until we need to resolve it.

Here's a simple example that creates a custom deepmerge function that does not merge arrays.

```js
import type { DeepMergeLeafURI } from "deepmerge-ts";
import { deepmergeCustom } from "deepmerge-ts";

const customDeepmerge = deepmergeCustom<{
DeepMergeArraysURI: DeepMergeLeafURI; // <-- Needed for correct output type.
}>({
mergeArrays: false,
});

const x = { foo: [1, 2], bar: [3, 4] };
const y = { foo: [5, 6] };

customDeepmerge(x, y); // => { foo: [5, 6], bar: [3, 4] }
```

When resolving a HKT, we use a lookup inside an interface called `DeepMergeMergeFunctionURItoKind`.
This interface needs to contain all the mappings of the URIs to their actual type.

When defining your own HKT for use with deepmerge, you need to extend this interface with your mapping.
This can be done using [Declaration Merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) by declaring a module block for this library and defining the same interface.

```ts
declare module "deepmerge-ts" {
interface DeepMergeMergeFunctionURItoKind<Ts extends ReadonlyArray<unknown>, MF extends DeepMergeMergeFunctionsURIs> {
readonly MyCustomMergeURI: MyValue;
}
}
```

Here's an example of creating a custom deepmerge function that amalgamates dates into an array.

```ts
import type { DeepMergeMergeFunctionURItoKind, DeepMergeMergeFunctionsURIs, Leaf } from "deepmerge-ts";
import { deepmergeCustom } from "deepmerge-ts";

const customizedDeepmerge = deepmergeCustom<{
DeepMergeOthersURI: "MyDeepMergeDatesURI"; // <-- Needed for correct output type.
}>({
mergeOthers: (values, utils) => {
// If every value is a date, the return the amalgamated array.
if (values.every((value) => value instanceof Date)) {
return values;
}
// Otherwise, use the default merging strategy.
return utils.defaultMergeFunctions.mergeOthers(values, utils);
},
});

const x = { foo: new Date("2020-01-01") };
const y = { foo: new Date("2021-02-02") };
const z = { foo: new Date("2022-03-03") };

customDeepmerge(x, y, z); // => { foo: [Date, Date, Date] }

declare module "deepmerge-ts" {
interface DeepMergeMergeFunctionURItoKind<
Ts extends ReadonlyArray<unknown>,
MF extends DeepMergeMergeFunctionsURIs
> {
readonly MyDeepMergeDatesURI: EveryIsDate<Ts> extends true ? Ts : Leaf<Ts>;
}
}

type EveryIsDate<Ts extends ReadonlyArray<unknown>> = Ts extends readonly [
infer Head,
...infer Rest
]
? Head extends Date
? EveryIsDate<Rest>
: false
: true;
```

Note: If you want to use HKTs in your own project, not related to deepmerge-ts, we recommend checking out [fp-ts](https://gcanti.github.io/fp-ts/modules/HKT.ts.html).

## API

[See deepmerge custom API](../README.md#deepmergeCustom-options).

0 comments on commit 4a4e57b

Please sign in to comment.