Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

baobab

Package Overview
Dependencies
Maintainers
1
Versions
64
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

baobab - npm Package Compare versions

Comparing version 0.2.2 to 0.3.0

src/combination.js

2

bower.json
{
"name": "baobab",
"main": "build/baobab.min.js",
"version": "0.2.2",
"version": "0.3.0",
"homepage": "https://github.com/Yomguithereal/baobab",

@@ -6,0 +6,0 @@ "author": {

@@ -9,4 +9,5 @@ {

"mixins": [],
"shiftReferences": true,
"typology": null,
"validate": null
}

@@ -7,10 +7,14 @@ /**

*/
var Baobab = require('./src/baobab.js');
var Baobab = require('./src/baobab.js'),
helpers = require('./src/helpers.js');
// Non-writable version
Object.defineProperty(Baobab, 'version', {
value: '0.2.2'
value: '0.3.0'
});
// Exposing helpers
Baobab.getIn = helpers.getIn;
// Exporting
module.exports = Baobab;
The MIT License (MIT)
Copyright (c) 2014 Guillaume Plique (Yomguithereal)
Copyright (c) 2014-2015 Guillaume Plique (Yomguithereal)

@@ -5,0 +5,0 @@ Permission is hereby granted, free of charge, to any person obtaining a copy

{
"name": "baobab",
"version": "0.2.2",
"version": "0.3.0",
"description": "JavaScript data tree with cursors.",

@@ -19,4 +19,4 @@ "main": "index.js",

"gulp-uglify": "^1.0.2",
"jsdom": "^2.0.0",
"lodash.clonedeep": "^2.4.1",
"jsdom": "^3.1.0",
"lodash.clonedeep": "^3.0.0",
"mocha": "^2.0.1",

@@ -23,0 +23,0 @@ "react": "^0.12.2",

@@ -29,3 +29,5 @@ [![Build Status](https://travis-ci.org/Yomguithereal/baobab.svg)](https://travis-ci.org/Yomguithereal/baobab)

* [Chaining mutations](#chaining-mutations)
* [Cursor combinations](#cursor-combinations)
* [Data validation](#data-validation)
* [Common pitfalls](#common-pitfalls)
* [Contribution](#contribution)

@@ -72,2 +74,8 @@ * [License](#license)

Or install with bower:
```js
bower install baobab
```
## Usage

@@ -179,2 +187,8 @@

*Merging objects*
```js
cursor.merge({hello: 'world'});
```
#### Events

@@ -297,2 +311,7 @@

// Cursor data is then available either through:
var data = this.cursor.get();
// Or
var data = this.state.cursor;
return <ul>{this.cursor.get().map(renderItem)}</ul>;

@@ -311,2 +330,7 @@ }

// Cursor data is then available either through:
var data = this.cursors[0].get();
// Or
var data = this.state.cursors[0];
return (

@@ -333,2 +357,7 @@ <div>

// Cursor data is then available either through:
var data = this.cursors.name.get();
// Or
var data = this.state.cursors.name;
return (

@@ -374,3 +403,4 @@ <div>

name: 'fancy',
colors: ['blue', 'yellow', 'green']
colors: ['blue', 'yellow', 'green'],
items: [{id: 'one', value: 'Hey'}, {id: 'two', value: 'Ho'}]
}

@@ -394,2 +424,17 @@ });

>>> ['blue', 'yellow', 'green']
// Retrieving or selecting data by passing a function in the path
var complexCursor = tree.select('palette', 'colors', function(color) {
return color === 'green';
});
tree.get('palette', 'colors', function(color) {
return color === 'green';
});
>>> 'green'
// Retrieving or selecting data by passing a descriptor object in the path
var complexCursor = tree.select('items', {id: 'one'}, 'value');
tree.get('items', {id: 'one'}, 'value');
>>> 'Hey'
```

@@ -432,2 +477,10 @@

*Check information about the cursor's location in the tree*
```js
cursor.isRoot();
cursor.isBranch();
cursor.isLeaf();
```
#### Options

@@ -463,2 +516,3 @@

* **mixins** *array*: optional mixins to merge with baobab's ones. Recommending the [pure render](http://facebook.github.io/react/docs/pure-render-mixin.html) one from react.
* **shiftReferences** *boolean* [`false`]: tell the tree to shift references of the objects it updates so that functions performing shallow comparisons (such as the one used by the `PureRenderMixin`, for instance), can assess that data changed.
* **typology** *Typology|object*: a custom typology to be used to validate the tree's data.

@@ -514,2 +568,3 @@ * **validate** *object*: a [typology](https://github.com/jacomyal/typology) schema ensuring the tree's data is valid.

* `$unshift`
* `$merge`

@@ -534,12 +589,14 @@ *Example*

tree.update({
john: {
firstname: {
$set: 'John the 3rd'
}
},
jack: {
firstname: {
$apply: function(firstname) {
return firstname + ' the 2nd';
users: {
john: {
firstname: {
$set: 'John the 3rd'
}
},
jack: {
firstname: {
$apply: function(firstname) {
return firstname + ' the 2nd';
}
}
}

@@ -550,3 +607,3 @@ }

// From cursor
var cursor = tree.select('john');
var cursor = tree.select('users', 'john');
cursor.update({

@@ -579,2 +636,25 @@ firstname: {

#### Cursor combinations
At times, you might want to listen to updates concerning a logical combination of cursors. For instance, you might want to know when two cursors both updated or when either one or the other did.
You can build cursor combination likewise:
```js
// Simple "or" combination
var combination = cursor1.or(cursor2);
// Simple "and" combination
var combination = cursor1.and(cursor2);
// Complex combination
var combination = cursor1.or(cursor2).or(cursor3).and(cursor4);
// Listening to events
combination.on('update', handler);
// Releasing a combination to avoid leaks
combination.release();
```
#### Data validation

@@ -623,2 +703,49 @@

#### Common pitfalls
*Controlled input state*
If you need to store a react controlled input's state into a baobab tree, remember you have to commit changes synchronously through the `commit` method if you don't want to observe nasty cursor jumps.
```jsx
var tree = new Boabab({inputValue: null});
var Input = React.createClass({
mixins: [tree.mixin],
cursor: ['inputValue'],
onChange: function(e) {
var newValue = e.target.value;
// If one edits the tree normally, i.e. asynchronously, the cursor will hop
this.cursor.edit(newValue);
// One has to commit synchronously the update for the input to work correctly
this.cursor.edit(newValue);
this.tree.commit();
},
render: function() {
return <input onChange={this.onChange} value={this.cursor.get()} />;
}
});
```
*Immutable behaviour*
TL;DR: Don't mutate things in your *baobab* tree. Let the tree handle its own mutations.
For performance and size reasons *baobab* does not (yet?) use an immutable data structure. However, because it aims at producing a one-way data flow for your application state (like **React** would at component level), it must be used like an immutable data structure.
For this reason, don't be surprised if you mutate things and break your tree.
```js
// This is bad:
var users = tree.get('users');
users[0].name = 'Jonathan';
// This is also bad:
var o = {hello: 'world'};
tree.set('key', o);
o.hello = 'other world';
```
## Contribution

@@ -625,0 +752,0 @@

@@ -33,4 +33,4 @@ /**

// Merging defaults
this.options = merge(opts, defaults);
this._cloner = this.options.cloningFunction || helpers.clone;
this.options = helpers.shallowMerge(defaults, opts);
this._cloner = this.options.cloningFunction || helpers.deepClone;

@@ -135,3 +135,3 @@ // Privates

var record = this._archive();
log = update(this.data, this._futureUpdate);
log = update(this.data, this._futureUpdate, this.options);

@@ -187,4 +187,14 @@ if (record)

// Casting to array
path = (typeof path === 'string') ? [path] : path;
path = (types.get(path) !== 'array') ? [path] : path;
// Complex path?
var complex = path.some(function(step) {
return types.check(step, 'complexStep');
});
var solvedPath;
if (complex)
solvedPath = helpers.solvePath(this.data, path);
// Registering a new cursor or giving the already existing one for path

@@ -198,3 +208,3 @@ if (!this.options.cursorSingletons) {

if (!this._registeredCursors[hash]) {
var cursor = new Cursor(this, path);
var cursor = new Cursor(this, path, solvedPath);
this._registeredCursors[hash] = cursor;

@@ -275,3 +285,3 @@ return cursor;

Baobab.prototype.toJSON = function() {
return this.get();
return this.reference();
};

@@ -278,0 +288,0 @@

@@ -8,2 +8,3 @@ /**

var EventEmitter = require('emmett'),
Combination = require('./combination.js'),
mixins = require('./mixins.js'),

@@ -16,3 +17,3 @@ helpers = require('./helpers.js'),

*/
function Cursor(root, path) {
function Cursor(root, path, solvedPath) {
var self = this;

@@ -31,2 +32,6 @@

// Complex path?
this.complexPath = !!solvedPath;
this.solvedPath = this.complexPath ? solvedPath : this.path;
// Root listeners

@@ -38,2 +43,10 @@ this.root.on('update', function(e) {

// Solving path if needed
if (self.complexPath)
self.solvedPath = helpers.solvePath(self.root.data, self.path);
// If no handlers are attached, we stop
if (!this._handlers.update.length && !this._handlersAll.length)
return;
// If selector listens at root, we fire

@@ -52,7 +65,7 @@ if (!self.path.length)

// If path is not relevant to us, we break
if (p !== self.path[j])
if (p !== self.solvedPath[j])
break;
// If we reached last item and we are relevant, we fire
if (j + 1 === m || j + 1 === self.path.length) {
if (j + 1 === m || j + 1 === self.solvedPath.length) {
shouldFire = true;

@@ -95,3 +108,3 @@ break root;

Cursor.prototype._stack = function(spec) {
this.root._stack(helpers.pathObject(this.path, spec));
this.root._stack(helpers.pathObject(this.solvedPath, spec));
return this;

@@ -101,4 +114,19 @@ };

/**
* Prototype
* Predicates
*/
Cursor.prototype.isRoot = function() {
return !this.path.length;
};
Cursor.prototype.isLeaf = function() {
return types.check(this.reference(), 'primitive');
};
Cursor.prototype.isBranch = function() {
return !this.isLeaf() && !this.isRoot();
};
/**
* Traversal
*/
Cursor.prototype.select = function(path) {

@@ -114,10 +142,10 @@ if (arguments.length > 1)

Cursor.prototype.up = function() {
if (this.path.length)
if (this.solvedPath && this.solvedPath.length)
return this.root.select(this.path.slice(0, -1));
else
return this.root.select([]);
return null;
};
Cursor.prototype.left = function() {
var last = +this.path[this.path.length - 1];
var last = +this.solvedPath[this.solvedPath.length - 1];

@@ -127,7 +155,9 @@ if (isNaN(last))

return this.root.select(this.path.slice(0, -1).concat(last - 1));
return last ?
this.root.select(this.solvedPath.slice(0, -1).concat(last - 1)) :
null;
};
Cursor.prototype.leftmost = function() {
var last = +this.path[this.path.length - 1];
var last = +this.solvedPath[this.solvedPath.length - 1];

@@ -137,7 +167,7 @@ if (isNaN(last))

return this.root.select(this.path.slice(0, -1).concat(0));
return this.root.select(this.solvedPath.slice(0, -1).concat(0));
};
Cursor.prototype.right = function() {
var last = +this.path[this.path.length - 1];
var last = +this.solvedPath[this.solvedPath.length - 1];

@@ -147,7 +177,10 @@ if (isNaN(last))

return this.root.select(this.path.slice(0, -1).concat(last + 1));
if (last + 1 === this.up().reference().length)
return null;
return this.root.select(this.solvedPath.slice(0, -1).concat(last + 1));
};
Cursor.prototype.rightmost = function() {
var last = +this.path[this.path.length - 1];
var last = +this.solvedPath[this.solvedPath.length - 1];

@@ -159,14 +192,17 @@ if (isNaN(last))

return this.root.select(this.path.slice(0, -1).concat(list.length - 1));
return this.root.select(this.solvedPath.slice(0, -1).concat(list.length - 1));
};
Cursor.prototype.down = function() {
var last = +this.path[this.path.length - 1];
var last = +this.solvedPath[this.solvedPath.length - 1];
if (!(this.reference() instanceof Array))
throw Error('baobab.Cursor.down: cannot descend on a non-list type.');
return null;
return this.root.select(this.path.concat(0));
return this.root.select(this.solvedPath.concat(0));
};
/**
* Access
*/
Cursor.prototype.get = function(path) {

@@ -176,6 +212,6 @@ if (arguments.length > 1)

if (types.check(path, 'string|number|array'))
return this.root.get(this.path.concat(path));
if (types.check(path, 'step'))
return this.root.get(this.solvedPath.concat(path));
else
return this.root.get(this.path);
return this.root.get(this.solvedPath);
};

@@ -187,6 +223,6 @@

if (types.check(path, 'string|number|array'))
return this.root.reference(this.path.concat(path));
if (types.check(path, 'step'))
return this.root.reference(this.solvedPath.concat(path));
else
return this.root.reference(this.path);
return this.root.reference(this.solvedPath);
};

@@ -198,8 +234,11 @@

if (types.check(path, 'string|number|array'))
return this.root.clone(this.path.concat(path));
if (types.check(path, 'step'))
return this.root.clone(this.solvedPath.concat(path));
else
return this.root.clone(this.path);
return this.root.clone(this.solvedPath);
};
/**
* Update
*/
Cursor.prototype.set = function(key, value) {

@@ -254,2 +293,12 @@ if (arguments.length < 2)

Cursor.prototype.merge = function(o) {
if (!types.check(o, 'object'))
throw Error('baobab.Cursor.merge: trying to merge a non-object.');
if (!types.check(this.reference(), 'object'))
throw Error('baobab.Cursor.merge: trying to merge into a non-object.');
this.update({$merge: o});
};
Cursor.prototype.update = function(spec) {

@@ -260,2 +309,13 @@ return this._stack(spec);

/**
* Combination
*/
Cursor.prototype.or = function(otherCursor) {
return new Combination('or', this, otherCursor);
};
Cursor.prototype.and = function(otherCursor) {
return new Combination('and', this, otherCursor);
};
/**
* Type definition

@@ -271,3 +331,3 @@ */

Cursor.prototype.toJSON = function() {
return this.get();
return this.reference();
};

@@ -274,0 +334,0 @@

@@ -14,34 +14,63 @@ /**

// Deep clone an object
function clone(item) {
if (!item)
// Shallow merge
function shallowMerge(o1, o2) {
var o = {},
k;
for (k in o1) o[k] = o1[k];
for (k in o2) o[k] = o2[k];
return o;
}
// Shallow clone
function shallowClone(item) {
if (!item || !(item instanceof Object))
return item;
var result,
i,
k,
l;
// Array
if (types.get(item) === 'array')
return item.slice(0);
if (types.check(item, 'array')) {
result = [];
// Date
if (types.get(item) === 'date')
return new Date(item.getTime());
// Object
if (types.get(item) === 'object') {
var k, o = {};
for (k in item)
o[k] = item[k];
return o;
}
return item;
}
// Deep clone
function deepClone(item) {
if (!item || !(item instanceof Object))
return item;
// Array
if (types.get(item) === 'array') {
var i, l, a = [];
for (i = 0, l = item.length; i < l; i++)
result.push(clone(item[i]));
a.push(deepClone(item[i]));
return a;
}
} else if (types.check(item, 'date')) {
result = new Date(item.getTime());
// Date
if (types.get(item) === 'date')
return new Date(item.getTime());
} else if (types.check(item, 'object')) {
if (item.nodeType && typeof item.cloneNode === 'function')
result = item;
else if (!item.prototype) {
result = {};
for (i in item)
result[i] = clone(item[i]);
} else
result = item;
} else {
result = item;
// Object
if (types.get(item) === 'object') {
var k, o = {};
for (k in item)
o[k] = deepClone(item[k]);
return o;
}
return result;
return item;
}

@@ -56,2 +85,54 @@

// Get first item matching predicate in list
function first(a, fn) {
var i, l;
for (i = 0, l = a.length; i < l; i++) {
if (fn(a[i]))
return a[i];
}
return;
}
function index(a, fn) {
var i, l;
for (i = 0, l = a.length; i < l; i++) {
if (fn(a[i]))
return i;
}
return -1;
}
// Compare object to spec
function compare(object, spec) {
var ok = true,
k;
for (k in spec) {
if (types.get(spec[k]) === 'object') {
ok = ok && compare(object[k]);
}
else if (types.get(spec[k]) === 'array') {
ok = ok && !!~spec[k].indexOf(object[k]);
}
else {
if (object[k] !== spec[k])
return false;
}
}
return ok;
}
function firstByComparison(object, spec) {
return first(object, function(e) {
return compare(e, spec);
});
}
function indexByComparison(object, spec) {
return indexOf(object, function(e) {
return compare(e, spec);
});
}
// Retrieve nested objects

@@ -68,3 +149,18 @@ function getIn(object, path) {

return;
c = c[path[i]];
if (typeof path[i] === 'function') {
if (types.get(c) !== 'array')
return;
c = first(c, path[i]);
}
else if (typeof path[i] === 'object') {
if (types.get(c) !== 'array')
return;
c = firstByComparison(c, path[i]);
}
else {
c = c[path[i]];
}
}

@@ -75,2 +171,39 @@

// Solve a complex path
function solvePath(object, path) {
var solvedPath = [],
c = object,
idx,
i,
l;
for (i = 0, l = path.length; i < l; i++) {
if (!c)
return null;
if (typeof path[i] === 'function') {
if (types.get(c) !== 'array')
return;
idx = index(c, path[i]);
solvedPath.push(idx);
c = c[idx];
}
else if (typeof path[i] === 'object') {
if (types.get(c) !== 'array')
return;
idx = index(indexByComparison(c, path[i]));
solvedPath.push(idx);
c = c[idx];
}
else {
solvedPath.push(path[i]);
c = c[path[i]];
}
}
return solvedPath;
}
// Return a fake object relative to the given path

@@ -111,3 +244,5 @@ function pathObject(path, spec) {

arrayOf: arrayOf,
clone: clone,
deepClone: deepClone,
shallowClone: shallowClone,
shallowMerge: shallowMerge,
compose: compose,

@@ -117,3 +252,4 @@ getIn: getIn,

later: later,
pathObject: pathObject
pathObject: pathObject,
solvePath: solvePath
};

@@ -43,26 +43,34 @@ /**

// Upper $set/$apply and conflicts
// TODO: Boooo! Ugly...
if (hasOneOf(arguments[i], ['$set', '$apply', '$chain'])) {
if (res.$set && (arguments[i].$apply || arguments[i].$chain)) {
delete res.$set;
res.$apply = arguments[i].$apply || arguments[i].$chain;
}
else if (res.$apply && arguments[i].$set) {
delete res.$apply;
res.$set = arguments[i].$set;
}
else if (arguments[i].$set) {
res.$set = arguments[i].$set;
}
else if (arguments[i].$apply) {
res.$apply = arguments[i].$apply;
}
else if (arguments[i].$chain) {
if (res.$apply)
res.$apply = helpers.compose(res.$apply, arguments[i].$chain);
else
res.$apply = arguments[i].$chain;
}
// Upper $set/$apply... and conflicts
// When solving conflicts, here is the priority to apply:
// -- 1) $set
// -- 2) $merge
// -- 3) $apply
// -- 4) $chain
if (arguments[i].$set) {
delete res.$apply;
delete res.$merge;
res.$set = arguments[i].$set;
continue;
}
else if (arguments[i].$merge) {
delete res.$set;
delete res.$apply;
res.$merge = arguments[i].$merge;
continue;
}
else if (arguments[i].$apply){
delete res.$set;
delete res.$merge;
res.$apply = arguments[i].$apply;
continue;
}
else if (arguments[i].$chain) {
delete res.$set;
delete res.$merge;
if (res.$apply)
res.$apply = helpers.compose(res.$apply, arguments[i].$chain);
else
res.$apply = arguments[i].$chain;
continue;

@@ -69,0 +77,0 @@ }

@@ -7,3 +7,4 @@ /**

*/
var types = require('./typology.js');
var types = require('./typology.js'),
Combination = require('./combination.js');

@@ -13,83 +14,95 @@ module.exports = {

return {
mixins: baobab.options.mixins,
componentWillMount: function() {
// Binding baobab to instance
this.baobab = baobab;
this.__type = null;
// Run Baobab mixin first to allow mixins to access cursors
mixins: [{
getInitialState: function() {
// Is there any cursors to create?
if (this.cursor && this.cursors)
throw Error('baobab.mixin: you cannot have both ' +
'`component.cursor` and `component.cursors`. Please ' +
'make up your mind.');
// Binding baobab to instance
this.tree = baobab;
this.__type = null;
if (this.cursor) {
if (!types.check(this.cursor, 'string|number|array|cursor'))
throw Error('baobab.mixin.cursor: invalid data (cursor, string or array).');
// Is there any cursors to create?
if (this.cursor && this.cursors)
throw Error('baobab.mixin: you cannot have both ' +
'`component.cursor` and `component.cursors`. Please ' +
'make up your mind.');
if (!types.check(this.cursor, 'cursor'))
this.cursor = baobab.select(this.cursor);
this.__type = 'single';
}
else if (this.cursors) {
if (!types.check(this.cursors, 'object|array'))
throw Error('baobab.mixin.cursor: invalid data (object or array).');
// Making update handler
this.__updateHandler = (function() {
this.setState(this.__getCursorData());
}).bind(this);
if (types.check(this.cursors, 'array')) {
this.cursors = this.cursors.map(function(path) {
return types.check(path, 'cursor') ? path : baobab.select(path);
});
this.__type = 'array';
if (this.cursor) {
if (!types.check(this.cursor, 'string|number|array|cursor'))
throw Error('baobab.mixin.cursor: invalid data (cursor, string or array).');
if (!types.check(this.cursor, 'cursor'))
this.cursor = baobab.select(this.cursor);
this.__getCursorData = (function() {
return {cursor: this.cursor.get()};
}).bind(this);
this.__type = 'single';
}
else {
// TODO: better validation
for (var k in this.cursors) {
if (!types.check(this.cursors[k], 'cursor'))
this.cursors[k] = baobab.select(this.cursors[k]);
else if (this.cursors) {
if (!types.check(this.cursors, 'object|array'))
throw Error('baobab.mixin.cursor: invalid data (object or array).');
if (types.check(this.cursors, 'array')) {
this.cursors = this.cursors.map(function(path) {
return types.check(path, 'cursor') ? path : baobab.select(path);
});
this.__getCursorData = (function() {
return {cursors: this.cursors.map(function(cursor) {
return cursor.get();
})};
}).bind(this);
this.__type = 'array';
}
this.__type = 'object';
else {
for (var k in this.cursors) {
if (!types.check(this.cursors[k], 'cursor'))
this.cursors[k] = baobab.select(this.cursors[k]);
}
this.__getCursorData = (function() {
var d = {};
for (k in this.cursors)
d[k] = this.cursors[k].get();
return {cursors: d};
}).bind(this);
this.__type = 'object';
}
}
}
// Making update handler
var fired = false;
this.__updateHandler = (function() {
if (!fired) {
this.forceUpdate();
fired = true;
setTimeout(function() {
fired = false;
}, 0);
return this.__getCursorData();
},
componentDidMount: function() {
if (this.__type === 'single') {
this.cursor.on('update', this.__updateHandler);
}
}).bind(this);
},
componentDidMount: function() {
if (this.__type === 'single') {
this.cursor.on('update', this.__updateHandler);
else if (this.__type === 'array') {
this.__combination = new Combination('or', this.cursors);
this.__combination.on('update', this.__updateHandler);
}
else if (this.__type === 'object') {
this.__combination = new Combination(
'or',
Object.keys(this.cursors).map(function(k) {
return this.cursors[k];
}, this)
);
this.__combination.on('update', this.__updateHandler);
}
},
componentWillUnmount: function() {
if (this.__type === 'single') {
this.cursor.off('update', this.__updateHandler);
}
else {
this.__combination.release();
}
}
else if (this.__type === 'array') {
this.cursors.forEach(function(cursor) {
cursor.on('update', this.__updateHandler);
}, this);
}
else if (this.__type === 'object') {
for (var k in this.cursors)
this.cursors[k].on('update', this.__updateHandler);
}
},
componentWillUnmount: function() {
if (this.__type === 'single') {
this.cursor.off('update', this.__updateHandler);
}
else if (this.__type === 'array') {
this.cursors.forEach(function(cursor) {
cursor.off('update', this.__updateHandler);
}, this);
}
else if (this.__type === 'object') {
for (var k in this.cursors)
this.cursors[k].off('update', this.__updateHandler);
}
}
}].concat(baobab.options.mixins)
};

@@ -99,25 +112,30 @@ },

return {
mixins: cursor.root.options.mixins,
componentWillMount: function() {
// Binding cursor to instance
this.cursor = cursor;
// Run cursor mixin first to allow mixins to access cursors
mixins: [{
getInitialState: function() {
// Making update handler
this.__updateHandler = (function() {
this.forceUpdate();
}).bind(this);
},
componentDidMount: function() {
// Binding cursor to instance
this.cursor = cursor;
// Listening to updates
this.cursor.on('update', this.__updateHandler);
},
componentWillUnmount: function() {
// Making update handler
this.__updateHandler = (function() {
this.setState({cursor: this.cursor.get()});
}).bind(this);
// Unbinding handler
this.cursor.off('update', this.__updateHandler);
}
return {cursor: this.cursor.get()};
},
componentDidMount: function() {
// Listening to updates
this.cursor.on('update', this.__updateHandler);
},
componentWillUnmount: function() {
// Unbinding handler
this.cursor.off('update', this.__updateHandler);
}
}].concat(cursor.root.options.mixins)
};
}
};

@@ -10,4 +10,7 @@ /**

var typology = new Typology({
complexStep: 'function|object',
step: 'string|number|array|function|object',
path: function(v) {
return this.check(v, '?string|number') || this.check(v, ['string|number']);
return this.check(v, '?string|number|function|object') ||
this.check(v, ['string|number|function|object']);
},

@@ -14,0 +17,0 @@ typology: function(v) {

@@ -8,3 +8,4 @@ /**

*/
var types = require('./typology.js');
var types = require('./typology.js'),
helpers = require('./helpers.js');

@@ -16,3 +17,4 @@ var COMMANDS = {};

'$unshift',
'$apply'
'$apply',
'$merge'
].forEach(function(c) {

@@ -31,91 +33,117 @@ COMMANDS[c] = true;

// Function mutating the object for performance reasons
function mutator(log, o, spec, path) {
path = path || [];
// Core function
function update(target, spec, opts) {
opts = opts || {};
var log = {};
var hash = path.join('λ'),
fn,
h,
k,
v,
i,
l;
// Closure mutating the internal object
(function mutator(o, spec, path) {
path = path || [];
for (k in spec) {
if (COMMANDS[k]) {
v = spec[k];
var hash = path.join('λ'),
fn,
h,
k,
v,
i,
l;
// Logging update
if (hash && !log[hash])
for (k in spec) {
if (COMMANDS[k]) {
v = spec[k];
// Logging update
log[hash] = true;
// Applying
switch (k) {
case '$push':
if (!types.check(o, 'array'))
throw makeError(path, 'using command $push to a non array');
// Applying
switch (k) {
case '$push':
if (!types.check(o, 'array'))
throw makeError(path, 'using command $push to a non array');
if (!types.check(v, 'array'))
o.push(v);
else
o.push.apply(o, v);
break;
case '$unshift':
if (!types.check(o, 'array'))
throw makeError(path, 'using command $unshift to a non array');
if (!types.check(v, 'array'))
o.push(v);
else
o.push.apply(o, v);
break;
case '$unshift':
if (!types.check(o, 'array'))
throw makeError(path, 'using command $unshift to a non array');
if (!types.check(v, 'array'))
o.unshift(v);
else
o.unshift.apply(o, v);
break;
if (!types.check(v, 'array'))
o.unshift(v);
else
o.unshift.apply(o, v);
break;
}
}
}
else {
if ('$set' in (spec[k] || {})) {
else {
h = hash ? hash + 'λ' + k : k;
v = spec[k].$set;
// Logging update
if (h && !log[h])
if ('$set' in (spec[k] || {})) {
v = spec[k].$set;
// Logging update
log[h] = true;
o[k] = v;
}
else if ('$apply' in (spec[k] || {})) {
h = hash ? hash + 'λ' + k : k;
fn = spec[k].$apply;
o[k] = v;
}
else if ('$apply' in (spec[k] || {})) {
fn = spec[k].$apply;
if (typeof fn !== 'function')
throw makeError(path.concat(k), 'using command $apply with a non function');
if (typeof fn !== 'function')
throw makeError(path.concat(k), 'using command $apply with a non function');
// Logging update
if (h && !log[h])
// Logging update
log[h] = true;
o[k] = fn.call(null, o[k]);
}
else {
o[k] = fn.call(null, o[k]);
}
else if ('$merge' in (spec[k] || {})) {
v = spec[k].$merge;
// If nested object does not exist, we create it
if (typeof o[k] === 'undefined')
o[k] = {};
if (!types.check(o[k], 'object'))
throw makeError(path.concat(k), 'using command $merge on a non-object');
// Recur
mutator(
log,
o[k],
spec[k],
path.concat(k)
);
// Logging update
log[h] = true;
o[k] = helpers.shallowMerge(o[k], v);
}
else if (opts.shiftReferences &&
('$push' in (spec[k] || {}) ||
'$unshift' in (spec[k] || {}))) {
if ('$push' in (spec[k] || {})) {
v = spec[k].$push;
if (!types.check(o[k], 'array'))
throw makeError(path.concat(k), 'using command $push to a non array');
o[k] = o[k].concat(v);
}
if ('$unshift' in (spec[k] || {})) {
v = spec[k].$unshift;
if (!types.check(o[k], 'array'))
throw makeError(path.concat(k), 'using command $unshift to a non array');
o[k] = (v instanceof Array ? v : [v]).concat(o[k]);
}
// Logging update
log[h] = true;
}
else {
// If nested object does not exist, we create it
if (typeof o[k] === 'undefined')
o[k] = {};
// Recur
mutator(
o[k],
spec[k],
path.concat(k)
);
}
}
}
}
}
})(target, spec);
// Core function
function update(target, spec) {
var log = {};
mutator(log, target, spec);
return Object.keys(log).map(function(hash) {

@@ -122,0 +150,0 @@ return hash.split('λ');

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc