mutastate
Version:
Mutable state management
1,624 lines (1,300 loc) • 59.5 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('bluebird'), require('lodash.clone')) :
typeof define === 'function' && define.amd ? define(['exports', 'bluebird', 'lodash.clone'], factory) :
(global = global || self, factory(global.mutastate = {}, global.bluebird, global.clone$1));
}(this, (function (exports, bluebird, clone$1) { 'use strict';
bluebird = bluebird && Object.prototype.hasOwnProperty.call(bluebird, 'default') ? bluebird['default'] : bluebird;
clone$1 = clone$1 && Object.prototype.hasOwnProperty.call(clone$1, 'default') ? clone$1['default'] : clone$1;
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 _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
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 ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly) symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
});
keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread2(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
if (i % 2) {
ownKeys(Object(source), true).forEach(function (key) {
_defineProperty(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
}
return target;
}
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
writable: true,
configurable: true
}
});
if (superClass) _setPrototypeOf(subClass, superClass);
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
function _isNativeReflectConstruct() {
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
return true;
} catch (e) {
return false;
}
}
function _objectWithoutPropertiesLoose(source, excluded) {
if (source == null) return {};
var target = {};
var sourceKeys = Object.keys(source);
var key, i;
for (i = 0; i < sourceKeys.length; i++) {
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
target[key] = source[key];
}
return target;
}
function _objectWithoutProperties(source, excluded) {
if (source == null) return {};
var target = _objectWithoutPropertiesLoose(source, excluded);
var key, i;
if (Object.getOwnPropertySymbols) {
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
for (i = 0; i < sourceSymbolKeys.length; i++) {
key = sourceSymbolKeys[i];
if (excluded.indexOf(key) >= 0) continue;
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
target[key] = source[key];
}
}
return target;
}
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
function _possibleConstructorReturn(self, call) {
if (call && (typeof call === "object" || typeof call === "function")) {
return call;
}
return _assertThisInitialized(self);
}
function _createSuper(Derived) {
var hasNativeReflectConstruct = _isNativeReflectConstruct();
return function _createSuperInternal() {
var Super = _getPrototypeOf(Derived),
result;
if (hasNativeReflectConstruct) {
var NewTarget = _getPrototypeOf(this).constructor;
result = Reflect.construct(Super, arguments, NewTarget);
} else {
result = Super.apply(this, arguments);
}
return _possibleConstructorReturn(this, result);
};
}
function _slicedToArray(arr, i) {
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _iterableToArrayLimit(arr, i) {
if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return;
var _arr = [];
var _n = true;
var _d = false;
var _e = undefined;
try {
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);
if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"] != null) _i["return"]();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
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 _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _createForOfIteratorHelper(o, allowArrayLike) {
var it;
if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) {
if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
if (it) o = it;
var i = 0;
var F = function () {};
return {
s: F,
n: function () {
if (i >= o.length) return {
done: true
};
return {
done: false,
value: o[i++]
};
},
e: function (e) {
throw e;
},
f: F
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
var normalCompletion = true,
didErr = false,
err;
return {
s: function () {
it = o[Symbol.iterator]();
},
n: function () {
var step = it.next();
normalCompletion = step.done;
return step;
},
e: function (e) {
didErr = true;
err = e;
},
f: function () {
try {
if (!normalCompletion && it.return != null) it.return();
} finally {
if (didErr) throw err;
}
}
};
}
/**
* Objer module, interact with objects
* @module objer
*/
/**
* Set value at an object subpath
* @param {Object} object
* @param {string|array} path
* @param {*} value
*/
function set(object, path, value) {
var subObject = object;
var keys = getObjectPath(path);
if (keys.length === 0) return value; // We cannot modify the original value to be the new value no matter how hard we try
for (var keydex = 0; keydex < keys.length; keydex += 1) {
var key = keys[keydex];
if (key !== '') {
if (keydex !== keys.length - 1) {
if (subObject[key] === null || _typeof(subObject[key]) !== 'object') {
subObject[key] = {};
}
subObject = subObject[key];
} else {
subObject[key] = value;
}
}
}
return object;
}
/**
* Get array of keys in an object
* @param {Object} object
*/
function keys(object) {
var stringType = getTypeString(object);
if (stringType === 'object' || stringType === 'array') {
if (typeof Object.keys !== 'undefined') return Object.keys(object);
var _keys = [];
for (var key in object) {
if (object.hasOwnProperty(key)) {
_keys.push(key);
}
}
return _keys;
}
return [];
}
function assassinate(source, path) {
var pathArray = getObjectPath(path);
if (pathArray.length > 0) {
var parentPath = pathArray.slice(0, pathArray.length - 1);
if (has(source, parentPath) || parentPath.length === 0) {
var original = get(source, parentPath);
var originalType = getTypeString(original);
var pathKey = pathArray[pathArray.length - 1];
if (originalType === 'object') {
delete original[pathKey];
} else if (originalType === 'array' && typeof pathKey === 'number') {
original.splice(pathKey, 1);
}
}
}
return source;
}
function clone(source) {
var stringType = getTypeString(source);
if (stringType === 'object') {
var sourceKeys = keys(source);
var result = {};
for (var keydex = 0; keydex < sourceKeys.length; keydex += 1) {
result[sourceKeys[keydex]] = clone(source[sourceKeys[keydex]]);
}
return result;
} else if (stringType === 'array') {
var length = source.length;
var _result = [];
for (var dex = 0; dex < length; dex += 1) {
_result.push(clone(source[dex]));
}
return _result;
}
return source;
}
/**
* Check if an object has a value at a path
* @param {Object} object
* @param {string|array} path
*/
function has(object, path) {
var subObject = object;
var keys = getObjectPath(path);
if (keys.length === 0) return false;
for (var keydex = 0; keydex < keys.length; keydex += 1) {
var key = keys[keydex];
if (!hasRoot(subObject, key)) return false;
subObject = subObject[key];
}
return true;
}
/**
* Check if an object has a top level key, hasRoot({ a: 1 }, 'a'); is true, hasRoot({ a: { b: 1 } }, 'a.b'); is false
* @param {Object} object
* @param {string} key
*/
function hasRoot(object, key) {
if (object !== null && _typeof(object) === 'object') {
return key in object;
}
return false;
}
/**
* Retrieve value from within an object or array
* @param {Object} object
* @param {string|array} path
* @param {*} [defaultValue]
*/
function get(object, path) {
var defaultValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : undefined;
var subObject = object;
var keys = getObjectPath(path);
for (var keydex = 0; keydex < keys.length; keydex += 1) {
var key = keys[keydex];
if (key !== '') {
if (!hasRoot(subObject, key)) return defaultValue;
subObject = subObject[key];
}
}
return subObject;
}
/**
* Resolve a path to a path array 'a.b.c' returns ['a', 'b', 'c']
* @param {string|array} path
*/
function getObjectPath(path) {
var inputType = getTypeString(path);
if (inputType === 'array') return path;
if (inputType !== 'string') {
if (inputType === 'number') return [path];
return [];
}
var inBrackets = false;
var partBegin = 0;
var split = false;
var exitBrackets = false;
var pathlen = path.length;
var parts = [];
for (var dex = 0; dex < pathlen + 1; dex += 1) {
var _char = path[dex];
if (inBrackets && !exitBrackets) {
if (_char === ']') {
exitBrackets = true;
}
} else if (_char === '.') {
split = true;
} else if (_char === '[') {
split = true;
inBrackets = true;
}
if (split || dex === pathlen) {
var nextPart = path.substr(partBegin, dex - partBegin - (exitBrackets ? 1 : 0));
if (inBrackets) {
var parsed = parseInt(nextPart, 10);
if (!isNaN(parsed)) {
nextPart = parsed;
}
}
parts.push(nextPart);
partBegin = dex + 1;
split = false;
if (exitBrackets) inBrackets = false;
exitBrackets = false;
}
}
return parts;
}
/**
* If this subkey doesn't exist, initialize it to defaultValue
* @param {Object} object
* @param {string|array} path
* @param {*} defaultValue
*/
function assurePathExists(object, path) {
var defaultValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var arrayPath = getObjectPath(path);
var currentObject = object;
for (var arraydex = 0; arraydex < arrayPath.length; arraydex += 1) {
var key = arrayPath[arraydex];
if (!hasRoot(currentObject, key)) {
// TODO: Address problems where key exists already and is not an array or object
var nextKey = arraydex === arrayPath.length - 1 ? null : arrayPath[arraydex + 1];
if (nextKey === null) {
currentObject[key] = defaultValue;
} else if (getTypeString(nextKey) === 'number') {
currentObject[key] = [];
} else {
currentObject[key] = {};
}
}
currentObject = currentObject[key];
}
return currentObject;
}
/**
* Return simplified type as a string. [] returns 'array' new Date() returns 'date'
* @param {*} data
*/
function getTypeString(data) {
var stringType = _typeof(data);
if (stringType === 'object') {
if (data === null) return 'null';
var stringified = toString.apply(data);
if (stringified.length > 2 && stringified[0] === '[' && stringified[stringified.length - 1] === ']') {
var splits = stringified.substr(1, stringified.length - 2).split(' ');
if (splits.length > 1) {
return splits.slice(1).join(' ').toLowerCase();
}
}
return 'unknown';
}
if (stringType === 'number') {
if (isNaN(data)) return 'nan';
}
return stringType;
}
/**
* Check if both parameters are equal, check all nested keys of objects and arrays
* @param {*} obja
* @param {*} objb
*/
function deepEq(left, right) {
var leftType = getTypeString(left);
var rightType = getTypeString(right);
if (leftType !== rightType) return false;
if (leftType === 'nan') return true;
if (leftType === 'object') {
if (left === right) return true; // if they are the same thing, don't check children
var leftKeys = keys(left).sort(); // unsorted could be unequal
var rightKeys = keys(right).sort();
if (!deepEq(leftKeys, rightKeys)) return false;
for (var keydex = 0; keydex < leftKeys.length; keydex += 1) {
if (!deepEq(left[leftKeys[keydex]], right[leftKeys[keydex]])) return false;
}
return true;
}
if (leftType === 'array') {
if (left === right) return true; // if they are the same thing, don't check children
if (left.length !== right.length) return false;
for (var dex = 0; dex < left.length; dex += 1) {
if (!deepEq(left[dex], right[dex])) return false;
}
return true;
}
return left === right;
}
var BaseAgent = /*#__PURE__*/function () {
function BaseAgent(mutastate, onChange) {
var _this = this;
_classCallCheck(this, BaseAgent);
_defineProperty(this, "getComposedState", function (initialData, key, value) {
if (key instanceof Array && key.length === 0 || key === null) return value;
set(initialData, key, value);
return initialData;
});
_defineProperty(this, "setComposedState", function (key, value) {
_this.data = _this.getComposedState(_this.data, key, value);
});
_defineProperty(this, "translate", function (inputKey, outputKey, translationFunction) {
var _ref = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {},
_ref$killOnCleanup = _ref.killOnCleanup,
killOnCleanup = _ref$killOnCleanup === void 0 ? true : _ref$killOnCleanup,
_ref$throttleTime = _ref.throttleTime,
throttleTime = _ref$throttleTime === void 0 ? null : _ref$throttleTime;
_this.mutastate.translate(inputKey, outputKey, translationFunction, {
batch: killOnCleanup ? _this : undefined,
throttleTime: throttleTime
});
});
_defineProperty(this, "setAlias", function (key, alias) {
set(_this.aliasObject, alias, key);
set(_this.reverseAliasObject, key, alias);
});
_defineProperty(this, "clearAlias", function (key) {
var alias = get(_this.reverseAliasObject, key);
if (alias) {
assassinate(_this.reverseAliasObject, key);
assassinate(_this.aliasObject, alias);
}
});
_defineProperty(this, "clearAllAliases", function () {
_this.aliasObject = {};
_this.reverseAliasObject = {};
});
_defineProperty(this, "resolveKey", function (key) {
var keyArray = getObjectPath(key);
var firstKey = keyArray instanceof Array ? keyArray[0] : keyArray;
return has(_this.aliasObject, firstKey) ? [].concat(get(_this.aliasObject, firstKey)).concat(keyArray.slice(1)) : keyArray;
});
_defineProperty(this, "resolve", this.resolveKey);
_defineProperty(this, "listen", function (key) {
var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
alias = _ref2.alias,
transform = _ref2.transform,
_ref2$initialLoad = _ref2.initialLoad,
initialLoad = _ref2$initialLoad === void 0 ? true : _ref2$initialLoad,
defaultValue = _ref2.defaultValue;
var keyArray = getObjectPath(key);
var modifiedListener = {
alias: alias,
transform: transform,
initialLoad: initialLoad,
defaultValue: defaultValue,
callback: _this.handleChange,
batch: _this
};
_this.mutastate.listen(keyArray, modifiedListener);
if (alias) {
_this.setAlias(keyArray, alias);
}
if (initialLoad) {
_this.ignoreChange = true;
var listenData = _this.mutastate.getForListener(keyArray, modifiedListener);
_this.setComposedState(alias || keyArray, listenData.value);
if (!_this.inListenBatch && _this.onChange) {
_this.onChange(_this.data);
}
_this.ignoreChange = false;
}
return _this.data;
});
_defineProperty(this, "listenFlat", function (key) {
var _ref3 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
alias = _ref3.alias,
transform = _ref3.transform,
_ref3$initialLoad = _ref3.initialLoad,
initialLoad = _ref3$initialLoad === void 0 ? true : _ref3$initialLoad,
defaultValue = _ref3.defaultValue;
var fullKey = getObjectPath(key);
var derivedAlias = alias ? alias : fullKey[fullKey.length - 1];
return _this.listen(fullKey, {
alias: derivedAlias,
transform: transform,
initialLoad: initialLoad,
defaultValue: defaultValue
});
});
_defineProperty(this, "batchListen", function (childFunction) {
_this.inListenBatch = true;
try {
childFunction();
} finally {
if (_this.onChange) _this.onChange(_this.data);
_this.inListenBatch = false;
}
return _this.data;
});
_defineProperty(this, "multiListen", function (listeners) {
var _ref4 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref4$flat = _ref4.flat,
flat = _ref4$flat === void 0 ? true : _ref4$flat;
var listenFunc = flat ? _this.listenFlat : _this.listen;
return _this.batchListen(function () {
listeners.forEach(function (listener) {
var isString = getTypeString(listener) === 'string';
var key = isString ? listener : get(listener, 'key');
listenFunc(key, isString ? undefined : listener);
});
});
});
_defineProperty(this, "unlisten", function (key) {
var keyArray = getObjectPath(key);
var result = _this.mutastate.unlisten(keyArray, _this.handleChange);
_this.clearAlias(keyArray);
return result;
});
_defineProperty(this, "unlistenFromAll", function () {
_this.mutastate.unlistenBatch(_this);
_this.clearAllAliases();
});
_defineProperty(this, "handleChange", function (changeEvents) {
_this.ignoreChange = true;
for (var changedex = 0; changedex < changeEvents.length; changedex += 1) {
var changeEvent = changeEvents[changedex];
var alias = changeEvent.alias,
key = changeEvent.key,
value = changeEvent.value;
_this.setComposedState(alias || key, value);
}
if (_this.onChange && !_this.paused) {
_this.onChange(_this.data);
}
_this.ignoreChange = false;
});
_defineProperty(this, "get", function (key) {
return _this.mutastate.get(key);
});
_defineProperty(this, "set", function (key, value, options) {
return _this.mutastate.set(key, value, options);
});
_defineProperty(this, "delete", function (key) {
return _this.mutastate["delete"](key);
});
_defineProperty(this, "assign", function (key, value) {
return _this.mutastate.assign(key, value);
});
_defineProperty(this, "push", function (key, value, options) {
return _this.mutastate.push(key, value, options);
});
_defineProperty(this, "pop", function (key, options) {
return _this.mutastate.pop(key, options);
});
_defineProperty(this, "has", function (key) {
return _this.mutastate.has(key);
});
_defineProperty(this, "assure", function (key, defaultValue) {
return _this.mutastate.assure(key, defaultValue);
});
_defineProperty(this, "getEverything", function () {
return _this.mutastate.getEverything();
});
_defineProperty(this, "setEverything", function (data) {
return _this.mutastate.setEverything(data);
});
_defineProperty(this, "getAgentData", function () {
return _this.data;
});
_defineProperty(this, "pause", function () {
return _this.paused = true;
});
_defineProperty(this, "resume", function () {
var executeCallback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
_this.paused = false;
if (executeCallback) _this.onChange(_this.data);
});
this.mutastate = mutastate;
this.data = {};
this.onChange = onChange;
this.aliasObject = {};
this.reverseAliasObject = {};
}
_createClass(BaseAgent, [{
key: "cleanup",
value: function cleanup() {
return this.unlistenFromAll();
}
}]);
return BaseAgent;
}();
var MutastateAgent = /*#__PURE__*/function (_BaseAgent) {
_inherits(MutastateAgent, _BaseAgent);
var _super = _createSuper(MutastateAgent);
function MutastateAgent(mutastate, onChange) {
var _this;
_classCallCheck(this, MutastateAgent);
_this = _super.call(this, mutastate, onChange);
_this.mutastate = mutastate;
_this.data = {};
_this.onChange = onChange;
return _this;
}
return MutastateAgent;
}(BaseAgent);
function isBlankKey(key) {
return key === null || key instanceof Array && key.length === 0;
}
function findIndex(array, callback, context) {
for (var keydex = 0; keydex < array.length; keydex += 1) {
if (callback(array[keydex])) return keydex;
}
return -1;
} // ** COPIED FROM UNDERSCORE JS **
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time. Normally, the throttled function will run
// as much as it can, without ever going more than once per `wait` duration;
// but if you'd like to disable the execution on the leading edge, pass
// `{leading: false}`. To disable execution on the trailing edge, ditto.
function throttle(func, wait, options) {
var timeout, context, args, result;
var previous = 0;
if (!options) options = {};
var later = function later() {
previous = options.leading === false ? 0 : new Date().getTime();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function throttled() {
var now = new Date().getTime();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
throttled.cancel = function () {
clearTimeout(timeout);
previous = 0;
timeout = context = args = null;
};
return throttled;
}
function getKeyFilledObject(key, value) {
if (isBlankKey(key)) return value;
var result = {};
assurePathExists(result, key);
set(result, key, value);
return result;
}
function getPromiseFunction() {
if (typeof global !== 'undefined' && typeof global.Promise !== 'undefined') {
return global.Promise;
} else if (typeof window !== 'undefined' && typeof window.Promise !== 'undefined') {
return window.Promise;
}
return bluebird;
}
// console.log('getting', property)
// try {
// return new Proxy(target[property], handler);
// } catch (err) {
// return Reflect.get(target, property, receiver);
// }
// }
// const handleDefineCreator = ({ meta, onChange }) => (target, property, descriptor) => {
// onChange({ type: 'define', property, meta });
// return Reflect.defineProperty(target, property, descriptor);
// }
// const handleDeleteCreator = ({ meta, onChange }) => (target, property) => {
// onChange({ type: 'delete', property, meta });
// return Reflect.deleteProperty(target, property);
// }
// const handleSetCreator = ({ meta, handleDefine, onChange }) => (target, property, value, receiver) => {
// const incomingTypeString = getTypeString(value);
// if (incomingTypeString === 'object') {
// const passMeta = { ...(meta || {}), key: ((meta && meta.key) || []).concat(property) };
// console.log('creating proxy thing at', property)
// target[property] = wrapChanges(value, onChange, passMeta);
// } else {
// console.log('not proxying', property)
// target[property] = value;
// }
// handleDefine(target, property, { value });
// return true;
// }
// function wrapChanges(object, onChange, meta) {
// if (!meta) meta = { key: [] };
// // const handleGet = handleGetCreator({ meta });
// const handleDefine = handleDefineCreator({ meta, onChange });
// const handleDelete = handleDeleteCreator({ meta, onChange });
// const handleSet = handleSetCreator({ meta, onChange, handleDefine });
// const handler = {
// // get: handleGet,
// defineProperty: handleDefine,
// deleteProperty: handleDelete,
// set: handleSet,
// };
// return new Proxy(object, handler);
// };
function getHandler(onChange, keyInput) {
var key = getObjectPath(keyInput);
var handler = {
get: function get(target, property, receiver) {
var desc = Object.getOwnPropertyDescriptor(target, property);
var value = Reflect.get(target, property, receiver);
if (desc && !desc.writable && !desc.configurable) return value;
try {
return new Proxy(target[property], getHandler(onChange, key.concat(property)));
} catch (err) {
return value;
}
},
set: function set(target, property, value) {
if (!(target instanceof Array && property === 'length')) {
// ignore length changes
onChange({
type: 'set',
key: key.concat(property),
value: value
});
}
return Reflect.set(target, property, value);
},
// defineProperty(target, property, descriptor) {
// onChange({ type: 'define', key: key.concat(property) });
// return Reflect.defineProperty(target, property, descriptor);
// },
deleteProperty: function deleteProperty(target, property) {
onChange({
type: 'delete',
key: key.concat(property)
});
return Reflect.deleteProperty(target, property);
}
};
return handler;
}
var changeWrapper = (function (object, onChange) {
return new Proxy(object, getHandler(onChange, []));
});
var ProxyAgent = /*#__PURE__*/function (_BaseAgent) {
_inherits(ProxyAgent, _BaseAgent);
var _super = _createSuper(ProxyAgent);
function ProxyAgent(mutastate, onChange) {
var _this;
_classCallCheck(this, ProxyAgent);
_this = _super.call(this, mutastate, onChange);
_defineProperty(_assertThisInitialized(_this), "proxyChange", function (data) {
if (!_this.ignoreChange) {
var type = data.type,
key = data.key,
value = data.value;
if (type === 'set') {
_this.set(_this.resolveKey(key), value);
} else if (type === 'delete') {
_this["delete"](_this.resolveKey(key));
}
}
});
_defineProperty(_assertThisInitialized(_this), "getComposedState", function (initialData, key, value) {
if (key instanceof Array && key.length === 0 || key === null) return value;
set(initialData, key, value);
return initialData;
});
_defineProperty(_assertThisInitialized(_this), "setComposedState", function (key, value) {
var resultData = _this.getComposedState(_this.data, key, value);
if (resultData !== _this.data) _this.data = changeWrapper(resultData, _this.proxyChange);
});
_this.mutastate = mutastate;
_this.data = changeWrapper({}, _this.proxyChange);
_this.onChange = onChange;
return _this;
} // TODO manage push, pop, shift, unshift, splice, etc
return ProxyAgent;
}(BaseAgent);
/**
* Core mutastate class, this class stores data and informs listeners of changes
*/
var Mutastate = /*#__PURE__*/function () {
function Mutastate() {
var _this = this;
_classCallCheck(this, Mutastate);
_defineProperty(this, "getAgent", function (onChange) {
return new MutastateAgent(_this, onChange);
});
_defineProperty(this, "getProxyAgent", function (onChange) {
return new ProxyAgent(_this, onChange);
});
_defineProperty(this, "getListenersAtPath", function (key) {
var keyArray = isBlankKey(key) ? ['default'] : getObjectPath(key);
var currentListenObject = _this.listenerObject;
for (var keydex = 0; keydex < keyArray.length - 1; keydex += 1) {
// Go through all keys except the last, which is where out final request will go
var subKey = keyArray[keydex];
currentListenObject = assurePathExists(currentListenObject, ['subkeys', subKey], {});
}
var finalKey = keyArray[keyArray.length - 1];
return assurePathExists(currentListenObject, ['subkeys', finalKey, 'listeners'], []);
});
_defineProperty(this, "addChangeHook", function (listener) {
_this.removeChangeHook(listener);
_this.globalListeners.push(listener);
});
_defineProperty(this, "removeChangeHook", function (listener) {
var removed = 0;
for (var dex = _this.globalListeners.length - 1; dex >= 0; dex -= 1) {
if (_this.globalListeners[dex] === listener) {
_this.globalListeners.splice(dex, 1);
removed += 1;
}
}
return removed;
});
_defineProperty(this, "listen", function (key) {
var listener = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
callback: function callback() {},
alias: null,
batch: null,
transform: null,
defaultValue: undefined
};
var listeners = _this.getListenersAtPath(key);
var matchedListeners = listeners.reduce(function (results, existingListener, dex) {
if (existingListener.callback === listener.callback) results.push(dex);
return results;
}, []);
for (var dex = matchedListeners.length - 1; dex >= 0; dex -= 1) {
listeners.splice(matchedListeners[dex], 1);
}
listeners.push(listener);
return true;
});
_defineProperty(this, "unlisten", function (key, callback) {
var listeners = _this.getListenersAtPath(key);
var matchedListeners = listeners.reduce(function (results, existingListener, dex) {
if (existingListener.callback === callback) results.push(dex);
return results;
}, []);
for (var dex = matchedListeners.length - 1; dex >= 0; dex -= 1) {
listeners.splice(matchedListeners[dex], 1);
}
});
_defineProperty(this, "unlistenBatch", function (batch, basePath) {
var patharray = basePath || [];
var subkeypath = (basePath || []).concat('subkeys');
var subKeys = keys(get(_this.listenerObject, subkeypath));
for (var keydex = 0; keydex < subKeys.length; keydex += 1) {
_this.unlistenBatch(batch, subkeypath.concat([subKeys[keydex]]));
}
var listeners = get(_this.listenerObject, patharray.concat('listeners'));
var matchedListeners = (listeners || []).reduce(function (results, existingListener, dex) {
if (existingListener.batch === batch) results.push(dex);
return results;
}, []);
for (var dex = matchedListeners.length - 1; dex >= 0; dex -= 1) {
listeners.splice(matchedListeners[dex], 1);
}
});
_defineProperty(this, "getForListener", function (key, listener) {
var alias = listener.alias,
callback = listener.callback,
transform = listener.transform,
defaultValue = listener.defaultValue;
var keyArray = getObjectPath(key);
var value = null;
if (has(_this.data, keyArray)) {
value = get(_this.data, keyArray);
} else {
var clonedValue = clone(defaultValue);
if (defaultValue !== undefined) {
set(_this.data, keyArray, clonedValue);
_this.notifyGlobals(keyArray, clonedValue, {
defaultValue: true
});
}
value = clonedValue;
}
return {
alias: alias,
callback: callback,
key: keyArray,
value: transform ? transform(value) : value
};
});
_defineProperty(this, "getAllChildListeners", function (listenerObject) {
var currentKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
var type = arguments.length > 2 ? arguments[2] : undefined;
var result = [];
var currentListeners = get(listenerObject, 'listeners') || [];
var subkeyObject = get(listenerObject, 'subkeys') || {};
var subkeys = keys(subkeyObject);
for (var listenerdex = 0; listenerdex < currentListeners.length; listenerdex += 1) {
result.push({
listener: currentListeners[listenerdex],
key: currentKey,
type: type
});
}
for (var keydex = 0; keydex < subkeys.length; keydex += 1) {
var subkey = subkeys[keydex];
result = result.concat(_this.getAllChildListeners(subkeyObject[subkey], currentKey.concat(subkey), type));
}
return result;
});
_defineProperty(this, "getDeleteListeners", function (original, incoming, listenerObject) {
var currentKey = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];
var result = [];
var originalType = getTypeString(original);
if (originalType === 'object' || originalType === 'array') {
var incomingType = getTypeString(incoming);
if (incomingType !== originalType) {
result = result.concat(_this.getAllChildListeners(listenerObject, currentKey, 'delete')); // Send delete notification to every child of the original key
} else {
var originalKeys = keys(original);
if (has(listenerObject, 'subkeys')) {
for (var keydex = 0; keydex < originalKeys.length; keydex += 1) {
var originalKey = originalKeys[keydex];
if (has(listenerObject.subkeys, originalKey)) {
if (!has(incoming, originalKey)) {
result = result.concat(_this.getAllChildListeners(listenerObject.subkeys[originalKey], currentKey.concat(originalKey), 'delete'));
} else {
result = result.concat(_this.getDeleteListeners(get(original, originalKey), get(incoming, originalKey), listenerObject.subkeys[originalKey], currentKey.concat(originalKey)));
}
}
}
}
}
}
return result;
});
_defineProperty(this, "notifyGlobals", function (keyArray, value, meta) {
for (var dex = 0; dex < _this.globalListeners.length; dex += 1) {
var passData = {
key: keyArray,
value: value
};
if (meta) passData.meta = meta;
_this.globalListeners[dex](passData);
}
});
_defineProperty(this, "get", function (key) {
return get(_this.data, key);
});
_defineProperty(this, "set", function (key, value) {
var _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {},
_ref$notify = _ref.notify,
notify = _ref$notify === void 0 ? true : _ref$notify;
var keyArray = getObjectPath(key);
var listeners = _this.getRelevantListeners(keyArray, value); // Consider a pre-notify here
set(_this.data, key, value);
if (notify) _this.notify(listeners, keyArray, value);
});
_defineProperty(this, "delete", function (key) {
_this.set(key, undefined);
assassinate(_this.data, key);
});
_defineProperty(this, "assign", function (key, value) {
var original = get(_this.data, key);
var originalType = getTypeString(original);
var incomingType = getTypeString(value);
if (originalType === 'object' && incomingType === 'object') {
_this.set(key, Object.assign({}, original, value));
} else {
_this.set(key, value);
}
});
_defineProperty(this, "push", function (key, value) {
var _ref2 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {},
_ref2$notify = _ref2.notify,
notify = _ref2$notify === void 0 ? true : _ref2$notify;
var keyArray = getObjectPath(key);
var original = get(_this.data, keyArray);
var originalType = getTypeString(original);
if (originalType === 'array') {
var extendedKey = keyArray.concat(original.length);
var listeners = _this.getRelevantListeners(extendedKey, value); // Consider a pre-notify here
original.push(value);
if (notify) _this.notify(listeners, extendedKey, value);
return true;
} else if (originalType === 'undefined' || originalType === 'null') {
_this.set(keyArray, [value]);
return true;
}
return false;
});
_defineProperty(this, "pop", function (key) {
var _ref3 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref3$notify = _ref3.notify,
notify = _ref3$notify === void 0 ? true : _ref3$notify;
var keyArray = getObjectPath(key);
var original = get(_this.data, keyArray);
var originalType = getTypeString(original);
if (originalType === 'array' && original.length > 0) {
var extendedKey = keyArray.concat(original.length - 1);
var listeners = _this.getRelevantListeners(extendedKey, undefined); // Consider a pre-notify here
original.pop();
if (notify) _this.notify(listeners, extendedKey);
return true;
}
return false;
});
_defineProperty(this, "has", function (key) {
return has(_this.data, key);
});
_defineProperty(this, "assure", function (key, defaultValue) {
if (!_this.has(key)) _this.set(key, defaultValue);
return _this.get(key);
});
_defineProperty(this, "getEverything", function () {
return _this.data;
});
_defineProperty(this, "setEverything", function (data) {
var _ref4 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref4$noDefaults = _ref4.noDefaults,
noDefaults = _ref4$noDefaults === void 0 ? false : _ref4$noDefaults;
var defaultedData = noDefaults ? data : Object.assign(_this.getDefaults(), data || {});
var listeners = _this.getRelevantListeners([], defaultedData);
_this.data = defaultedData;
_this.notify(listeners, [], defaultedData);
});
_defineProperty(this, "getDefaults", function () {
if (!_this.listenerObject) return {};
var result = {};
var listeners = _this.getAllChildListeners(_this.listenerObject, []);
listeners.forEach(function (listener) {
if (has(listener, 'defaultValue') && listener.defaultValue !== undefined) {
set(result, listener.key, listener.defaultValue);
}
});
return result;
});
_defineProperty(this, "translate", function (inputKey, outputKey, translationFunction) {
var _ref5 = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {},
batch = _ref5.batch,
_ref5$throttleTime = _ref5.throttleTime,
throttleTime = _ref5$throttleTime === void 0 ? null : _ref5$throttleTime;
var callback = function callback(input) {
if (input.length > 0) {
var output = translationFunction(input[input.length - 1].value, get(_this.data, inputKey));
if (output instanceof Promise) {
output.then(function (result) {
return _this.set(outputKey, result);
});
} else {
_this.set(outputKey, output);
}
}
};
var shouldThrottle = getTypeString(throttleTime) === 'number' && throttleTime > 0;
_this.listen(inputKey, {
initialLoad: true,
batch: batch,
callback: shouldThrottle ? throttle(callback, throttleTime) : callback
});
});
_defineProperty(this, "replicate", function (_ref6) {
var send = _ref6.send,
primary = _ref6.primary,
ignore = _ref6.ignore,
sendInitial = _ref6.sendInitial,
_ref6$canSetEverythin = _ref6.canSetEverything,
canSetEverything = _ref6$canSetEverythin === void 0 ? true : _ref6$canSetEverythin;
var ignoreObject = {};
if (ignore) {
var _iterator = _createForOfIteratorHelper(ignore),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var key = _step.value;
set(ignoreObject, key, true);
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
}
var replicator = {
send: send,
primary: primary,
ignore: ignore,
id: ++_this.nextReplicatorId,
ignores: {},
ignoreEverything: false
};
_this.replicators.push(replicator);
var changeHook = function changeHook(data) {
if (get(replicator.ignores, data.key) === true) return false;
if (get(ignoreObject, data.key[0]) === true) return false;
if (replicator.ignoreEverything) return false;
replicator.send(data);
return true;
};
replicator.changeHook = changeHook;
_this.addChangeHook(changeHook);
var receiver = function receiver(data) {
set(replicator.ignores, data.key, true);
if (data.key.length === 0) {
if (canSetEverything) {
replicator.ignoreEverything = true;
_this.setEverything(data.value);
replicator.ignoreEverything = false;
}
} else {
_this.set(data.key, data.value);
}
assassinate(replicator.ignores, data.key);
};
if (sendInitial) {
changeHook({
key: [],
value: _this.getEverything()
});
}
return receiver;
});
_defineProperty(this, "stopReplicating", function (receiverFunction) {
var replicator = _this.replicators.find(function (replicator) {
return replicator.receiver === receiverFunction;
});
if (replicator) {
_this.removeChangeHook(replicator.changeHook);
_this.replicators = _this.replicators.filter(function (replicator) {
return replicator.id !== replicator.id;
});
}
});
_defineProperty(this, "stopAllReplication", function () {
var _iterator2 = _createForOfIteratorHelper(_this.replicators),
_step2;
try {
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
var replicator = _step2.value;
_this.stopReplicating(replicator.receiver);
}
} catch (err) {
_iterator2.e(err);
} finally {
_iterator2.f();
}
});
this.listenerObject = {
subkeys: {}
};
this.replicators = [];
this.globalListeners = [];
this.data = {};
this.promise = getPromiseFunction();
this.nextReplicatorId = 0;
}
/**
* @method
* @description
* An agent is a listener with alias and transform capabilities
* @param {function} onChange
*/
_createClass(Mutastate, [{
key: "getChangeListeners",
/**
* @method
* @description
* given a change, notify all parents of the relevant key, and for every subkey of the incoming data notify listeners
* this function does not notify any listers of removed data, getDeleteListeners fulfills that role
*/
value: