Comparing version 1.0.0 to 2.0.0
@@ -7,14 +7,14 @@ var mutable = require('./mutable.js'); | ||
/** | ||
* Creates untangled copy (deep clone) of the provided object, | ||
* and deeply merges properties of the rest of the provided objects. | ||
* Creates untangled copy (deep clone) of the provided value, | ||
* and deeply merges rest of the provided values. | ||
* | ||
* @param {...object} object - objects to merge/clone | ||
* @param {function} [reduceArrays] - reduce function for custom array merging | ||
* @returns {object} deep merged copy of all the provided objects | ||
* @param {...mixed} value - value(s) to merge/clone | ||
* @returns {mixed} - deep merged copy of all the provided values | ||
*/ | ||
function immutable(/* a[, b[, ...[, reduceArrays]]] */) | ||
function immutable(/* a[, b[, ...]] */) | ||
{ | ||
// invoke mutable with new object as first argument | ||
var args = Array.prototype.slice.call(arguments, 0); | ||
return mutable.apply(this, [{}].concat(args)); | ||
// use `undefined` as always-override value | ||
return mutable.apply(this, [undefined].concat(args)); | ||
} |
10
index.js
@@ -1,2 +0,4 @@ | ||
var mutable = require('./mutable.js') | ||
var behaviors = require('./flags.js') | ||
, adapters = require('./adapters.js') | ||
, mutable = require('./mutable.js') | ||
, immutable = require('./immutable.js') | ||
@@ -8,4 +10,8 @@ ; | ||
module.exports = immutable; | ||
// expose both options | ||
// expose both variants | ||
module.exports.mutable = mutable; | ||
module.exports.immutable = immutable; | ||
// expose behavior flags | ||
module.exports.behaviors = behaviors; | ||
// expose available adapters | ||
module.exports.adapters = adapters; |
@@ -0,42 +1,22 @@ | ||
var merge = require('./merge.js'); | ||
// Public API | ||
module.exports = deeply; | ||
module.exports = mutable; | ||
/** | ||
* Deeply merges properties of the provided objects, | ||
* into the first object. | ||
* Deeply merges properties of the provided objects, into the first object. | ||
* | ||
* @param {...object} object - objects to merge | ||
* @param {function} [reduceArrays] - reduce function for custom array merging | ||
* @returns {object} first object with merged in properties from other objects | ||
* @param {...mixed} value - values to merge | ||
* @returns {mixed} first value with merged in properties from other values | ||
*/ | ||
function deeply(/* a[, b[, ...[, reduceArrays]]] */) | ||
function mutable(/* a[, b[, ...]] */) | ||
{ | ||
var o | ||
, prop | ||
, args = Array.prototype.slice.call(arguments) | ||
, result = args.shift() | ||
, reduceArrays = typeof args[args.length-1] == 'function' ? args.pop() : undefined | ||
var source | ||
, args = Array.prototype.slice.call(arguments) | ||
, result = args.shift() | ||
; | ||
while ((o = args.shift())) | ||
while ((source = args.shift())) | ||
{ | ||
for (prop in o) | ||
{ | ||
if (!o.hasOwnProperty(prop)) continue; | ||
if (typeof o[prop] == 'object' && preciseTypeOf(o[prop]) == 'object') | ||
{ | ||
result[prop] = deeply(result[prop] || {}, o[prop], reduceArrays); | ||
} | ||
// check if there is custom reduce function for array merging | ||
else if (reduceArrays && Array.isArray(o[prop])) | ||
{ | ||
// make sure it's all untangled | ||
result[prop] = reduceArrays(result[prop] || [], Array.prototype.slice.call(o[prop])); | ||
} | ||
else | ||
{ | ||
result[prop] = o[prop]; | ||
} | ||
} | ||
result = merge.call(this, result, source); | ||
} | ||
@@ -46,12 +26,1 @@ | ||
} | ||
/** | ||
* Detects real type of the objects like `new Number(1)` and `new Boolean(true)`` | ||
* | ||
* @param {mixed} obj - object to get type of | ||
* @returns {string} precise type | ||
*/ | ||
function preciseTypeOf(obj) | ||
{ | ||
return Object.prototype.toString.call(obj).match(/\[object\s*([^\]]+)\]/)[1].toLowerCase(); | ||
} |
{ | ||
"name": "deeply", | ||
"version": "1.0.0", | ||
"description": "Universal (nodejs + browser) library that deeply merges properties of the provided objects, returns untangled copy (clone). Mutable operations also available.", | ||
"version": "2.0.0", | ||
"description": "A toolkit for deep structure manipulations, provides deep merge/clone functionality out of the box, and exposes hooks and custom adapters for more control and greater flexibility.", | ||
"main": "index.js", | ||
"scripts": { | ||
"lint": "eslint *.js test/*.js", | ||
"test": "nyc --reporter=lcov --reporter=text tape ./test/*.js", | ||
"posttest": "nyc check-coverage --lines 98 --functions 100 --branches 98", | ||
"browser": "browserify test/compatability.js | ghostface | tap-set-exit" | ||
"clean": "rimraf coverage", | ||
"lint": "eslint *.js adapters/*.js test/*.js", | ||
"test": "nyc --reporter=json tape test/*.js | tap-spec", | ||
"browser": "browserify -t browserify-istanbul test/compatability.js | obake --coverage | tap-spec", | ||
"report": "istanbul report", | ||
"size": "browserify index.js | size-table deeply", | ||
"toc": "toc-md README.md", | ||
"files": "pkgfiles --sort=name", | ||
"debug": "tape test/*.js | tap-spec" | ||
}, | ||
"pre-commit": [ | ||
"clean", | ||
"lint", | ||
"test" | ||
"test", | ||
"browser", | ||
"report", | ||
"size", | ||
"toc", | ||
"files" | ||
], | ||
@@ -27,3 +38,10 @@ "engines": { | ||
"clone", | ||
"mutable", | ||
"immutable", | ||
"object", | ||
"array", | ||
"function", | ||
"prototype", | ||
"util", | ||
"hook", | ||
"browser", | ||
@@ -39,15 +57,22 @@ "client", | ||
"devDependencies": { | ||
"browserify": "^12.0.1", | ||
"codacy-coverage": "^1.1.3", | ||
"coveralls": "^2.11.6", | ||
"eslint": "^1.10.3", | ||
"ghostface": "^1.5.0", | ||
"lodash.partialright": "^3.1.1", | ||
"nyc": "^5.3.0", | ||
"phantomjs": "^1.9.19", | ||
"browserify": "^13.0.0", | ||
"browserify-istanbul": "^2.0.0", | ||
"coveralls": "^2.11.8", | ||
"eslint": "^2.4.0", | ||
"istanbul": "^0.4.2", | ||
"lodash.partialright": "^4.1.2", | ||
"nyc": "^6.1.1", | ||
"obake": "^0.1.2", | ||
"phantomjs-prebuilt": "^2.1.5", | ||
"pre-commit": "^1.1.2", | ||
"reamde": "^1.1.0", | ||
"tap-set-exit": "^1.1.1", | ||
"tape": "^4.4.0" | ||
"rimraf": "^2.5.2", | ||
"size-table": "^0.2.0", | ||
"tap-spec": "^4.1.1", | ||
"tape": "^4.5.1", | ||
"toc-md": "^0.1.0" | ||
}, | ||
"dependencies": { | ||
"precise-typeof": "^1.0.2" | ||
} | ||
} |
416
README.md
# Deeply [![NPM Module](https://img.shields.io/npm/v/deeply.svg?style=flat)](https://www.npmjs.com/package/deeply) | ||
Universal (nodejs + browser) library that deeply merges properties of the provided objects, returns untangled copy (clone). Mutable operations also available. | ||
A toolkit for deep structure manipulations, provides deep merge/clone functionality out of the box, | ||
and exposes hooks and custom adapters for more control and greater flexibility. | ||
@@ -11,11 +12,40 @@ [![PhantomJS Build](https://img.shields.io/travis/alexindigo/deeply/master.svg?label=browser&style=flat)](https://travis-ci.org/alexindigo/deeply) | ||
[![Dependency Status](https://img.shields.io/david/alexindigo/deeply.svg?style=flat)](https://david-dm.org/alexindigo/deeply) | ||
![Readme](https://img.shields.io/badge/readme-tested-brightgreen.svg?style=flat) | ||
<!-- No flat badge yet [![bitHound Overall Score](https://www.bithound.io/github/alexindigo/deeply/badges/score.svg)](https://www.bithound.io/github/alexindigo/deeply) | ||
Too many false positives [![Codacy Badge](https://img.shields.io/codacy/5f1289b78b7346498797f9f3cd674408.svg)](https://www.codacy.com/app/alexindigo/deeply) --> | ||
[![bitHound Overall Score](https://www.bithound.io/github/alexindigo/deeply/badges/score.svg)](https://www.bithound.io/github/alexindigo/deeply) | ||
[![Readme](https://img.shields.io/badge/readme-tested-brightgreen.svg?style=flat)](https://www.npmjs.com/package/reamde) | ||
| compression | size | | ||
| :--------------- | ------: | | ||
| deeply.js | 15.2 kB | | ||
| deeply.min.js | 5.06 kB | | ||
| deeply.min.js.gz | 1.52 kB | | ||
## Table of Contents | ||
<!-- TOC --> | ||
- [Install](#install) | ||
- [Examples](#examples) | ||
- [Merging](#merging) | ||
- [Cloning](#cloning) | ||
- [Arrays Custom Merging](#arrays-custom-merging) | ||
- [Default Behavior](#default-behavior) | ||
- [Combining Arrays](#combining-arrays) | ||
- [Appending Arrays](#appending-arrays) | ||
- [Appending Arrays and Keeping Unique Elements Only](#appending-arrays-and-keeping-unique-elements-only) | ||
- [Custom Merge Function](#custom-merge-function) | ||
- [Cloning Functions](#cloning-functions) | ||
- [Cloning Prototype Chain](#cloning-prototype-chain) | ||
- [Extend Original Function Prototype](#extend-original-function-prototype) | ||
- [Custom hooks](#custom-hooks) | ||
- [Mutable Operations](#mutable-operations) | ||
- [Ludicrous Mode](#ludicrous-mode) | ||
- [Want to Know More?](#want-to-know-more) | ||
- [License](#license) | ||
<!-- TOC END --> | ||
## Install | ||
> Version `1.0.0` backwards compatible with `0.1.0` version. | ||
``` | ||
```sh | ||
$ npm install deeply --save | ||
@@ -26,16 +56,22 @@ ``` | ||
### merge | ||
– Deeply merges two or more objects. | ||
By default it provides interface for immutable operations, | ||
also available via explicit require `require('deeply/immutable')` | ||
or `require('deeply').immutable` property. | ||
### Merging | ||
Deeply merges two or more objects. | ||
```javascript | ||
var merge = require('deeply'); | ||
var clone = merge({a: {a1: 1}}, {a: {a2: 2}}); | ||
var result = merge({a: {a1: 1}}, {a: {a2: 2}}, {b: {b3: 3}}); | ||
assert.deepEqual(clone, {a: {a1: 1, a2: 2}}); | ||
assert.equal(result, {a: {a1: 1, a2: 2}, b: {b3: 3}}); | ||
``` | ||
### clone | ||
– As degenerated case of merging one object on itself, it's possible to use deeply as deep clone function. | ||
### Cloning | ||
As degenerated case of merging one object on itself, it's possible to use deeply as deep clone function. | ||
```javascript | ||
@@ -50,35 +86,315 @@ var merge = require('deeply'); | ||
assert.deepEqual(x.a.b.c, 1); | ||
assert.equal(x.a.b.c, 1); | ||
``` | ||
### arrays custom merging | ||
– By default array treated as primitive values and being replaced upon conflict, for more meaningful array merge strategy, provide custom reduce function as last argument. | ||
### Arrays Custom Merging | ||
#### default behavior | ||
By default array treated as primitive values and being replaced upon conflict, | ||
for more meaningful array merge strategy, provide one of the pre-built array merge helpers | ||
or a custom reduce function within invocation context. | ||
#### Default Behavior | ||
```javascript | ||
var merge = require('deeply'); | ||
var clone = merge({ a: { b: [0, 2, 4] }}, { a: {b: [1, 3, 5] }}); | ||
var result = merge({ a: { b: [0, 2, 4, {a: 'A'}], c: 'first' }}, { a: {b: [1, 3, 5, {b: 'B'}], d: 'second' }}); | ||
assert.deepEqual(clone, { a: { b: [1, 3, 5] }}); | ||
assert.equal(result, { a: { b: [1, 3, 5, {b: 'B'}], c: 'first', d: 'second' }}); | ||
``` | ||
#### custom merge function | ||
#### Combining Arrays | ||
```javascript | ||
var merge = require('deeply'); | ||
var result; | ||
function customMerge(a, b) | ||
var context = | ||
{ | ||
return (a||[]).concat(b); | ||
useCustomAdapters: merge.behaviors.useCustomAdapters, | ||
'array' : merge.adapters.arraysCombine | ||
}; | ||
// it might be useful when you have array of objects | ||
result = merge.call(context, { a: { b: [0, {a: 'A1', b: 'B1'}, 4] }}, { a: {b: [1, {a: 'A2', c: 'C2'}, 5] }}); | ||
assert.equal(result, { a: { b: [1, {a: 'A2', b: 'B1', c: 'C2'}, 5] }}); | ||
``` | ||
#### Appending Arrays | ||
```javascript | ||
var merge = require('deeply'); | ||
var context = | ||
{ | ||
useCustomAdapters: merge.behaviors.useCustomAdapters, | ||
'array' : merge.adapters.arraysAppend | ||
}; | ||
var result = merge.call(context, { a: { b: [0, 2, 4, 4, 2, 0] }}, { a: {b: [1, 3, 5, 5, 3, 1] }}); | ||
assert.equal(result, { a: { b: [0, 2, 4, 4, 2, 0, 1, 3, 5, 5, 3, 1] }}); | ||
``` | ||
#### Appending Arrays and Keeping Unique Elements Only | ||
```javascript | ||
var merge = require('deeply'); | ||
var context = | ||
{ | ||
useCustomAdapters: merge.behaviors.useCustomAdapters, | ||
'array' : merge.adapters.arraysAppendUnique | ||
}; | ||
var result = merge.call(context, { a: { b: [0, 2, 4, 4, 2, 0] }}, { a: {b: [1, 3, 5, 5, 3, 1] }}); | ||
assert.equal(result, { a: { b: [0, 2, 4, 1, 3, 5] }}); | ||
``` | ||
#### Custom Merge Function | ||
For example we need to have merging arrays to be appended, | ||
with only unique elements and sort the result array. | ||
```javascript | ||
var merge = require('deeply'); | ||
var context = | ||
{ | ||
useCustomAdapters: merge.behaviors.useCustomAdapters, | ||
'array' : appendUniqueAndSort | ||
}; | ||
var result = merge.call(context, { a: { b: [0, 2, 4, 4, 2, 0] }}, { a: {b: [1, 3, 5, 5, 3, 1] }}); | ||
assert.equal(result, { a: { b: [0, 1, 2, 3, 4, 5] }}); | ||
// Custom array adapter | ||
function appendUniqueAndSort(to, from, merge) | ||
{ | ||
// append only if new element isn't present yet | ||
from.forEach(function(v) { to.indexOf(v) == -1 && to.push(merge(undefined, v)); }); | ||
// and sort | ||
return to.sort(); | ||
} | ||
``` | ||
var clone = merge({ a: { b: [0, 2, 4] }}, { a: {b: [1, 3, 5] }}, customMerge); | ||
### Cloning Functions | ||
assert.deepEqual(clone, { a: { b: [0, 2, 4, 1, 3, 5] }}); | ||
By default, functions copied as is to the result object, | ||
basically being treated as primitive values. | ||
You can use `functionsClone` adapter to clone functions, | ||
creating new function object with the same signature as original. | ||
```javascript | ||
var clone = require('deeply'); | ||
function subj(a, b) | ||
{ | ||
return a + b + 10; | ||
} | ||
subj.customProp = 13; | ||
subj.prototype.A = 1; | ||
subj.prototype.B = {isB: 'true'}; | ||
var context = | ||
{ | ||
useCustomAdapters: clone.behaviors.useCustomAdapters, | ||
'function' : clone.adapters.functionsClone | ||
}; | ||
var result = clone.call(context, { a: { b: subj}}); | ||
// cloned object function named subj | ||
assert.equal(result, { a: { b: subj}}); | ||
// same signature | ||
assert.equal(subj.name, result.a.b.name); | ||
assert.equal(subj.length, result.a.b.length); | ||
assert.equal(subj.customProp, result.a.b.customProp); | ||
// separate objects | ||
subj.isOriginal = true; | ||
result.a.b.isCopy = true; | ||
assert.equal(subj.isOriginal, true); | ||
assert.equal(subj.isCopy, undefined); | ||
assert.equal(result.a.b.isOriginal, undefined); | ||
assert.equal(result.a.b.isCopy, true); | ||
// same output | ||
assert.equal(subj(3, 4), result.a.b(3, 4)); | ||
``` | ||
### mutable operations | ||
#### Cloning Prototype Chain | ||
It will also clone prototype objects, | ||
so use this option with caution. | ||
```javascript | ||
var clone = require('deeply'); | ||
function Subj() | ||
{ | ||
this.boom = 'Zap!'; | ||
} | ||
Subj.prototype.A = 1; | ||
Subj.prototype.B = {isB: 'true'}; | ||
var context = | ||
{ | ||
useCustomAdapters: clone.behaviors.useCustomAdapters, | ||
'function' : clone.adapters.functionsClone | ||
}; | ||
var result = clone.call(context, { class: Subj }); | ||
// cloned object function named Subj | ||
assert.equal(result, { class: Subj }); | ||
// has prototype properties | ||
assert.equal(result.class.prototype.A, 1); | ||
assert.equal(result.class.prototype.B.isB, 'true'); | ||
// prototypes are decoupled | ||
Subj.prototype.C = 2; | ||
assert.equal(Subj.prototype.C, 2); | ||
assert.equal(result.class.prototype.C, undefined); | ||
// instances | ||
var s1 = new Subj(); | ||
var s2 = new result.class(); | ||
assert.equal(s1.A, s2.A); | ||
assert.equal(s1.B, s2.B); | ||
assert.equal(s1.C, 2); | ||
assert.equal(s2.C, undefined); | ||
assert.equal(s1.boom, s2.boom); | ||
// but reported instanceof isn't the same | ||
assert.equal(s1 instanceof Subj, true); | ||
assert.equal(s2 instanceof Subj, false); | ||
``` | ||
#### Extend Original Function Prototype | ||
When having proper `instanceof` results matters, | ||
you can use `functionsExtend` helper instead. | ||
```javascript | ||
var clone = require('deeply'); | ||
function Subj() | ||
{ | ||
this.boom = 'Zap!'; | ||
return this.boom; | ||
} | ||
Subj.customProp = 13; | ||
Subj.prototype.A = 1; | ||
Subj.prototype.B = {isB: 'true'}; | ||
var context = | ||
{ | ||
useCustomAdapters: clone.behaviors.useCustomAdapters, | ||
'function' : clone.adapters.functionsExtend | ||
}; | ||
var result = clone.call(context, { class: Subj }); | ||
// cloned object function named Subj | ||
assert.equal(result, { class: Subj }); | ||
// same signature | ||
assert.equal(Subj.name, result.class.name); | ||
assert.equal(Subj.length, result.class.length); | ||
assert.equal(Subj.customProp, result.class.customProp); | ||
// separate objects | ||
Subj.isOriginal = true; | ||
result.class.isCopy = true; | ||
assert.equal(Subj.isOriginal, true); | ||
assert.equal(Subj.isCopy, undefined); | ||
assert.equal(result.class.isOriginal, undefined); | ||
assert.equal(result.class.isCopy, true); | ||
// same output | ||
assert.equal(Subj(), result.class()); | ||
// has prototype properties | ||
assert.equal(result.class.prototype.A, 1); | ||
assert.equal(result.class.prototype.B.isB, 'true'); | ||
// prototypes are extended | ||
Subj.prototype.X = 67; | ||
assert.equal(Subj.prototype.X, 67); | ||
assert.equal(result.class.prototype.X, 67); | ||
// instances | ||
var s1 = new Subj(); | ||
var s2 = new result.class(); | ||
assert.equal(s1.A, s2.A); | ||
assert.equal(s1.B, s2.B); | ||
assert.equal(s1.boom, s2.boom); | ||
// but reported instanceof isn't the same | ||
assert.equal(s1 instanceof Subj, true); | ||
assert.equal(s2 instanceof Subj, true); | ||
``` | ||
### Custom hooks | ||
As shown in [Custom Merge Function](#custom-merge-function) example, | ||
you can add custom adapters for any data type | ||
that supported by [precise-typeof](https://www.npmjs.com/precise-typeof). | ||
For this example we will combine arrays of number, | ||
by performing addition operation on array elements. | ||
```javascript | ||
var merge = require('deeply'); | ||
var context = | ||
{ | ||
useCustomAdapters: merge.behaviors.useCustomAdapters, | ||
'array' : merge.adapters.arraysCombine, | ||
'number' : addNumbers | ||
}; | ||
var result = merge.call(context, { a: { b: [0, 2, 4, 4, 2, 0] }}, { a: {b: [1, 3, 5, 5, 3, 1] }}, { a: {b: [7, 8, 9, 10, 11, 12] }}); | ||
assert.equal(result, { a: { b: [8, 13, 18, 19, 16, 13] }}); | ||
// Custom number adapter | ||
function addNumbers(to, from) | ||
{ | ||
return (to || 0) + from; | ||
} | ||
``` | ||
### Mutable Operations | ||
Mutable interface supports all the described operations, | ||
and available via explicit require `require('deeply/mutable')` | ||
or `require('deeply').mutable` property. | ||
```javascript | ||
var merge = require('deeply/mutable'); | ||
@@ -89,9 +405,55 @@ var myObj = {a: {a1: 1, a2: 2}, b: {b1: 11, b2: 12}}; | ||
assert.deepEqual(myObj, {a: {a1: 1, a2: 2}, b: {b1: 11, b2: 12}, c: 'c', d: 'd', x: {y: {z: -Infinity}}}); | ||
assert.equal(myObj, {a: {a1: 1, a2: 2}, b: {b1: 11, b2: 12}, c: 'c', d: 'd', x: {y: {z: -Infinity}}}); | ||
``` | ||
More examples can be found in ```test/index.js```. | ||
### Ludicrous Mode | ||
Also as shortcut and a homage to Tesla, ludicrous mode is available, | ||
that will clone functions and it's prototype objects by default. :) | ||
Details could be found in [Cloning Functions](#cloning-functions) examples. | ||
```javascript | ||
var ludicrous = require('deeply/ludicrous'); | ||
var scopeVar = 6; | ||
function original(a, b) | ||
{ | ||
return a + b + scopeVar; | ||
} | ||
var cloned = ludicrous({ func: original }); | ||
// cloned object function named subj | ||
assert.equal(cloned, { func: original }); | ||
// same signature | ||
assert.equal(original.name, cloned.func.name); | ||
assert.equal(original.length, cloned.func.length); | ||
// separate objects | ||
original.isOriginal = true; | ||
cloned.func.isCopy = true; | ||
assert.equal(original.isOriginal, true); | ||
assert.equal(original.isCopy, undefined); | ||
assert.equal(cloned.func.isOriginal, undefined); | ||
assert.equal(cloned.func.isCopy, true); | ||
// same output | ||
assert.equal(original(1, 2), cloned.func(1, 2)); | ||
``` | ||
_Note: `ludicrous` isn't included into the main `deeply` package, so it won't be automatically pulled in, | ||
if you're bundling using `browserify deeply/index.js`, to use ludicrous in the browser you'd need to explicitly | ||
require it in your modules or specify direct path in your bundler config._ | ||
## Want to Know More? | ||
More examples can be found in [test/compatability.js](test/compatability.js). | ||
Or open an [issue](https://github.com/alexindigo/deeply/issues) with questions and/or suggestions. | ||
## License | ||
Deeply is licensed under the MIT license. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
26848
20
403
456
1
16
1
+ Addedprecise-typeof@^1.0.2
+ Addedis-buffer@1.1.6(transitive)
+ Addedprecise-typeof@1.0.2(transitive)