diff --git a/README.md b/README.md index 552cfcc..56cbfa2 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ A toolkit for deep structure manipulations, provides deep merge/clone functional and exposes hooks and custom adapters for more control and greater flexibility. [![PhantomJS Build](https://img.shields.io/travis/alexindigo/deeply/canary.svg?label=browser&style=flat)](https://travis-ci.org/alexindigo/deeply) -[![Linux Build](https://img.shields.io/travis/alexindigo/deeply/canary.svg?label=linux:0.10-6.x&style=flat)](https://travis-ci.org/alexindigo/deeply) -[![MacOS Build](https://img.shields.io/travis/alexindigo/deeply/canary.svg?label=macos:0.10-6.x&style=flat)](https://travis-ci.org/alexindigo/deeply) -[![Windows Build](https://img.shields.io/appveyor/ci/alexindigo/deeply/canary.svg?label=windows:0.10-6.x&style=flat)](https://ci.appveyor.com/project/alexindigo/deeply) +[![Linux Build](https://img.shields.io/travis/alexindigo/deeply/canary.svg?label=linux:6.x-11.x&style=flat)](https://travis-ci.org/alexindigo/deeply) +[![MacOS Build](https://img.shields.io/travis/alexindigo/deeply/canary.svg?label=macos:6.x-11.x&style=flat)](https://travis-ci.org/alexindigo/deeply) +[![Windows Build](https://img.shields.io/appveyor/ci/alexindigo/deeply/canary.svg?label=windows:6.x-11.x&style=flat)](https://ci.appveyor.com/project/alexindigo/deeply) [![Coverage Status](https://img.shields.io/coveralls/alexindigo/deeply/canary.svg?label=code+coverage&style=flat)](https://coveralls.io/github/alexindigo/deeply?branch=canary) [![Dependency Status](https://img.shields.io/david/alexindigo/deeply.svg?style=flat)](https://david-dm.org/alexindigo/deeply) @@ -14,11 +14,11 @@ and exposes hooks and custom adapters for more control and greater flexibility. [![Readme](https://img.shields.io/badge/readme-tested-brightgreen.svg?style=flat)](https://www.npmjs.com/package/reamde) -| compression | size | -| :--------------- | -------: | -| deeply.js | 15.08 kB | -| deeply.min.js | 4.95 kB | -| deeply.min.js.gz | 1.48 kB | +| compression | size | +| :--------------- | ------: | +| deeply.js | 15.6 kB | +| deeply.min.js | 5.11 kB | +| deeply.min.js.gz | 1.53 kB | ## Table of Contents @@ -38,6 +38,8 @@ and exposes hooks and custom adapters for more control and greater flexibility. - [Cloning Prototype Chain](#cloning-prototype-chain) - [Extend Original Function Prototype](#extend-original-function-prototype) - [Custom hooks](#custom-hooks) + - [`useCustomAdapters`](#usecustomadapters) + - [`useCustomTypeOf`](#usecustomtypeof) - [Mutable Operations](#mutable-operations) - [Ludicrous Mode](#ludicrous-mode) - [Want to Know More?](#want-to-know-more) @@ -360,6 +362,8 @@ assert.equal(s2 instanceof Subj, true); ### Custom hooks +#### `useCustomAdapters` + 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). @@ -388,6 +392,33 @@ function addNumbers(to, from) } ``` +#### `useCustomTypeOf` + +In some cases you might need to have more control over type detection, +for that you can supply your own type detection function. + +In following example we'd use same `precise-typeof` library, +but with `pojoOnly: true` flag: + +```javascript +var merge = require('deeply'); +var typeOf = require('precise-typeof'); +var moment = require('moment'); + +var context = +{ + useCustomTypeOf: merge.behaviors.useCustomTypeOf, + 'typeof' : (input) => typeOf(input, {pojoOnly: true}) +}; + +var result = merge.call(context, { a: {someField: 'value'}, b: 'other thing'}, { a: moment.utc('2018-11-27') }); + +assert.equal(result, { a: moment.utc('2018-11-27'), b: 'other thing' }); +``` + +In the above example, it would treat `moment` object as atomic, +and won't mix it's properties with other properties. + ### Mutable Operations Mutable interface supports all the described operations, diff --git a/flags.js b/flags.js index 8c3d84e..73f1abb 100644 --- a/flags.js +++ b/flags.js @@ -2,5 +2,6 @@ module.exports = { // to prevent (reduce chance of) accidental leaking of the global variables into runtime flags - useCustomAdapters: 'deeply:useCustomAdapters:' + Math.random() + useCustomAdapters: 'deeply:useCustomAdapters:' + Math.random(), + useCustomTypeOf: 'deeply:useCustomTypeOf:' + Math.random() }; diff --git a/merge.js b/merge.js index 62aba19..e8d1631 100644 --- a/merge.js +++ b/merge.js @@ -20,13 +20,14 @@ function merge(to, from) // if no suitable adapters found // just return overriding value var result = from - , type = preciseTypeOf(from) - , adapter = getTypeAdapter.call(this, type) + , typeOf = getTypeOfAdapter.call(this) + , type = typeOf(from) + , adapter = getMergeByTypeAdapter.call(this, type) ; // if target object isn't the same type as the source object, // then override with new instance of the same type - if (preciseTypeOf(to) != type) + if (typeOf(to) != type) { to = getInitialValue(type, adapter); } @@ -38,6 +39,25 @@ function merge(to, from) return result; } +/** + * Returns typeof adapter, either default one or custom one if provided + * + * @returns {function} - typeof custom adapter or default one + */ +function getTypeOfAdapter() +{ + var adapter = preciseTypeOf; + + // only if usage of custom adapters is authorized + // to prevent global context leaking in + if (this.useCustomTypeOf === behaviors.useCustomTypeOf) + { + adapter = this['typeof']; + } + + return adapter; +} + /** * Returns merge adapter for the requested type * either default one or custom one if provided @@ -45,7 +65,7 @@ function merge(to, from) * @param {string} type - hook type to look for * @returns {function} - merge adapter or pass-thru function, if not adapter found */ -function getTypeAdapter(type) +function getMergeByTypeAdapter(type) { var adapter = adapters[type] || passThru; diff --git a/package.json b/package.json index a127350..f978a9b 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "eslint": "^5.9.0", "istanbul": "^0.4.5", "lodash.partialright": "^4.2.1", + "moment": "^2.22.2", "nyc": "^13.1.0", "obake": "^0.1.2", "phantomjs-prebuilt": "^2.1.12", diff --git a/test/test-compatibility.js b/test/test-compatibility.js index 90395ce..674a449 100644 --- a/test/test-compatibility.js +++ b/test/test-compatibility.js @@ -1,6 +1,8 @@ var test = require('tape').test , partial = require('lodash.partialright') + , typeOf = require('precise-typeof') , deeply = require('../') + , moment = require('moment') , stringify = partial(require('util').inspect, {depth: 8}) , now = new Date() , withStuffOnPrototype @@ -55,6 +57,30 @@ var inout = [ } } + // non-pojo objects + , { + customTypeOf: { + typeof: function(input) { return typeOf(input, {pojoOnly: true}); } + }, + in: [ + { + a: {datetime: 'now'}, + b: moment.utc('2017-11-27'), + c: moment.utc('2018-05-25') + }, + { + a: moment.utc('2018-11-27'), + b: {pojo: {on: 'top'}}, + c: moment.utc('2018-08-12') + } + ], + out: { + a: moment.utc('2018-11-27'), + b: {pojo: {on: 'top'}}, + c: moment.utc('2018-08-12') + } + } + // default array merging: replacement , { in: [ { a: { b: [0, 2, 4] }}, { a: {b: [1, 3, 5] }} ], @@ -200,6 +226,11 @@ test('merge', function test_deep_merge(t) context = pair.customAdapters; context['useCustomAdapters'] = deeply.behaviors.useCustomAdapters; } + else if ('customTypeOf' in pair) + { + context = pair.customTypeOf; + context['useCustomTypeOf'] = deeply.behaviors.useCustomTypeOf; + } // default - immutable diff --git a/yarn.lock b/yarn.lock index b7dc29f..51765a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3214,6 +3214,11 @@ module-deps@^6.0.0: through2 "^2.0.0" xtend "^4.0.0" +moment@^2.22.2: + version "2.22.2" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" + integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y= + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -4992,9 +4997,9 @@ uglify-js@^3.1.4: commander "~2.17.1" source-map "~0.6.1" -"uglify-stream@git+https://github.com/michaelrhodes/uglify-stream#1.1.1": +uglify-stream@michaelrhodes/uglify-stream#1.1.1: version "1.1.1" - resolved "git+https://github.com/michaelrhodes/uglify-stream#0a6a56b9101e2339af159a346d4b901d9480c5c8" + resolved "https://codeload.github.com/michaelrhodes/uglify-stream/tar.gz/0a6a56b9101e2339af159a346d4b901d9480c5c8" dependencies: duplexify "^3.0.1" uglify-js "^2.4.15"