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

dist/monkey.js

5

CHANGELOG.md

@@ -12,4 +12,5 @@ # Changelog

* Adding `cursor.watch`.
* Changing the way you can define computed data in the tree, aka "facets". Facets are now to be defined within the tree itself and can be accessed using the exact same API as normal data.
* Adding an alternative facet definition syntax for convenience.
* Adding the `pure` option.
* Changing the way you can define computed data in the tree, aka "facets". Facets are now to be defined within the tree itself, are called "monkeys", and can be accessed using the exact same API as normal data.
* Adding an alternative dynamic node definition syntax for convenience.
* Dropped the `syncwrite` option. The tree is now writing synchronously but still emits its updates asynchronously by default.

@@ -16,0 +17,0 @@ * Max number of records is now set to `Infinity` by default, meaning there is no limit.

202

dist/baobab.js

@@ -17,8 +17,8 @@ /**

var _get = function get(_x2, _x3, _x4) { var _again = true; _function: while (_again) { var object = _x2, property = _x3, receiver = _x4; 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 { _x2 = parent; _x3 = property; _x4 = 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); } } };
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }

@@ -36,2 +36,4 @@

var _monkey = require('./monkey');
var _watcher = require('./watcher');

@@ -41,6 +43,2 @@

var _facet = require('./facet');
var _facet2 = _interopRequireDefault(_facet);
var _type = require('./type');

@@ -50,8 +48,20 @@

var _update3 = require('./update');
var _update2 = require('./update');
var _update4 = _interopRequireDefault(_update3);
var _update3 = _interopRequireDefault(_update2);
var _helpers = require('./helpers');
var helpers = _interopRequireWildcard(_helpers);
var arrayFrom = helpers.arrayFrom;
var coercePath = helpers.coercePath;
var deepFreeze = helpers.deepFreeze;
var getIn = helpers.getIn;
var makeError = helpers.makeError;
var deepMerge = helpers.deepMerge;
var pathObject = helpers.pathObject;
var shallowMerge = helpers.shallowMerge;
var uniqid = helpers.uniqid;
/**

@@ -93,3 +103,3 @@ * Baobab defaults

return '/' + path.map(function (step) {
if (_type2['default']['function'](step) || _type2['default'].object(step)) return '#' + (0, _helpers.uniqid)() + '#';else return step;
if (_type2['default']['function'](step) || _type2['default'].object(step)) return '#' + uniqid() + '#';else return step;
}).join('/');

@@ -108,2 +118,4 @@ }

* @param {boolean} [opts.immutable] - Should the tree be immutable?
* @param {boolean} [opts.persistent] - Should the tree be persistent?
* @param {boolean} [opts.pure] - Should the tree be pure?
* @param {function} [opts.validate] - Validation function.

@@ -127,9 +139,12 @@ * @param {string} [opts.validationBehaviour] - "rollback" or "notify".

// Checking whether given initial data is valid
if (!_type2['default'].object(initialData) && !_type2['default'].array(initialData)) throw (0, _helpers.makeError)('Baobab: invalid data.', { data: initialData });
if (!_type2['default'].object(initialData) && !_type2['default'].array(initialData)) throw makeError('Baobab: invalid data.', { data: initialData });
// Merging given options with defaults
this.options = (0, _helpers.shallowMerge)({}, DEFAULTS, opts);
this.options = shallowMerge({}, DEFAULTS, opts);
// Disabling immutability if persistence if disabled
if (!this.options.persistent) this.options.immutable = false;
// Disabling immutability & persistence if persistence if disabled
if (!this.options.persistent) {
this.options.immutable = false;
this.options.pure = false;
}

@@ -142,3 +157,3 @@ // Privates

this._affectedPathsIndex = {};
this._facets = {};
this._monkeys = {};
this._previousData = null;

@@ -151,3 +166,3 @@ this._data = initialData;

// Does the user want an immutable tree?
if (this.options.immutable) (0, _helpers.deepFreeze)(this._data);
if (this.options.immutable) deepFreeze(this._data);

@@ -164,39 +179,68 @@ // Bootstrapping root cursor's getters and setters

// Creating the computed data index for the first time
this._initializeComputedDataIndex();
// Registering the initial monkeys
this._refreshMonkeys();
}
/**
* Version
* Creating statics
*/
/**
* Private method used to initialize the internal computed data index of the
* tree.
* Internal method used to refresh the tree's monkey register on every
* update.
* Note 1) For the time being, placing monkeys beneath array nodes is not
* allowed for performance reasons.
*
* @param {mixed} node - The starting node.
* @param {array} path - The starting node's path.
* @param {string} operation - The operation that lead to a refreshment.
* @return {Baobab} - The tree instance for chaining purposes.
*/
_createClass(Baobab, [{
key: '_initializeComputedDataIndex',
value: function _initializeComputedDataIndex() {
key: '_refreshMonkeys',
value: function _refreshMonkeys(node, path, operation) {
var _this2 = this;
// Refreshing the whole tree
var walk = function walk(data) {
var clean = function clean(data) {
var p = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1];
// Object iteration
// TODO: handle arrays?
if (data instanceof _monkey.Monkey) {
data.release();
(0, _update3['default'])(_this2._monkeys, p, { type: 'unset' }, {
immutable: false,
persistent: false,
pure: false
});
return;
}
if (_type2['default'].object(data)) {
var k = undefined;
for (var k in data) {
clean(data[k], p.concat(k));
}
}
};
for (k in data) {
var walk = function walk(data) {
var p = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1];
// Creating a facet if needed
if (k[0] === '$') {
var facet = new _facet2['default'](_this2, p.concat(k), data[k]);
// Should we sit a monkey in the tree?
if (data instanceof _monkey.MonkeyDefinition) {
var monkey = new _monkey.Monkey(_this2, p, data);
(0, _helpers.deepMerge)(_this2._facets, (0, _helpers.pathObject)(p, _defineProperty({}, k, facet)));
} else {
walk(data[k], p.concat(k));
}
(0, _update3['default'])(_this2._monkeys, p, { type: 'set', value: monkey }, {
immutable: false,
persistent: false,
pure: false
});
return;
}
// Object iteration
if (_type2['default'].object(data)) {
for (var k in data) {
walk(data[k], p.concat(k));
}

@@ -207,3 +251,13 @@ }

// Walking the whole tree
return walk(this._data);
if (!arguments.length) walk(this._data);else {
var monkeysNode = getIn(this._monkeys, path).data;
// Is this required that we clean some already existing monkeys?
if (monkeysNode) clean(monkeysNode, path);
// Let's walk the tree only from the updated point
if (operation !== 'unset') walk(node, path);
}
return this;
}

@@ -231,6 +285,6 @@

// Variadic
if (arguments.length > 1) path = (0, _helpers.arrayFrom)(arguments);
if (arguments.length > 1) path = arrayFrom(arguments);
// Checking that given path is valid
if (!_type2['default'].path(path)) throw (0, _helpers.makeError)('Baobab.select: invalid path.', { path: path });
if (!_type2['default'].path(path)) throw makeError('Baobab.select: invalid path.', { path: path });

@@ -274,17 +328,24 @@ // Casting to array

// Coercing path
path = (0, _helpers.coercePath)(path);
path = coercePath(path);
if (!_type2['default'].operationType(operation.type)) throw (0, _helpers.makeError)('Baobab.update: unknown operation type "' + operation.type + '".', { operation: operation });
if (!_type2['default'].operationType(operation.type)) throw makeError('Baobab.update: unknown operation type "' + operation.type + '".', { operation: operation });
// Solving the given path
var _getIn = (0, _helpers.getIn)(this._data, path);
var _getIn = getIn(this._data, path);
// If we couldn't solve the path, we throw
var solvedPath = _getIn.solvedPath;
var exists = _getIn.exists;
if (!solvedPath) throw (0, _helpers.makeError)('Baobab.update: could not solve the given path.', {
// If we couldn't solve the path, we throw
if (!solvedPath) throw makeError('Baobab.update: could not solve the given path.', {
path: solvedPath
});
// Read-only path?
var monkeyPath = _type2['default'].monkeyPath(this._monkeys, solvedPath);
if (monkeyPath && solvedPath.length > monkeyPath.length) throw makeError('Baobab.update: attempting to update a read-only path.', {
path: solvedPath
});
// We don't unset irrelevant paths

@@ -296,17 +357,26 @@ if (operation.type === 'unset' && !exists) return;

var hash = hashPath(solvedPath);
// Applying the operation
var result = (0, _update3['default'])(this._data, solvedPath, operation, this.options);
var _update2 = (0, _update4['default'])(this._data, solvedPath, operation, this.options);
var data = result.data;
var node = result.node;
// If because of purity, the update was moot, we stop here
if (!('data' in result)) return node;
// If the operation is push, the affected path is slightly different
var affectedPath = solvedPath.concat(operation.type === 'push' ? node.length - 1 : []);
var hash = hashPath(affectedPath);
// Updating data and transaction
var data = _update2.data;
var node = _update2.node;
this._data = data;
this._affectedPathsIndex[hash] = true;
this._transaction.push(_extends({}, operation, { path: solvedPath }));
this._transaction.push(_extends({}, operation, { path: affectedPath }));
// Updating the monkeys
this._refreshMonkeys(node, solvedPath, operation.type);
// Emitting a `write` event
this.emit('write', { path: solvedPath });
this.emit('write', { path: affectedPath });

@@ -408,2 +478,4 @@ // Should we let the user commit?

this.emit('release');
delete this._data;

@@ -413,3 +485,3 @@ delete this._previousData;

delete this._affectedPathsIndex;
delete this._facets;
delete this._monkeys;

@@ -451,12 +523,30 @@ // Releasing cursors

exports['default'] = Baobab;
Object.defineProperty(Baobab, 'version', {
value: '2.0.0-rc1'
});
Baobab.monkey = function () {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
if (!args.length) throw new Error('Baobab.monkey: missing definition.');
if (args.length === 1) return new _monkey.MonkeyDefinition(args[0]);else return new _monkey.MonkeyDefinition(args);
};
Baobab.dynamicNode = Baobab.monkey;
/**
* Exposing the Cursor class
* Exposing some internals for convenience
*/
Baobab.Cursor = _cursor2['default'];
Baobab.MonkeyDefinition = _monkey.MonkeyDefinition;
Baobab.Monkey = _monkey.Monkey;
Baobab.type = _type2['default'];
Baobab.helpers = helpers;
/**
* Version
*/
Object.defineProperty(Baobab, 'version', {
value: '2.0.0-rc2'
});
/**
* Exporting

@@ -463,0 +553,0 @@ */

@@ -27,2 +27,4 @@ /**

var _monkey = require('./monkey');
var _type = require('./type');

@@ -86,6 +88,6 @@

// Checking whether the given will meet a facet
this._facetPath = _type2['default'].facetPath(this.path);
// Checking whether the given path will meet a monkey
this._monkeyPath = _type2['default'].monkeyPath(this.tree._monkeys, this.path);
if (!this._dynamicPath) this.solvedPath = this.path;else this.solvedPath = this._getIn(this.path).solvedPath;
if (!this._dynamicPath) this.solvedPath = this.path;else this.solvedPath = (0, _helpers.getIn)(this.tree._data, this.path).solvedPath;

@@ -103,3 +105,3 @@ /**

_this.solvedPath = _this._getIn(_this.path).solvedPath;
_this.solvedPath = (0, _helpers.getIn)(_this.tree._data, _this.path).solvedPath;
};

@@ -111,2 +113,5 @@

*
* @note: probably should wrap the current solvedPath in closure to avoid
* for tricky cases where it would fail.
*
* @param {mixed} previousData - the tree's previous data.

@@ -207,24 +212,9 @@ */

/**
* 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.
* Method returning the paths of the tree watched over by the cursor and that
* should be taken into account when solving a potential update.
*
* @todo: probably useless now...
*
* @param {array} path - The path to get in the tree.
* @return {object} - The result of the `getIn` helper.
* @return {array} - Array of paths to compare with a given update.
*/
_createClass(Cursor, [{
key: '_getIn',
value: function _getIn(path) {
return (0, _helpers.getIn)(this.tree._data, path);
}
/**
* Method returning the paths of the tree watched over by the cursor and that
* should be taken into account when solving a potential update.
*
* @return {array} - Array of paths to compare with a given update.
*/
}, {
key: '_getComparedPaths',

@@ -234,3 +224,3 @@ value: function _getComparedPaths() {

// Checking whether we should keep track of some dependencies
var additionalPaths = this._facetPath ? (0, _helpers.getIn)(this.tree._facets, this._facetPath).data.relatedPaths() : [];
var additionalPaths = this._monkeyPath ? (0, _helpers.getIn)(this.tree._monkeys, this._monkeyPath).data.relatedPaths() : [];

@@ -462,3 +452,3 @@ return [this.solvedPath].concat(additionalPaths);

return this._getIn(this.solvedPath.concat(path));
return (0, _helpers.getIn)(this.tree._data, this.solvedPath.concat(path));
}

@@ -510,5 +500,6 @@

// 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) });

@@ -523,3 +514,4 @@

*
* @todo: should be more performant.
* @todo: should be more performant as the cloning should happen as well as
* when dropping computed data.
*

@@ -536,14 +528,25 @@ * Arity (1):

key: 'serialize',
value: function serialize() {
var data = (0, _helpers.deepClone)(this.get.apply(this, arguments));
value: function serialize(path) {
path = (0, _helpers.coercePath)(path);
var dropComputedData = function dropComputedData(d) {
if (!_type2['default'].object(d)) return;
if (arguments.length > 1) path = (0, _helpers.arrayFrom)(arguments);
for (var k in d) {
if (k[0] === '$') delete d[k];else dropComputedData(d[k]);
if (!_type2['default'].path(path)) throw (0, _helpers.makeError)('Baobab.Cursor.getters: invalid path.', { path: path });
if (!this.solvedPath) return undefined;
var fullPath = this.solvedPath.concat(path);
var data = (0, _helpers.deepClone)((0, _helpers.getIn)(this.tree._data, fullPath).data),
monkeys = (0, _helpers.getIn)(this.tree._monkeys, fullPath).data;
var dropComputedData = function dropComputedData(d, m) {
if (!_type2['default'].object(m) || !_type2['default'].object(d)) return;
for (var k in m) {
if (m[k] instanceof _monkey.Monkey) delete d[k];else dropComputedData(d[k], m[k]);
}
};
dropComputedData(data);
dropComputedData(data, monkeys);
return data;

@@ -785,5 +788,2 @@ }

// Checking we are not trying to update a read-only path
if (_type2['default'].readOnlyPath(fullPath)) throw (0, _helpers.makeError)('Baobab.Cursor.' + name + ': trying to update a read-only path.', { path: fullPath });
// Filing the update to the tree

@@ -790,0 +790,0 @@ return this.tree.update(fullPath, {

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

exports.makeError = makeError;
exports.merger = merger;
exports.pathObject = pathObject;

@@ -30,10 +29,11 @@ exports.solveUpdate = solveUpdate;

var _monkey = require('./monkey');
var _type = require('./type');
var _type2 = _interopRequireDefault(_type);
/**
* Noop function
*/
var _type2 = _interopRequireDefault(_type);
var noop = Function.prototype;

@@ -176,3 +176,3 @@

function cloner(deep, item) {
if (!item || typeof item !== 'object' || item instanceof Error || 'ArrayBuffer' in global && item instanceof ArrayBuffer) return item;
if (!item || typeof item !== 'object' || item instanceof Error || item instanceof _monkey.MonkeyDefinition || 'ArrayBuffer' in global && item instanceof ArrayBuffer) return item;

@@ -204,3 +204,3 @@ // Array

// 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];
for (k in item) if (item.hasOwnProperty(k)) o[k] = deep ? cloner(true, item[k]) : item[k];
return o;

@@ -219,2 +219,3 @@ }

exports.shallowClone = shallowClone;
exports.deepClone = deepClone;

@@ -227,3 +228,2 @@ /**

*/
exports.deepClone = deepClone;

@@ -310,2 +310,3 @@ function coercePath(target) {

exports.freeze = freeze;
exports.deepFreeze = deepFreeze;

@@ -326,3 +327,2 @@ /**

*/
exports.deepFreeze = deepFreeze;
var notFoundObject = { data: undefined, solvedPath: null, exists: false };

@@ -406,4 +406,3 @@

* Note 2): the first object will be mutated to allow for perf scenarios.
* Note 3): this function will take not `$.` keys into account and should only
* be used by Baobab's internal and would be unsuited in any other case.
* Note 3): this function will consider monkeys as leaves.
*

@@ -414,3 +413,2 @@ * @param {boolean} deep - Whether the merge should be deep or not.

*/
function merger(deep) {

@@ -431,3 +429,3 @@ for (var _len = arguments.length, objects = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {

for (k in t) {
if (deep && typeof t[k] === 'object' && k[0] !== '$') {
if (deep && typeof t[k] === 'object' && !(t[k] instanceof _monkey.Monkey)) {
o[k] = merger(true, o[k] || {}, t[k]);

@@ -450,2 +448,3 @@ } else {

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

@@ -460,3 +459,2 @@ /**

*/
exports.deepMerge = deepMerge;

@@ -463,0 +461,0 @@ function pathObject(path, leaf) {

@@ -14,2 +14,5 @@ /**

});
var _monkey = require('./monkey');
var type = {};

@@ -152,10 +155,12 @@

/**
* Retrieve any facet subpath in the given path or null if the path never comes
* Retrieve any monkey subpath in the given path or null if the path never comes
* across computed data.
*
* @param {mixed} path - The path to test.
* @param {mixed} data - The data to test.
* @param {array} path - The path to test.
* @return {boolean}
*/
type.facetPath = function (path) {
type.monkeyPath = function (data, path) {
var subpath = [],
c = data,
i = undefined,

@@ -167,3 +172,7 @@ l = undefined;

if (path[i][0] === '$') return subpath;
if (typeof c !== 'object') return null;
c = c[path[i]];
if (c instanceof _monkey.Monkey) return subpath;
}

@@ -175,26 +184,13 @@

/**
* Checking whether the given path is read-only.
* Note: this should of course apply to a solved path and not a dynamic one.
* Returns the type of the given monkey definition or `null` if invalid.
*
* @param {mixed} path - The path to test.
* @return {boolean}
*/
type.readOnlyPath = function (path) {
return path.some(function (step) {
return step[0] === '$';
});
};
/**
* Returns the type of the given facet definition or `null` if invalid.
*
* @param {mixed} definition - The definition to check.
* @return {string|null}
*/
type.facetDefinition = function (definition) {
type.monkeyDefinition = function (definition) {
if (type.object(definition)) {
if (!type['function'](definition.get) || definition.cursors && !Object.keys(definition).every(function (k) {
return type.path(definition[k]);
})) return null;else return 'object';
if (!type['function'](definition.get) || definition.cursors && (!type.object(definition.cursors) || !Object.keys(definition.cursors).every(function (k) {
return type.path(definition.cursors[k]);
}))) return null;else return 'object';
} else if (type.array(definition)) {

@@ -201,0 +197,0 @@ if (!type['function'](definition[definition.length - 1]) || !definition.slice(0, -1).every(function (p) {

@@ -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,2 +73,6 @@ dummyPath = ['root'].concat(_toConsumableArray(path));

if (operationType === 'set') {
// Purity check
if (opts.pure && p[s] === value) return { node: p[s] };
p[s] = opts.persistent ? (0, _helpers.shallowClone)(value) : value;

@@ -78,62 +82,72 @@ }

/**
* Apply
* Monkey
*/
else if (operationType === 'apply') {
var result = value(p[s]);
p[s] = opts.persistent ? (0, _helpers.shallowClone)(result) : result;
else if (operationType === 'monkey') {
Object.defineProperty(p, s, { get: value, enumerable: true });
}
/**
* Push
* Apply
*/
else if (operationType === 'push') {
if (!_type2['default'].array(p[s])) throw err('push', 'array', currentPath);
else if (operationType === 'apply') {
var result = value(p[s]);
if (opts.persistent) p[s] = p[s].concat([value]);else p[s].push(value);
// Purity check
if (opts.pure && result === value) return { node: p[s] };
p[s] = opts.persistent ? (0, _helpers.shallowClone)(result) : result;
}
/**
* Unshift
* Push
*/
else if (operationType === 'unshift') {
if (!_type2['default'].array(p[s])) throw err('unshift', 'array', currentPath);
else if (operationType === 'push') {
if (!_type2['default'].array(p[s])) throw err('push', '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(value);
}
/**
* Concat
* Unshift
*/
else if (operationType === 'concat') {
if (!_type2['default'].array(p[s])) throw err('concat', 'array', currentPath);
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.apply(p[s], value);
if (opts.persistent) p[s] = [value].concat(p[s]);else p[s].unshift(value);
}
/**
* Splice
* Concat
*/
else if (operationType === 'splice') {
if (!_type2['default'].array(p[s])) throw err('splice', 'array', currentPath);
else if (operationType === 'concat') {
if (!_type2['default'].array(p[s])) throw err('concat', 'array', currentPath);
if (opts.persistent) p[s] = _helpers.splice.apply(null, [p[s]].concat(value));else p[s].splice.apply(p[s], value);
if (opts.persistent) p[s] = p[s].concat(value);else p[s].push.apply(p[s], value);
}
/**
* Unset
* Splice
*/
else if (operationType === 'unset') {
if (_type2['default'].object(p)) delete p[s];else if (_type2['default'].array(p)) p.splice(s, 1);
else if (operationType === 'splice') {
if (!_type2['default'].array(p[s])) throw err('splice', 'array', currentPath);
if (opts.persistent) p[s] = _helpers.splice.apply(null, [p[s]].concat(value));else p[s].splice.apply(p[s], value);
}
/**
* Merge
* Unset
*/
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);
else if (operationType === 'unset') {
if (_type2['default'].object(p)) delete p[s];else if (_type2['default'].array(p)) p.splice(s, 1);
}
/**
* 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);

@@ -140,0 +154,0 @@

@@ -100,3 +100,3 @@ /**

// Dynamic path?
if (_type2['default'].dynamicPath(p)) p = (0, _helpers.getIn)(_this2.tree._data, p, _this2.tree._facets).solvedPath;
if (_type2['default'].dynamicPath(p)) p = (0, _helpers.getIn)(_this2.tree._data, p).solvedPath;

@@ -106,5 +106,5 @@ if (!p) return cp;

// Facet path?
var facetPath = _type2['default'].facetPath(p);
var monkeyPath = _type2['default'].monkeyPath(_this2.tree._monkeys, p);
if (facetPath) return cp.concat((0, _helpers.getIn)(_this2.tree._facets, p).data.relatedPaths());
if (monkeyPath) return cp.concat((0, _helpers.getIn)(_this2.tree._monkeys, p).data.relatedPaths());

@@ -111,0 +111,0 @@ return cp.concat([p]);

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

@@ -16,3 +16,3 @@ "main": "./dist/baobab.js",

"browserify": "^11.0.1",
"eslint": "^0.24.0",
"eslint": "^1.1.0",
"gulp": "^3.8.10",

@@ -19,0 +19,0 @@ "gulp-header": "^1.2.2",

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

For a concise introduction about the library and how it can be used in a React/Flux application, you can head toward **@christianalfoni**'s [article](http://christianalfoni.github.io/javascript/2015/02/06/plant-a-baobab-tree-in-your-flux-application.html) on the subject.
**Fun fact**: A [Baobab](http://en.wikipedia.org/wiki/Adansonia_digitata), or *Adansonia digitata*, is a very big and magnificient African tree.

@@ -28,3 +26,3 @@

* [Polymorphisms](#polymorphisms)
* [Computed data](#computed-data)
* [Computed data](#computed-data-or-monkey-business)
* [Specialized getters](#specialized-getters)

@@ -86,3 +84,3 @@ * [Traversal](#traversal)

The library (as a standalone) currently weights ~25ko minified and ~7ko gzipped.
The library (as a standalone) currently weights ~8ko gzipped.

@@ -485,13 +483,22 @@ ## Usage

#### Computed data (facets)
#### Computed data or "Monkey Business"
For convenience, **Baobab** allows you to store computed data within the tree.
Computed data node can be considered as a "view" or "facet" over some parts of the data stored within your tree (a filtered version of an array, for instance).
It does so by letting you create "monkeys" that you should really consider as dynamic nodes within your tree (*v1 users*: "monkeys" are merely the evolution of "facets").
Those specific nodes must have, by convention, a key starting with `$` and can define dependencies to some paths within the tree.
As such, while monkeys represent reduction of the current state (a filtered list used by multiple component throughout your app, for instance), they do have a physical existence within the tree.
This means that you can add / modify / move / remove monkeys from the tree at runtime and place them wherever you want.
The reason why computed data now sits within the tree itself is so that components don't need to know from which kind of data, static or computed, they must draw their dependencies and so that read/select API might stay the same across the whole library.
*Example*
```js
var monkey = Baobab.monkey;
// Or if you hate similes and fancy naming
var dynamicNode = Baobab.dynamicNode;
// Declarative definition syntax
var tree = new Baobab({

@@ -501,3 +508,3 @@ user: {

surname: 'Smith',
$fullname: {
fullname: monkey({
cursors: {

@@ -510,3 +517,3 @@ name: ['user', 'name'],

}
}
})
},

@@ -518,3 +525,3 @@ data: {

],
$fromJohn: {
fromJohn: monkey({
cursors: {

@@ -528,3 +535,3 @@ messages: ['data', 'messages'],

}
}
})
}

@@ -538,3 +545,3 @@ });

surname: 'Smith',
$fullname: [
fullname: monkey(
['user', 'name'],

@@ -545,3 +552,3 @@ ['user', 'surname'],

}
]
)
},

@@ -553,3 +560,3 @@ data: {

],
$fromJohn: [
fromJohn: monkey(
['data', 'messages'],

@@ -561,3 +568,3 @@ function(messages) {

}
]
)
}

@@ -567,20 +574,38 @@ });

// You can then access or select data naturally
tree.get('user', '$fullname');
tree.get('user', 'fullname');
>>> 'John Smith'
tree.get('data', '$fromJohn');
tree.get('data', 'fromJohn');
>>> [{from: 'John', txt: 'Hey'}]
tree.get('data', '$fromJohn', 'txt');
// You can also access/select data beneath a monkey
tree.get('data', 'fromJohn', 'txt');
>>> 'Hey'
var cursor = tree.select('data', 'fromJohn', 'txt');
// Just note that computed data node is read-only and that the tree
// will throw if you try to update a path lying beyond a computed node
tree.set(['data', '$fromJohn', 'txt'], 'Yay');
tree.set(['data', 'fromJohn', 'txt'], 'Yay');
>>> Error!
// You can add / remove / modify a monkey at runtime using the same API
tree.set(['data', 'fromJack'], monkey({
cursors: {
messages: ['data', 'messages'],
function(messages) {
return messages.filter(function(m) {
return m.from === 'Jack';
});
}
}
}));
```
The computed data node will of course automatically update whenever at least one of the watched paths is updated.
*Notes*
It is not possible, at the time being, to modify facets' definition at runtime. It may however be allowed in further versions.
* The dynamic nodes will of course automatically update whenever at least one of the watched paths is updated.
* The dynamic nodes are lazy and won't actually be computed before you get them (plus they will only compute once before they need to change, so if you get the same dynamic node twice, the computation won't rerun).
* There are cases where it is clearly overkill to rely on a dynamic node. For instance, if only a single component of your app needs to access a computed version of the central state, then compute this version into the rendering logic of said component for simplicity's sake (a React component's render function for instance). Dynamic nodes are somewhat part of an optimization scheme.
* Know that the `tree/cursor.serialize` method exists would you need to retrieve data stripped of dynamic nodes from your tree.

@@ -768,2 +793,3 @@ #### Specialized getters

* **persistent** *boolean* [`true`]: should the tree be persistent. Know that disabling this option, while bringing a significant performance boost on heavy data, will make you lose the benefits of your tree's history and `O(1)` comparisons of objects.
* **pure** *boolean* [`true`]: by default, on `set` and `apply` operations, the tree will check if the given value and the target node are stricly equal. If they indeed are, the tree won't update.
* **validate** *function*: a function in charge of validating the tree whenever it updates. See below for an example of such function.

@@ -891,4 +917,4 @@ * **validationBehavior** *string* [`rollback`]: validation behavior of the tree. If `rollback`, the tree won't apply the current update and fire an `invalid` event while `notify` will only emit the event and let the tree enter the invalid state anyway.

* The strange concat-like behavior of the `push` and `unshift` method was dropped in favor of the `concat` method.
* Facets are now full-fledged computed data node sitting within the tree itself.
* The weird `$cursor` sugar has now been dropped.
* Facets are now full-fledged dynamic nodes called monkeys.
* The weird `$cursor` sugar has been dropped.
* The update specifications have been dropped.

@@ -895,0 +921,0 @@

@@ -9,7 +9,9 @@ /**

import Cursor from './cursor';
import {MonkeyDefinition, Monkey} from './monkey';
import Watcher from './watcher';
import Facet from './facet';
import type from './type';
import update from './update';
import {
import * as helpers from './helpers';
const {
arrayFrom,

@@ -24,3 +26,3 @@ coercePath,

uniqid
} from './helpers';
} = helpers;

@@ -80,2 +82,4 @@ /**

* @param {boolean} [opts.immutable] - Should the tree be immutable?
* @param {boolean} [opts.persistent] - Should the tree be persistent?
* @param {boolean} [opts.pure] - Should the tree be pure?
* @param {function} [opts.validate] - Validation function.

@@ -99,5 +103,7 @@ * @param {string} [opts.validationBehaviour] - "rollback" or "notify".

// Disabling immutability if persistence if disabled
if (!this.options.persistent)
// Disabling immutability & persistence if persistence if disabled
if (!this.options.persistent) {
this.options.immutable = false;
this.options.pure = false;
}

@@ -110,3 +116,3 @@ // Privates

this._affectedPathsIndex = {};
this._facets = {};
this._monkeys = {};
this._previousData = null;

@@ -145,40 +151,85 @@ this._data = initialData;

// Creating the computed data index for the first time
this._initializeComputedDataIndex();
// Registering the initial monkeys
this._refreshMonkeys();
}
/**
* Private method used to initialize the internal computed data index of the
* tree.
* Internal method used to refresh the tree's monkey register on every
* update.
* Note 1) For the time being, placing monkeys beneath array nodes is not
* allowed for performance reasons.
*
* @param {mixed} node - The starting node.
* @param {array} path - The starting node's path.
* @param {string} operation - The operation that lead to a refreshment.
* @return {Baobab} - The tree instance for chaining purposes.
*/
_initializeComputedDataIndex() {
_refreshMonkeys(node, path, operation) {
// Refreshing the whole tree
const walk = (data, p=[]) => {
const clean = (data, p=[]) => {
if (data instanceof Monkey) {
data.release();
update(
this._monkeys,
p,
{type: 'unset'},
{
immutable: false,
persistent: false,
pure: false
}
);
// Object iteration
// TODO: handle arrays?
return;
}
if (type.object(data)) {
let k;
for (let k in data)
clean(data[k], p.concat(k));
}
};
for (k in data) {
const walk = (data, p=[]) => {
// Creating a facet if needed
if (k[0] === '$') {
const facet = new Facet(this, p.concat(k), data[k]);
// Should we sit a monkey in the tree?
if (data instanceof MonkeyDefinition) {
const monkey = new Monkey(this, p, data);
deepMerge(
this._facets,
pathObject(p, {[k]: facet})
);
update(
this._monkeys,
p,
{type: 'set', value: monkey},
{
immutable: false,
persistent: false,
pure: false
}
else {
walk(data[k], p.concat(k));
}
}
);
return;
}
// Object iteration
if (type.object(data)) {
for (let k in data)
walk(data[k], p.concat(k));
}
};
// Walking the whole tree
return walk(this._data);
if (!arguments.length)
walk(this._data);
else {
const monkeysNode = getIn(this._monkeys, path).data;
// Is this required that we clean some already existing monkeys?
if (monkeysNode)
clean(monkeysNode, path);
// Let's walk the tree only from the updated point
if (operation !== 'unset')
walk(node, path);
}
return this;
}

@@ -265,2 +316,9 @@

// Read-only path?
const monkeyPath = type.monkeyPath(this._monkeys, solvedPath);
if (monkeyPath && solvedPath.length > monkeyPath.length)
throw makeError('Baobab.update: attempting to update a read-only path.', {
path: solvedPath
});
// We don't unset irrelevant paths

@@ -274,6 +332,4 @@ if (operation.type === 'unset' && !exists)

const hash = hashPath(solvedPath);
// Applying the operation
const {data, node} = update(
const result = update(
this._data,

@@ -285,9 +341,25 @@ solvedPath,

const {data, node} = result;
// If because of purity, the update was moot, we stop here
if (!('data' in result))
return node;
// If the operation is push, the affected path is slightly different
let affectedPath = solvedPath.concat(
operation.type === 'push' ? node.length - 1 : []
);
const hash = hashPath(affectedPath);
// Updating data and transaction
this._data = data;
this._affectedPathsIndex[hash] = true;
this._transaction.push({...operation, path: solvedPath});
this._transaction.push({...operation, path: affectedPath});
// Updating the monkeys
this._refreshMonkeys(node, solvedPath, operation.type);
// Emitting a `write` event
this.emit('write', {path: solvedPath});
this.emit('write', {path: affectedPath});

@@ -389,2 +461,4 @@ // Should we let the user commit?

this.emit('release');
delete this._data;

@@ -394,3 +468,3 @@ delete this._previousData;

delete this._affectedPathsIndex;
delete this._facets;
delete this._monkeys;

@@ -426,16 +500,36 @@ // Releasing cursors

/**
* Version
* Creating statics
*/
Object.defineProperty(Baobab, 'version', {
value: '2.0.0-rc1'
});
Baobab.monkey = function(...args) {
if (!args.length)
throw new Error('Baobab.monkey: missing definition.');
if (args.length === 1)
return new MonkeyDefinition(args[0]);
else
return new MonkeyDefinition(args);
};
Baobab.dynamicNode = Baobab.monkey;
/**
* Exposing the Cursor class
* Exposing some internals for convenience
*/
Baobab.Cursor = Cursor;
Baobab.MonkeyDefinition = MonkeyDefinition;
Baobab.Monkey = Monkey;
Baobab.type = type;
Baobab.helpers = helpers;
/**
* Version
*/
Object.defineProperty(Baobab, 'version', {
value: '2.0.0-rc2'
});
/**
* Exporting
*/
export default Baobab;

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

import Emitter from 'emmett';
import {Monkey} from './monkey';
import type from './type';

@@ -72,4 +73,4 @@ import {

// Checking whether the given will meet a facet
this._facetPath = type.facetPath(this.path);
// Checking whether the given path will meet a monkey
this._monkeyPath = type.monkeyPath(this.tree._monkeys, this.path);

@@ -79,3 +80,3 @@ if (!this._dynamicPath)

else
this.solvedPath = this._getIn(this.path).solvedPath;
this.solvedPath = getIn(this.tree._data, this.path).solvedPath;

@@ -93,3 +94,3 @@ /**

this.solvedPath = this._getIn(this.path).solvedPath;
this.solvedPath = getIn(this.tree._data, this.path).solvedPath;
};

@@ -101,2 +102,5 @@

*
* @note: probably should wrap the current solvedPath in closure to avoid
* for tricky cases where it would fail.
*
* @param {mixed} previousData - the tree's previous data.

@@ -178,18 +182,2 @@ */

/**
* 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.
*/
_getIn(path) {
return getIn(
this.tree._data,
path
);
}
/**
* Method returning the paths of the tree watched over by the cursor and that

@@ -203,4 +191,4 @@ * should be taken into account when solving a potential update.

// Checking whether we should keep track of some dependencies
const additionalPaths = this._facetPath ?
getIn(this.tree._facets, this._facetPath)
const additionalPaths = this._monkeyPath ?
getIn(this.tree._monkeys, this._monkeyPath)
.data

@@ -430,3 +418,3 @@ .relatedPaths() :

return this._getIn(this.solvedPath.concat(path));
return getIn(this.tree._data, this.solvedPath.concat(path));
}

@@ -486,3 +474,4 @@

*
* @todo: should be more performant.
* @todo: should be more performant as the cloning should happen as well as
* when dropping computed data.
*

@@ -497,18 +486,32 @@ * Arity (1):

*/
serialize() {
const data = deepClone(this.get.apply(this, arguments));
serialize(path) {
path = coercePath(path);
const dropComputedData = function(d) {
if (!type.object(d))
if (arguments.length > 1)
path = arrayFrom(arguments);
if (!type.path(path))
throw makeError('Baobab.Cursor.getters: invalid path.', {path});
if (!this.solvedPath)
return undefined;
const fullPath = this.solvedPath.concat(path);
const data = deepClone(getIn(this.tree._data, fullPath).data),
monkeys = getIn(this.tree._monkeys, fullPath).data;
const dropComputedData = (d, m) => {
if (!type.object(m) || !type.object(d))
return;
for (let k in d) {
if (k[0] === '$')
for (let k in m) {
if (m[k] instanceof Monkey)
delete d[k];
else
dropComputedData(d[k]);
dropComputedData(d[k], m[k]);
}
};
dropComputedData(data);
dropComputedData(data, monkeys);
return data;

@@ -752,9 +755,2 @@ }

// Checking we are not trying to update a read-only path
if (type.readOnlyPath(fullPath))
throw makeError(
`Baobab.Cursor.${name}: trying to update a read-only path.`,
{path: fullPath}
);
// Filing the update to the tree

@@ -761,0 +757,0 @@ return this.tree.update(

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

*/
import {Monkey, MonkeyDefinition} from './monkey';
import type from './type';

@@ -136,2 +137,3 @@

item instanceof Error ||
item instanceof MonkeyDefinition ||
('ArrayBuffer' in global && item instanceof ArrayBuffer))

@@ -168,3 +170,3 @@ return item;

if (item.hasOwnProperty(k))
o[k] = (deep && k[0] !== '$') ? cloner(true, item[k]) : item[k];
o[k] = deep ? cloner(true, item[k]) : item[k];
return o;

@@ -382,4 +384,3 @@ }

* Note 2): the first object will be mutated to allow for perf scenarios.
* Note 3): this function will take not `$.` keys into account and should only
* be used by Baobab's internal and would be unsuited in any other case.
* Note 3): this function will consider monkeys as leaves.
*

@@ -390,3 +391,3 @@ * @param {boolean} deep - Whether the merge should be deep or not.

*/
export function merger(deep, ...objects) {
function merger(deep, ...objects) {
let o = objects[0],

@@ -404,3 +405,3 @@ t,

typeof t[k] === 'object' &&
k[0] !== '$') {
!(t[k] instanceof Monkey)) {
o[k] = merger(true, o[k] || {}, t[k]);

@@ -407,0 +408,0 @@ }

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

*/
import {Monkey} from './monkey';
const type = {};

@@ -148,10 +150,12 @@

/**
* Retrieve any facet subpath in the given path or null if the path never comes
* Retrieve any monkey subpath in the given path or null if the path never comes
* across computed data.
*
* @param {mixed} path - The path to test.
* @param {mixed} data - The data to test.
* @param {array} path - The path to test.
* @return {boolean}
*/
type.facetPath = function(path) {
type.monkeyPath = function(data, path) {
let subpath = [],
c = data,
i,

@@ -163,3 +167,8 @@ l;

if (path[i][0] === '$')
if (typeof c !== 'object')
return null;
c = c[path[i]];
if (c instanceof Monkey)
return subpath;

@@ -172,19 +181,8 @@ }

/**
* Checking whether the given path is read-only.
* Note: this should of course apply to a solved path and not a dynamic one.
* Returns the type of the given monkey definition or `null` if invalid.
*
* @param {mixed} path - The path to test.
* @return {boolean}
*/
type.readOnlyPath = function(path) {
return path.some(step => step[0] === '$');
};
/**
* Returns the type of the given facet definition or `null` if invalid.
*
* @param {mixed} definition - The definition to check.
* @return {string|null}
*/
type.facetDefinition = function(definition) {
type.monkeyDefinition = function(definition) {

@@ -194,3 +192,4 @@ if (type.object(definition)) {

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

@@ -197,0 +196,0 @@ else

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

if (operationType === 'set') {
// Purity check
if (opts.pure && p[s] === value)
return {node: p[s]};
p[s] = opts.persistent ? shallowClone(value) : value;

@@ -72,2 +77,9 @@ }

/**
* Monkey
*/
else if (operationType === 'monkey') {
Object.defineProperty(p, s, {get: value, enumerable: true});
}
/**
* Apply

@@ -78,2 +90,6 @@ */

// Purity check
if (opts.pure && result === value)
return {node: p[s]};
p[s] = opts.persistent ? shallowClone(result) : result;

@@ -80,0 +96,0 @@ }

@@ -74,3 +74,3 @@ /**

if (type.dynamicPath(p))
p = getIn(this.tree._data, p, this.tree._facets).solvedPath;
p = getIn(this.tree._data, p).solvedPath;

@@ -81,7 +81,7 @@ if (!p)

// Facet path?
const facetPath = type.facetPath(p);
const monkeyPath = type.monkeyPath(this.tree._monkeys, p);
if (facetPath)
if (monkeyPath)
return cp.concat(
getIn(this.tree._facets, p).data.relatedPaths()
getIn(this.tree._monkeys, p).data.relatedPaths()
);

@@ -88,0 +88,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