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 2.0.0-dev9 to 2.0.0-rc1

dist/watcher.js

146

dist/baobab.js

@@ -25,3 +25,3 @@ /**

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; }
function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

@@ -36,2 +36,6 @@ var _emmett = require('emmett');

var _watcher = require('./watcher');
var _watcher2 = _interopRequireDefault(_watcher);
var _facet = require('./facet');

@@ -68,2 +72,5 @@

// Should the tree's update be pure?
pure: true,
// Validation specifications

@@ -131,12 +138,11 @@ validate: null,

this._affectedPathsIndex = {};
this._computedDataIndex = {};
this._facets = {};
this._previousData = null;
this._data = initialData;
// Properties
this.log = [];
this.previousData = null;
this.data = initialData;
this.root = this.select();
// Does the user want an immutable tree?
if (this.options.immutable) (0, _helpers.deepFreeze)(this.data);
if (this.options.immutable) (0, _helpers.deepFreeze)(this._data);

@@ -154,16 +160,17 @@ // Bootstrapping root cursor's getters and setters

// Creating the computed data index for the first time
this._refreshComputedDataIndex();
this._initializeComputedDataIndex();
}
/**
* Version
*/
/**
* Private method used to initialize the internal computed data index of the
* tree.
*/
_createClass(Baobab, [{
key: '_refreshComputedDataIndex',
/**
* Private method used to refresh the internal computed data index of the
* tree.
*
* @param {array} [path] - Path to the modified node.
* @return {Baobab} - The tree itself for chaining purposes.
*/
value: function _refreshComputedDataIndex(path) {
key: '_initializeComputedDataIndex',
value: function _initializeComputedDataIndex() {
var _this2 = this;

@@ -175,5 +182,2 @@

// Have we reached the end?
if (_type2['default'].primitive(data)) return;
// Object iteration

@@ -190,3 +194,3 @@ // TODO: handle arrays?

(0, _helpers.deepMerge)(_this2._computedDataIndex, (0, _helpers.pathObject)(p, _defineProperty({}, k, facet)));
(0, _helpers.deepMerge)(_this2._facets, (0, _helpers.pathObject)(p, _defineProperty({}, k, facet)));
} else {

@@ -199,17 +203,5 @@ walk(data[k], p.concat(k));

if (!path || !path.length) {
// Walk the whole tree
return walk(this.data);
} else {
// Retrieving parent of affected node
var parentNode = (0, _helpers.getIn)(this.data, path.slice(0, -1)).data;
// Walk the affected leaf
return walk(parentNode, path.slice(0, -1));
}
// Walking the whole tree
return walk(this._data);
}
}, {
key: 'select',

@@ -228,2 +220,4 @@ /**

*/
}, {
key: 'select',
value: function select(path) {

@@ -252,3 +246,3 @@

if (!cursor) {
cursor = new _cursor2['default'](this, path, { hash: hash });
cursor = new _cursor2['default'](this, path, hash);
this._cursors[hash] = cursor;

@@ -261,4 +255,2 @@ }

}
}, {
key: 'update',

@@ -275,2 +267,4 @@ /**

*/
}, {
key: 'update',
value: function update(path, operation) {

@@ -280,3 +274,3 @@ var _this3 = this;

// Coercing path
path = path || path === 0 ? path : [];
path = (0, _helpers.coercePath)(path);

@@ -287,8 +281,7 @@ if (!_type2['default'].operationType(operation.type)) throw (0, _helpers.makeError)('Baobab.update: unknown operation type "' + operation.type + '".', { operation: operation });

var _getIn = (0, _helpers.getIn)(this.data, path, this._computedDataIndex);
var _getIn = (0, _helpers.getIn)(this._data, path);
// If we couldn't solve the path, we throw
var solvedPath = _getIn.solvedPath;
var exists = _getIn.exists;
// If we couldn't solve the path, we throw
if (!solvedPath) throw (0, _helpers.makeError)('Baobab.update: could not solve the given path.', {

@@ -302,3 +295,3 @@ path: solvedPath

// Stashing previous data if this is the frame's first update
if (!this._transaction.length) this.previousData = this.data;
if (!this._transaction.length) this._previousData = this.get();

@@ -309,16 +302,11 @@ var hash = hashPath(solvedPath);

var _update2 = (0, _update4['default'])(this.data, solvedPath, operation, this.options);
var _update2 = (0, _update4['default'])(this._data, solvedPath, operation, this.options);
// Updating data and transaction
var data = _update2.data;
var node = _update2.node;
// Updating data and transaction
this.data = data;
this._data = data;
this._affectedPathsIndex[hash] = true;
this._transaction.push(_extends({}, operation, { path: solvedPath }));
// Refreshing facet index
// TODO: provide a setting to disable this or at least selectively for perf
this._refreshComputedDataIndex(solvedPath);
// Emitting a `write` event

@@ -344,4 +332,2 @@ this.emit('write', { path: solvedPath });

}
}, {
key: 'commit',

@@ -353,2 +339,4 @@ /**

*/
}, {
key: 'commit',
value: function commit() {

@@ -369,3 +357,3 @@

if (typeof validate === 'function') {
var error = validate.call(this, this.previousData, this.data, affectedPaths);
var error = validate.call(this, this._previousData, this._data, affectedPaths);

@@ -376,6 +364,6 @@ if (error instanceof Error) {

if (behavior === 'rollback') {
this.data = this.previousData;
this._data = this._previousData;
this._affectedPathsIndex = {};
this._transaction = [];
this.previousData = this.data;
this._previousData = this._data;
return this;

@@ -388,14 +376,14 @@ }

var transaction = this._transaction,
previousData = this.previousData;
previousData = this._previousData;
this._affectedPathsIndex = {};
this._transaction = [];
this.previousData = this.data;
this._previousData = this._data;
// Emitting update event
this.emit('update', {
paths: affectedPaths,
currentData: this._data,
transaction: transaction,
previousData: previousData,
data: this.data,
paths: affectedPaths
previousData: previousData
});

@@ -405,4 +393,2 @@

}
}, {
key: 'watch',

@@ -413,12 +399,10 @@ /**

*
* @param {object|array} paths - Paths to listen.
* @return {Cursor} - A special cursor that can be listened.
* @param {object} mapping - Mapping of paths to listen.
* @return {Cursor} - The created watcher.
*/
value: function watch(paths) {
if (!_type2['default'].object(paths) && !_type2['default'].array(paths)) throw Error('Baobab.watch: wrong argument.');
return new _cursor2['default'](this, null, { watched: paths });
}, {
key: 'watch',
value: function watch(mapping) {
return new _watcher2['default'](this, mapping);
}
}, {
key: 'release',

@@ -428,8 +412,12 @@ /**

*/
}, {
key: 'release',
value: function release() {
var k = undefined;
delete this.data;
delete this._data;
delete this._previousData;
delete this._transaction;
delete this._affectedPathsIndex;
delete this._facets;

@@ -443,4 +431,2 @@ // Releasing cursors

}
}, {
key: 'toJSON',

@@ -452,7 +438,7 @@ /**

*/
}, {
key: 'toJSON',
value: function toJSON() {
return this.serialize();
}
}, {
key: 'toString',

@@ -464,2 +450,4 @@ /**

*/
}, {
key: 'toString',
value: function toString() {

@@ -474,8 +462,4 @@ return this._identity;

exports['default'] = Baobab;
/**
* Version
*/
Object.defineProperty(Baobab, 'version', {
value: '2.0.0-dev9'
value: '2.0.0-rc1'
});

@@ -482,0 +466,0 @@

@@ -15,3 +15,3 @@ /**

var _get = function get(_x4, _x5, _x6) { var _again = true; _function: while (_again) { var object = _x4, property = _x5, receiver = _x6; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x4 = parent; _x5 = property; _x6 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
var _get = function get(_x3, _x4, _x5) { var _again = true; _function: while (_again) { var object = _x3, property = _x4, receiver = _x5; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x3 = parent; _x4 = property; _x5 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };

@@ -22,3 +22,3 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; }
function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

@@ -49,12 +49,6 @@ var _emmett = require('emmett');

*
* Note: opts.watched is not called opts.watch not to tamper with experimental
* `Object.prototype.watch`.
*
* @constructor
* @param {Baobab} tree - The cursor's root.
* @param {array} path - The cursor's path in the tree.
* @param {object} [opts] - Options
* @param {string} [opts.hash] - The path's hash computed ahead by the tree.
* @param {array} [opts.watched] - Parts of the tree the cursor is meant to
* watch over.
* @param {string} hash - The path's hash computed ahead by the tree.
*/

@@ -65,7 +59,5 @@

function Cursor(tree, path) {
function Cursor(tree, path, hash) {
var _this = this;
var opts = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
_classCallCheck(this, Cursor);

@@ -85,6 +77,7 @@

this.path = path;
this.hash = opts.hash;
this.hash = hash;
// State
this.state = {
killed: false,
recording: false,

@@ -94,18 +87,2 @@ undoing: false

// Checking whether the cursor is a watcher
if (opts.watched) {
this._watched = (0, _helpers.shallowClone)(opts.watched);
// Normalizing path
for (var k in this._watched) {
if (this._watched[k] instanceof Cursor) this._watched[k] = this._watched[k].path;
} // Keeping track of watched paths
this._watchedPaths = opts.watched && (!_type2['default'].array(this._watched) ? Object.keys(this._watched).map(function (k) {
return _this._watched[k];
}) : this._watched);
// Overriding the cursor's get method
this.get = this.tree.project.bind(this.tree, this._watched);
}
// Checking whether the given path is dynamic or not

@@ -128,3 +105,3 @@ this._dynamicPath = _type2['default'].dynamicPath(this.path);

if (!(0, _helpers.solveUpdate)([data.path], _this._getComparedPaths())) return;
if (_this.state.killed || !(0, _helpers.solveUpdate)([data.path], _this._getComparedPaths())) return;

@@ -141,15 +118,26 @@ _this.solvedPath = _this._getIn(_this.path).solvedPath;

var fireUpdate = function fireUpdate(previousData) {
var self = _this;
if (_this._watched) return _this.emit('update');
var eventData = Object.defineProperties({}, {
previousData: {
get: function get() {
return (0, _helpers.getIn)(previousData, self.solvedPath).data;
},
configurable: true,
enumerable: true
},
currentData: {
get: function get() {
return self.get();
},
configurable: true,
enumerable: true
}
});
var record = (0, _helpers.getIn)(previousData, _this.solvedPath).data;
if (_this.state.recording && !_this.state.undoing) _this.archive.add(eventData.previousData);
if (_this.state.recording && !_this.state.undoing) _this.archive.add(record);
_this.state.undoing = false;
return _this.emit('update', {
data: _this._get().data,
previousData: record
});
return _this.emit('update', eventData);
};

@@ -168,2 +156,4 @@

this._updateHandler = function (event) {
if (_this.state.killed) return;
var _event$data = event.data;

@@ -201,22 +191,37 @@ var paths = _event$data.paths;

/**
* Setter Methods
* ---------------
*
* Those methods are dynamically assigned to the class for DRY reasons.
*/
/**
* Function creating a setter method for the Cursor class.
*
* @param {string} name - the method's name.
* @param {function} [typeChecker] - a function checking that the given value is
* valid for the given operation.
*/
/**
* Internal helpers
* -----------------
*/
/**
* Curried version of the `getIn` helper and ready to serve a cursor instance
* purpose without having to write endlessly the same args over and over.
*
* @todo: probably useless now...
*
* @param {array} path - The path to get in the tree.
* @return {object} - The result of the `getIn` helper.
*/
_createClass(Cursor, [{
key: '_getIn',
/**
* Internal helpers
* -----------------
*/
/**
* Curried version of the `getIn` helper and ready to serve a cursor instance
* purpose without having to write endlessly the same args over and over.
*
* @param {array} path - The path to get in the tree.
* @return {object} - The result of the `getIn` helper.
*/
value: function _getIn(path) {
return (0, _helpers.getIn)(this.tree.data, path, this.tree._computedDataIndex, this.tree.options);
return (0, _helpers.getIn)(this.tree._data, path);
}
}, {
key: '_getComparedPaths',

@@ -229,35 +234,11 @@ /**

*/
}, {
key: '_getComparedPaths',
value: function _getComparedPaths() {
var _this2 = this;
var comparedPaths = undefined;
// Checking whether we should keep track of some dependencies
var additionalPaths = this._facetPath ? (0, _helpers.getIn)(this.tree._facets, this._facetPath).data.relatedPaths() : [];
// Standard cursor
if (!this._watched) {
// Checking whether we should keep track of some dependencies
var additionalPaths = this._facetPath ? (0, _helpers.getIn)(this.tree._computedDataIndex, this._facetPath).data.relatedPaths() : [];
comparedPaths = [this.solvedPath].concat(additionalPaths);
}
// Watcher cursor
else {
comparedPaths = this._watchedPaths.reduce(function (cp, p) {
if (_type2['default'].dynamicPath(p)) p = _this2._getIn(p).solvedPath;
if (!p) return cp;
var facetPath = _type2['default'].facetPath(p);
if (facetPath) return cp.concat((0, _helpers.getIn)(_this2.tree._computedDataIndex, p).data.relatedPaths());
return cp.concat([p]);
}, []);
}
return comparedPaths;
return [this.solvedPath].concat(additionalPaths);
}
}, {
key: 'isRoot',

@@ -274,7 +255,7 @@ /**

*/
}, {
key: 'isRoot',
value: function isRoot() {
return !this.path.length;
}
}, {
key: 'isLeaf',

@@ -286,7 +267,7 @@ /**

*/
}, {
key: 'isLeaf',
value: function isLeaf() {
return _type2['default'].primitive(this._get().data);
}
}, {
key: 'isBranch',

@@ -298,7 +279,7 @@ /**

*/
}, {
key: 'isBranch',
value: function isBranch() {
return !this.isRoot() && !this.isLeaf();
}
}, {
key: 'root',

@@ -315,7 +296,7 @@ /**

*/
}, {
key: 'root',
value: function root() {
return this.tree.root;
}
}, {
key: 'select',

@@ -333,2 +314,4 @@ /**

*/
}, {
key: 'select',
value: function select(path) {

@@ -339,4 +322,2 @@ if (arguments.length > 1) path = (0, _helpers.arrayFrom)(arguments);

}
}, {
key: 'up',

@@ -349,7 +330,7 @@ /**

*/
}, {
key: 'up',
value: function up() {
if (!this.isRoot()) return this.tree.select(this.path.slice(0, -1));else return null;
}
}, {
key: 'down',

@@ -361,2 +342,4 @@ /**

*/
}, {
key: 'down',
value: function down() {

@@ -369,4 +352,2 @@ checkPossibilityOfDynamicTraversal('down', this.solvedPath);

}
}, {
key: 'left',

@@ -379,2 +360,4 @@ /**

*/
}, {
key: 'left',
value: function left() {

@@ -389,4 +372,2 @@ checkPossibilityOfDynamicTraversal('left', this.solvedPath);

}
}, {
key: 'right',

@@ -399,2 +380,4 @@ /**

*/
}, {
key: 'right',
value: function right() {

@@ -411,4 +394,2 @@ checkPossibilityOfDynamicTraversal('right', this.solvedPath);

}
}, {
key: 'leftmost',

@@ -421,2 +402,4 @@ /**

*/
}, {
key: 'leftmost',
value: function leftmost() {

@@ -431,4 +414,2 @@ checkPossibilityOfDynamicTraversal('leftmost', this.solvedPath);

}
}, {
key: 'rightmost',

@@ -441,2 +422,4 @@ /**

*/
}, {
key: 'rightmost',
value: function rightmost() {

@@ -453,4 +436,2 @@ checkPossibilityOfDynamicTraversal('rightmost', this.solvedPath);

}
}, {
key: 'map',

@@ -464,2 +445,4 @@ /**

*/
}, {
key: 'map',
value: function map(fn, scope) {

@@ -477,4 +460,2 @@ checkPossibilityOfDynamicTraversal('map', this.solvedPath);

}
}, {
key: '_get',

@@ -496,2 +477,4 @@ /**

*/
}, {
key: '_get',
value: function _get() {

@@ -506,4 +489,2 @@ var path = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0];

}
}, {
key: 'exists',

@@ -522,4 +503,6 @@ /**

*/
}, {
key: 'exists',
value: function exists(path) {
path = path || path === 0 ? path : [];
path = (0, _helpers.coercePath)(path);

@@ -530,4 +513,2 @@ if (arguments.length > 1) path = (0, _helpers.arrayFrom)(arguments);

}
}, {
key: 'get',

@@ -547,4 +528,6 @@ /**

*/
}, {
key: 'get',
value: function get(path) {
path = path || path === 0 ? path : [];
path = (0, _helpers.coercePath)(path);

@@ -555,6 +538,5 @@ if (arguments.length > 1) path = (0, _helpers.arrayFrom)(arguments);

// Emitting the event
var data = _get2.data;
var solvedPath = _get2.solvedPath;
// Emitting the event
this.tree.emit('get', { data: data, solvedPath: solvedPath, path: this.path.concat(path) });

@@ -564,4 +546,2 @@

}
}, {
key: 'serialize',

@@ -572,2 +552,4 @@ /**

*
* @todo: should be more performant.
*
* Arity (1):

@@ -581,9 +563,18 @@ * @param {path} path - Path to serialize in the tree.

*/
}, {
key: 'serialize',
value: function serialize() {
var data = this.get.apply(this, arguments);
var data = (0, _helpers.deepClone)(this.get.apply(this, arguments));
return (0, _helpers.deepClone)(data);
var dropComputedData = function dropComputedData(d) {
if (!_type2['default'].object(d)) return;
for (var k in d) {
if (k[0] === '$') delete d[k];else dropComputedData(d[k]);
}
};
dropComputedData(data);
return data;
}
}, {
key: 'project',

@@ -596,2 +587,4 @@ /**

*/
}, {
key: 'project',
value: function project(projection) {

@@ -614,4 +607,2 @@ if (_type2['default'].object(projection)) {

}
}, {
key: 'startRecording',

@@ -631,2 +622,4 @@ /**

*/
}, {
key: 'startRecording',
value: function startRecording(maxRecords) {

@@ -648,4 +641,2 @@ maxRecords = maxRecords || Infinity;

}
}, {
key: 'stopRecording',

@@ -657,2 +648,4 @@ /**

*/
}, {
key: 'stopRecording',
value: function stopRecording() {

@@ -662,4 +655,2 @@ this.state.recording = false;

}
}, {
key: 'undo',

@@ -672,2 +663,4 @@ /**

*/
}, {
key: 'undo',
value: function undo() {

@@ -687,4 +680,2 @@ var steps = arguments.length <= 0 || arguments[0] === undefined ? 1 : arguments[0];

}
}, {
key: 'hasHistory',

@@ -696,7 +687,7 @@ /**

*/
}, {
key: 'hasHistory',
value: function hasHistory() {
return !!(this.archive && this.archive.get().length);
}
}, {
key: 'getHistory',

@@ -708,7 +699,7 @@ /**

*/
}, {
key: 'getHistory',
value: function getHistory() {
return this.archive ? this.archive.get() : [];
}
}, {
key: 'clearHistory',

@@ -720,2 +711,4 @@ /**

*/
}, {
key: 'clearHistory',
value: function clearHistory() {

@@ -725,4 +718,2 @@ if (this.archive) this.archive.clear();

}
}, {
key: 'release',

@@ -737,2 +728,4 @@ /**

*/
}, {
key: 'release',
value: function release() {

@@ -756,5 +749,4 @@

this.kill();
this.state.killed = true;
}
}, {
key: 'toJSON',

@@ -771,7 +763,7 @@ /**

*/
}, {
key: 'toJSON',
value: function toJSON() {
return this.serialize();
}
}, {
key: 'toString',

@@ -783,2 +775,4 @@ /**

*/
}, {
key: 'toString',
value: function toString() {

@@ -793,17 +787,2 @@ return this._identity;

exports['default'] = Cursor;
/**
* Setter Methods
* ---------------
*
* Those methods are dynamically assigned to the class for DRY reasons.
*/
/**
* Function creating a setter method for the Cursor class.
*
* @param {string} name - the method's name.
* @param {function} [typeChecker] - a function checking that the given value is
* valid for the given operation.
*/
function makeSetter(name, typeChecker) {

@@ -842,3 +821,3 @@

// Coerce path
path = path || path === 0 ? path : [];
path = (0, _helpers.coercePath)(path);

@@ -873,4 +852,4 @@ // Checking the path's validity

makeSetter('unshift');
makeSetter('splice', _type2['default'].array);
makeSetter('splice', _type2['default'].splicer);
makeSetter('merge', _type2['default'].object);
module.exports = exports['default'];

@@ -23,2 +23,6 @@ /**

var _update2 = require('./update');
var _update3 = _interopRequireDefault(_update2);
var _helpers = require('./helpers');

@@ -49,2 +53,3 @@

this.tree = tree;
this.path = pathInTree;
this.computedData = null;

@@ -77,3 +82,4 @@ this.type = definitionType;

this.state = {
computed: false
computed: false,
killed: false
};

@@ -87,2 +93,4 @@

* so that the data may be recomputed when needed.
*
* In addition, the Facet will replace the according node within the tree.
*/

@@ -92,2 +100,4 @@ this.listener = function (_ref) {

if (_this.state.killed) return;
// Is this facet affected by the current write event?

@@ -99,2 +109,4 @@ var concerned = (0, _helpers.solveUpdate)([path], _this.relatedPaths());

_this.state.computed = false;
_this.update();
}

@@ -105,12 +117,15 @@ };

tree.on('write', this.listener);
// Updating the tree's data
this.update();
}
/**
* Method returning solved paths related to the facet.
*
* @return {array} - An array of related paths.
*/
_createClass(Facet, [{
key: 'relatedPaths',
/**
* Method returning solved paths related to the facet.
*
* @return {array} - An array of related paths.
*/
value: function relatedPaths() {

@@ -122,18 +137,30 @@ var _this2 = this;

if (this._hasDynamicPaths) paths = this.paths.map(function (p) {
return (0, _helpers.getIn)(_this2.tree.data, p, _this2.tree._computedDataIndex).solvedPath;
return (0, _helpers.getIn)(_this2.tree._data, p, _this2.tree._facets).solvedPath;
});else paths = this.paths;
if (!this.isRecursive) return paths;else return paths.reduce(function (paths, path) {
if (!this.isRecursive) return paths;else return paths.reduce(function (accumulatedPaths, path) {
var facetPath = _type2['default'].facetPath(path);
if (!facetPath) return paths.concat(path);
if (!facetPath) return accumulatedPaths.concat([path]);
// Solving recursive path
var relatedFacet = (0, _helpers.getIn)(_this2.tree._computedDataIndex, facetPath).data;
var relatedFacet = (0, _helpers.getIn)(_this2.tree._facets, facetPath).data;
return paths.concat(relatedFacet.relatedPaths());
return accumulatedPaths.concat(relatedFacet.relatedPaths());
}, []);
}
/**
* Method used to update the tree's internal data with the Facet's freshly
* computed data.
*
* @return {Facet} - Returns itself.
*/
}, {
key: 'get',
key: 'update',
value: function update() {
this.tree._data = (0, _update3['default'])(this.tree._data, this.path, { type: 'set', value: this.get() }, this.tree.options).data;
return this;
}
/**

@@ -144,2 +171,4 @@ * Getter method

*/
}, {
key: 'get',
value: function get() {

@@ -162,4 +191,2 @@

}
}, {
key: 'release',

@@ -169,2 +196,4 @@ /**

*/
}, {
key: 'release',
value: function release() {

@@ -179,2 +208,3 @@

this.tree.off('write', this.listener);
this.state.killed = true;
}

@@ -181,0 +211,0 @@ }]);

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

exports.before = before;
exports.coercePath = coercePath;
exports.getIn = getIn;

@@ -29,13 +30,10 @@ exports.makeError = makeError;

var _facet = require('./facet');
var _facet2 = _interopRequireDefault(_facet);
var _type = require('./type');
var _type2 = _interopRequireDefault(_type);
/**
* Noop function
*/
var _type2 = _interopRequireDefault(_type);
var noop = Function.prototype;

@@ -58,15 +56,21 @@

/**
* Function creating a real array from what should be an array but is not.
* I'm looking at you nasty `arguments`...
*
* @param {mixed} culprit - The culprit to convert.
* @return {array} - The real array.
*/
/**
* Method retrieving the records.
*
* @return {array} - The records.
*/
_createClass(Archive, [{
key: 'get',
/**
* Method retrieving the records.
*
* @return {array} - The records.
*/
value: function get() {
return this.records;
}
}, {
key: 'add',

@@ -79,2 +83,4 @@ /**

*/
}, {
key: 'add',
value: function add(record) {

@@ -88,4 +94,2 @@ this.records.unshift(record);

}
}, {
key: 'clear',

@@ -97,2 +101,4 @@ /**

*/
}, {
key: 'clear',
value: function clear() {

@@ -102,4 +108,2 @@ this.records = [];

}
}, {
key: 'back',

@@ -112,2 +116,4 @@ /**

*/
}, {
key: 'back',
value: function back(steps) {

@@ -126,10 +132,2 @@ var record = this.records[steps - 1];

/**
* Function creating a real array from what should be an array but is not.
* I'm looking at you nasty `arguments`...
*
* @param {mixed} culprit - The culprit to convert.
* @return {array} - The real array.
*/
function arrayFrom(culprit) {

@@ -211,5 +209,4 @@ return slice(culprit);

if (item.constructor && item.constructor !== Object) o = Object.create(item.constructor.prototype);
for (k in item) if (item.hasOwnProperty(k) && k[0] !== '$') o[k] = deep ? cloner(true, item[k]) : item[k];
// NOTE: could be possible to erase computed properties through `null`.
for (k in item) if (item.hasOwnProperty(k)) o[k] = deep && k[0] !== '$' ? cloner(true, item[k]) : item[k];
return o;

@@ -228,4 +225,16 @@ }

exports.shallowClone = shallowClone;
/**
* Coerce the given variable into a full-fledged path.
*
* @param {mixed} target - The variable to coerce.
* @return {array} - The array path.
*/
exports.deepClone = deepClone;
function coercePath(target) {
if (target || target === 0 || target === '') return target;
return [];
}
/**

@@ -306,36 +315,4 @@ * Function comparing an object's properties to a given descriptive

exports.freeze = freeze;
exports.deepFreeze = deepFreeze;
/**
* Function used to solve a computed data mask by recursively walking a tree
* and patching it.
*
* @param {boolean} immutable - Is the data immutable?
* @param {mixed} data - Data to patch.
* @param {object} mask - Computed data mask.
* @param {object} [parent] - Parent object in the iteration.
* @param {string} [lastKey] - Current value's key in parent.
*/
function solveMask(immutable, data, mask, parent) {
for (var k in mask) {
if (k[0] === '$') {
// Patching
data[k] = mask[k].get();
if (immutable) deepFreeze(parent);
} else {
if (immutable) {
data[k] = shallowClone(data[k]);
if (parent) freeze(parent);
}
solveMask(immutable, data[k], mask[k], data);
}
}
return data;
}
/**
* Function retrieving nested data within the given object and according to

@@ -347,15 +324,13 @@ * the given path.

*
* @param {object} object - The object we need to get data from.
* @param {array} path - The path to follow.
* @param {object} [mask] - An optional computed data index.
* @return {object} result - The result.
* @return {mixed} result.data - The data at path, or `undefined`.
* @return {array} result.solvedPath - The solved path or `null`.
* @param {object} object - The object we need to get data from.
* @param {array} path - The path to follow.
* @return {object} result - The result.
* @return {mixed} result.data - The data at path, or `undefined`.
* @return {array} result.solvedPath - The solved path or `null`.
* @return {boolean} result.exists - Does the path exists in the tree?
*/
exports.deepFreeze = deepFreeze;
var notFoundObject = { data: undefined, solvedPath: null, exists: false };
function getIn(object, path) {
var mask = arguments.length <= 2 || arguments[2] === undefined ? null : arguments[2];
var opts = arguments.length <= 3 || arguments[3] === undefined ? {} : arguments[3];
if (!path) return notFoundObject;

@@ -365,3 +340,2 @@

c = object,
cm = mask,
idx = undefined,

@@ -394,22 +368,6 @@ i = undefined,

solvedPath.push(path[i]);
// Solving data from a facet if needed
if (cm && path[i][0] === '$') {
c = cm[path[i]].get();
cm = null;
} else {
c = c[path[i]];
// Walking the mask
if (cm) cm = cm[path[i]] || null;
}
c = c[path[i]];
}
}
// If the mask is still relevant, we solve it down to the leaves
if (cm && Object.keys(cm).length) {
var patchedData = solveMask(opts.immutable, { root: c }, { root: cm });
c = patchedData.root;
}
return { data: c, solvedPath: solvedPath, exists: c !== undefined };

@@ -457,4 +415,2 @@ }

* be used by Baobab's internal and would be unsuited in any other case.
* Note 4): this function will release any facet found on its path to the
* leaves for cleanup reasons.
*

@@ -484,6 +440,2 @@ * @param {boolean} deep - Whether the merge should be deep or not.

} else {
// Releasing
if (o[k] instanceof _facet2['default']) o[k].release();
o[k] = t[k];

@@ -504,3 +456,2 @@ }

exports.shallowMerge = shallowMerge;
exports.deepMerge = deepMerge;

@@ -515,2 +466,3 @@ /**

*/
exports.deepMerge = deepMerge;

@@ -517,0 +469,0 @@ function pathObject(path, leaf) {

@@ -107,2 +107,15 @@ /**

/**
* Checking whether the given variable is a valid splicer.
*
* @param {mixed} target - Variable to test.
* @param {array} [allowed] - Optional valid types in path.
* @return {boolean}
*/
type.splicer = function (target) {
if (!type.array(target) || target.length < 2) return false;
return anyOf(target[0], ['number', 'function', 'object']) && type.number(target[1]);
};
/**
* Checking whether the given variable is a valid cursor path.

@@ -114,10 +127,11 @@ *

*/
type.path = function (target, allowed) {
if (!target && target !== 0) return false;
// Order of allowed types is important for perf reasons
allowed = allowed || ['string', 'number', 'function', 'object'];
// Order is important for performance reasons
var ALLOWED_FOR_PATH = ['string', 'number', 'function', 'object'];
type.path = function (target) {
if (!target && target !== 0 && target !== '') return false;
return [].concat(target).every(function (step) {
return anyOf(step, allowed);
return anyOf(step, ALLOWED_FOR_PATH);
});

@@ -167,3 +181,3 @@ };

type.readOnlyPath = function (path) {
return path.slice(0, -1).some(function (step) {
return path.some(function (step) {
return step[0] === '$';

@@ -182,5 +196,9 @@ });

if (type.object(definition)) {
if (!type['function'](definition.get)) return null;else return 'object';
if (!type['function'](definition.get) || definition.cursors && !Object.keys(definition).every(function (k) {
return type.path(definition[k]);
})) return null;else return 'object';
} else if (type.array(definition)) {
if (!type['function'](definition[definition.length - 1])) return null;else return 'array';
if (!type['function'](definition[definition.length - 1]) || !definition.slice(0, -1).every(function (p) {
return type.path(p);
})) return null;else return 'array';
}

@@ -192,2 +210,14 @@

/**
* Checking whether the given watcher definition is valid.
*
* @param {mixed} definition - The definition to check.
* @return {boolean}
*/
type.watcherMapping = function (definition) {
return type.object(definition) && Object.keys(definition).every(function (k) {
return type.path(definition[k]);
});
};
/**
* Checking whether the given string is a valid operation type.

@@ -194,0 +224,0 @@ *

@@ -42,5 +42,5 @@ /**

var operationType = operation.type;
var value = operation.value;
// Dummy root, so we can shift and alter the root
var value = operation.value;
var dummy = { root: data },

@@ -73,3 +73,3 @@ dummyPath = ['root'].concat(_toConsumableArray(path));

if (operationType === 'set') {
p[s] = value;
p[s] = opts.persistent ? (0, _helpers.shallowClone)(value) : value;
}

@@ -81,57 +81,59 @@

else if (operationType === 'apply') {
p[s] = value(p[s]);
}
var result = value(p[s]);
/**
* Push
*/
else if (operationType === 'push') {
if (!_type2['default'].array(p[s])) throw err('push', 'array', currentPath);
p[s] = opts.persistent ? (0, _helpers.shallowClone)(result) : result;
}
if (opts.persistent) p[s] = p[s].concat([value]);else p[s].push(value);
}
/**
* Push
*/
else if (operationType === 'push') {
if (!_type2['default'].array(p[s])) throw err('push', 'array', currentPath);
/**
* Unshift
*/
else if (operationType === 'unshift') {
if (!_type2['default'].array(p[s])) throw err('unshift', 'array', currentPath);
if (opts.persistent) p[s] = p[s].concat([value]);else p[s].push(value);
}
if (opts.persistent) p[s] = [value].concat(p[s]);else p[s].unshift(value);
}
/**
* Unshift
*/
else if (operationType === 'unshift') {
if (!_type2['default'].array(p[s])) throw err('unshift', 'array', currentPath);
/**
* Concat
*/
else if (operationType === 'concat') {
if (!_type2['default'].array(p[s])) throw err('concat', 'array', currentPath);
if (opts.persistent) p[s] = [value].concat(p[s]);else p[s].unshift(value);
}
if (opts.persistent) p[s] = p[s].concat(value);else p[s].push.apply(p[s], value);
}
/**
* Concat
*/
else if (operationType === 'concat') {
if (!_type2['default'].array(p[s])) throw err('concat', 'array', currentPath);
/**
* Splice
*/
else if (operationType === 'splice') {
if (!_type2['default'].array(p[s])) throw err('splice', 'array', currentPath);
if (opts.persistent) p[s] = p[s].concat(value);else p[s].push.apply(p[s], value);
}
if (opts.persistent) p[s] = _helpers.splice.apply(null, [p[s]].concat(value));else p[s].splice.apply(p[s], value);
}
/**
* Splice
*/
else if (operationType === 'splice') {
if (!_type2['default'].array(p[s])) throw err('splice', 'array', currentPath);
/**
* Unset
*/
else if (operationType === 'unset') {
if (_type2['default'].object(p)) delete p[s];else if (_type2['default'].array(p)) p.splice(s, 1);
}
if (opts.persistent) p[s] = _helpers.splice.apply(null, [p[s]].concat(value));else p[s].splice.apply(p[s], value);
}
/**
* Merge
*/
else if (operationType === 'merge') {
if (!_type2['default'].object(p[s])) throw err('merge', 'object', currentPath);
/**
* Unset
*/
else if (operationType === 'unset') {
if (_type2['default'].object(p)) delete p[s];else if (_type2['default'].array(p)) p.splice(s, 1);
}
if (opts.persistent) p[s] = (0, _helpers.shallowMerge)({}, p[s], value);else p[s] = (0, _helpers.shallowMerge)(p[s], value);
}
/**
* Merge
*/
else if (operationType === 'merge') {
if (!_type2['default'].object(p[s])) throw err('merge', 'object', currentPath);
if (opts.persistent) p[s] = (0, _helpers.shallowMerge)({}, p[s], value);else p[s] = (0, _helpers.shallowMerge)(p[s], value);
}
if (opts.immutable) (0, _helpers.deepFreeze)(p);

@@ -144,9 +146,9 @@

else if (_type2['default'].primitive(p[s])) {
p[s] = {};
}
p[s] = {};
}
// Else, we shift the reference and continue the path
else {
p[s] = (0, _helpers.shallowClone)(p[s]);
}
// Else, we shift the reference and continue the path
else if (opts.persistent) {
p[s] = (0, _helpers.shallowClone)(p[s]);
}

@@ -153,0 +155,0 @@ // Should we freeze the current step before continuing?

{
"name": "baobab",
"version": "2.0.0-dev9",
"version": "2.0.0-rc1",
"description": "JavaScript persistent data tree with cursors.",

@@ -12,6 +12,6 @@ "main": "./dist/baobab.js",

"babel": "^5.6.14",
"babel-eslint": "^3.1.20",
"babel-eslint": "^4.0.5",
"babelify": "^6.1.2",
"benchmark": "^1.0.0",
"browserify": "^10.2.4",
"browserify": "^11.0.1",
"eslint": "^0.24.0",

@@ -18,0 +18,0 @@ "gulp": "^3.8.10",

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

If you really need to fire an update synchronously (typically if you store a form's state within your app's state, for instance), your remain free to use the `tree.commit` method or tweak the tree's [options](#options) to fit your needs.
If you really need to fire an update synchronously (typically if you store a form's state within your app's state, for instance), your remain free to use the `tree.commit()` method or tweak the tree's [options](#options) to fit your needs.
**Important**: Note that the tree, being a persistent data structure, will shift the references of the objects it stores in order to enable *immutabley* comparisons between one version of the state and another (this is especially useful when using strategies as such as React's [pure rendering](https://facebook.github.io/react/docs/pure-render-mixin.html)).
**Important**: Note that the tree, being a persistent data structure, will shift the references of the objects it stores in order to enable *immutable* comparisons between one version of the state and another (this is especially useful when using strategies as such as React's [pure rendering](https://facebook.github.io/react/docs/pure-render-mixin.html)).

@@ -361,3 +361,3 @@ *Example*

console.log('New data:', eventData.data);
console.log('Current data:', eventData.currentData);
console.log('Previous data:', eventData.previousData);

@@ -419,3 +419,6 @@ console.log('Transaction details:', eventData.transaction);

```js
cursor.on('update', fn);
cursor.on('update', function(e) {
console.log('Current data:', eventData.currentData);
console.log('Previous data:', eventData.previousData);
});
```

@@ -572,3 +575,3 @@

Note that the getter function is lazy and that data won't be computed before you access it.
It is not possible, at the time being, to modify facets' definition at runtime. It may however be allowed in further versions.

@@ -635,2 +638,8 @@ #### Specialized getters

});
watcher.get();
>>> {
name: 'John',
surname: 'Smith'
}
```

@@ -774,3 +783,3 @@

```js
// Asynchronous tree so that examples are simpler
// Synchronous tree so that examples are simpler
var baobab = new Baobab({colors: ['blue']}, {asynchronous: false}),

@@ -777,0 +786,0 @@ cursor = baobab.select('colors');

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

import Cursor from './cursor';
import Watcher from './watcher';
import Facet from './facet';

@@ -15,2 +16,3 @@ import type from './type';

arrayFrom,
coercePath,
deepFreeze,

@@ -42,2 +44,5 @@ getIn,

// Should the tree's update be pure?
pure: true,
// Validation specifications

@@ -104,8 +109,7 @@ validate: null,

this._affectedPathsIndex = {};
this._computedDataIndex = {};
this._facets = {};
this._previousData = null;
this._data = initialData;
// Properties
this.log = [];
this.previousData = null;
this.data = initialData;
this.root = this.select();

@@ -115,3 +119,3 @@

if (this.options.immutable)
deepFreeze(this.data);
deepFreeze(this._data);

@@ -142,13 +146,10 @@ // Bootstrapping root cursor's getters and setters

// Creating the computed data index for the first time
this._refreshComputedDataIndex();
this._initializeComputedDataIndex();
}
/**
* Private method used to refresh the internal computed data index of the
* Private method used to initialize the internal computed data index of the
* tree.
*
* @param {array} [path] - Path to the modified node.
* @return {Baobab} - The tree itself for chaining purposes.
*/
_refreshComputedDataIndex(path) {
_initializeComputedDataIndex() {

@@ -158,6 +159,2 @@ // Refreshing the whole tree

// Have we reached the end?
if (type.primitive(data))
return;
// Object iteration

@@ -175,3 +172,3 @@ // TODO: handle arrays?

deepMerge(
this._computedDataIndex,
this._facets,
pathObject(p, {[k]: facet})

@@ -187,15 +184,4 @@ );

if (!path || !path.length) {
// Walk the whole tree
return walk(this.data);
}
else {
// Retrieving parent of affected node
const parentNode = getIn(this.data, path.slice(0, -1)).data;
// Walk the affected leaf
return walk(parentNode, path.slice(0, -1));
}
// Walking the whole tree
return walk(this._data);
}

@@ -240,3 +226,3 @@

if (!cursor) {
cursor = new Cursor(this, path, {hash});
cursor = new Cursor(this, path, hash);
this._cursors[hash] = cursor;

@@ -263,3 +249,3 @@ }

// Coercing path
path = path || path === 0 ? path : [];
path = coercePath(path);

@@ -274,5 +260,4 @@ if (!type.operationType(operation.type))

const {solvedPath, exists} = getIn(
this.data,
path,
this._computedDataIndex
this._data,
path
);

@@ -292,3 +277,3 @@

if (!this._transaction.length)
this.previousData = this.data;
this._previousData = this.get();

@@ -299,3 +284,3 @@ const hash = hashPath(solvedPath);

const {data, node} = update(
this.data,
this._data,
solvedPath,

@@ -307,10 +292,6 @@ operation,

// Updating data and transaction
this.data = data;
this._data = data;
this._affectedPathsIndex[hash] = true;
this._transaction.push({...operation, path: solvedPath});
// Refreshing facet index
// TODO: provide a setting to disable this or at least selectively for perf
this._refreshComputedDataIndex(solvedPath);
// Emitting a `write` event

@@ -360,4 +341,4 @@ this.emit('write', {path: solvedPath});

this,
this.previousData,
this.data,
this._previousData,
this._data,
affectedPaths

@@ -370,6 +351,6 @@ );

if (behavior === 'rollback') {
this.data = this.previousData;
this._data = this._previousData;
this._affectedPathsIndex = {};
this._transaction = [];
this.previousData = this.data;
this._previousData = this._data;
return this;

@@ -382,14 +363,14 @@ }

const transaction = this._transaction,
previousData = this.previousData;
previousData = this._previousData;
this._affectedPathsIndex = {};
this._transaction = [];
this.previousData = this.data;
this._previousData = this._data;
// Emitting update event
this.emit('update', {
paths: affectedPaths,
currentData: this._data,
transaction,
previousData,
data: this.data,
paths: affectedPaths
previousData
});

@@ -404,10 +385,7 @@

*
* @param {object|array} paths - Paths to listen.
* @return {Cursor} - A special cursor that can be listened.
* @param {object} mapping - Mapping of paths to listen.
* @return {Cursor} - The created watcher.
*/
watch(paths) {
if (!type.object(paths) && !type.array(paths))
throw Error('Baobab.watch: wrong argument.');
return new Cursor(this, null, {watched: paths});
watch(mapping) {
return new Watcher(this, mapping);
}

@@ -421,5 +399,7 @@

delete this.data;
delete this._data;
delete this._previousData;
delete this._transaction;
delete this._affectedPathsIndex;
delete this._facets;

@@ -458,3 +438,3 @@ // Releasing cursors

Object.defineProperty(Baobab, 'version', {
value: '2.0.0-dev9'
value: '2.0.0-rc1'
});

@@ -461,0 +441,0 @@

@@ -13,6 +13,6 @@ /**

before,
coercePath,
deepClone,
getIn,
makeError,
shallowClone,
solveUpdate

@@ -41,15 +41,9 @@ } from './helpers';

*
* Note: opts.watched is not called opts.watch not to tamper with experimental
* `Object.prototype.watch`.
*
* @constructor
* @param {Baobab} tree - The cursor's root.
* @param {array} path - The cursor's path in the tree.
* @param {object} [opts] - Options
* @param {string} [opts.hash] - The path's hash computed ahead by the tree.
* @param {array} [opts.watched] - Parts of the tree the cursor is meant to
* watch over.
* @param {string} hash - The path's hash computed ahead by the tree.
*/
export default class Cursor extends Emitter {
constructor(tree, path, opts={}) {
constructor(tree, path, hash) {
super();

@@ -67,6 +61,7 @@

this.path = path;
this.hash = opts.hash;
this.hash = hash;
// State
this.state = {
killed: false,
recording: false,

@@ -76,20 +71,2 @@ undoing: false

// Checking whether the cursor is a watcher
if (opts.watched) {
this._watched = shallowClone(opts.watched);
// Normalizing path
for (let k in this._watched)
if (this._watched[k] instanceof Cursor)
this._watched[k] = this._watched[k].path;
// Keeping track of watched paths
this._watchedPaths = opts.watched && (!type.array(this._watched) ?
Object.keys(this._watched).map(k => this._watched[k]) :
this._watched);
// Overriding the cursor's get method
this.get = this.tree.project.bind(this.tree, this._watched);
}
// Checking whether the given path is dynamic or not

@@ -113,3 +90,4 @@ this._dynamicPath = type.dynamicPath(this.path);

this._writeHandler = ({data}) => {
if (!solveUpdate([data.path], this._getComparedPaths()))
if (this.state.killed ||
!solveUpdate([data.path], this._getComparedPaths()))
return;

@@ -127,17 +105,19 @@

const fireUpdate = (previousData) => {
const self = this;
if (this._watched)
return this.emit('update');
const eventData = {
get previousData() {
return getIn(previousData, self.solvedPath).data;
},
get currentData() {
return self.get();
}
};
const record = getIn(previousData, this.solvedPath).data;
if (this.state.recording && !this.state.undoing)
this.archive.add(record);
this.archive.add(eventData.previousData);
this.state.undoing = false;
return this.emit('update', {
data: this._get().data,
previousData: record
});
return this.emit('update', eventData);
};

@@ -156,2 +136,5 @@

this._updateHandler = (event) => {
if (this.state.killed)
return;
const {paths, previousData} = event.data,

@@ -200,2 +183,4 @@ update = fireUpdate.bind(this, previousData),

*
* @todo: probably useless now...
*
* @param {array} path - The path to get in the tree.

@@ -206,6 +191,4 @@ * @return {object} - The result of the `getIn` helper.

return getIn(
this.tree.data,
path,
this.tree._computedDataIndex,
this.tree.options
this.tree._data,
path
);

@@ -221,37 +204,11 @@ }

_getComparedPaths() {
let comparedPaths;
// Standard cursor
if (!this._watched) {
// Checking whether we should keep track of some dependencies
const additionalPaths = this._facetPath ?
getIn(this.tree._facets, this._facetPath)
.data
.relatedPaths() :
[];
// Checking whether we should keep track of some dependencies
const additionalPaths = this._facetPath ?
getIn(this.tree._computedDataIndex, this._facetPath)
.data
.relatedPaths() :
[];
comparedPaths = [this.solvedPath].concat(additionalPaths);
}
// Watcher cursor
else {
comparedPaths = this._watchedPaths.reduce((cp, p) => {
if (type.dynamicPath(p))
p = this._getIn(p).solvedPath;
if (!p)
return cp;
const facetPath = type.facetPath(p);
if (facetPath)
return cp.concat(
getIn(this.tree._computedDataIndex, p).data.relatedPaths());
return cp.concat([p]);
}, []);
}
return comparedPaths;
return [this.solvedPath].concat(additionalPaths);
}

@@ -492,3 +449,3 @@

exists(path) {
path = path || path === 0 ? path : [];
path = coercePath(path);

@@ -515,3 +472,3 @@ if (arguments.length > 1)

get(path) {
path = path || path === 0 ? path : [];
path = coercePath(path);

@@ -533,2 +490,4 @@ if (arguments.length > 1)

*
* @todo: should be more performant.
*
* Arity (1):

@@ -543,5 +502,18 @@ * @param {path} path - Path to serialize in the tree.

serialize() {
const data = this.get.apply(this, arguments);
const data = deepClone(this.get.apply(this, arguments));
return deepClone(data);
const dropComputedData = function(d) {
if (!type.object(d))
return;
for (let k in d) {
if (k[0] === '$')
delete d[k];
else
dropComputedData(d[k]);
}
};
dropComputedData(data);
return data;
}

@@ -697,2 +669,3 @@

this.kill();
this.state.killed = true;
}

@@ -772,3 +745,3 @@

// Coerce path
path = path || path === 0 ? path : [];
path = coercePath(path);

@@ -812,3 +785,3 @@ // Checking the path's validity

makeSetter('unshift');
makeSetter('splice', type.array);
makeSetter('splice', type.splicer);
makeSetter('merge', type.object);

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

import type from './type';
import update from './update';
import {

@@ -40,2 +41,3 @@ deepFreeze,

this.tree = tree;
this.path = pathInTree;
this.computedData = null;

@@ -64,3 +66,4 @@ this.type = definitionType;

this.state = {
computed: false
computed: false,
killed: false
};

@@ -74,4 +77,8 @@

* so that the data may be recomputed when needed.
*
* In addition, the Facet will replace the according node within the tree.
*/
this.listener = ({data: {path}}) => {
if (this.state.killed)
return;

@@ -84,2 +91,4 @@ // Is this facet affected by the current write event?

this.state.computed = false;
this.update();
}

@@ -90,2 +99,5 @@ };

tree.on('write', this.listener);
// Updating the tree's data
this.update();
}

@@ -103,3 +115,3 @@

paths = this.paths.map(
p => getIn(this.tree.data, p, this.tree._computedDataIndex).solvedPath
p => getIn(this.tree._data, p, this.tree._facets).solvedPath
);

@@ -112,15 +124,31 @@ else

else
return paths.reduce((paths, path) => {
return paths.reduce((accumulatedPaths, path) => {
const facetPath = type.facetPath(path);
if (!facetPath)
return paths.concat(path);
return accumulatedPaths.concat([path]);
// Solving recursive path
const relatedFacet = getIn(this.tree._computedDataIndex, facetPath).data;
const relatedFacet = getIn(this.tree._facets, facetPath).data;
return paths.concat(relatedFacet.relatedPaths());
return accumulatedPaths.concat(relatedFacet.relatedPaths());
}, []);
}
/**
* Method used to update the tree's internal data with the Facet's freshly
* computed data.
*
* @return {Facet} - Returns itself.
*/
update() {
this.tree._data = update(
this.tree._data,
this.path,
{type: 'set', value: this.get()},
this.tree.options
).data;
return this;
}
/**

@@ -168,3 +196,4 @@ * Getter method

this.tree.off('write', this.listener);
this.state.killed = true;
}
}

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

*/
import Facet from './facet';
import type from './type';

@@ -165,8 +164,6 @@

if (item.constructor && item.constructor !== Object)
o = Object.create(item.constructor.prototype);
// NOTE: could be possible to erase computed properties through `null`.
for (k in item)
if (item.hasOwnProperty(k) && k[0] !== '$')
o[k] = deep ? cloner(true, item[k]) : item[k];
if (item.hasOwnProperty(k))
o[k] = (deep && k[0] !== '$') ? cloner(true, item[k]) : item[k];
return o;

@@ -187,2 +184,14 @@ }

/**
* Coerce the given variable into a full-fledged path.
*
* @param {mixed} target - The variable to coerce.
* @return {array} - The array path.
*/
export function coercePath(target) {
if (target || target === 0 || target === '')
return target;
return [];
}
/**
* Function comparing an object's properties to a given descriptive

@@ -276,36 +285,2 @@ * object.

/**
* Function used to solve a computed data mask by recursively walking a tree
* and patching it.
*
* @param {boolean} immutable - Is the data immutable?
* @param {mixed} data - Data to patch.
* @param {object} mask - Computed data mask.
* @param {object} [parent] - Parent object in the iteration.
* @param {string} [lastKey] - Current value's key in parent.
*/
function solveMask(immutable, data, mask, parent) {
for (let k in mask) {
if (k[0] === '$') {
// Patching
data[k] = mask[k].get();
if (immutable)
deepFreeze(parent);
}
else {
if (immutable) {
data[k] = shallowClone(data[k]);
if (parent)
freeze(parent);
}
solveMask(immutable, data[k], mask[k], data);
}
}
return data;
}
/**
* Function retrieving nested data within the given object and according to

@@ -317,12 +292,12 @@ * the given path.

*
* @param {object} object - The object we need to get data from.
* @param {array} path - The path to follow.
* @param {object} [mask] - An optional computed data index.
* @return {object} result - The result.
* @return {mixed} result.data - The data at path, or `undefined`.
* @return {array} result.solvedPath - The solved path or `null`.
* @param {object} object - The object we need to get data from.
* @param {array} path - The path to follow.
* @return {object} result - The result.
* @return {mixed} result.data - The data at path, or `undefined`.
* @return {array} result.solvedPath - The solved path or `null`.
* @return {boolean} result.exists - Does the path exists in the tree?
*/
const notFoundObject = {data: undefined, solvedPath: null, exists: false};
export function getIn(object, path, mask=null, opts={}) {
export function getIn(object, path) {
if (!path)

@@ -333,3 +308,2 @@ return notFoundObject;

c = object,
cm = mask,
idx,

@@ -367,24 +341,6 @@ i,

solvedPath.push(path[i]);
// Solving data from a facet if needed
if (cm && path[i][0] === '$') {
c = cm[path[i]].get();
cm = null;
}
else {
c = c[path[i]];
// Walking the mask
if (cm)
cm = cm[path[i]] || null;
}
c = c[path[i]];
}
}
// If the mask is still relevant, we solve it down to the leaves
if (cm && Object.keys(cm).length) {
let patchedData = solveMask(opts.immutable, {root: c}, {root: cm});
c = patchedData.root;
}
return {data: c, solvedPath, exists: c !== undefined};

@@ -432,4 +388,2 @@ }

* be used by Baobab's internal and would be unsuited in any other case.
* Note 4): this function will release any facet found on its path to the
* leaves for cleanup reasons.
*

@@ -457,7 +411,2 @@ * @param {boolean} deep - Whether the merge should be deep or not.

else {
// Releasing
if (o[k] instanceof Facet)
o[k].release();
o[k] = t[k];

@@ -464,0 +413,0 @@ }

@@ -104,2 +104,17 @@ /**

/**
* Checking whether the given variable is a valid splicer.
*
* @param {mixed} target - Variable to test.
* @param {array} [allowed] - Optional valid types in path.
* @return {boolean}
*/
type.splicer = function(target) {
if (!type.array(target) || target.length < 2)
return false;
return anyOf(target[0], ['number', 'function', 'object']) &&
type.number(target[1]);
};
/**
* Checking whether the given variable is a valid cursor path.

@@ -111,10 +126,11 @@ *

*/
type.path = function(target, allowed) {
if (!target && target !== 0)
// Order is important for performance reasons
const ALLOWED_FOR_PATH = ['string', 'number', 'function', 'object'];
type.path = function(target) {
if (!target && target !== 0 && target !== '')
return false;
// Order of allowed types is important for perf reasons
allowed = allowed || ['string', 'number', 'function', 'object'];
return [].concat(target).every(step => anyOf(step, allowed));
return [].concat(target).every(step => anyOf(step, ALLOWED_FOR_PATH));
};

@@ -162,3 +178,3 @@

type.readOnlyPath = function(path) {
return path.slice(0, -1).some(step => step[0] === '$');
return path.some(step => step[0] === '$');
};

@@ -175,3 +191,5 @@

if (type.object(definition)) {
if (!type.function(definition.get))
if (!type.function(definition.get) ||
(definition.cursors &&
!(Object.keys(definition).every(k => type.path(definition[k])))))
return null;

@@ -182,3 +200,4 @@ else

else if (type.array(definition)) {
if (!type.function(definition[definition.length - 1]))
if (!type.function(definition[definition.length - 1]) ||
!definition.slice(0, -1).every(p => type.path(p)))
return null;

@@ -193,2 +212,13 @@ else

/**
* Checking whether the given watcher definition is valid.
*
* @param {mixed} definition - The definition to check.
* @return {boolean}
*/
type.watcherMapping = function(definition) {
return type.object(definition) &&
Object.keys(definition).every(k => type.path(definition[k]));
};
/**
* Checking whether the given string is a valid operation type.

@@ -195,0 +225,0 @@ *

@@ -67,3 +67,3 @@ /**

if (operationType === 'set') {
p[s] = value;
p[s] = opts.persistent ? shallowClone(value) : value;
}

@@ -75,3 +75,5 @@

else if (operationType === 'apply') {
p[s] = value(p[s]);
const result = value(p[s]);
p[s] = opts.persistent ? shallowClone(result) : result;
}

@@ -187,3 +189,3 @@

// Else, we shift the reference and continue the path
else {
else if (opts.persistent) {
p[s] = shallowClone(p[s]);

@@ -190,0 +192,0 @@ }

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