Skip to content

Commit

Permalink
chore(release): 1.1.6 [skip ci]
Browse files Browse the repository at this point in the history
## [1.1.6](v1.1.5...v1.1.6) (2021-11-22)

### Performance Improvements

* convert recursive types to tail-recursive versions ([#15](#15)) ([4401ac2](4401ac2))
  • Loading branch information
semantic-release-bot committed Nov 22, 2021
1 parent 01d0190 commit b317e60
Show file tree
Hide file tree
Showing 10 changed files with 330 additions and 108 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Changelog
All notable changes to this project will be documented in this file. Dates are displayed in UTC.

## [1.1.6](https://github.com/RebeccaStevens/deepmerge-ts/compare/v1.1.5...v1.1.6) (2021-11-22)


### Performance Improvements

* convert recursive types to tail-recursive versions ([#15](https://github.com/RebeccaStevens/deepmerge-ts/issues/15)) ([4401ac2](https://github.com/RebeccaStevens/deepmerge-ts/commit/4401ac2d1651093ab855d3d4bdf6c9628c0767ab))

## [1.1.5](https://github.com/RebeccaStevens/deepmerge-ts/compare/v1.1.4...v1.1.5) (2021-10-18)


Expand Down
7 changes: 7 additions & 0 deletions deno-dist/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Changelog
All notable changes to this project will be documented in this file. Dates are displayed in UTC.

## [1.1.5](https://github.com/RebeccaStevens/deepmerge-ts/compare/v1.1.4...v1.1.5) (2021-10-18)


### Bug Fixes

* **deno:** deno release fixup ([4b8ca98](https://github.com/RebeccaStevens/deepmerge-ts/commit/4b8ca9868de78228244b099dc2040c4cb16a649d))

## [1.1.4](https://github.com/RebeccaStevens/deepmerge-ts/compare/v1.1.3...v1.1.4) (2021-10-18)

## [1.1.3](https://github.com/RebeccaStevens/deepmerge-ts/compare/v1.1.2...v1.1.3) (2021-09-21)
Expand Down
7 changes: 3 additions & 4 deletions deno-dist/deepmerge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import type {
DeepMergeSetsDefaultHKT,
DeepMergeMergeFunctionUtils,
GetDeepMergeMergeFunctionsURIs,
RecordProperty,
} from "./types/index.ts";
import {
getIterableOfIterables,
Expand Down Expand Up @@ -137,7 +136,7 @@ function mergeUnknowns<
switch (type) {
case ObjectType.RECORD:
return utils.mergeFunctions.mergeRecords(
values as ReadonlyArray<Readonly<Record<RecordProperty, unknown>>>,
values as ReadonlyArray<Readonly<Record<PropertyKey, unknown>>>,
utils
) as DeepMergeHKT<Ts, MF>;

Expand Down Expand Up @@ -173,11 +172,11 @@ function mergeUnknowns<
* @param values - The records.
*/
function mergeRecords<
Ts extends ReadonlyArray<Record<RecordProperty, unknown>>,
Ts extends ReadonlyArray<Record<PropertyKey, unknown>>,
U extends DeepMergeMergeFunctionUtils,
MF extends DeepMergeMergeFunctionsURIs
>(values: Ts, utils: U) {
const result: Record<RecordProperty, unknown> = {};
const result: Record<PropertyKey, unknown> = {};

/* eslint-disable functional/no-loop-statement, functional/no-conditional-statement -- using a loop here is more performant. */

Expand Down
73 changes: 73 additions & 0 deletions deno-dist/docs/API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# API

## deepmerge(x, y, ...)

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 determine 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 it 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: `(values: Set<unknown>[], utils: DeepMergeMergeFunctionUtils) => unknown`

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

Note: This includes merging mixed types, such as merging 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 infinite loop.
94 changes: 94 additions & 0 deletions deno-dist/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 { DeepMergeLeaf, DeepMergeMergeFunctionURItoKind, DeepMergeMergeFunctionsURIs } 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 : DeepMergeLeaf<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](./API.md#deepmergecustomoptions).
76 changes: 36 additions & 40 deletions deno-dist/types/defaults.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { RecordProperty } from "./basics.ts";
import type {
DeepMergeHKT,
DeepMergeLeafURI,
Expand Down Expand Up @@ -77,57 +76,42 @@ type DeepMergeRecordsDefaultHKTInternalProps<
MF extends DeepMergeMergeFunctionsURIs
> = {
[K in OptionalKeysOf<Ts>]?: DeepMergeHKT<
FilterOutNever<
DeepMergeRecordsDefaultHKTInternalPropsToMerge<
DeepMergeRecordsDefaultHKTInternalPropValue<Ts, K>
>
>,
DeepMergeRecordsDefaultHKTInternalPropValue<Ts, K>,
MF
>;
} & {
[K in RequiredKeysOf<Ts>]: DeepMergeHKT<
FilterOutNever<
DeepMergeRecordsDefaultHKTInternalPropsToMerge<
DeepMergeRecordsDefaultHKTInternalPropValue<Ts, K>
>
>,
DeepMergeRecordsDefaultHKTInternalPropValue<Ts, K>,
MF
>;
};

/**
* Get the properties to merge.
* Get the value of the property.
*/
type DeepMergeRecordsDefaultHKTInternalPropsToMerge<
Ts extends readonly [unknown, unknown]
> = Ts extends readonly [infer First, infer Second]
? IsNever<First> extends true
? Second extends readonly [unknown, unknown]
? DeepMergeRecordsDefaultHKTInternalPropsToMerge<Second>
: Second extends readonly [unknown]
? Second
: []
: Second extends readonly [unknown, unknown]
? [First, ...DeepMergeRecordsDefaultHKTInternalPropsToMerge<Second>]
: Second extends readonly [unknown]
? [First, Second[0]]
: []
: never;
type DeepMergeRecordsDefaultHKTInternalPropValue<
Ts extends readonly [unknown, ...unknown[]],
K extends PropertyKey
> = FilterOutNever<
DeepMergeRecordsDefaultHKTInternalPropValueHelper<Ts, K, []>
>;

/**
* Get the value of the property.
* Tail-recursive helper type for DeepMergeRecordsDefaultHKTInternalPropValue.
*/
type DeepMergeRecordsDefaultHKTInternalPropValue<
type DeepMergeRecordsDefaultHKTInternalPropValueHelper<
Ts extends readonly [unknown, ...unknown[]],
K extends RecordProperty
K extends PropertyKey,
Acc extends ReadonlyArray<unknown>
> = Ts extends readonly [infer Head, ...infer Rest]
? Head extends Record<RecordProperty, unknown>
? Head extends Record<PropertyKey, unknown>
? Rest extends readonly [unknown, ...unknown[]]
? [
ValueOfKey<Head, K>,
DeepMergeRecordsDefaultHKTInternalPropValue<Rest, K>
]
: [ValueOfKey<Head, K>]
? DeepMergeRecordsDefaultHKTInternalPropValueHelper<
Rest,
K,
[...Acc, ValueOfKey<Head, K>]
>
: [...Acc, ValueOfKey<Head, K>]
: never
: never;

Expand All @@ -137,11 +121,23 @@ type DeepMergeRecordsDefaultHKTInternalPropValue<
export type DeepMergeArraysDefaultHKT<
Ts extends ReadonlyArray<unknown>,
MF extends DeepMergeMergeFunctionsURIs
> = Ts extends [infer Head, ...infer Rest]
> = DeepMergeArraysDefaultHKTHelper<Ts, MF, []>;

/**
* Tail-recursive helper type for DeepMergeArraysDefaultHKT.
*/
type DeepMergeArraysDefaultHKTHelper<
Ts extends ReadonlyArray<unknown>,
MF extends DeepMergeMergeFunctionsURIs,
Acc extends ReadonlyArray<unknown>
> = Ts extends readonly [infer Head, ...infer Rest]
? Head extends ReadonlyArray<unknown>
? Rest extends [ReadonlyArray<unknown>, ...ReadonlyArray<unknown[]>]
? [...Head, ...DeepMergeArraysDefaultHKT<Rest, MF>]
: Head
? Rest extends readonly [
ReadonlyArray<unknown>,
...ReadonlyArray<unknown[]>
]
? DeepMergeArraysDefaultHKTHelper<Rest, MF, [...Acc, ...Head]>
: [...Acc, ...Head]
: never
: never;

Expand Down
1 change: 0 additions & 1 deletion deno-dist/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from "./basics.ts";
export * from "./defaults.ts";
export * from "./merging.ts";
export * from "./options.ts";
4 changes: 1 addition & 3 deletions deno-dist/types/options.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import type { DeepMergeMergeFunctionsDefaults } from "@/deepmerge DENOIFY: DEPENDENCY UNMET (BUILTIN)";

import type { RecordProperty } from "./basics.ts";

/**
* The options the user can pass to customize deepmerge.
*/
Expand All @@ -23,7 +21,7 @@ type DeepMergeOptionsFull = Readonly<{
*/
type DeepMergeMergeFunctions = Readonly<{
mergeRecords: <
Ts extends ReadonlyArray<Readonly<Record<RecordProperty, unknown>>>,
Ts extends ReadonlyArray<Readonly<Record<PropertyKey, unknown>>>,
U extends DeepMergeMergeFunctionUtils
>(
records: Ts,
Expand Down
Loading

0 comments on commit b317e60

Please sign in to comment.