Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.yml]
indent_size = 2
16 changes: 16 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"extends": ["xo", "xo-typescript", "prettier"],
"plugins": ["@typescript-eslint", "prettier"],
"rules": {
"@typescript-eslint/naming-convention": "off",
"@typescript-eslint/no-confusing-void-expression": "off",
"@typescript-eslint/object-curly-spacing": "off",
"@typescript-eslint/indent": "off",
"operator-linebreak": "off",
"import/no-anonymous-default-export": "off",
"@typescript-eslint/comma-dangle": "off",
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/no-unsafe-call": "off",
"unicorn/prefer-module": "off"
}
}
26 changes: 13 additions & 13 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@ name: Node.js CI

on:
push:
branches: [ "master" ]
branches: ['master']
pull_request:
branches: [ "master" ]
branches: ['master']

jobs:
build:

runs-on: ubuntu-latest

strategy:
Expand All @@ -20,13 +19,14 @@ jobs:
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: yarn
# no --if-present support in yarn
# - run: yarn run build --if-present
- run: yarn test
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm i -g npm@latest
- run: npm install
- run: npm run lint
- run: npm run build --if-present
- run: npm test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
node_modules
coverage
.nyc_output
dist
6 changes: 6 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"printWidth": 120
}
6 changes: 2 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,5 @@ node_js:
- '12'
- '14'
- '15'
script:
npm run test-coverage
after_success:
npm run coverage
script: npm run test-coverage
after_success: npm run coverage
3 changes: 1 addition & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
{
}
{}
10 changes: 10 additions & 0 deletions .xo-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@
"rules": {
"unicorn/prefer-spread": "off",
"unicorn/no-array-reduce": "off",
"import/extensions": "off",
"@typescript-eslint/naming-convention": "off",
"@typescript-eslint/no-confusing-void-expression": "off",
"@typescript-eslint/object-curly-spacing": "off",
"@typescript-eslint/indent": "off",
"operator-linebreak": "off",
"import/no-anonymous-default-export": "off",
"@typescript-eslint/comma-dangle": "off",
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/no-unsafe-call": "off",
"unicorn/prefer-module": "off"
}
}
82 changes: 52 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ yarn add human-object-diff

## Usage

```js
Common JS

```typescript
const HumanDiff = require('human-object-diff');

const lhs = { foo: 'bar' };
Expand All @@ -51,20 +53,20 @@ console.log(diff(lhs, rhs));
// -> ['"Foo", with a value of "bar" (at Obj.foo) was changed to "baz"']
```

## Configuring

### Options

`human-object-diff` supports a variety of options to allow you to take control over the output of your object diff.

| Option | type | Default | Description |
| -------------- | ---------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------- |
| objectName | String | 'Obj' | This is the object name when presented in the path. ie... "Obj.foo" ignored if hidePath is true |
| prefilter | \[String\]\|Func | | see [prefiltering](#prefiltering) |
| dateFormat | String | 'MM/dd/yyyy hh:mm a' | dateFns format string see [below](#support-for-dates) |
| ignoreArrays | Bool | false | If array differences aren't needed. Set to true and skip processing |
| templates | Object | see [templates](#custom-templates) | Completely customize the output. |
| sensitivePaths | \[String\] | | Paths that will use the sensitive field templates if they are defined |
| Option | type | Default | Description |
| ------------------------------- | ------------ | ---------------------------------- | --------------------------------------------------------------------------------------- | --------------------------------- |
| objectName | String | 'Obj' | This is the object name when presented in the path. ie... "Obj.foo" ignored if hidePath |
| is true |
| prefilter | \[String\]\ | Func | | see [prefiltering](#prefiltering) |
| dateFormat | String | 'MM/dd/yyyy hh:mm a' | dateFns format string |
| see [below](#support-for-dates) |
| ignoreArrays | Bool | false | If array differences aren't needed. Set to true and skip processing |
| templates | Object | see [templates](#custom-templates) | Completely customize the output. |
| sensitivePaths | \[String\] | | Paths that will use the sensitive field templates if they are defined |

### Custom Templates

Expand All @@ -76,14 +78,10 @@ The default template looks like the following:
const templates = {
N: '"FIELD", with a value of "NEWVALUE" (at DOTPATH) was added',
D: '"FIELD", with a value of "OLDVALUE" (at DOTPATH) was removed',
E:
'"FIELD", with a value of "OLDVALUE" (at DOTPATH) was changed to "NEWVALUE"',
I:
'Array "FIELD" (at DOTPATH), had a value of "NEWVALUE" inserted at index INDEX',
R:
'Array "FIELD" (at DOTPATH), had a value of "OLDVALUE" removed at index INDEX',
AE:
'Array "FIELD" (at DOTPATH), had a value of "OLDVALUE" changed to "NEWVALUE" at index INDEX',
E: '"FIELD", with a value of "OLDVALUE" (at DOTPATH) was changed to "NEWVALUE"',
I: 'Array "FIELD" (at DOTPATH), had a value of "NEWVALUE" inserted at index INDEX',
R: 'Array "FIELD" (at DOTPATH), had a value of "OLDVALUE" removed at index INDEX',
AE: 'Array "FIELD" (at DOTPATH), had a value of "OLDVALUE" changed to "NEWVALUE" at index INDEX',
NS: '"FIELD" (at DOTPATH) was added',
DS: '"FIELD" (at DOTPATH) was removed',
ES: '"FIELD" (at DOTPATH) was changed',
Expand All @@ -93,21 +91,30 @@ const templates = {
};
```

Where N is a new key, D is a deleted key, E is an edited key, I is an inserted array value, R is a removed array value, and AE is an edited array property.
Where N is a new key, D is a deleted key, E is an edited key, I is an inserted array value, R is a removed array value,
and AE is an edited array property.

We also expose a sensitiveFields array option which will cause a path to use the S option template.

You can define each sentence in the templates to be whatever you'd like. The following tokens can be used to replace their diff values in the final output.
You can define each sentence in the templates to be whatever you'd like. The following tokens can be used to replace
their diff values in the final output.

The available tokens that can plug in to your sentence templates are `FIELD`, `DOTPATH`,`NEWVALUE`,`OLDVALUE`, `INDEX`, `POSITION`. Position is just index+1. Be aware that not all sentence types will have values for each token. For instance, non-array changes will not have a position or an index.
The available tokens that can plug in to your sentence templates
are `FIELD`, `DOTPATH`,`NEWVALUE`,`OLDVALUE`, `INDEX`, `POSITION`. Position is just index+1. Be aware that not all
sentence types will have values for each token. For instance, non-array changes will not have a position or an index.

### Support for Dates

`human-object-diff` uses `date-fns` format function under the hood to show human readable date differences. We also supply a `dateFormat` option where you can supply your own date formatting string. Please note, that date-fns format strings are different from moment.js format strings. Please refer to the documentation [here](https://date-fns.org/v2.8.1/docs/format) and [here](https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md)
`human-object-diff` uses `date-fns` format function under the hood to show human readable date differences. We also
supply a `dateFormat` option where you can supply your own date formatting string. Please note, that date-fns format
strings are different from moment.js format strings. Please refer to the
documentation [here](https://date-fns.org/v2.8.1/docs/format)
and [here](https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md)

### Prefiltering

There may be some paths in your object diffs that you'd like to ignore. You can do that with prefiltering. As a convenience, you can add this option as an array of strings, which are the keys of the root paths of the objects.
There may be some paths in your object diffs that you'd like to ignore. You can do that with prefiltering. As a
convenience, you can add this option as an array of strings, which are the keys of the root paths of the objects.

for instance

Expand All @@ -122,9 +129,12 @@ diff(lhs, rhs);

You would still see the diffs for `biz.foo` but you would ignore the diff for `foo`.

You can also pass a function for this option which will be directly passed to the [underlying diff library](https://www.npmjs.com/package/deep-diff).
You can also pass a function for this option which will be directly passed to
the [underlying diff library](https://www.npmjs.com/package/deep-diff).

The prefilter function takes a signature of `function(path, key)`. Here path is an array that represents the path leading up to the object property. The key is the key, or what would be the final element of the path. The function returns true for any paths you would want to ignore.
The prefilter function takes a signature of `function(path, key)`. Here path is an array that represents the path
leading up to the object property. The key is the key, or what would be the final element of the path. The function
returns true for any paths you would want to ignore.

For instance, in the object below:

Expand All @@ -146,16 +156,24 @@ const prefilter = (path, key) => path[0] === 'foo' && key === 'bar';

## A Note On Arrays

> \*\*There are known bug related to arrays of objects. We plan to release different array processing algorithms in the future that can handle more complex objects. As of the latest version it is reccomended to only diff between flat arrays of strings and numbers. Otherwise there isn't guarantee of accuracy or if diffs won't be duplicated in some ways.
> \*\*There are known bug related to arrays of objects. We plan to release different array processing algorithms in the
> future that can handle more complex objects. As of the latest version it is reccomended to only diff between flat
> arrays
> of strings and numbers. Otherwise there isn't guarantee of accuracy or if diffs won't be duplicated in some ways.

`human-object-diff` parses arrays in an opinionated way. It does it's best to resolve Arrays into groups of insertions and removals. Typical diff libraries look at arrays on an element by element basis and emit a difference for every changes element. While this is benefical for many programatic tasks, humans typically don't look at arrays in the same way. `human-object-diff` attempts to reduce array changes to a number of insertions, removals, and edits. An example can better describe the difference.
`human-object-diff` parses arrays in an opinionated way. It does it's best to resolve Arrays into groups of insertions
and removals. Typical diff libraries look at arrays on an element by element basis and emit a difference for every
changes element. While this is benefical for many programatic tasks, humans typically don't look at arrays in the same
way. `human-object-diff` attempts to reduce array changes to a number of insertions, removals, and edits. An example can
better describe the difference.

```js
const lhs = [1, 2, 3, 4];
const rhs = [0, 1, 2, 3, 4];
```

Consider the above arrays and their differences. A typical array diff would behave like this and output something like the following.
Consider the above arrays and their differences. A typical array diff would behave like this and output something like
the following.

1. A change at index 0 from 1 to 0
2. A change at index 1 from 2 to 1
Expand All @@ -171,7 +189,8 @@ This is much more understandable to a human brain. We've simply inserted a numbe

## Diff Memory

The diff engine object created when `new HumanDiff()` is invoked contains a `sentences` property which you can use to recall the last diff that was computed.
The diff engine object created when `new HumanDiff()` is invoked contains a `sentences` property which you can use to
recall the last diff that was computed.

```js
const diffEngine = new HumanDiff();
Expand All @@ -194,3 +213,6 @@ diffEngine.sentences; // -> same as the output of the last diff

[npm]: https://www.npmjs.com/
[yarn]: https://yarnpkg.com/

Real world example
I selected open source package and decided to add dual support for it.
6 changes: 6 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
transform: {
'^.+\\.tsx?$': 'esbuild-jest'
},
testEnvironment: 'node'
};
Loading