tree-changes
Advanced tools
Comparing version 0.5.0 to 0.5.1
@@ -1,14 +0,14 @@ | ||
interface IObject { | ||
interface IPlainObject { | ||
[key: string]: any; | ||
} | ||
export declare type TypeInput = string | boolean | number | string[] | boolean[] | number[] | IObject; | ||
export declare type IData = IObject | IObject[]; | ||
export declare type TypeInput = string | boolean | number | IPlainObject | Array<string | boolean | number | IPlainObject>; | ||
export declare type IData = IPlainObject | IPlainObject[]; | ||
export interface ITreeChanges { | ||
changed: (key?: string) => boolean; | ||
changedFrom: (key: string, previous: TypeInput, actual?: TypeInput) => boolean; | ||
changedTo: (key: string, actual: TypeInput) => boolean; | ||
increased: (key: string) => boolean; | ||
decreased: (key: string) => boolean; | ||
changed: (key?: string | number) => boolean; | ||
changedFrom: (key: string | number, previous: TypeInput, actual?: TypeInput) => boolean; | ||
changedTo: (key: string | number, actual: TypeInput) => boolean; | ||
increased: (key: string | number) => boolean; | ||
decreased: (key: string | number) => boolean; | ||
} | ||
export default function treeChanges(data: IData, nextData: IData): ITreeChanges; | ||
export {}; |
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var deep_diff_1 = __importDefault(require("deep-diff")); | ||
var nested_property_1 = __importDefault(require("nested-property")); | ||
var deep_diff_1 = require("deep-diff"); | ||
// @ts-ignore | ||
var nested_property_1 = require("nested-property"); | ||
function isPlainObj() { | ||
@@ -18,4 +16,4 @@ var args = []; | ||
var prototype = Object.getPrototypeOf(d); | ||
return Object.prototype.toString.call(d) | ||
.slice(8, -1) === 'Object' && (prototype === null || prototype === Object.getPrototypeOf({})); | ||
return (Object.prototype.toString.call(d).slice(8, -1) === 'Object' && | ||
(prototype === null || prototype === Object.getPrototypeOf({}))); | ||
}); | ||
@@ -28,3 +26,3 @@ } | ||
} | ||
return args.every(function (d) { return Array.isArray(d); }); | ||
return args.every(Array.isArray); | ||
} | ||
@@ -44,7 +42,6 @@ function isNumber() { | ||
changed: function (key) { | ||
var left = nested_property_1.default.get(data, key); | ||
var right = nested_property_1.default.get(nextData, key); | ||
if ((isArray(left, right)) || (isPlainObj(left, right))) { | ||
var diff = deep_diff_1.default.diff(left, right); | ||
return !!diff; | ||
var left = nested_property_1.get(data, key); | ||
var right = nested_property_1.get(nextData, key); | ||
if (isArray(left, right) || isPlainObj(left, right)) { | ||
return !!deep_diff_1.diff(left, right); | ||
} | ||
@@ -54,9 +51,11 @@ return left !== right; | ||
changedFrom: function (key, previous, actual) { | ||
if (!key) { | ||
if (typeof key === 'undefined') { | ||
throw new Error('Key parameter is required'); | ||
} | ||
var useActual = typeof previous !== 'undefined' && typeof actual !== 'undefined'; | ||
var left = nested_property_1.default.get(data, key); | ||
var right = nested_property_1.default.get(nextData, key); | ||
var leftComparator = Array.isArray(previous) ? previous.indexOf(left) >= 0 : left === previous; | ||
var left = nested_property_1.get(data, key); | ||
var right = nested_property_1.get(nextData, key); | ||
var leftComparator = Array.isArray(previous) | ||
? previous.indexOf(left) >= 0 | ||
: left === previous; | ||
var rightComparator = Array.isArray(actual) ? actual.indexOf(right) >= 0 : right === actual; | ||
@@ -66,7 +65,7 @@ return leftComparator && (useActual ? rightComparator : !useActual); | ||
changedTo: function (key, actual) { | ||
if (!key) { | ||
if (typeof key === 'undefined') { | ||
throw new Error('Key parameter is required'); | ||
} | ||
var left = nested_property_1.default.get(data, key); | ||
var right = nested_property_1.default.get(nextData, key); | ||
var left = nested_property_1.get(data, key); | ||
var right = nested_property_1.get(nextData, key); | ||
var leftComparator = Array.isArray(actual) ? actual.indexOf(left) < 0 : left !== actual; | ||
@@ -77,12 +76,14 @@ var rightComparator = Array.isArray(actual) ? actual.indexOf(right) >= 0 : right === actual; | ||
increased: function (key) { | ||
if (!key) { | ||
if (typeof key === 'undefined') { | ||
throw new Error('Key parameter is required'); | ||
} | ||
return isNumber(nested_property_1.default.get(data, key), nested_property_1.default.get(nextData, key)) && nested_property_1.default.get(data, key) < nested_property_1.default.get(nextData, key); | ||
return (isNumber(nested_property_1.get(data, key), nested_property_1.get(nextData, key)) && | ||
nested_property_1.get(data, key) < nested_property_1.get(nextData, key)); | ||
}, | ||
decreased: function (key) { | ||
if (!key) { | ||
if (typeof key === 'undefined') { | ||
throw new Error('Key parameter is required'); | ||
} | ||
return isNumber(nested_property_1.default.get(data, key), nested_property_1.default.get(nextData, key)) && nested_property_1.default.get(data, key) > nested_property_1.default.get(nextData, key); | ||
return (isNumber(nested_property_1.get(data, key), nested_property_1.get(nextData, key)) && | ||
nested_property_1.get(data, key) > nested_property_1.get(nextData, key)); | ||
}, | ||
@@ -92,1 +93,2 @@ }; | ||
exports.default = treeChanges; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "tree-changes", | ||
"version": "0.5.0", | ||
"version": "0.5.1", | ||
"description": "Helpers function to get tree changes between two datasets", | ||
@@ -30,11 +30,15 @@ "author": "Gil Barbara <gilbarbara@gmail.com>", | ||
"devDependencies": { | ||
"@gilbarbara/tsconfig": "^0.1.0", | ||
"@size-limit/preset-small-lib": "^2.1.0", | ||
"@types/deep-diff": "^1.0.0", | ||
"@types/jest": "^24.0.15", | ||
"@types/node": "^12.6.3", | ||
"bundlesize": "^0.18.0", | ||
"@types/jest": "^24.0.17", | ||
"@types/node": "^12.7.2", | ||
"chalk": "^2.4.2", | ||
"cross-env": "^5.2.0", | ||
"husky": "^3.0.0", | ||
"jest": "^24.8.0", | ||
"rimraf": "^2.6.3", | ||
"husky": "^3.0.4", | ||
"jest": "^24.9.0", | ||
"prettier": "^1.18.2", | ||
"repo-tools": "^0.2.0", | ||
"rimraf": "^3.0.0", | ||
"size-limit": "^2.1.0", | ||
"ts-jest": "^24.0.2", | ||
@@ -52,12 +56,21 @@ "tslint": "^5.18.0", | ||
"lint": "tslint -p tsconfig.json", | ||
"test": "jest --coverage", | ||
"test": "jest", | ||
"test:coverage": "jest --coverage", | ||
"test:watch": "jest --watch --verbose", | ||
"bundlesize": "bundlesize", | ||
"validate": "npm run lint && npm test && npm run build && bundlesize", | ||
"format": "prettier \"**/*.{js,jsx,json,yml,yaml,css,less,scss,ts,tsx,md,graphql,mdx}\" --write", | ||
"validate": "npm run lint && npm run test:coverage && npm run size", | ||
"size": "npm run build && size-limit", | ||
"prepublishOnly": "npm run validate" | ||
}, | ||
"bundlesize": [ | ||
"prettier": { | ||
"trailingComma": "all", | ||
"singleQuote": true, | ||
"arrowParens": "avoid", | ||
"printWidth": 100, | ||
"proseWrap": "never" | ||
}, | ||
"size-limit": [ | ||
{ | ||
"path": "./lib/index.js", | ||
"maxSize": "1 kB" | ||
"limit": "3 kB" | ||
} | ||
@@ -67,5 +80,6 @@ ], | ||
"hooks": { | ||
"pre-commit": "npm run validate" | ||
"pre-commit": "repo-tools check-remote && npm run validate", | ||
"post-merge": "repo-tools install-packages" | ||
} | ||
} | ||
} |
125
README.md
tree-changes | ||
=== | ||
[![NPM version](https://badge.fury.io/js/tree-changes.svg)](https://www.npmjs.com/package/tree-changes) | ||
[![build status](https://travis-ci.org/gilbarbara/tree-changes.svg)](https://travis-ci.org/gilbarbara/tree-changes) | ||
[![Maintainability](https://api.codeclimate.com/v1/badges/93528e49029782f5f7d2/maintainability)](https://codeclimate.com/github/gilbarbara/tree-changes/maintainability) | ||
[![Test Coverage](https://api.codeclimate.com/v1/badges/93528e49029782f5f7d2/test_coverage)](https://codeclimate.com/github/gilbarbara/tree-changes/test_coverage) | ||
[![NPM version](https://badge.fury.io/js/tree-changes.svg)](https://www.npmjs.com/package/tree-changes) [![build status](https://travis-ci.org/gilbarbara/tree-changes.svg)](https://travis-ci.org/gilbarbara/tree-changes) [![Maintainability](https://api.codeclimate.com/v1/badges/93528e49029782f5f7d2/maintainability)](https://codeclimate.com/github/gilbarbara/tree-changes/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/93528e49029782f5f7d2/test_coverage)](https://codeclimate.com/github/gilbarbara/tree-changes/test_coverage) | ||
Get changes between two versions of the same object. | ||
A good use for this is in [React](https://reactjs.org/) lifecycle methods, like `componentWillReceiveProps` or `componentDidUpdate`. | ||
Get changes between two versions of data with similar shape. | ||
### Setup | ||
## Setup | ||
@@ -18,3 +14,3 @@ ```bash | ||
### Usage | ||
## Usage | ||
@@ -24,25 +20,37 @@ ```js | ||
const A = { | ||
status: 'idle', | ||
const savedData = { | ||
data: { a: 1 }, | ||
hasData: false, | ||
data: { a: 1 }, | ||
items: [{ name: 'test' }], | ||
ratio: 0.45, | ||
ratio: 0.9, | ||
retries: 0, | ||
sort: { | ||
data: [{ type: 'asc' }, { type: 'desc' }], | ||
status: 'idle', | ||
}, | ||
switch: false, | ||
}; | ||
const B = { | ||
status: 'done', | ||
const newData = { | ||
data: { a: 1 }, | ||
hasData: true, | ||
data: { a: 1 }, | ||
items: [], | ||
ratio: 0.4, | ||
ratio: 0.5, | ||
retries: 1, | ||
sort: { | ||
data: [{ type: 'desc' }, { type: 'asc' }], | ||
status: 'success', | ||
}, | ||
}; | ||
const { changed, changedFrom, changedTo, increased, decreased } = treeChanges(objA, objB); | ||
const { | ||
changed, | ||
changedFrom, | ||
changedTo, | ||
increased, | ||
decreased, | ||
} = treeChanges(savedData, newData); | ||
if (changed('status')) { | ||
// do something | ||
if (changed('hasData')) { | ||
// execute some side-effect | ||
} | ||
@@ -55,7 +63,16 @@ | ||
// works with array values too | ||
if (changedFrom('status', 'idle', ['done', 'ready']) { | ||
if (changedFrom('sort.status', 'idle', ['done', 'success']) { | ||
// status has changed! | ||
} | ||
// support nested match | ||
if (changedTo('sort.data.0.type', 'desc') { | ||
// update the type | ||
} | ||
if (decreased('ratio')) { | ||
// do something! | ||
} | ||
if (increased('retries')) { | ||
// hey, slow down. | ||
@@ -65,3 +82,3 @@ } | ||
### With React | ||
#### Works with arrays too. | ||
@@ -71,2 +88,19 @@ ```js | ||
const { changed, changedTo } = treeChanges([0, { id: 2 }], [0, { id: 4 }]); | ||
changed(); // true | ||
changed(0); // false | ||
changed(1); // true | ||
changedTo('1.id', 4); // true | ||
``` | ||
> It uses [deep-diff](https://github.com/flitbit/diff) to compare plain objects/arrays and [nested-property](https://github.com/cosmosio/nested-property) to get the nested key. | ||
## With React | ||
### Class components | ||
```js | ||
import treeChanges from 'tree-changes'; | ||
class Comp extends React.Component { | ||
@@ -89,20 +123,53 @@ ... | ||
### API | ||
### Functional components with hooks | ||
**changed(key: string)** | ||
```jsx | ||
import React, { useEffect, useRef } from 'react'; | ||
import treeChanges from 'tree-changes'; | ||
function usePrevious(value) { | ||
const ref = useRef(); | ||
useEffect(() => { | ||
ref.current = value; | ||
}); | ||
return ref.current; | ||
} | ||
function useTreeChanges(props) { | ||
const prevProps = usePrevious(props) || {}; | ||
return treeChanges(prevProps, props); | ||
} | ||
const Page = (props) => { | ||
const { changedTo } = useTreeChanges(props); | ||
if (changedTo('isLoaded', true)) { | ||
sendAnalyticsEvent('load', 'MySuperPage') | ||
} | ||
return <div>...</div>; | ||
}; | ||
``` | ||
## API | ||
**changed**(`key: KeyType`) | ||
Check if the value has changed. Supports objects and arrays. | ||
**changedFrom(key: string, previous: string | boolean | number, actual: string | boolean | number)** | ||
**changedFrom**(`key: KeyType`, `previous: InputType`, `actual?: InputType`) | ||
Check if the value has changed from `previous` to `actual`. | ||
**changedTo(key: string, actual: string | boolean | number)** | ||
**changedTo**(`key: KeyType`, `actual: InputType`) | ||
Check if the value has changed to `actual`. | ||
**increased(key: string)** | ||
**increased**(`key: KeyType`) | ||
Check if both versions are numbers and the value has increased. | ||
**decreased(key: string)** | ||
**decreased**(`key: KeyType`) | ||
Check if both versions are numbers and the value has decreased. | ||
> type KeyType = string | number; | ||
type InputType = string | boolean | number | object | Array<string | boolean | number | object>; |
@@ -1,23 +0,28 @@ | ||
import deep from 'deep-diff'; | ||
import { diff } from 'deep-diff'; | ||
// @ts-ignore | ||
import nested from 'nested-property'; | ||
import { get as nested } from 'nested-property'; | ||
interface IObject { | ||
interface IPlainObject { | ||
[key: string]: any; | ||
} | ||
export type TypeInput = string | boolean | number | string[] | boolean[] | number[] | IObject; | ||
export type TypeInput = | ||
| string | ||
| boolean | ||
| number | ||
| IPlainObject | ||
| Array<string | boolean | number | IPlainObject>; | ||
export type IData = IObject | IObject[]; | ||
export type IData = IPlainObject | IPlainObject[]; | ||
export interface ITreeChanges { | ||
changed: (key?: string) => boolean; | ||
changedFrom: (key: string, previous: TypeInput, actual?: TypeInput) => boolean; | ||
changedTo: (key: string, actual: TypeInput) => boolean; | ||
increased: (key: string) => boolean; | ||
decreased: (key: string) => boolean; | ||
changed: (key?: string | number) => boolean; | ||
changedFrom: (key: string | number, previous: TypeInput, actual?: TypeInput) => boolean; | ||
changedTo: (key: string | number, actual: TypeInput) => boolean; | ||
increased: (key: string | number) => boolean; | ||
decreased: (key: string | number) => boolean; | ||
} | ||
function isPlainObj(...args: any): boolean { | ||
return args.every((d:any) => { | ||
return args.every((d: any) => { | ||
if (!d) { | ||
@@ -28,4 +33,6 @@ return false; | ||
return Object.prototype.toString.call(d) | ||
.slice(8, -1) === 'Object' && (prototype === null || prototype === Object.getPrototypeOf({})); | ||
return ( | ||
Object.prototype.toString.call(d).slice(8, -1) === 'Object' && | ||
(prototype === null || prototype === Object.getPrototypeOf({})) | ||
); | ||
}); | ||
@@ -35,3 +42,3 @@ } | ||
function isArray(...args: any): boolean { | ||
return args.every((d: any) => Array.isArray(d)); | ||
return args.every(Array.isArray); | ||
} | ||
@@ -49,10 +56,8 @@ | ||
return { | ||
changed(key?: string): boolean { | ||
const left = nested.get(data, key); | ||
const right = nested.get(nextData, key); | ||
changed(key?: string | number): boolean { | ||
const left = nested(data, key); | ||
const right = nested(nextData, key); | ||
if ((isArray(left, right)) || (isPlainObj(left, right))) { | ||
const diff = deep.diff(left, right); | ||
return !!diff; | ||
if (isArray(left, right) || isPlainObj(left, right)) { | ||
return !!diff(left, right); | ||
} | ||
@@ -62,4 +67,4 @@ | ||
}, | ||
changedFrom(key: string, previous: TypeInput, actual?: TypeInput): boolean { | ||
if (!key) { | ||
changedFrom(key: string | number, previous: TypeInput, actual?: TypeInput): boolean { | ||
if (typeof key === 'undefined') { | ||
throw new Error('Key parameter is required'); | ||
@@ -69,36 +74,45 @@ } | ||
const useActual = typeof previous !== 'undefined' && typeof actual !== 'undefined'; | ||
const left = nested.get(data, key); | ||
const right = nested.get(nextData, key); | ||
const leftComparator = Array.isArray(previous) ? previous.indexOf(left as never) >= 0 : left === previous; | ||
const rightComparator = Array.isArray(actual) ? actual.indexOf(right as never) >= 0 : right === actual; | ||
const left = nested(data, key); | ||
const right = nested(nextData, key); | ||
const leftComparator = Array.isArray(previous) | ||
? previous.indexOf(left) >= 0 | ||
: left === previous; | ||
const rightComparator = Array.isArray(actual) ? actual.indexOf(right) >= 0 : right === actual; | ||
return leftComparator && (useActual ? rightComparator : !useActual); | ||
}, | ||
changedTo(key: string, actual: TypeInput): boolean { | ||
if (!key) { | ||
changedTo(key: string | number, actual: TypeInput): boolean { | ||
if (typeof key === 'undefined') { | ||
throw new Error('Key parameter is required'); | ||
} | ||
const left = nested.get(data, key); | ||
const right = nested.get(nextData, key); | ||
const leftComparator = Array.isArray(actual) ? actual.indexOf(left as never) < 0 : left !== actual; | ||
const rightComparator = Array.isArray(actual) ? actual.indexOf(right as never) >= 0 : right === actual; | ||
const left = nested(data, key); | ||
const right = nested(nextData, key); | ||
const leftComparator = Array.isArray(actual) ? actual.indexOf(left) < 0 : left !== actual; | ||
const rightComparator = Array.isArray(actual) ? actual.indexOf(right) >= 0 : right === actual; | ||
return leftComparator && rightComparator; | ||
}, | ||
increased(key: string): boolean { | ||
if (!key) { | ||
increased(key: string | number): boolean { | ||
if (typeof key === 'undefined') { | ||
throw new Error('Key parameter is required'); | ||
} | ||
return isNumber(nested.get(data, key), nested.get(nextData, key)) && nested.get(data, key) < nested.get(nextData, key); | ||
return ( | ||
isNumber(nested(data, key), nested(nextData, key)) && | ||
nested(data, key) < nested(nextData, key) | ||
); | ||
}, | ||
decreased(key: string): boolean { | ||
if (!key) { | ||
decreased(key: string | number): boolean { | ||
if (typeof key === 'undefined') { | ||
throw new Error('Key parameter is required'); | ||
} | ||
return isNumber(nested.get(data, key), nested.get(nextData, key)) && nested.get(data, key) > nested.get(nextData, key); | ||
return ( | ||
isNumber(nested(data, key), nested(nextData, key)) && | ||
nested(data, key) > nested(nextData, key) | ||
); | ||
}, | ||
}; | ||
} |
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
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
170
18063
18
194