js-data
Version:
Robust, framework-agnostic in-memory data store.
1,654 lines (1,501 loc) • 496 kB
JavaScript
/*!
* js-data
* @version 3.0.10 - Homepage <http://www.js-data.io/>
* @author js-data project authors
* @copyright (c) 2014-2016 js-data project authors
* @license MIT <https://github.com/js-data/js-data/blob/master/LICENSE>
*
* @overview js-data is a framework-agnostic, datastore-agnostic ORM/ODM for Node.js and the Browser.
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define('js-data', ['exports'], factory) :
(global = global || self, factory(global.JSData = {}));
}(this, (function (exports) { 'use strict';
function _typeof(obj) {
"@babel/helpers - typeof";
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function (obj) {
return typeof obj;
};
} else {
_typeof = function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
}
return _typeof(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 _toConsumableArray(arr) {
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
}
function _arrayWithoutHoles(arr) {
if (Array.isArray(arr)) return _arrayLikeToArray(arr);
}
function _iterableToArray(iter) {
if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
/**
* Utility methods used by JSData.
*
* @example
* import { utils } from 'js-data';
* console.log(utils.isString('foo')); // true
*
* @namespace utils
* @type {Object}
*/
var DOMAIN = 'utils';
var INFINITY = 1 / 0;
var MAX_INTEGER = 1.7976931348623157e308;
var BOOL_TAG = '[object Boolean]';
var DATE_TAG = '[object Date]';
var FUNC_TAG = '[object Function]';
var NUMBER_TAG = '[object Number]';
var OBJECT_TAG = '[object Object]';
var REGEXP_TAG = '[object RegExp]';
var STRING_TAG = '[object String]';
var objToString = Object.prototype.toString;
var PATH = /^(.+)\.(.+)$/;
var ERRORS = {
'400': function _() {
return "expected: ".concat(arguments[0], ", found: ").concat(arguments[2] ? arguments[1] : _typeof(arguments[1]));
},
'404': function _() {
return "".concat(arguments[0], " not found");
}
};
var toInteger = function toInteger(value) {
if (!value) {
return 0;
} // Coerce to number
value = +value;
if (value === INFINITY || value === -INFINITY) {
var sign = value < 0 ? -1 : 1;
return sign * MAX_INTEGER;
}
var remainder = value % 1;
return value === value ? remainder ? value - remainder : value : 0; // eslint-disable-line
};
var toStr = function toStr(value) {
return objToString.call(value);
};
var isPlainObject = function isPlainObject(value) {
return !!value && _typeof(value) === 'object' && value.constructor === Object;
};
var mkdirP = function mkdirP(object, path) {
if (!path) {
return object;
}
var parts = path.split('.');
parts.forEach(function (key) {
if (isPrototypePolluted(key)) return;
if (!object[key]) {
object[key] = {};
}
object = object[key];
});
return object;
};
var isPrototypePolluted = function isPrototypePolluted(key) {
return ['__proto__', 'prototype', 'constructor'].includes(key);
};
var utils = {
/**
* Reference to the Promise constructor used by JSData. Defaults to
* `window.Promise` or `global.Promise`.
*
* @example <caption>Make JSData use a different `Promise` constructor</caption>
* import Promise from 'bluebird';
* import { utils } from 'js-data';
* utils.Promise = Promise;
*
* @name utils.Promise
* @since 3.0.0
* @type {Function}
*/
Promise: Promise,
/**
* Shallow copy properties that meet the following criteria from `src` to
* `dest`:
*
* - own enumerable
* - not a function
* - does not start with "_"
*
* @method utils._
* @param {object} dest Destination object.
* @param {object} src Source object.
* @private
* @since 3.0.0
*/
_: function _(dest, src) {
utils.forOwn(src, function (value, key) {
if (key && dest[key] === undefined && !utils.isFunction(value) && key.indexOf('_') !== 0) {
dest[key] = value;
}
});
},
/**
* Recursively iterates over relations found in `opts.with`.
*
* @method utils._forRelation
* @param {object} opts Configuration options.
* @param {Relation} def Relation definition.
* @param {Function} fn Callback function.
* @param {*} [thisArg] Execution context for the callback function.
* @private
* @since 3.0.0
*/
_forRelation: function _forRelation(opts, def, fn, thisArg) {
var relationName = def.relation;
var containedName = null;
var index;
opts || (opts = {});
opts.with || (opts.with = []);
if ((index = utils._getIndex(opts.with, relationName)) >= 0) {
containedName = relationName;
} else if ((index = utils._getIndex(opts.with, def.localField)) >= 0) {
containedName = def.localField;
}
if (opts.withAll) {
fn.call(thisArg, def, {});
return;
} else if (!containedName) {
return;
}
var optsCopy = {};
utils.fillIn(optsCopy, def.getRelation());
utils.fillIn(optsCopy, opts);
optsCopy.with = opts.with.slice();
optsCopy._activeWith = optsCopy.with.splice(index, 1)[0];
optsCopy.with.forEach(function (relation, i) {
if (relation && relation.indexOf(containedName) === 0 && relation.length >= containedName.length && relation[containedName.length] === '.') {
optsCopy.with[i] = relation.substr(containedName.length + 1);
} else {
optsCopy.with[i] = '';
}
});
fn.call(thisArg, def, optsCopy);
},
/**
* Find the index of a relation in the given list
*
* @method utils._getIndex
* @param {string[]} list List to search.
* @param {string} relation Relation to find.
* @private
* @returns {number}
*/
_getIndex: function _getIndex(list, relation) {
var index = -1;
list.forEach(function (_relation, i) {
if (_relation === relation) {
index = i;
return false;
} else if (utils.isObject(_relation)) {
if (_relation.relation === relation) {
index = i;
return false;
}
}
});
return index;
},
/**
* Define hidden (non-enumerable), writable properties on `target` from the
* provided `props`.
*
* @example
* import { utils } from 'js-data';
* function Cat () {}
* utils.addHiddenPropsToTarget(Cat.prototype, {
* say () {
* console.log('meow');
* }
* });
* const cat = new Cat();
* cat.say(); // "meow"
*
* @method utils.addHiddenPropsToTarget
* @param {object} target That to which `props` should be added.
* @param {object} props Properties to be added to `target`.
* @since 3.0.0
*/
addHiddenPropsToTarget: function addHiddenPropsToTarget(target, props) {
var map = {};
Object.keys(props).forEach(function (propName) {
var descriptor = Object.getOwnPropertyDescriptor(props, propName);
descriptor.enumerable = false;
map[propName] = descriptor;
});
Object.defineProperties(target, map);
},
/**
* Return whether the two objects are deeply different.
*
* @example
* import { utils } from 'js-data';
* utils.areDifferent({}, {}); // false
* utils.areDifferent({ a: 1 }, { a: 1 }); // false
* utils.areDifferent({ foo: 'bar' }, {}); // true
*
* @method utils.areDifferent
* @param {object} a Base object.
* @param {object} b Comparison object.
* @param {object} [opts] Configuration options.
* @param {Function} [opts.equalsFn={@link utils.deepEqual}] Equality function.
* @param {array} [opts.ignore=[]] Array of strings or RegExp of fields to ignore.
* @returns {boolean} Whether the two objects are deeply different.
* @see utils.diffObjects
* @since 3.0.0
*/
areDifferent: function areDifferent(newObject, oldObject, opts) {
opts || (opts = {});
var diff = utils.diffObjects(newObject, oldObject, opts);
var diffCount = Object.keys(diff.added).length + Object.keys(diff.removed).length + Object.keys(diff.changed).length;
return diffCount > 0;
},
/**
* Verified that the given constructor is being invoked via `new`, as opposed
* to just being called like a normal function.
*
* @example
* import { utils } from 'js-data';
* function Cat () {
* utils.classCallCheck(this, Cat);
* }
* const cat = new Cat(); // this is ok
* Cat(); // this throws an error
*
* @method utils.classCallCheck
* @param {*} instance Instance that is being constructed.
* @param {Constructor} ctor Constructor function used to construct the
* instance.
* @since 3.0.0
* @throws {Error} Throws an error if the constructor is being improperly
* invoked.
*/
classCallCheck: function classCallCheck(instance, ctor) {
if (!(instance instanceof ctor)) {
throw utils.err("".concat(ctor.name))(500, 'Cannot call a class as a function');
}
},
/**
* Deep copy a value.
*
* @example
* import { utils } from 'js-data';
* const a = { foo: { bar: 'baz' } };
* const b = utils.copy(a);
* a === b; // false
* utils.areDifferent(a, b); // false
*
* @param {*} from Value to deep copy.
* @param {*} [to] Destination object for the copy operation.
* @param {*} [stackFrom] For internal use.
* @param {*} [stackTo] For internal use.
* @param {string[]|RegExp[]} [blacklist] List of strings or RegExp of
* properties to skip.
* @param {boolean} [plain] Whether to make a plain copy (don't try to use
* original prototype).
* @returns {*} Deep copy of `from`.
* @since 3.0.0
*/
copy: function copy(from, to, stackFrom, stackTo, blacklist, plain) {
if (!to) {
to = from;
if (from) {
if (utils.isArray(from)) {
to = utils.copy(from, [], stackFrom, stackTo, blacklist, plain);
} else if (utils.isDate(from)) {
to = new Date(from.getTime());
} else if (utils.isRegExp(from)) {
to = new RegExp(from.source, from.toString().match(/[^/]*$/)[0]);
to.lastIndex = from.lastIndex;
} else if (utils.isObject(from)) {
if (plain) {
to = utils.copy(from, {}, stackFrom, stackTo, blacklist, plain);
} else {
to = utils.copy(from, Object.create(Object.getPrototypeOf(from)), stackFrom, stackTo, blacklist, plain);
}
}
}
} else {
if (from === to) {
throw utils.err("".concat(DOMAIN, ".copy"))(500, 'Cannot copy! Source and destination are identical.');
}
stackFrom = stackFrom || [];
stackTo = stackTo || [];
if (utils.isObject(from)) {
var index = stackFrom.indexOf(from);
if (index !== -1) {
return stackTo[index];
}
stackFrom.push(from);
stackTo.push(to);
}
var result;
if (utils.isArray(from)) {
var i;
to.length = 0;
for (i = 0; i < from.length; i++) {
result = utils.copy(from[i], null, stackFrom, stackTo, blacklist, plain);
if (utils.isObject(from[i])) {
stackFrom.push(from[i]);
stackTo.push(result);
}
to.push(result);
}
} else {
if (utils.isArray(to)) {
to.length = 0;
} else {
utils.forOwn(to, function (value, key) {
delete to[key];
});
}
for (var key in from) {
if (Object.hasOwnProperty.call(from, key)) {
if (utils.isBlacklisted(key, blacklist)) {
continue;
}
result = utils.copy(from[key], null, stackFrom, stackTo, blacklist, plain);
if (utils.isObject(from[key])) {
stackFrom.push(from[key]);
stackTo.push(result);
}
to[key] = result;
}
}
}
}
return to;
},
/**
* Recursively shallow fill in own enumerable properties from `source` to
* `dest`.
*
* @example
* import { utils } from 'js-data';
* const a = { foo: { bar: 'baz' }, beep: 'boop' };
* const b = { beep: 'bip' };
* utils.deepFillIn(b, a);
* console.log(b); // {"foo":{"bar":"baz"},"beep":"bip"}
*
* @method utils.deepFillIn
* @param {object} dest The destination object.
* @param {object} source The source object.
* @see utils.fillIn
* @see utils.deepMixIn
* @since 3.0.0
*/
deepFillIn: function deepFillIn(dest, source) {
if (source) {
utils.forOwn(source, function (value, key) {
if (isPrototypePolluted(key)) return;
var existing = dest[key];
if (isPlainObject(value) && isPlainObject(existing)) {
utils.deepFillIn(existing, value);
} else if (!Object.hasOwnProperty.call(dest, key) || dest[key] === undefined) {
dest[key] = value;
}
});
}
return dest;
},
/**
* Recursively shallow copy enumerable properties from `source` to `dest`.
*
* @example
* import { utils } from 'js-data';
* const a = { foo: { bar: 'baz' }, beep: 'boop' };
* const b = { beep: 'bip' };
* utils.deepFillIn(b, a);
* console.log(b); // {"foo":{"bar":"baz"},"beep":"boop"}
*
* @method utils.deepMixIn
* @param {object} dest The destination object.
* @param {object} source The source object.
* @see utils.fillIn
* @see utils.deepFillIn
* @since 3.0.0
*/
deepMixIn: function deepMixIn(dest, source) {
if (source) {
for (var key in source) {
if (isPrototypePolluted(key)) continue;
var value = source[key];
var existing = dest[key];
if (isPlainObject(value) && isPlainObject(existing)) {
utils.deepMixIn(existing, value);
} else {
dest[key] = value;
}
}
}
return dest;
},
/**
* Return a diff of the base object to the comparison object.
*
* @example
* import { utils } from 'js-data';
* const oldObject = { foo: 'bar', a: 1234 };
* const newObject = { beep: 'boop', a: 5678 };
* const diff = utils.diffObjects(oldObject, newObject);
* console.log(diff.added); // {"beep":"boop"}
* console.log(diff.changed); // {"a":5678}
* console.log(diff.removed); // {"foo":undefined}
*
* @method utils.diffObjects
* @param {object} newObject Comparison object.
* @param {object} oldObject Base object.
* @param {object} [opts] Configuration options.
* @param {Function} [opts.equalsFn={@link utils.deepEqual}] Equality function.
* @param {array} [opts.ignore=[]] Array of strings or RegExp of fields to ignore.
* @returns {Object} The diff from the base object to the comparison object.
* @see utils.areDifferent
* @since 3.0.0
*/
diffObjects: function diffObjects(newObject, oldObject, opts) {
opts || (opts = {});
var equalsFn = opts.equalsFn;
var blacklist = opts.ignore;
var diff = {
added: {},
changed: {},
removed: {}
};
if (!utils.isFunction(equalsFn)) {
equalsFn = utils.deepEqual;
}
var newKeys = Object.keys(newObject).filter(function (key) {
return !utils.isBlacklisted(key, blacklist);
});
var oldKeys = Object.keys(oldObject).filter(function (key) {
return !utils.isBlacklisted(key, blacklist);
}); // Check for properties that were added or changed
newKeys.forEach(function (key) {
var oldValue = oldObject[key];
var newValue = newObject[key];
if (equalsFn(oldValue, newValue)) {
return;
}
if (oldValue === undefined) {
diff.added[key] = newValue;
} else {
diff.changed[key] = newValue;
}
}); // Check for properties that were removed
oldKeys.forEach(function (key) {
var oldValue = oldObject[key];
var newValue = newObject[key];
if (newValue === undefined && oldValue !== undefined) {
diff.removed[key] = undefined;
}
});
return diff;
},
/**
* Return whether the two values are equal according to the `==` operator.
*
* @example
* import { utils } from 'js-data';
* console.log(utils.equal(1,1)); // true
* console.log(utils.equal(1,'1')); // true
* console.log(utils.equal(93, 66)); // false
*
* @method utils.equal
* @param {*} a First value in the comparison.
* @param {*} b Second value in the comparison.
* @returns {boolean} Whether the two values are equal according to `==`.
* @since 3.0.0
*/
equal: function equal(a, b) {
return a == b; // eslint-disable-line
},
/**
* Produce a factory function for making Error objects with the provided
* metadata. Used throughout the various js-data components.
*
* @example
* import { utils } from 'js-data';
* const errorFactory = utils.err('domain', 'target');
* const error400 = errorFactory(400, 'expected type', 'actual type');
* console.log(error400); // [Error: [domain:target] expected: expected type, found: string
http://www.js-data.io/v3.0/docs/errors#400]
* @method utils.err
* @param {string} domain Namespace.
* @param {string} target Target.
* @returns {Function} Factory function.
* @since 3.0.0
*/
err: function err(domain, target) {
return function (code) {
var prefix = "[".concat(domain, ":").concat(target, "] ");
var message = ERRORS[code].apply(null, Array.prototype.slice.call(arguments, 1));
message = "".concat(prefix).concat(message, "\nhttp://www.js-data.io/v3.0/docs/errors#").concat(code);
return new Error(message);
};
},
/**
* Add eventing capabilities into the target object.
*
* @example
* import { utils } from 'js-data';
* const user = { name: 'John' };
* utils.eventify(user);
* user.on('foo', () => console.log(arguments));
* user.emit('foo', 1, 'bar'); // should log to console values (1, "bar")
*
* @method utils.eventify
* @param {object} target Target object.
* @param {Function} [getter] Custom getter for retrieving the object's event
* listeners.
* @param {Function} [setter] Custom setter for setting the object's event
* listeners.
* @since 3.0.0
*/
eventify: function eventify(target, getter, setter) {
target = target || this;
var _events = {};
if (!getter && !setter) {
getter = function getter() {
return _events;
};
setter = function setter(value) {
_events = value;
};
}
Object.defineProperties(target, {
emit: {
value: function value() {
var events = getter.call(this) || {};
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
var type = args.shift();
var listeners = events[type] || [];
var i;
for (i = 0; i < listeners.length; i++) {
listeners[i].f.apply(listeners[i].c, args);
}
listeners = events.all || [];
args.unshift(type);
for (i = 0; i < listeners.length; i++) {
listeners[i].f.apply(listeners[i].c, args);
}
}
},
off: {
value: function value(type, func) {
var events = getter.call(this);
var listeners = events[type];
if (!listeners) {
setter.call(this, {});
} else if (func) {
for (var i = 0; i < listeners.length; i++) {
if (listeners[i].f === func) {
listeners.splice(i, 1);
break;
}
}
} else {
listeners.splice(0, listeners.length);
}
}
},
on: {
value: function value(type, func, thisArg) {
if (!getter.call(this)) {
setter.call(this, {});
}
var events = getter.call(this);
events[type] = events[type] || [];
events[type].push({
c: thisArg,
f: func
});
}
}
});
},
/**
* Used for sublcassing. Invoke this method in the context of a superclass to
* to produce a subclass based on `props` and `classProps`.
*
* @example
* import { utils } from 'js-data';
* function Animal () {}
* Animal.extend = utils.extend;
* const Cat = Animal.extend({
* say () {
* console.log('meow');
* }
* });
* const cat = new Cat();
* cat instanceof Animal; // true
* cat instanceof Cat; // true
* cat.say(); // "meow"
*
* @method utils.extend
* @param {object} props Instance properties for the subclass.
* @param {object} [props.constructor] Provide a custom constructor function
* to use as the subclass.
* @param {object} props Static properties for the subclass.
* @returns {Constructor} A new subclass.
* @since 3.0.0
*/
extend: function extend(props, classProps) {
var superClass = this;
var _subClass;
props || (props = {});
classProps || (classProps = {});
if (Object.hasOwnProperty.call(props, 'constructor')) {
_subClass = props.constructor;
delete props.constructor;
} else {
_subClass = function subClass() {
utils.classCallCheck(this, _subClass);
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
superClass.apply(this, args);
};
} // Setup inheritance of instance members
_subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
configurable: true,
enumerable: false,
value: _subClass,
writable: true
}
});
var obj = Object; // Setup inheritance of static members
if (obj.setPrototypeOf) {
obj.setPrototypeOf(_subClass, superClass);
} else if (classProps.strictEs6Class) {
_subClass.__proto__ = superClass; // eslint-disable-line
} else {
utils.forOwn(superClass, function (value, key) {
_subClass[key] = value;
});
}
if (!Object.hasOwnProperty.call(_subClass, '__super__')) {
Object.defineProperty(_subClass, '__super__', {
configurable: true,
value: superClass
});
}
utils.addHiddenPropsToTarget(_subClass.prototype, props);
utils.fillIn(_subClass, classProps);
return _subClass;
},
/**
* Shallow copy own enumerable properties from `src` to `dest` that are on
* `src` but are missing from `dest.
*
* @example
* import { utils } from 'js-data';
* const a = { foo: 'bar', beep: 'boop' };
* const b = { beep: 'bip' };
* utils.fillIn(b, a);
* console.log(b); // {"foo":"bar","beep":"bip"}
*
* @method utils.fillIn
* @param {object} dest The destination object.
* @param {object} source The source object.
* @see utils.deepFillIn
* @see utils.deepMixIn
* @since 3.0.0
*/
fillIn: function fillIn(dest, src) {
utils.forOwn(src, function (value, key) {
if (!Object.hasOwnProperty.call(dest, key) || dest[key] === undefined) {
dest[key] = value;
}
});
},
/**
* Find the last index of an item in an array according to the given checker function.
*
* @example
* import { utils } from 'js-data';
*
* const john = { name: 'John', age: 20 };
* const sara = { name: 'Sara', age: 25 };
* const dan = { name: 'Dan', age: 20 };
* const users = [john, sara, dan];
*
* console.log(utils.findIndex(users, (user) => user.age === 25)); // 1
* console.log(utils.findIndex(users, (user) => user.age > 19)); // 2
* console.log(utils.findIndex(users, (user) => user.name === 'John')); // 0
* console.log(utils.findIndex(users, (user) => user.name === 'Jimmy')); // -1
*
* @method utils.findIndex
* @param {array} array The array to search.
* @param {Function} fn Checker function.
* @returns {number} Index if found or -1 if not found.
* @since 3.0.0
*/
findIndex: function findIndex(array, fn) {
var index = -1;
if (!array) {
return index;
}
array.forEach(function (record, i) {
if (fn(record)) {
index = i;
return false;
}
});
return index;
},
/**
* Recursively iterate over a {@link Mapper}'s relations according to
* `opts.with`.
*
* @method utils.forEachRelation
* @param {Mapper} mapper Mapper.
* @param {object} opts Configuration options.
* @param {Function} fn Callback function.
* @param {*} thisArg Execution context for the callback function.
* @since 3.0.0
*/
forEachRelation: function forEachRelation(mapper, opts, fn, thisArg) {
var relationList = mapper.relationList || [];
if (!relationList.length) {
return;
}
relationList.forEach(function (def) {
utils._forRelation(opts, def, fn, thisArg);
});
},
/**
* Iterate over an object's own enumerable properties.
*
* @example
* import { utils } from 'js-data';
* const a = { b: 1, c: 4 };
* let sum = 0;
* utils.forOwn(a, function (value, key) {
* sum += value;
* });
* console.log(sum); // 5
*
* @method utils.forOwn
* @param {object} object The object whose properties are to be enumerated.
* @param {Function} fn Iteration function.
* @param {object} [thisArg] Content to which to bind `fn`.
* @since 3.0.0
*/
forOwn: function forOwn(obj, fn, thisArg) {
var keys = Object.keys(obj);
var len = keys.length;
var i;
for (i = 0; i < len; i++) {
if (fn.call(thisArg, obj[keys[i]], keys[i], obj) === false) {
break;
}
}
},
/**
* Proxy for `JSON.parse`.
*
* @example
* import { utils } from 'js-data';
*
* const a = utils.fromJson('{"name" : "John"}');
* console.log(a); // { name: 'John' }
*
* @method utils.fromJson
* @param {string} json JSON to parse.
* @returns {Object} Parsed object.
* @see utils.toJson
* @since 3.0.0
*/
fromJson: function fromJson(json) {
return utils.isString(json) ? JSON.parse(json) : json;
},
/**
* Retrieve the specified property from the given object. Supports retrieving
* nested properties.
*
* @example
* import { utils } from 'js-data';
* const a = { foo: { bar: 'baz' }, beep: 'boop' };
* console.log(utils.get(a, 'beep')); // "boop"
* console.log(utils.get(a, 'foo.bar')); // "baz"
*
* @method utils.get
* @param {object} object Object from which to retrieve a property's value.
* @param {string} prop Property to retrieve.
* @returns {*} Value of the specified property.
* @see utils.set
* @since 3.0.0
*/
get: function get(object, prop) {
if (!prop) {
return;
}
var parts = prop.split('.');
var last = parts.pop();
while (prop = parts.shift()) {
// eslint-disable-line
object = object[prop];
if (object == null) {
// eslint-disable-line
return;
}
}
return object[last];
},
/**
* Return the superclass for the given instance or subclass. If an instance is
* provided, then finds the parent class of the instance's constructor.
*
* @example
* import { utils } from 'js-data';
* // using ES2015 classes
* class Foo {}
* class Bar extends Foo {}
* const barInstance = new Bar();
* let baseType = utils.getSuper(barInstance);
* console.log(Foo === baseType); // true
*
* // using Function constructor with utils.extend
* function Foo () {}
* Foo.extend = utils.extend;
* const Bar = Foo.extend();
* const barInstance = new Bar();
* let baseType = utils.getSuper(barInstance);
* console.log(Foo === baseType); // true
*
* @method utils.getSuper
* @param {Object|Function} instance Instance or constructor.
* @param {boolean} [isCtor=false] Whether `instance` is a constructor.
* @returns {Constructor} The superclass (grandparent constructor).
* @since 3.0.0
*/
getSuper: function getSuper(instance, isCtor) {
var ctor = isCtor ? instance : instance.constructor;
if (Object.hasOwnProperty.call(ctor, '__super__')) {
return ctor.__super__;
}
return Object.getPrototypeOf(ctor) || ctor.__proto__; // eslint-disable-line
},
/**
* Return the intersection of two arrays.
*
* @example
* import { utils } from 'js-data';
* const arrA = ['green', 'red', 'blue', 'red'];
* const arrB = ['green', 'yellow', 'red'];
* const intersected = utils.intersection(arrA, arrB);
*
* console.log(intersected); // ['green', 'red'])
*
* @method utils.intersection
* @param {array} array1 First array.
* @param {array} array2 Second array.
* @returns {Array} Array of elements common to both arrays.
* @since 3.0.0
*/
intersection: function intersection(array1, array2) {
if (!array1 || !array2) {
return [];
}
array1 = Array.isArray(array1) ? array1 : [array1];
array2 = Array.isArray(array2) ? array2 : [array2];
var result = [];
var item;
var i;
var len = array1.length;
for (i = 0; i < len; i++) {
item = array1[i];
if (result.indexOf(item) !== -1) {
continue;
}
if (array2.indexOf(item) !== -1) {
result.push(item);
}
}
return result;
},
/**
* Proxy for `Array.isArray`.
*
* @example
* import { utils } from 'js-data';
* const a = [1,2,3,4,5];
* const b = { foo: "bar" };
* console.log(utils.isArray(a)); // true
* console.log(utils.isArray(b)); // false
*
* @method utils.isArray
* @param {*} value The value to test.
* @returns {boolean} Whether the provided value is an array.
* @since 3.0.0
*/
isArray: Array.isArray,
/**
* Return whether `prop` is matched by any string or regular expression in
* `blacklist`.
*
* @example
* import { utils } from 'js-data';
* const blacklist = [/^\$hashKey/g, /^_/g, 'id'];
* console.log(utils.isBlacklisted("$hashKey", blacklist)); // true
* console.log(utils.isBlacklisted("id", blacklist)); // true
* console.log(utils.isBlacklisted("_myProp", blacklist)); // true
* console.log(utils.isBlacklisted("my_id", blacklist)); // false
*
* @method utils.isBlacklisted
* @param {string} prop The name of a property to check.
* @param {array} blacklist Array of strings and regular expressions.
* @returns {boolean} Whether `prop` was matched.
* @since 3.0.0
*/
isBlacklisted: function isBlacklisted(prop, blacklist) {
if (!blacklist || !blacklist.length) {
return false;
}
var matches;
for (var i = 0; i < blacklist.length; i++) {
if (toStr(blacklist[i]) === REGEXP_TAG && blacklist[i].test(prop) || blacklist[i] === prop) {
matches = prop;
return !!matches;
}
}
return !!matches;
},
/**
* Return whether the provided value is a boolean.
*
* @example
* import { utils } from 'js-data';
* const a = true;
* const b = { foo: "bar" };
* console.log(utils.isBoolean(a)); // true
* console.log(utils.isBoolean(b)); // false
*
* @method utils.isBoolean
* @param {*} value The value to test.
* @returns {boolean} Whether the provided value is a boolean.
* @since 3.0.0
*/
isBoolean: function isBoolean(value) {
return toStr(value) === BOOL_TAG;
},
/**
* Return whether the provided value is a date.
*
* @example
* import { utils } from 'js-data';
* const a = new Date();
* const b = { foo: "bar" };
* console.log(utils.isDate(a)); // true
* console.log(utils.isDate(b)); // false
*
* @method utils.isDate
* @param {*} value The value to test.
* @returns {Date} Whether the provided value is a date.
* @since 3.0.0
*/
isDate: function isDate(value) {
return value && _typeof(value) === 'object' && toStr(value) === DATE_TAG;
},
/**
* Return whether the provided value is a function.
*
* @example
* import { utils } from 'js-data';
* const a = function () { console.log('foo bar'); };
* const b = { foo: "bar" };
* console.log(utils.isFunction(a)); // true
* console.log(utils.isFunction(b)); // false
*
* @method utils.isFunction
* @param {*} value The value to test.
* @returns {boolean} Whether the provided value is a function.
* @since 3.0.0
*/
isFunction: function isFunction(value) {
return typeof value === 'function' || value && toStr(value) === FUNC_TAG;
},
/**
* Return whether the provided value is an integer.
*
* @example
* import { utils } from 'js-data';
* const a = 1;
* const b = 1.25;
* const c = '1';
* console.log(utils.isInteger(a)); // true
* console.log(utils.isInteger(b)); // false
* console.log(utils.isInteger(c)); // false
*
* @method utils.isInteger
* @param {*} value The value to test.
* @returns {boolean} Whether the provided value is an integer.
* @since 3.0.0
*/
isInteger: function isInteger(value) {
return toStr(value) === NUMBER_TAG && value == toInteger(value); // eslint-disable-line
},
/**
* Return whether the provided value is `null`.
*
* @example
* import { utils } from 'js-data';
* const a = null;
* const b = { foo: "bar" };
* console.log(utils.isNull(a)); // true
* console.log(utils.isNull(b)); // false
*
* @method utils.isNull
* @param {*} value The value to test.
* @returns {boolean} Whether the provided value is `null`.
* @since 3.0.0
*/
isNull: function isNull(value) {
return value === null;
},
/**
* Return whether the provided value is a number.
*
* @example
* import { utils } from 'js-data';
* const a = 1;
* const b = -1.25;
* const c = '1';
* console.log(utils.isNumber(a)); // true
* console.log(utils.isNumber(b)); // true
* console.log(utils.isNumber(c)); // false
*
* @method utils.isNumber
* @param {*} value The value to test.
* @returns {boolean} Whether the provided value is a number.
* @since 3.0.0
*/
isNumber: function isNumber(value) {
var type = _typeof(value);
return type === 'number' || value && type === 'object' && toStr(value) === NUMBER_TAG;
},
/**
* Return whether the provided value is an object.
*
* @example
* import { utils } from 'js-data';
* const a = { foo: "bar" };
* const b = 'foo bar';
* console.log(utils.isObject(a)); // true
* console.log(utils.isObject(b)); // false
*
* @method utils.isObject
* @param {*} value The value to test.
* @returns {boolean} Whether the provided value is an object.
* @since 3.0.0
*/
isObject: function isObject(value) {
return toStr(value) === OBJECT_TAG;
},
/**
* Return whether the provided value is a regular expression.
*
* @example
* import { utils } from 'js-data';
* const a = /^\$.+$/ig;
* const b = new RegExp('^\$.+$', 'ig');
* const c = { foo: "bar" };
* console.log(utils.isRegExp(a)); // true
* console.log(utils.isRegExp(b)); // true
* console.log(utils.isRegExp(c)); // false
*
* @method utils.isRegExp
* @param {*} value The value to test.
* @returns {boolean} Whether the provided value is a regular expression.
* @since 3.0.0
*/
isRegExp: function isRegExp(value) {
return toStr(value) === REGEXP_TAG;
},
/**
* Return whether the provided value is a string or a number.
*
* @example
* import { utils } from 'js-data';
* console.log(utils.isSorN('')); // true
* console.log(utils.isSorN(-1.65)); // true
* console.log(utils.isSorN('my string')); // true
* console.log(utils.isSorN({})); // false
* console.log(utils.isSorN([1,2,4])); // false
*
* @method utils.isSorN
* @param {*} value The value to test.
* @returns {boolean} Whether the provided value is a string or a number.
* @since 3.0.0
*/
isSorN: function isSorN(value) {
return utils.isString(value) || utils.isNumber(value);
},
/**
* Return whether the provided value is a string.
*
* @example
* import { utils } from 'js-data';
* console.log(utils.isString('')); // true
* console.log(utils.isString('my string')); // true
* console.log(utils.isString(100)); // false
* console.log(utils.isString([1,2,4])); // false
*
* @method utils.isString
* @param {*} value The value to test.
* @returns {boolean} Whether the provided value is a string.
* @since 3.0.0
*/
isString: function isString(value) {
return typeof value === 'string' || value && _typeof(value) === 'object' && toStr(value) === STRING_TAG;
},
/**
* Return whether the provided value is a `undefined`.
*
* @example
* import { utils } from 'js-data';
* const a = undefined;
* const b = { foo: "bar"};
* console.log(utils.isUndefined(a)); // true
* console.log(utils.isUndefined(b.baz)); // true
* console.log(utils.isUndefined(b)); // false
* console.log(utils.isUndefined(b.foo)); // false
*
* @method utils.isUndefined
* @param {*} value The value to test.
* @returns {boolean} Whether the provided value is a `undefined`.
* @since 3.0.0
*/
isUndefined: function isUndefined(value) {
return value === undefined;
},
/**
* Mix in logging capabilities to the target.
*
* @example
* import { utils } from 'js-data';
* const a = { foo: "bar"};
*
* // Add standard logging to an object
* utils.logify(a);
* a.log('info', 'test log info'); // output 'test log info' to console.
*
* // Toggle debug output of an object
* a.dbg('test debug output'); // does not output because debug is off.
* a.debug = true;
* a.dbg('test debug output'); // output 'test debug output' to console.
*
* @method utils.logify
* @param {*} target The target.
* @since 3.0.0
*/
logify: function logify(target) {
utils.addHiddenPropsToTarget(target, {
dbg: function dbg() {
if (utils.isFunction(this.log)) {
for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
args[_key3] = arguments[_key3];
}
this.log.apply(this, ['debug'].concat(args));
}
},
log: function log(level) {
for (var _len4 = arguments.length, args = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) {
args[_key4 - 1] = arguments[_key4];
}
if (level && !args.length) {
args.push(level);
level = 'debug';
}
if (level === 'debug' && !this.debug) {
return;
}
var prefix = "".concat(level.toUpperCase(), ": (").concat(this.name || this.constructor.name, ")");
if (utils.isFunction(console[level])) {
var _console;
(_console = console)[level].apply(_console, [prefix].concat(args));
} else {
var _console2;
(_console2 = console).log.apply(_console2, [prefix].concat(args));
}
}
});
},
/**
* Adds the given record to the provided array only if it's not already in the
* array.
*
* @example
* import { utils } from 'js-data';
* const colors = ['red', 'green', 'yellow'];
*
* console.log(colors.length); // 3
* utils.noDupeAdd(colors, 'red');
* console.log(colors.length); // 3, red already exists
*
* utils.noDupeAdd(colors, 'blue');
* console.log(colors.length); // 4, blue was added
*
* @method utils.noDupeAdd
* @param {array} array The array.
* @param {*} record The value to add.
* @param {Function} fn Callback function passed to {@link utils.findIndex}.
* @since 3.0.0
*/
noDupeAdd: function noDupeAdd(array, record, fn) {
if (!array) {
return;
}
var index = this.findIndex(array, fn);
if (index < 0) {
array.push(record);
}
},
/**
* Return a shallow copy of the provided object, minus the properties
* specified in `keys`.
*
* @example
* import { utils } from 'js-data';
* const a = { name: 'John', $hashKey: 1214910 };
*
* let b = utils.omit(a, ['$hashKey']);
* console.log(b); // { name: 'John' }
*
* @method utils.omit
* @param {object} props The object to copy.
* @param {string[]} keys Array of strings, representing properties to skip.
* @returns {Object} Shallow copy of `props`, minus `keys`.
* @since 3.0.0
*/
omit: function omit(props, keys) {
var _props = {};
utils.forOwn(props, function (value, key) {
if (keys.indexOf(key) === -1) {
_props[key] = value;
}
});
return _props;
},
/**
* Return a shallow copy of the provided object, but only include the
* properties specified in `keys`.
*
* @example
* import { utils } from 'js-data';
* const a = { name: 'John', $hashKey: 1214910 };
*
* let b = utils.pick(a, ['$hashKey']);
* console.log(b); // { $hashKey: 1214910 }
*
* @method utils.pick
* @param {object} props The object to copy.
* @param {string[]} keys Array of strings, representing properties to keep.
* @returns {Object} Shallow copy of `props`, but only including `keys`.
* @since 3.0.0
*/
pick: function pick(props, keys) {
return keys.reduce(function (map, key) {
map[key] = props[key];
return map;
}, {});
},
/**
* Return a plain copy of the given value.
*
* @example
* import { utils } from 'js-data';
* const a = { name: 'John' };
* let b = utils.plainCopy(a);
* console.log(a === b); // false
*
* @method utils.plainCopy
* @param {*} value The value to copy.
* @returns {*} Plain copy of `value`.
* @see utils.copy
* @since 3.0.0
*/
plainCopy: function plainCopy(value) {
return utils.copy(value, undefined, undefined, undefined, undefined, true);
},
/**
* Shortcut for `utils.Promise.reject(value)`.
*
* @example
* import { utils } from 'js-data';
*
* utils.reject("Testing static reject").then(function (data) {
* // not called
* }).catch(function (reason) {
* console.log(reason); // "Testing static reject"
* });
*
* @method utils.reject
* @param {*} [value] Value with which to reject the Promise.
* @returns {Promise} Promise reject with `value`.
* @see utils.Promise
* @since 3.0.0
*/
reject: function reject(value) {
return utils.Promise.reject(value);
},
/**
* Remove the last item found in array according to the given checker function.
*
* @example
* import { utils } from 'js-data';
*
* const colors = ['red', 'green', 'yellow', 'red'];
* utils.remove(colors, (color) => color === 'red');
* console.log(colors); // ['red', 'green', 'yellow']
*
* @method utils.remove
* @param {array} array The array to search.
* @param {Function} fn Checker function.
*/
remove: function remove(array, fn) {
if (!array || !array.length) {
return;
}
var index = this.findIndex(array, fn);
if (index >= 0) {
array.splice(index, 1); // todo should this be recursive?
}
},
/**
* Shortcut for `utils.Promise.resolve(value)`.
*
* @example
* import { utils } from 'js-data';
*
* utils.resolve("Testing static resolve").then(function (data) {
* console.log(data); // "Testing static resolve"
* }).catch(function (reason) {
* // not called
* });
*
* @param {*} [value] Value with which to resolve the Promise.
* @returns {Promise} Promise resolved with `value`.
* @see utils.Promise
* @since 3.0.0
*/
resolve: function resolve(value) {
return utils.Promise.resolve(value);
},
/**
* Set the value at the provided key or path.
*
* @example
* import { utils } from 'js-data';
*
* const john = {
* name: 'John',
* age: 25,
* parent: {
* name: 'John's Mom',
* age: 50
* }
* };
* // set value by key
* utils.set(john, 'id', 98);
* console.log(john.id); // 98
*
* // set value by path
* utils.set(john, 'parent.id', 20);
* console.log(john.parent.id); // 20
*
* // set value by path/value map
* utils.set(john, {
* 'id': 1098,
* 'parent': { id: 1020 },
* 'parent.age': '55'
* });
* console.log(john.id); // 1098
* console.log(john.parent.id); // 1020
* console.log(john.parent.age); // 55
*
* @method utils.set
* @param {object} object The object on which to set a property.
* @param {(string|Object)} path The key or path to the property. Can also
* pass in an object of path/value pairs, which will all be set on the target
* object.
* @param {*} [value] The value to set.
*/
set: function set(object, path, value) {
if (utils.isObject(path)) {
utils.forOwn(path, function (value, _path) {
utils.set(object, _path, value);
});
} else {
var parts = PATH.exec(path);
if (parts) {
mkdirP(object, parts[1])[parts[2]] = value;
} else {
object[path] = value;
}
}
},
/**
* Check whether the two provided objects are deeply equal.
*
* @example
* import { utils } from 'js-data';
*
* const objA = {
* name: 'John',
* id: 27,
* nested: {
* item: 'item 1',
* colors: ['red', 'green', 'blue']
* }
* };
*
* const objB = {
* name: 'John',
* id: 27,
* nested: {
* item: 'item 1',
* colors: ['red', 'green', 'blue']
* }
* };
*
* console.log(utils.deepEqual(a,b)); // true
* objB.nested.colors.add('yellow'); // make a change to a nested object's array
* console.