Skip to content

Commit

Permalink
Initial implementation (#1)
Browse files Browse the repository at this point in the history
* initial implementation

* remove examples

* add all Option

* 100% test coverage

* write Readme.md

* add Licensing Info to old deepmerge

* fix descriptin

* add CI-badge

* add primitive benchmark

* remove clone option in tests

* Update LICENSE

Co-authored-by: Matteo Collina <[email protected]>

* improve benchmarks

* add unit test

* add test for prototype pollution

* fix prototype pollution,
improve performance of all

* add use strict everywhere

* improve perf by using less branches and less lookup methods

* less branches

* rename map to clone

* add test for non-enumerable symbol keys

* add with lint to ci.yml

* Apply suggestions from code review

Co-authored-by: Manuel Spigolon <[email protected]>

* Update test/index.test.js

Co-authored-by: Manuel Spigolon <[email protected]>

* make bechmarks nicer

* fix npm scrpits

Co-authored-by: Matteo Collina <[email protected]>
Co-authored-by: Manuel Spigolon <[email protected]>
  • Loading branch information
3 people authored Jun 30, 2022
1 parent d44f7cd commit bfed5b0
Show file tree
Hide file tree
Showing 22 changed files with 1,484 additions and 37 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ on:
jobs:
test:
uses: fastify/workflows/.github/workflows/plugins-ci.yml@v3
with:
lint: true
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock=false
3 changes: 3 additions & 0 deletions .taprc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ts: false
jsx: false
coverage: true
24 changes: 24 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
MIT License

Copyright (c) The Fastify Team

The Fastify team members are listed at https://github.com/fastify/fastify#team
and in the README file.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
102 changes: 65 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,65 @@
# skeleton

Template repository to create standardized Fastify plugins.

# Getting started

- Click on `Use this template` above to create a new repository based on this repository.

# What's included?

1. Github CI Actions for installing, testing your package.
2. Github CI Actions to validate different package managers.
3. Dependabot V2 config to automate dependency updates.
4. Template for the GitHub App [Stale](https://github.com/apps/stale) to mark issues as stale.
5. Template for the GitHub App [tests-checker](https://github.com/apps/tests-checker) to check if a PR contains tests.

# Repository structure

```
├── .github
│ ├── workflows
│ │ ├── ci.yml
│ │ └── package-manager-ci.yml
│ ├── .stale.yml
│ ├── dependabot.yml
│ └── tests_checker.yml
├── docs (Documentation)
├── examples (Code examples)
├── test (Application tests)
├── types (Typescript types)
└── README.md
```
# @fastify/deepmerge

![CI](https://github.com/fastify/deepmerge/workflows/CI/badge.svg)
[![NPM version](https://img.shields.io/npm/v/@fastify/deepmerge.svg?style=flat)](https://www.npmjs.com/package/@fastify/deepmerge)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/)

Merges the enumerable properties of two or more objects deeply. Fastest implementation of deepmerge, see section 'Benchmarks'.

### Install
```
npm i @fastify/deepmerge
```

### Usage

The module exports a function, which provides a function to deepmerge Objects.

```
deepmerge(options)
```

`options` is optional and can contain following values

- `symbols` (`boolean`, optional) - should also merge object-keys which are symbols, default is false
- `all` (`boolean`, optional) - merges all parameters, default is false

```js
const deepmerge = require('@fastify/deepmegre')()
const result = deepmerge({a: 'value'}, { b: 404 })
console.log(result) // {a: 'value', b: 404 }
```

```js
const deepmerge = require('@fastify/deepmegre')({ all: true })
const result = deepmerge({a: 'value'}, { b: 404 }, { a: 404 })
console.log(result) // {a: 404, b: 404 }
```

## Benchmarks

The benchmarks are available in the benchmark-folder.

`npm run bench` - benchmark various use cases of deepmerge:
```
@fastify/deepmerge: merge regex with date x 1,266,447,885 ops/sec ±0.14% (97 runs sampled)
@fastify/deepmerge: merge object with a primitive x 1,266,435,016 ops/sec ±0.33% (97 runs sampled)
@fastify/deepmerge: merge two arrays containing strings x 25,591,739 ops/sec ±0.24% (98 runs sampled)
@fastify/deepmerge: two merge arrays containing objects x 976,182 ops/sec ±0.46% (98 runs sampled)
@fastify/deepmerge: merge two flat objects x 10,027,879 ops/sec ±0.36% (94 runs sampled)
@fastify/deepmerge: merge nested objects x 5,341,227 ops/sec ±0.67% (94 runs sampled)
```

`npm run bench:compare` - comparison of @fastify/deepmerge with other popular deepmerge implementation:
```
@fastify/deepmerge x 403,777 ops/sec ±0.22% (98 runs sampled)
deepmerge x 21,143 ops/sec ±0.83% (93 runs sampled)
merge-deep x 89,447 ops/sec ±0.59% (95 runs sampled)
ts-deepmerge x 185,601 ops/sec ±0.59% (96 runs sampled)
deepmerge-ts x 185,310 ops/sec ±0.50% (92 runs sampled)
lodash.merge x 89,053 ops/sec ±0.37% (99 runs sampled)
```

## License

Licensed under [MIT](./LICENSE).
1 change: 1 addition & 0 deletions benchmark/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock=false
53 changes: 53 additions & 0 deletions benchmark/bench.all.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use strict'

const Benchmark = require('benchmark')
const deepmerge = require('..')({ symbol: false, all: true })

const sourceSimple = { key1: 'changed', key2: 'value2' }
const targetSimple = { key1: 'value1', key3: 'value3' }

const sourceNested = {
key1: {
subkey1: 'subvalue1',
subkey2: 'subvalue2'
}
}
const targetNested = {
key1: 'value1',
key2: 'value2'
}

const primitive = 'primitive'

const date = new Date()
const regex = /a/g

const simpleArrayTarget = ['a1', 'a2', 'c1', 'f1', 'p1']
const simpleArraySource = ['t1', 's1', 'c2', 'r1', 'p2', 'p3']

const complexArraySource = [{ ...sourceSimple }, { ...sourceSimple }, { ...sourceSimple }, { ...sourceSimple }, { ...sourceSimple }]
const complexArrayTarget = [{ ...targetSimple }, { ...targetSimple }, { ...targetSimple }, { ...targetSimple }, { ...targetSimple }]

new Benchmark.Suite()
.add('@fastify/deepmerge: merge regex with date', function () {
deepmerge(regex, date)
})
.add('@fastify/deepmerge: merge object with a primitive', function () {
deepmerge(targetSimple, primitive)
})
.add('@fastify/deepmerge: merge two arrays containing strings', function () {
deepmerge(simpleArrayTarget, simpleArraySource)
})
.add('@fastify/deepmerge: two merge arrays containing objects', function () {
deepmerge(complexArrayTarget, complexArraySource)
})
.add('@fastify/deepmerge: merge two flat objects', function () {
deepmerge(targetSimple, sourceSimple)
})
.add('@fastify/deepmerge: merge nested objects', function () {
deepmerge(targetNested, sourceNested)
})
.on('cycle', function (event) {
console.log(String(event.target))
})
.run()
148 changes: 148 additions & 0 deletions benchmark/bench.compare.detailed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
'use strict'

const Benchmark = require('benchmark')
const fastifyDeepmerge = require('..')({ symbol: false })
const deepmerge = require('deepmerge')
const mergedeep = require('merge-deep')
const tsDeepmerge = require('ts-deepmerge').default
const deepmergeTs = require('deepmerge-ts').deepmerge
const lodashMerge = require('lodash.merge')

const sourceSimple = { key1: 'changed', key2: 'value2' }
const targetSimple = { key1: 'value1', key3: 'value3' }

const sourceNested = {
key1: {
subkey1: 'subvalue1',
subkey2: 'subvalue2'
}
}
const targetNested = {
key1: 'value1',
key2: 'value2'
}

const primitive = 'primitive'

const date = new Date()
const regex = /a/g

const simpleArrayTarget = ['a1', 'a2', 'c1', 'f1', 'p1']
const simpleArraySource = ['t1', 's1', 'c2', 'r1', 'p2', 'p3']

const complexArraySource = [{ ...sourceSimple }, { ...sourceSimple }, { ...sourceSimple }, { ...sourceSimple }, { ...sourceSimple }]
const complexArrayTarget = [{ ...targetSimple }, { ...targetSimple }, { ...targetSimple }, { ...targetSimple }, { ...targetSimple }]

new Benchmark.Suite()
.add('@fastify/deepmerge: merge regex with date', function () {
fastifyDeepmerge(regex, date)
})
.add('@fastify/deepmerge: merge object with a primitive', function () {
fastifyDeepmerge(targetSimple, primitive)
})
.add('@fastify/deepmerge: merge two arrays containing strings', function () {
fastifyDeepmerge(simpleArrayTarget, simpleArraySource)
})
.add('@fastify/deepmerge: two merge arrays containing objects', function () {
fastifyDeepmerge(complexArrayTarget, complexArraySource)
})
.add('@fastify/deepmerge: merge two flat objects', function () {
fastifyDeepmerge(targetSimple, sourceSimple)
})
.add('@fastify/deepmerge: merge nested objects', function () {
fastifyDeepmerge(targetNested, sourceNested)
})
.add('deepmerge: merge regex with date', function () {
deepmerge(regex, date)
})
.add('deepmerge: merge object with a primitive', function () {
deepmerge(targetSimple, primitive)
})
.add('deepmerge: merge two arrays containing strings', function () {
deepmerge(simpleArrayTarget, simpleArraySource)
})
.add('deepmerge: two merge arrays containing objects', function () {
deepmerge(complexArrayTarget, complexArraySource)
})
.add('deepmerge: merge two flat objects', function () {
deepmerge(targetSimple, sourceSimple)
})
.add('deepmerge: merge nested objects', function () {
deepmerge(targetNested, sourceNested)
})
.add('merge-deep: merge regex with date', function () {
mergedeep(regex, date)
})
.add('merge-deep: merge object with a primitive', function () {
mergedeep(targetSimple, primitive)
})
.add('merge-deep: merge two arrays containing strings', function () {
mergedeep(simpleArrayTarget, simpleArraySource)
})
.add('merge-deep: two merge arrays containing objects', function () {
mergedeep(complexArrayTarget, complexArraySource)
})
.add('merge-deep: merge two flat objects', function () {
mergedeep(targetSimple, sourceSimple)
})
.add('merge-deep: merge nested objects', function () {
mergedeep(targetNested, sourceNested)
})
.add('ts-deepmerge: merge regex with date', function () {
tsDeepmerge(regex, date)
})
.add('ts-deepmerge: merge object with a primitive', function () {
tsDeepmerge(targetSimple, primitive)
})
.add('ts-deepmerge: merge two arrays containing strings', function () {
tsDeepmerge(simpleArrayTarget, simpleArraySource)
})
.add('ts-deepmerge: two merge arrays containing objects', function () {
tsDeepmerge(complexArrayTarget, complexArraySource)
})
.add('ts-deepmerge: merge two flat objects', function () {
tsDeepmerge(targetSimple, sourceSimple)
})
.add('ts-deepmerge: merge nested objects', function () {
tsDeepmerge(targetNested, sourceNested)
})
.add('deepmerge-ts: merge regex with date', function () {
deepmergeTs(regex, date)
})
.add('deepmerge-ts: merge object with a primitive', function () {
deepmergeTs(targetSimple, primitive)
})
.add('deepmerge-ts: merge two arrays containing strings', function () {
deepmergeTs(simpleArrayTarget, simpleArraySource)
})
.add('deepmerge-ts: two merge arrays containing objects', function () {
deepmergeTs(complexArrayTarget, complexArraySource)
})
.add('deepmerge-ts: merge two flat objects', function () {
deepmergeTs(targetSimple, sourceSimple)
})
.add('deepmerge-ts: merge nested objects', function () {
deepmergeTs(targetNested, sourceNested)
})
.add('lodash.merge: merge regex with date', function () {
lodashMerge(regex, date)
})
.add('lodash.merge: merge object with a primitive', function () {
lodashMerge(targetSimple, primitive)
})
.add('lodash.merge: merge two arrays containing strings', function () {
lodashMerge(simpleArrayTarget, simpleArraySource)
})
.add('lodash.merge: two merge arrays containing objects', function () {
lodashMerge(complexArrayTarget, complexArraySource)
})
.add('lodash.merge: merge two flat objects', function () {
lodashMerge(targetSimple, sourceSimple)
})
.add('lodash.merge: merge nested objects', function () {
lodashMerge(targetNested, sourceNested)
})
.on('cycle', function (event) {
console.log(String(event.target))
})
.run()
Loading

0 comments on commit bfed5b0

Please sign in to comment.