2015-08-02 14:54:12 +00:00
|
|
|
# updeep
|
|
|
|
[![NPM version][npm-image]][npm-url]
|
|
|
|
[![Build Status][travis-image]][travis-url]
|
|
|
|
[![Code Climate][codeclimate-image]][codeclimate-url]
|
|
|
|
[![Dependency Status][daviddm-image]][daviddm-url]
|
|
|
|
[![peerDependency Status][daviddm-peer-image]][daviddm-peer-url]
|
|
|
|
|
2015-08-02 07:32:32 +00:00
|
|
|
> Easily update nested frozen objects and arrays in a declarative and immutable
|
|
|
|
> manner.
|
2015-07-31 15:53:25 +00:00
|
|
|
|
2015-08-02 14:40:32 +00:00
|
|
|
|
2015-08-01 17:09:25 +00:00
|
|
|
## About
|
|
|
|
|
|
|
|
Updating deeply nested objects/arrays is a bit of a pain.
|
|
|
|
updeep makes it painless by allowing you to declare the updates you would like
|
|
|
|
to make and it will take care of the rest.
|
|
|
|
It will recursively return the same instance if no changes have been made,
|
|
|
|
making it ideal for using reference equality checks to detect changes (like
|
2015-08-02 07:32:32 +00:00
|
|
|
[PureRenderMixin]). Because of this, everything returned by updeep is frozen.
|
2015-08-01 17:09:25 +00:00
|
|
|
|
2015-08-02 07:32:32 +00:00
|
|
|
updeep requires [lodash], but works very well with [lodash-fp] or [Ramda]. As a
|
|
|
|
matter of fact, many of the helpers functions are [curried][currying] [lodash]
|
|
|
|
functions with their parameters reversed (as [lodash-fp] do).
|
2015-08-01 17:09:25 +00:00
|
|
|
|
|
|
|
Note that the parameters may be backwards from what you are used to. updeep
|
2015-08-02 07:32:32 +00:00
|
|
|
supports [partial application][currying], so the parameter order is:
|
2015-08-05 07:27:56 +00:00
|
|
|
`updeep(updates, object)`.
|
2015-08-01 17:09:25 +00:00
|
|
|
|
2015-08-02 07:32:32 +00:00
|
|
|
## API and Examples
|
2015-08-01 17:09:25 +00:00
|
|
|
|
2015-08-01 17:26:13 +00:00
|
|
|
### Full example
|
2015-08-01 17:09:25 +00:00
|
|
|
```js
|
|
|
|
var u = require('updeep');
|
|
|
|
|
2015-08-01 17:18:10 +00:00
|
|
|
var person = {
|
2015-08-05 07:39:56 +00:00
|
|
|
name: { first: 'Bill', last: 'Sagat' },
|
|
|
|
children: [
|
|
|
|
{ name: 'Mary-Kate', age: 7 },
|
|
|
|
{ name: 'Ashley', age: 7 }
|
|
|
|
],
|
|
|
|
todo: [
|
|
|
|
'Be funny',
|
|
|
|
'Manage household'
|
|
|
|
],
|
2015-08-01 17:18:10 +00:00
|
|
|
email: 'bill@example.com',
|
|
|
|
version: 1
|
|
|
|
};
|
|
|
|
|
|
|
|
var inc = function(i) { return i + 1; }
|
2015-08-07 03:13:46 +00:00
|
|
|
var eq = function(x) { return function(y) { return x == y } };
|
2015-08-01 17:18:10 +00:00
|
|
|
|
|
|
|
var newPerson = u({
|
2015-08-05 07:39:56 +00:00
|
|
|
// Change first name
|
|
|
|
name: { first: 'Bob' },
|
|
|
|
// Increment all children's ages
|
|
|
|
children: u.map({ age: inc }),
|
|
|
|
// Update email
|
2015-08-01 17:18:10 +00:00
|
|
|
email: 'bob@example.com',
|
2015-08-05 07:39:56 +00:00
|
|
|
// Remove todo
|
2015-08-07 03:13:46 +00:00
|
|
|
todo: u.reject(eq('Be funny')),
|
2015-08-05 07:39:56 +00:00
|
|
|
// Increment version
|
2015-08-01 17:18:10 +00:00
|
|
|
version: inc
|
|
|
|
}, person);
|
|
|
|
// => {
|
2015-08-05 07:39:56 +00:00
|
|
|
// name: { first: 'Bob', last: 'Sagat' },
|
|
|
|
// children: [
|
|
|
|
// { name: 'Mary-Kate', age: 8 },
|
|
|
|
// { name: 'Ashley', age: 8 }
|
|
|
|
// ],
|
|
|
|
// todo: [
|
|
|
|
// 'Manage household'
|
|
|
|
// ],
|
|
|
|
// email: 'bob@example.com',
|
|
|
|
// version: 2
|
|
|
|
//}
|
2015-08-01 17:26:13 +00:00
|
|
|
```
|
2015-08-01 17:18:10 +00:00
|
|
|
|
2015-08-05 05:28:31 +00:00
|
|
|
**NOTE**: All functions are curried, so if you see `f(x(, y))`, it can be called with either `f(x, y)` or `f(x)(y)`.
|
|
|
|
|
|
|
|
### `u(updates(, object))`
|
|
|
|
|
|
|
|
#### Simple update
|
2015-08-01 17:18:10 +00:00
|
|
|
|
2015-08-01 17:26:13 +00:00
|
|
|
```js
|
2015-08-01 17:09:25 +00:00
|
|
|
u({ x: { b: 3 } }, { x: { a: 0, b: 0 } });
|
|
|
|
// => { x: { a: 0, b: 3 } }
|
2015-08-01 17:26:13 +00:00
|
|
|
```
|
2015-08-01 17:09:25 +00:00
|
|
|
|
2015-08-05 05:28:31 +00:00
|
|
|
#### Multiple updates, including an array
|
2015-08-01 17:18:10 +00:00
|
|
|
|
2015-08-01 17:26:13 +00:00
|
|
|
```js
|
2015-08-01 17:09:25 +00:00
|
|
|
u({ x: { b: 3 }, y: { 1: 4 } }, { x: { a: 0, b: 0 }, y: [0, 0] });
|
|
|
|
// => { x: { a: 0, b: 3 }, y: [0, 4] }
|
2015-08-01 17:26:13 +00:00
|
|
|
```
|
2015-08-01 17:09:25 +00:00
|
|
|
|
2015-08-05 05:28:31 +00:00
|
|
|
#### Use a function
|
2015-08-01 17:18:10 +00:00
|
|
|
|
2015-08-01 17:26:13 +00:00
|
|
|
```js
|
2015-08-02 07:42:02 +00:00
|
|
|
function inc(i) { return i + 1; }
|
2015-08-01 17:09:25 +00:00
|
|
|
u({ x: { b: inc } }, { x: { a: 0, b: 0 } });
|
|
|
|
// => { x: { a: 0, b: 1 } }
|
2015-08-01 17:26:13 +00:00
|
|
|
```
|
2015-08-01 17:09:25 +00:00
|
|
|
|
2015-08-05 05:28:31 +00:00
|
|
|
#### Partial application
|
2015-08-01 17:18:10 +00:00
|
|
|
|
2015-08-01 17:26:13 +00:00
|
|
|
```js
|
2015-08-01 17:09:25 +00:00
|
|
|
var setBTo3 = u({ b: 3 });
|
|
|
|
setBTo3({ a: 0, b: 0 });
|
|
|
|
// => { a: 0, b: 3 })
|
2015-08-01 17:26:13 +00:00
|
|
|
```
|
2015-08-01 17:09:25 +00:00
|
|
|
|
2015-08-05 05:28:31 +00:00
|
|
|
#### ES6 computed properties
|
|
|
|
|
|
|
|
```js
|
|
|
|
var key = 'b';
|
|
|
|
u({ x: { [key]: 3 } }, { x: { a: 0, b: 0 } });
|
|
|
|
// => { x: { a: 0, b: 3 } }
|
|
|
|
```
|
|
|
|
|
2015-08-07 03:52:06 +00:00
|
|
|
### `u.updateIn(path(, value)(, object))`
|
2015-08-05 05:28:31 +00:00
|
|
|
|
|
|
|
Update a single value with a simple string or array path.
|
|
|
|
|
|
|
|
```js
|
2015-08-07 03:52:06 +00:00
|
|
|
u.updateIn('a.b', 3, { a: { b: 0 } });
|
2015-08-05 05:28:31 +00:00
|
|
|
// => { a: { b: 3 } };
|
|
|
|
```
|
|
|
|
|
|
|
|
```js
|
|
|
|
function inc(i) { return i + 1; }
|
2015-08-07 03:52:06 +00:00
|
|
|
u.updateIn('a.b', inc, { a: { b: 0 } });
|
2015-08-05 05:28:31 +00:00
|
|
|
// => { a: { b: 1 } };
|
|
|
|
```
|
|
|
|
|
|
|
|
```js
|
|
|
|
u({
|
2015-08-07 03:52:06 +00:00
|
|
|
x: u.updateIn(['a', 'b'], 3)
|
2015-08-05 05:28:31 +00:00
|
|
|
}, { x: { a: { b: 0 } } });
|
|
|
|
// => { x: { a: { b: 3 } } };
|
|
|
|
```
|
|
|
|
|
2015-08-05 06:36:40 +00:00
|
|
|
### `u.if(predicate(, updates)(, object))`
|
|
|
|
|
2015-08-06 06:10:16 +00:00
|
|
|
Apply `updates` only if `predicate` is truthy or, if `predicate` is a function,
|
2015-08-05 07:42:49 +00:00
|
|
|
it evaluates to truthy when called with `object`.
|
2015-08-05 06:36:40 +00:00
|
|
|
|
|
|
|
```js
|
2015-08-05 07:27:56 +00:00
|
|
|
var object = { a: 2 };
|
2015-08-05 06:36:40 +00:00
|
|
|
function isEven(x) { return x % 2 === 0; }
|
|
|
|
function inc(x) { return x + 1; }
|
|
|
|
|
|
|
|
u({
|
|
|
|
a: u.if(isEven, inc),
|
2015-08-05 07:27:56 +00:00
|
|
|
}, object);
|
2015-08-05 06:36:40 +00:00
|
|
|
// => { a: 3 }
|
|
|
|
```
|
|
|
|
|
2015-08-06 06:10:16 +00:00
|
|
|
### `u.ifElse(predicate(, trueUpdates)(, falseUpdates)(, object))`
|
|
|
|
|
|
|
|
Apply `trueUpdates` if `predicate` is truthy or, if `predicate` is a function,
|
|
|
|
it evaluates to truthy when called with `object`. Otherwise, apply `falseUpdates`.
|
|
|
|
|
|
|
|
```js
|
|
|
|
var object = { a: 3 };
|
|
|
|
function isEven(x) { return x % 2 === 0; }
|
|
|
|
function inc(x) { return x + 1; }
|
|
|
|
function dec(x) { return x - 1; }
|
|
|
|
|
|
|
|
u({
|
|
|
|
a: u.if(isEven, inc),
|
|
|
|
}, object);
|
|
|
|
// => { a: 2 }
|
|
|
|
```
|
|
|
|
|
2015-08-05 07:25:34 +00:00
|
|
|
### `u.map(iteratee(, object))`
|
|
|
|
|
|
|
|
If iteratee is a function, map it over the values in `object`.
|
|
|
|
If it is an object, apply it as updates to each value in `object`,
|
2015-08-05 07:27:56 +00:00
|
|
|
which is equivalent to `u.map(u(...), object)`).
|
2015-08-05 07:25:34 +00:00
|
|
|
|
|
|
|
```js
|
|
|
|
function inc(x) { return x + 1; }
|
|
|
|
u({
|
|
|
|
a: u.map(inc)
|
|
|
|
}, { a: [0, 1] });
|
|
|
|
// => { a: [1, 2] }
|
|
|
|
```
|
|
|
|
|
|
|
|
```js
|
|
|
|
function inc(x) { return x + 1; }
|
|
|
|
u.map(inc, [0, 1, 2]);
|
|
|
|
// => [1, 2, 3]
|
|
|
|
|
|
|
|
u.map(inc, { a: 0, b: 1, c: 2});
|
|
|
|
// => { a: 1, b: 2, c: 3}
|
|
|
|
```
|
|
|
|
|
|
|
|
```js
|
|
|
|
u.map({ a: 2 }, [{ a: 0 }, { a: 1 }]);
|
|
|
|
// => [{ a: 2 }, { a: 2 }]
|
|
|
|
```
|
|
|
|
|
2015-08-05 05:28:31 +00:00
|
|
|
### `u.omit(predicate(, object))`
|
|
|
|
|
|
|
|
Remove properties. See [`_.omit`](https://lodash.com/docs#omit).
|
2015-08-01 17:23:10 +00:00
|
|
|
|
2015-08-01 17:26:13 +00:00
|
|
|
```js
|
2015-08-05 05:28:31 +00:00
|
|
|
u({ x: u.omit('b') }, { x: { a: 0, b: 0, c: 0 } });
|
|
|
|
// => { x: { a: 0, c: 0 } }
|
|
|
|
```
|
|
|
|
|
|
|
|
```js
|
|
|
|
u({ x: u.omit(['b', 'c']) }, { x: { a: 0, b: 0, c: 0 } });
|
2015-08-01 17:09:25 +00:00
|
|
|
// => { x: { a: 0 } }
|
2015-08-01 17:26:13 +00:00
|
|
|
```
|
2015-08-01 17:09:25 +00:00
|
|
|
|
2015-08-05 05:28:31 +00:00
|
|
|
### `u.reject(predicate(, object))`
|
|
|
|
|
|
|
|
Reject items from an array. See [`_.reject`](https://lodash.com/docs#reject).
|
2015-08-02 07:42:02 +00:00
|
|
|
|
|
|
|
```js
|
2015-08-07 03:48:04 +00:00
|
|
|
function isEven(i) { return i % 2 === 0; }
|
|
|
|
u({ x: u.reject(isEven) }, { x: [1, 2, 3, 4] });
|
2015-08-02 07:42:02 +00:00
|
|
|
// => { x: [1, 3] }
|
|
|
|
```
|
|
|
|
|
2015-08-05 05:28:31 +00:00
|
|
|
### `u.withDefault(default(, updates)(, object))`
|
|
|
|
|
|
|
|
Like `u()`, but start with the default value if the original value is undefined.
|
2015-08-01 17:18:10 +00:00
|
|
|
|
2015-08-01 17:26:13 +00:00
|
|
|
```js
|
2015-08-06 02:49:55 +00:00
|
|
|
u({ x: u.withDefault([], { 0: 3 }) }, {});
|
2015-08-01 17:09:25 +00:00
|
|
|
// => { x: [3] }
|
2015-08-01 17:26:13 +00:00
|
|
|
```
|
2015-08-01 17:09:25 +00:00
|
|
|
|
|
|
|
See the [tests] for more examples.
|
2015-07-31 15:53:25 +00:00
|
|
|
|
2015-08-07 03:48:04 +00:00
|
|
|
### `u.is(path(, predicate)(, object))`
|
|
|
|
|
|
|
|
Returns `true` if the `predicate` matches the `path` applied to the `object`.
|
|
|
|
If the `predicate` is a function, the result is returned. If not, they are compared with `===`.
|
|
|
|
|
|
|
|
```js
|
|
|
|
u.is('a.b', 4, { a: { b: 4 } });
|
|
|
|
// => true
|
|
|
|
```
|
|
|
|
|
|
|
|
```js
|
|
|
|
function isEven(i) { return i % 2 === 0; }
|
|
|
|
u.is('a.b', isEven, { a: { b: 4 } });
|
|
|
|
// => true
|
|
|
|
```
|
|
|
|
|
|
|
|
```js
|
|
|
|
u({
|
2015-08-07 03:52:06 +00:00
|
|
|
person: u.if(u.is('name.first', 'Jen'), u.updateIn('name.last', 'Simpson'))
|
2015-08-07 03:48:04 +00:00
|
|
|
}, { person: { name: { first: 'Jen', last: 'Matthews' } } });
|
|
|
|
// => { person: { name: { first: 'Jen', last: 'Simpson' } } }
|
|
|
|
```
|
|
|
|
|
2015-07-31 15:53:25 +00:00
|
|
|
## Install
|
|
|
|
|
|
|
|
```sh
|
|
|
|
$ npm install --save updeep
|
|
|
|
```
|
|
|
|
|
2015-08-01 17:09:25 +00:00
|
|
|
Requires [lodash] as a peer dependency, so make sure you have it installed as
|
|
|
|
well.
|
2015-07-31 15:53:25 +00:00
|
|
|
|
2015-08-02 07:42:02 +00:00
|
|
|
## Configuration
|
|
|
|
|
|
|
|
If `NODE_ENV` is `"production"`, updeep will not attempt to freeze objects.
|
|
|
|
This may yield a slight performance gain.
|
|
|
|
|
2015-08-01 17:09:25 +00:00
|
|
|
## Motivation
|
2015-07-31 15:53:25 +00:00
|
|
|
|
2015-08-01 17:09:25 +00:00
|
|
|
While creating reducers for use with [redux], I wanted something that made it
|
|
|
|
easy to work with frozen objects. Native javascript objects have some nice
|
|
|
|
advantages over things like [Immutable.js][immutablejs] such as debugging and
|
|
|
|
destructuring. I wanted something more powerful than [icepick] and more
|
|
|
|
composable than [React.addons.update].
|
2015-07-31 15:53:25 +00:00
|
|
|
|
2015-08-02 07:42:02 +00:00
|
|
|
If you're manipulating massive amounts of data frequently, you may want to
|
|
|
|
benchmark, as [Immutable.js][immutablejs] should be more efficient in that
|
|
|
|
case.
|
|
|
|
|
2015-08-01 17:09:25 +00:00
|
|
|
## Contributing
|
2015-08-01 16:17:21 +00:00
|
|
|
|
2015-08-01 17:09:25 +00:00
|
|
|
1. Fork it.
|
|
|
|
1. Create your feature branch (`git checkout -b my-new-feature`).
|
|
|
|
1. Run `gulp` to run tests and lint.
|
|
|
|
1. Commit your changes (`git commit -am 'Added some feature'`).
|
|
|
|
1. Push to the branch (`git push origin my-new-feature`).
|
|
|
|
1. Create new Pull Request.
|
2015-07-31 15:53:25 +00:00
|
|
|
|
2015-08-05 04:46:52 +00:00
|
|
|
## Releasing New Version
|
|
|
|
|
|
|
|
1. Login to npm, if you don't have access to the package, ask for it.
|
|
|
|
|
|
|
|
```bash
|
|
|
|
$ npm login
|
|
|
|
```
|
|
|
|
1. Make sure the build passes (best to let it pass on travis, but you can run it locally):
|
|
|
|
|
|
|
|
```bash
|
|
|
|
$ gulp
|
|
|
|
```
|
|
|
|
1. Bump the version:
|
|
|
|
|
|
|
|
```bash
|
|
|
|
$ npm version major|minor|patch
|
|
|
|
```
|
|
|
|
1. Update the `CHANGELOG.md`.
|
|
|
|
1. Add the new version and corresponding notes.
|
|
|
|
1. Add a link to the new version.
|
|
|
|
1. Update the `unreleased` link compare to be based off of the new version.
|
|
|
|
1. Publish and push:
|
|
|
|
|
|
|
|
```bash
|
|
|
|
$ npm publish
|
|
|
|
$ git push master --follow-tags
|
|
|
|
```
|
|
|
|
|
2015-07-31 15:53:25 +00:00
|
|
|
## License
|
|
|
|
|
2015-08-05 06:18:07 +00:00
|
|
|
MIT ©2015 [Substantial](http://substantial.com)
|
2015-07-31 15:53:25 +00:00
|
|
|
|
2015-08-05 15:14:28 +00:00
|
|
|
[npm-image]: https://badge.fury.io/js/updeep.svg
|
2015-07-31 15:53:25 +00:00
|
|
|
[npm-url]: https://npmjs.org/package/updeep
|
2015-08-05 15:19:14 +00:00
|
|
|
[travis-image]: https://travis-ci.org/substantial/updeep.svg?branch=master
|
|
|
|
[travis-url]: https://travis-ci.org/substantial/updeep
|
|
|
|
[daviddm-image]: https://david-dm.org/substantial/updeep.svg?theme=shields.io
|
|
|
|
[daviddm-url]: https://david-dm.org/substantial/updeep
|
|
|
|
[daviddm-peer-image]: https://david-dm.org/substantial/updeep/peer-status.svg
|
|
|
|
[daviddm-peer-url]:https://david-dm.org/substantial/updeep#info=peerDependencies
|
|
|
|
[codeclimate-image]: https://codeclimate.com/github/substantial/updeep/badges/gpa.svg
|
|
|
|
[codeclimate-url]: https://codeclimate.com/github/substantial/updeep
|
2015-08-01 17:09:25 +00:00
|
|
|
[lodash]: http://lodash.com
|
2015-08-02 07:32:32 +00:00
|
|
|
[lodash-fp]: https://github.com/lodash/lodash-fp
|
|
|
|
[Ramda]: http://ramdajs.com/
|
2015-08-01 17:09:25 +00:00
|
|
|
[PureRenderMixin]: https://facebook.github.io/react/docs/pure-render-mixin.html
|
|
|
|
[redux]: https://github.com/gaearon/redux
|
|
|
|
[immutablejs]: https://github.com/facebook/immutable-js
|
|
|
|
[icepick]: https://github.com/aearly/icepick
|
|
|
|
[React.addons.update]: https://facebook.github.io/react/docs/update.html
|
2015-08-05 15:19:14 +00:00
|
|
|
[tests]: https://github.com/substantial/updeep/blob/master/test/index.js
|
2015-08-01 17:23:10 +00:00
|
|
|
[currying]: http://www.datchley.name/currying-vs-partial-application/
|