@cloudflare/redux-grim
Version:
Generator for asynchronous Redux endpoint actions and reducers
1,563 lines (1,325 loc) • 57.6 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global['redux-grim'] = {})));
}(this, (function (exports) { 'use strict';
// The actions and reducers created by makeAction and makeReducer share a common
// method for modifying actions are reducer states: 'on'.
// E.g. on('start, () => ...).
//
// 'on' callbacks are executed in the order they are added, and the first
// parameter is the object to be modified (an action for makeAction, and the
// new state for makeReducer). Each callback must return the object, or its
// modified version, as callbacks are chained, the return value becoming the
// first parameter of the next call.
//
/**
* @param target {Object} - object to which the 'on' function will be added.
* @param hooks {Object} - a object consisting of a set of initials functions.
* also defined which hooks are valid for 'on'.
* @returns target - the original target, so allow easy chaining.
*/
function addHooks(target, hooks) {
target.on = function (hook, fn) {
if (!hook in hooks) throw "invalid hook " + hook;
var previousHook = hooks[hook];
if (previousHook) {
hooks[hook] = function (first) {
for (var _len = arguments.length, rest = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
rest[_key - 1] = arguments[_key];
}
return fn.apply(undefined, [previousHook.apply(undefined, [first].concat(rest))].concat(rest));
};
} else {
hooks[hook] = fn;
}
return target;
};
}
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
var asyncToGenerator = function (fn) {
return function () {
var gen = fn.apply(this, arguments);
return new Promise(function (resolve, reject) {
function step(key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(function (value) {
step("next", value);
}, function (err) {
step("throw", err);
});
}
}
return step("next");
});
};
};
var defineProperty = function (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;
};
var _extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
var slicedToArray = function () {
function sliceIterator(arr, i) {
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"]) _i["return"]();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
return function (arr, i) {
if (Array.isArray(arr)) {
return arr;
} else if (Symbol.iterator in Object(arr)) {
return sliceIterator(arr, i);
} else {
throw new TypeError("Invalid attempt to destructure non-iterable instance");
}
};
}();
var toArray = function (arr) {
return Array.isArray(arr) ? arr : Array.from(arr);
};
var toConsumableArray = function (arr) {
if (Array.isArray(arr)) {
for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
return arr2;
} else {
return Array.from(arr);
}
};
var bodyMethods = ['put', 'patch', 'post'];
// Regex to match the contents of () brackets
var parensReg = /\(([^)]+)\)/g; //
// Regex to match the contents of [] brackets
var squareParensReg = /\[([^)]+)\]/g;
// Actions can be modified using the 'on' method.
// E.g. action.on('start', () => ..)
//
// This is a list of the possible values callback function is called with, and
// the parameters they will be passed.
// Each function must return the action, modified or otherwise.
//
// action - the action that was created by makeAction.
// _namedParams - an object mapping from parameter name in the url to the value
// that was passed to the action.
// _restArgs - array of the rest of the params that were passed to the action.
// _options - options object that was passed to makeAction
var defaultHooks = {
start: function start(action, _namedParams, _restArgs, _options) {
return action;
},
success: function success(action, _namedParams, _restArgs, _options) {
return action;
},
error: function error(action, _namedParams, _restArgs, _options) {
return action;
},
// All applies to start, success, and error actions
all: function all(action, _namedParams, _restArgs, _options) {
return action;
}
};
// Some default behaviours when creating actions, that can be overridden.
// apiFetch allows the fetch behaviour to be modified. restArgs - the parameters
// that were passed to the action after those specified by the url, are also
// passed to this function.
var defaultFunctions = {
apiFetch: function apiFetch(method, url, body) {
return (
// eslint-disable-next-line compat/compat
fetch(url, {
method: method.toUpperCase(),
body: JSON.stringify(body)
})
);
}
};
/**
* Return true if the api and action functions require an object parameter
* @param {String} method
* @param {String} templateUrl
* @returns boolean
*/
function hasBodyParam(method, templateUrl) {
return bodyMethods.includes(method) || method === 'delete' && templateUrl.includes('[');
}
/**
* Extract an array of the named parameters in the url.
*
* E.g '/zone/(zoneId)/pool/[id]' -> ['zoneId', 'body'];
*
* If
* @param {String} method
* @param {String} templateUrl
* @returns {Array}
*/
function getNamedParameters(method, templateUrl) {
var params = new Set();
var match = void 0;
while (match = parensReg.exec(templateUrl)) {
// Urls can reference the same object multiple times.
// E.g. /(zone.id)/(zone.organization.id)
// This will only add one named parameter to the array ('zone')
var str = match[1];
var periodIndex = str.indexOf('.');
params.add(periodIndex === -1 ? str : str.substr(0, str.indexOf('.')));
}
if (hasBodyParam(method, templateUrl)) {
params.add('body');
}
return params.size ? [].concat(toConsumableArray(params)) : [];
}
/**
* Return a function that evaluates the url, using a params object to populate
* the variable parts of the url.
*
* E.g. const evaluate = makeUrlEvaluator('/zone/(zoneId)/pool/[id]');
* evaluate({ zoneId: 123, body: { id: 456 }})
* -> '/zone/123/create/456';
*
* @param {String} templateUrl
* @returns {Function}
*/
function makeUrlEvaluator(templateUrl) {
var url = templateUrl
// E.g. /(foo) -> /${params.foo}
.replace(parensReg, '$${params.$1}')
// Adding empty [] to a delete url creates an action which takes a body param
.replace('[]', '')
// E.g. /[foo] -> /${params.body.foo}
.replace(squareParensReg, '$${params.body.$1}');
return new Function('params', 'return `' + url + '`;');
}
/**
* Returns a function which evaluates the tempalte url, with the action
* arguments and returns an object containing the evaluated url, an object
* mapping the named parameters to their actual values, and the rest of the
* arguments supplied to the action.
*
* @param templateUrl {String}
* @param namedParams {Array}
* @returns {function}
*/
function makeGetApiData(templateUrl, namedParams) {
var urlEvaluator = makeUrlEvaluator(templateUrl);
return function () {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
var params = namedParams.reduce(function (o, param, ii) {
o[param] = args[ii];
return o;
}, {});
return {
url: urlEvaluator(params),
params: params,
restArgs: args.slice(namedParams.length)
};
};
}
/**
* Create an asynchronous actions to access http resources.
*
* @param entityType {String} - used to create action types and match actions
* and reducers
* @param method {String} - http method (get, post, etc)
* @param templateUrl {String} - defines the url to access and the parameters
* which the action will accept. See the readme for for information.
* @param options {Object} - An object which will be passed to the various
* hook functions. By default, this can only be used to log action creation,
* or a mismatch between expected parameters of an action and what is actually
* passed, by setting options = { debug: true }
*
*/
function makeActionCreator(entityType, method, templateUrl) {
var _this = this;
var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
var namedParams = getNamedParameters(method, templateUrl);
options.debug && logActionCreation(entityType, method, templateUrl, namedParams);
var hooks = _extends({}, defaultHooks);
var functions = _extends({}, defaultFunctions);
var getApiData = makeGetApiData(templateUrl, namedParams);
var action = function action() {
for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
return function () {
var _ref = asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(dispatch) {
var _getApiData, url, params, restArgs, startAction, response, successAction, result, errorAction;
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
if (options.debug) {
validateActionParameters(entityType, method, templateUrl, args, namedParams);
}
_getApiData = getApiData.apply(undefined, args), url = _getApiData.url, params = _getApiData.params, restArgs = _getApiData.restArgs;
startAction = {
type: entityType + '.start',
meta: {
entityType: entityType,
method: method
}
};
startAction = hooks.start(startAction, params, restArgs, options);
startAction = hooks.all(startAction, params, restArgs, options);
dispatch(startAction);
_context.prev = 6;
_context.next = 9;
return functions.apiFetch.apply(functions, [method, url, params.body].concat(toConsumableArray(restArgs)));
case 9:
response = _context.sent;
successAction = {
type: entityType + '.success',
payload: response,
meta: {
entityType: entityType,
method: method
}
};
successAction = hooks.success(successAction, params, restArgs, options, response);
successAction = hooks.all(successAction, params, restArgs, options, response);
result = successAction.payload;
dispatch(successAction);
return _context.abrupt('return', result);
case 18:
_context.prev = 18;
_context.t0 = _context['catch'](6);
errorAction = {
type: entityType + '.error',
payload: _context.t0,
error: true,
meta: {
entityType: entityType,
method: method
}
};
errorAction = hooks.error(errorAction, params, restArgs, options, _context.t0);
errorAction = hooks.all(errorAction, params, restArgs, options, _context.t0);
dispatch(errorAction);
throw _context.t0;
case 25:
case 'end':
return _context.stop();
}
}
}, _callee, _this, [[6, 18]]);
}));
return function (_x2) {
return _ref.apply(this, arguments);
};
}();
};
// All these functions return the action so they can be chained.
addHooks(action, hooks);
action.apiFetch = function (fn) {
return functions.apiFetch = fn, action;
};
return action;
}
/**
* Called automatically when options = { debug: true }, to log information
* about action creation.
*/
function logActionCreation(entityType, method, url, namedParams) {
console.log('Created action ' + entityType + ', ' + url + ', ' + method + '(' + namedParams.join(', ') + ')');
}
/**
* Called automatically when options = { debug: true } to warn when there's
* mismatch between the expected parameters of an action and what's actually
* created.
*/
function validateActionParameters(entityType, method, url, args, namedParams) {
var hasBody = url.indexOf('[') > -1 || ['post', 'put', 'patch'].includes(method);
// All parameters should be strings or numbers, unless there's a body, in
// which case the last parameter is an object.
if (args.length < namedParams.length) {
console.warn('For api call ' + entityType + ', ' + method + ', ' + url + ',\n Expected parameters: ' + namedParams.join(', ') + '\n Actual parameters: ' + args.join(', ') + '\n ');
}
namedParams.forEach(function (param, ii) {
var type = _typeof(args[ii]);
if (hasBody && ii === namedParams.length - 1) {
if (type !== 'object') {
console.warn(entityType + ', ' + method + ', ' + url + ': Expected parameter ' + param + ' to be an object. Actual value: ' + args[ii] + ' ' + type);
}
} else if (type !== 'string' && type !== 'number') {
console.warn(entityType + ', ' + method + ', ' + url + ': Expected parameter ' + param + ' to be a string or number. Actual value: ' + args[ii]);
}
});
}
function createCommonjsModule(fn, module) {
return module = { exports: {} }, fn(module, module.exports), module.exports;
}
var seamlessImmutable_development = createCommonjsModule(function (module, exports) {
(function() {
function immutableInit(config) {
// https://github.com/facebook/react/blob/v15.0.1/src/isomorphic/classic/element/ReactElement.js#L21
var REACT_ELEMENT_TYPE = typeof Symbol === 'function' && Symbol.for && Symbol.for('react.element');
var REACT_ELEMENT_TYPE_FALLBACK = 0xeac7;
var globalConfig = {
use_static: false
};
if (isObject(config)) {
if (config.use_static !== undefined) {
globalConfig.use_static = Boolean(config.use_static);
}
}
function isObject(data) {
return (
typeof data === 'object' &&
!Array.isArray(data) &&
data !== null
);
}
function instantiateEmptyObject(obj) {
var prototype = Object.getPrototypeOf(obj);
if (!prototype) {
return {};
} else {
return Object.create(prototype);
}
}
function addPropertyTo(target, methodName, value) {
Object.defineProperty(target, methodName, {
enumerable: false,
configurable: false,
writable: false,
value: value
});
}
function banProperty(target, methodName) {
addPropertyTo(target, methodName, function() {
throw new ImmutableError("The " + methodName +
" method cannot be invoked on an Immutable data structure.");
});
}
var immutabilityTag = "__immutable_invariants_hold";
function addImmutabilityTag(target) {
addPropertyTo(target, immutabilityTag, true);
}
function isImmutable(target) {
if (typeof target === "object") {
return target === null || Boolean(
Object.getOwnPropertyDescriptor(target, immutabilityTag)
);
} else {
// In JavaScript, only objects are even potentially mutable.
// strings, numbers, null, and undefined are all naturally immutable.
return true;
}
}
function isEqual(a, b) {
// Avoid false positives due to (NaN !== NaN) evaluating to true
return (a === b || (a !== a && b !== b));
}
function isMergableObject(target) {
return target !== null && typeof target === "object" && !(Array.isArray(target)) && !(target instanceof Date);
}
var mutatingObjectMethods = [
"setPrototypeOf"
];
var nonMutatingObjectMethods = [
"keys"
];
var mutatingArrayMethods = mutatingObjectMethods.concat([
"push", "pop", "sort", "splice", "shift", "unshift", "reverse"
]);
var nonMutatingArrayMethods = nonMutatingObjectMethods.concat([
"map", "filter", "slice", "concat", "reduce", "reduceRight"
]);
var mutatingDateMethods = mutatingObjectMethods.concat([
"setDate", "setFullYear", "setHours", "setMilliseconds", "setMinutes", "setMonth", "setSeconds",
"setTime", "setUTCDate", "setUTCFullYear", "setUTCHours", "setUTCMilliseconds", "setUTCMinutes",
"setUTCMonth", "setUTCSeconds", "setYear"
]);
function ImmutableError(message) {
this.name = 'MyError';
this.message = message;
this.stack = (new Error()).stack;
}
ImmutableError.prototype = new Error();
ImmutableError.prototype.constructor = Error;
function makeImmutable(obj, bannedMethods) {
// Tag it so we can quickly tell it's immutable later.
addImmutabilityTag(obj);
{
// Make all mutating methods throw exceptions.
for (var index in bannedMethods) {
if (bannedMethods.hasOwnProperty(index)) {
banProperty(obj, bannedMethods[index]);
}
}
// Freeze it and return it.
Object.freeze(obj);
}
return obj;
}
function makeMethodReturnImmutable(obj, methodName) {
var currentMethod = obj[methodName];
addPropertyTo(obj, methodName, function() {
return Immutable(currentMethod.apply(obj, arguments));
});
}
function arraySet(idx, value, config) {
var deep = config && config.deep;
if (idx in this) {
if (deep && this[idx] !== value && isMergableObject(value) && isMergableObject(this[idx])) {
value = Immutable.merge(this[idx], value, {deep: true, mode: 'replace'});
}
if (isEqual(this[idx], value)) {
return this;
}
}
var mutable = asMutableArray.call(this);
mutable[idx] = Immutable(value);
return makeImmutableArray(mutable);
}
var immutableEmptyArray = Immutable([]);
function arraySetIn(pth, value, config) {
var head = pth[0];
if (pth.length === 1) {
return arraySet.call(this, head, value, config);
} else {
var tail = pth.slice(1);
var thisHead = this[head];
var newValue;
if (typeof(thisHead) === "object" && thisHead !== null) {
// Might (validly) be object or array
newValue = Immutable.setIn(thisHead, tail, value);
} else {
var nextHead = tail[0];
// If the next path part is a number, then we are setting into an array, else an object.
if (nextHead !== '' && isFinite(nextHead)) {
newValue = arraySetIn.call(immutableEmptyArray, tail, value);
} else {
newValue = objectSetIn.call(immutableEmptyObject, tail, value);
}
}
if (head in this && thisHead === newValue) {
return this;
}
var mutable = asMutableArray.call(this);
mutable[head] = newValue;
return makeImmutableArray(mutable);
}
}
function makeImmutableArray(array) {
// Don't change their implementations, but wrap these functions to make sure
// they always return an immutable value.
for (var index in nonMutatingArrayMethods) {
if (nonMutatingArrayMethods.hasOwnProperty(index)) {
var methodName = nonMutatingArrayMethods[index];
makeMethodReturnImmutable(array, methodName);
}
}
if (!globalConfig.use_static) {
addPropertyTo(array, "flatMap", flatMap);
addPropertyTo(array, "asObject", asObject);
addPropertyTo(array, "asMutable", asMutableArray);
addPropertyTo(array, "set", arraySet);
addPropertyTo(array, "setIn", arraySetIn);
addPropertyTo(array, "update", update);
addPropertyTo(array, "updateIn", updateIn);
addPropertyTo(array, "getIn", getIn);
}
for(var i = 0, length = array.length; i < length; i++) {
array[i] = Immutable(array[i]);
}
return makeImmutable(array, mutatingArrayMethods);
}
function makeImmutableDate(date) {
if (!globalConfig.use_static) {
addPropertyTo(date, "asMutable", asMutableDate);
}
return makeImmutable(date, mutatingDateMethods);
}
function asMutableDate() {
return new Date(this.getTime());
}
/**
* Effectively performs a map() over the elements in the array, using the
* provided iterator, except that whenever the iterator returns an array, that
* array's elements are added to the final result instead of the array itself.
*
* @param {function} iterator - The iterator function that will be invoked on each element in the array. It will receive three arguments: the current value, the current index, and the current object.
*/
function flatMap(iterator) {
// Calling .flatMap() with no arguments is a no-op. Don't bother cloning.
if (arguments.length === 0) {
return this;
}
var result = [],
length = this.length,
index;
for (index = 0; index < length; index++) {
var iteratorResult = iterator(this[index], index, this);
if (Array.isArray(iteratorResult)) {
// Concatenate Array results into the return value we're building up.
result.push.apply(result, iteratorResult);
} else {
// Handle non-Array results the same way map() does.
result.push(iteratorResult);
}
}
return makeImmutableArray(result);
}
/**
* Returns an Immutable copy of the object without the given keys included.
*
* @param {array} keysToRemove - A list of strings representing the keys to exclude in the return value. Instead of providing a single array, this method can also be called by passing multiple strings as separate arguments.
*/
function without(remove) {
// Calling .without() with no arguments is a no-op. Don't bother cloning.
if (typeof remove === "undefined" && arguments.length === 0) {
return this;
}
if (typeof remove !== "function") {
// If we weren't given an array, use the arguments list.
var keysToRemoveArray = (Array.isArray(remove)) ?
remove.slice() : Array.prototype.slice.call(arguments);
// Convert numeric keys to strings since that's how they'll
// come from the enumeration of the object.
keysToRemoveArray.forEach(function(el, idx, arr) {
if(typeof(el) === "number") {
arr[idx] = el.toString();
}
});
remove = function(val, key) {
return keysToRemoveArray.indexOf(key) !== -1;
};
}
var result = instantiateEmptyObject(this);
for (var key in this) {
if (this.hasOwnProperty(key) && remove(this[key], key) === false) {
result[key] = this[key];
}
}
return makeImmutableObject(result);
}
function asMutableArray(opts) {
var result = [], i, length;
if(opts && opts.deep) {
for(i = 0, length = this.length; i < length; i++) {
result.push(asDeepMutable(this[i]));
}
} else {
for(i = 0, length = this.length; i < length; i++) {
result.push(this[i]);
}
}
return result;
}
/**
* Effectively performs a [map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) over the elements in the array, expecting that the iterator function
* will return an array of two elements - the first representing a key, the other
* a value. Then returns an Immutable Object constructed of those keys and values.
*
* @param {function} iterator - A function which should return an array of two elements - the first representing the desired key, the other the desired value.
*/
function asObject(iterator) {
// If no iterator was provided, assume the identity function
// (suggesting this array is already a list of key/value pairs.)
if (typeof iterator !== "function") {
iterator = function(value) { return value; };
}
var result = {},
length = this.length,
index;
for (index = 0; index < length; index++) {
var pair = iterator(this[index], index, this),
key = pair[0],
value = pair[1];
result[key] = value;
}
return makeImmutableObject(result);
}
function asDeepMutable(obj) {
if (
(!obj) ||
(typeof obj !== 'object') ||
(!Object.getOwnPropertyDescriptor(obj, immutabilityTag)) ||
(obj instanceof Date)
) { return obj; }
return Immutable.asMutable(obj, {deep: true});
}
function quickCopy(src, dest) {
for (var key in src) {
if (Object.getOwnPropertyDescriptor(src, key)) {
dest[key] = src[key];
}
}
return dest;
}
/**
* Returns an Immutable Object containing the properties and values of both
* this object and the provided object, prioritizing the provided object's
* values whenever the same key is present in both objects.
*
* @param {object} other - The other object to merge. Multiple objects can be passed as an array. In such a case, the later an object appears in that list, the higher its priority.
* @param {object} config - Optional config object that contains settings. Supported settings are: {deep: true} for deep merge and {merger: mergerFunc} where mergerFunc is a function
* that takes a property from both objects. If anything is returned it overrides the normal merge behaviour.
*/
function merge(other, config) {
// Calling .merge() with no arguments is a no-op. Don't bother cloning.
if (arguments.length === 0) {
return this;
}
if (other === null || (typeof other !== "object")) {
throw new TypeError("Immutable#merge can only be invoked with objects or arrays, not " + JSON.stringify(other));
}
var receivedArray = (Array.isArray(other)),
deep = config && config.deep,
mode = config && config.mode || 'merge',
merger = config && config.merger,
result;
// Use the given key to extract a value from the given object, then place
// that value in the result object under the same key. If that resulted
// in a change from this object's value at that key, set anyChanges = true.
function addToResult(currentObj, otherObj, key) {
var immutableValue = Immutable(otherObj[key]);
var mergerResult = merger && merger(currentObj[key], immutableValue, config);
var currentValue = currentObj[key];
if ((result !== undefined) ||
(mergerResult !== undefined) ||
(!currentObj.hasOwnProperty(key)) ||
!isEqual(immutableValue, currentValue)) {
var newValue;
if (mergerResult) {
newValue = mergerResult;
} else if (deep && isMergableObject(currentValue) && isMergableObject(immutableValue)) {
newValue = Immutable.merge(currentValue, immutableValue, config);
} else {
newValue = immutableValue;
}
if (!isEqual(currentValue, newValue) || !currentObj.hasOwnProperty(key)) {
if (result === undefined) {
// Make a shallow clone of the current object.
result = quickCopy(currentObj, instantiateEmptyObject(currentObj));
}
result[key] = newValue;
}
}
}
function clearDroppedKeys(currentObj, otherObj) {
for (var key in currentObj) {
if (!otherObj.hasOwnProperty(key)) {
if (result === undefined) {
// Make a shallow clone of the current object.
result = quickCopy(currentObj, instantiateEmptyObject(currentObj));
}
delete result[key];
}
}
}
var key;
// Achieve prioritization by overriding previous values that get in the way.
if (!receivedArray) {
// The most common use case: just merge one object into the existing one.
for (key in other) {
if (Object.getOwnPropertyDescriptor(other, key)) {
addToResult(this, other, key);
}
}
if (mode === 'replace') {
clearDroppedKeys(this, other);
}
} else {
// We also accept an Array
for (var index = 0, length = other.length; index < length; index++) {
var otherFromArray = other[index];
for (key in otherFromArray) {
if (otherFromArray.hasOwnProperty(key)) {
addToResult(result !== undefined ? result : this, otherFromArray, key);
}
}
}
}
if (result === undefined) {
return this;
} else {
return makeImmutableObject(result);
}
}
function objectReplace(value, config) {
var deep = config && config.deep;
// Calling .replace() with no arguments is a no-op. Don't bother cloning.
if (arguments.length === 0) {
return this;
}
if (value === null || typeof value !== "object") {
throw new TypeError("Immutable#replace can only be invoked with objects or arrays, not " + JSON.stringify(value));
}
return Immutable.merge(this, value, {deep: deep, mode: 'replace'});
}
var immutableEmptyObject = Immutable({});
function objectSetIn(path, value, config) {
if (!(Array.isArray(path)) || path.length === 0) {
throw new TypeError("The first argument to Immutable#setIn must be an array containing at least one \"key\" string.");
}
var head = path[0];
if (path.length === 1) {
return objectSet.call(this, head, value, config);
}
var tail = path.slice(1);
var newValue;
var thisHead = this[head];
if (this.hasOwnProperty(head) && typeof(thisHead) === "object" && thisHead !== null) {
// Might (validly) be object or array
newValue = Immutable.setIn(thisHead, tail, value);
} else {
newValue = objectSetIn.call(immutableEmptyObject, tail, value);
}
if (this.hasOwnProperty(head) && thisHead === newValue) {
return this;
}
var mutable = quickCopy(this, instantiateEmptyObject(this));
mutable[head] = newValue;
return makeImmutableObject(mutable);
}
function objectSet(property, value, config) {
var deep = config && config.deep;
if (this.hasOwnProperty(property)) {
if (deep && this[property] !== value && isMergableObject(value) && isMergableObject(this[property])) {
value = Immutable.merge(this[property], value, {deep: true, mode: 'replace'});
}
if (isEqual(this[property], value)) {
return this;
}
}
var mutable = quickCopy(this, instantiateEmptyObject(this));
mutable[property] = Immutable(value);
return makeImmutableObject(mutable);
}
function update(property, updater) {
var restArgs = Array.prototype.slice.call(arguments, 2);
var initialVal = this[property];
return Immutable.set(this, property, updater.apply(initialVal, [initialVal].concat(restArgs)));
}
function getInPath(obj, path) {
/*jshint eqnull:true */
for (var i = 0, l = path.length; obj != null && i < l; i++) {
obj = obj[path[i]];
}
return (i && i == l) ? obj : undefined;
}
function updateIn(path, updater) {
var restArgs = Array.prototype.slice.call(arguments, 2);
var initialVal = getInPath(this, path);
return Immutable.setIn(this, path, updater.apply(initialVal, [initialVal].concat(restArgs)));
}
function getIn(path, defaultValue) {
var value = getInPath(this, path);
return value === undefined ? defaultValue : value;
}
function asMutableObject(opts) {
var result = instantiateEmptyObject(this), key;
if(opts && opts.deep) {
for (key in this) {
if (this.hasOwnProperty(key)) {
result[key] = asDeepMutable(this[key]);
}
}
} else {
for (key in this) {
if (this.hasOwnProperty(key)) {
result[key] = this[key];
}
}
}
return result;
}
// Creates plain object to be used for cloning
function instantiatePlainObject() {
return {};
}
// Finalizes an object with immutable methods, freezes it, and returns it.
function makeImmutableObject(obj) {
if (!globalConfig.use_static) {
addPropertyTo(obj, "merge", merge);
addPropertyTo(obj, "replace", objectReplace);
addPropertyTo(obj, "without", without);
addPropertyTo(obj, "asMutable", asMutableObject);
addPropertyTo(obj, "set", objectSet);
addPropertyTo(obj, "setIn", objectSetIn);
addPropertyTo(obj, "update", update);
addPropertyTo(obj, "updateIn", updateIn);
addPropertyTo(obj, "getIn", getIn);
}
return makeImmutable(obj, mutatingObjectMethods);
}
// Returns true if object is a valid react element
// https://github.com/facebook/react/blob/v15.0.1/src/isomorphic/classic/element/ReactElement.js#L326
function isReactElement(obj) {
return typeof obj === 'object' &&
obj !== null &&
(obj.$$typeof === REACT_ELEMENT_TYPE_FALLBACK || obj.$$typeof === REACT_ELEMENT_TYPE);
}
function isFileObject(obj) {
return typeof File !== 'undefined' &&
obj instanceof File;
}
function isBlobObject(obj) {
return typeof Blob !== 'undefined' &&
obj instanceof Blob;
}
function isPromise(obj) {
return typeof obj === 'object' &&
typeof obj.then === 'function';
}
function isError(obj) {
return obj instanceof Error;
}
function Immutable(obj, options, stackRemaining) {
if (isImmutable(obj) || isReactElement(obj) || isFileObject(obj) || isBlobObject(obj) || isError(obj)) {
return obj;
} else if (isPromise(obj)) {
return obj.then(Immutable);
} else if (Array.isArray(obj)) {
return makeImmutableArray(obj.slice());
} else if (obj instanceof Date) {
return makeImmutableDate(new Date(obj.getTime()));
} else {
// Don't freeze the object we were given; make a clone and use that.
var prototype = options && options.prototype;
var instantiateEmptyObject =
(!prototype || prototype === Object.prototype) ?
instantiatePlainObject : (function() { return Object.create(prototype); });
var clone = instantiateEmptyObject();
{
/*jshint eqnull:true */
if (stackRemaining == null) {
stackRemaining = 64;
}
if (stackRemaining <= 0) {
throw new ImmutableError("Attempt to construct Immutable from a deeply nested object was detected." +
" Have you tried to wrap an object with circular references (e.g. React element)?" +
" See https://github.com/rtfeldman/seamless-immutable/wiki/Deeply-nested-object-was-detected for details.");
}
stackRemaining -= 1;
}
for (var key in obj) {
if (Object.getOwnPropertyDescriptor(obj, key)) {
clone[key] = Immutable(obj[key], undefined, stackRemaining);
}
}
return makeImmutableObject(clone);
}
}
// Wrapper to allow the use of object methods as static methods of Immutable.
function toStatic(fn) {
function staticWrapper() {
var args = [].slice.call(arguments);
var self = args.shift();
return fn.apply(self, args);
}
return staticWrapper;
}
// Wrapper to allow the use of object methods as static methods of Immutable.
// with the additional condition of choosing which function to call depending
// if argument is an array or an object.
function toStaticObjectOrArray(fnObject, fnArray) {
function staticWrapper() {
var args = [].slice.call(arguments);
var self = args.shift();
if (Array.isArray(self)) {
return fnArray.apply(self, args);
} else {
return fnObject.apply(self, args);
}
}
return staticWrapper;
}
// Wrapper to allow the use of object methods as static methods of Immutable.
// with the additional condition of choosing which function to call depending
// if argument is an array or an object or a date.
function toStaticObjectOrDateOrArray(fnObject, fnArray, fnDate) {
function staticWrapper() {
var args = [].slice.call(arguments);
var self = args.shift();
if (Array.isArray(self)) {
return fnArray.apply(self, args);
} else if (self instanceof Date) {
return fnDate.apply(self, args);
} else {
return fnObject.apply(self, args);
}
}
return staticWrapper;
}
// Export the library
Immutable.from = Immutable;
Immutable.isImmutable = isImmutable;
Immutable.ImmutableError = ImmutableError;
Immutable.merge = toStatic(merge);
Immutable.replace = toStatic(objectReplace);
Immutable.without = toStatic(without);
Immutable.asMutable = toStaticObjectOrDateOrArray(asMutableObject, asMutableArray, asMutableDate);
Immutable.set = toStaticObjectOrArray(objectSet, arraySet);
Immutable.setIn = toStaticObjectOrArray(objectSetIn, arraySetIn);
Immutable.update = toStatic(update);
Immutable.updateIn = toStatic(updateIn);
Immutable.getIn = toStatic(getIn);
Immutable.flatMap = toStatic(flatMap);
Immutable.asObject = toStatic(asObject);
if (!globalConfig.use_static) {
Immutable.static = immutableInit({
use_static: true
});
}
Object.freeze(Immutable);
return Immutable;
}
var Immutable = immutableInit();
/* istanbul ignore if */
if (typeof undefined === 'function' && undefined.amd) {
undefined(function() {
return Immutable;
});
} else {
module.exports = Immutable;
}
})();
});
var seamlessImmutable_development_1 = seamlessImmutable_development.static;
var seamlessImmutable_development_2 = seamlessImmutable_development.Immutable;
var defaultState = seamlessImmutable_development_1.from({
data: undefined,
error: null,
isRequesting: false,
isErrored: false
});
var defaultErrorKey = 'error';
function makeReducer(entityType, options) {
var hooks = {
start: function start(nextState, _prevState, _action, _options) {
return nextState;
},
success: function success(nextState, _state, _action, _options) {
return nextState;
},
error: function error(nextState, _state, _action, _options) {
return nextState;
},
all: function all(nextState, _state, _action, _options) {
return nextState;
},
default: function _default(state, _action, _options) {
return state;
}
};
var errorKey = options && options.errorKey || defaultErrorKey;
var initialState = errorKey === defaultErrorKey ? defaultState : initialState = seamlessImmutable_development_1.from(_extends({}, seamlessImmutable_development_1.without(defaultState, 'error'), defineProperty({}, errorKey, null)));
var reducer = function reducer() {
var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialState;
var action = arguments[1];
var nextState = void 0;
switch (action.type) {
case entityType + '.start':
nextState = _extends({}, state, defineProperty({
isRequesting: true,
isErrored: false
}, errorKey, null));
nextState = hooks.start(nextState, state, action, options);
nextState = hooks.all(nextState, state, action, options);
return seamlessImmutable_development_1.from(nextState);
case entityType + '.success':
nextState = _extends({}, state, {
data: action.payload,
isRequesting: false,
isErrored: false
});
nextState = hooks.success(nextState, state, action, options);
nextState = hooks.all(nextState, state, action, options);
return seamlessImmutable_development_1.from(nextState);
case entityType + '.error':
nextState = _extends({}, state, defineProperty({
isRequesting: false,
isErrored: true
}, errorKey, action.payload));
nextState = hooks.error(nextState, state, action, options);
nextState = hooks.all(nextState, state, action, options);
return seamlessImmutable_development_1.from(nextState);
case entityType + '.set':
// Restrict the change to the data property
var path = ['data'];
if (action.meta.path) {
path = path.concat(action.meta.path);
}
return seamlessImmutable_development_1.setIn(state, path, action.payload);
case 'grim.reset':
var _action$payload = action.payload,
includes = _action$payload.includes,
excludes = _action$payload.excludes;
if (includes) {
if (includes.includes(entityType)) {
return initialState;
}
} else if (!excludes || !excludes.includes(entityType)) {
return initialState;
}
default:
nextState = hooks.default ? hooks.default(state, action, options) : nextState;
return seamlessImmutable_development_1.from(nextState);
}
};
addHooks(reducer, hooks);
reducer.modifyInitialState = function (fn) {
return initialState = fn(initialState), reducer;
};
return reducer;
}
function setAction(entityType, payload, path) {
return {
type: entityType + '.set',
payload: payload,
meta: {
entityType: entityType,
path: path
}
};
}
function resetAction() {
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
includes = _ref.includes,
excludes = _ref.excludes;
return {
type: 'grim.reset',
payload: {
excludes: excludes,
includes: includes
}
};
}
var defaultId = 'id';
var idProp = 'idProp';
var targetProp = 'to';
var propertiesProp = 'nestedProps';
// If the rule has an alias, chain through all the rules to find the base rule.
function resolveRule(rules, type) {
var rule = rules[type];
var entityType = rule && type;
while (rule && rule[targetProp]) {
if (rule[idProp] || rule[propertiesProp]) {
throw '\'' + targetProp + '\' aliases cannot be used with \'' + idProp + '\' and \'' + propertiesProp + '\' properties';
}
entityType = rule[targetProp];
rule = rules[rule[targetProp]];
}
return { entityType: entityType, rule: rule };
}
/**
* @param {Object} rules - object specifying how entities are
* normalized
* @param {id | Array[id] | object} id - Item or items to be denormalised
* @oaram {String} entityType - key indicating the type of the normalized id/ids
* @param {Object} state - The part of the state tree mapping from
* entityType => id => to the entity data
*
* @return Denormalized object, or the original id parameter unchanged.
*/
function denormalize(rules, entityType, id, state) {
if (!id) return id;
var _resolveRule = resolveRule(rules, entityType),
rule = _resolveRule.rule,
type = _resolveRule.entityType;
if (Array.isArray(id)) {
return id.map(function (id) {
return denormalize(rules, type, id, state);
});
}
// Denormalize an object - treat it as a map and denormalize the values
if ((typeof id === 'undefined' ? 'undefined' : _typeof(id)) === 'object') {
return Object.entries(id).reduce(function (obj, _ref) {
var _ref2 = slicedToArray(_ref, 2),
prop = _ref2[0],
id = _ref2[1];
obj[prop] = denormalize(rules, type, id, state);
return obj;
}, {});
}
var stateEntity = state[type] && state[type][id];
// The entity data may not exist in the state (it may be fetched by a separate
// endpoint) in which case just return the id.
if (!stateEntity) return id;
var entity = _extends({}, stateEntity);
// Denormalize any child properties specified by the rule
if (rule[propertiesProp]) {
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = Object.entries(rule[propertiesProp])[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var _ref3 = _step.value;
var _ref4 = slicedToArray(_ref3, 2);
var prop = _ref4[0];
var _type = _ref4[1];
entity[prop] = denormalize(rules, _type, entity[prop], state);
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
}
return entity;
}
var _configSchema;
// Very simple schema parsing, using typeof types.
// propName: 'string' - propName must be defined and of type 'string'.
// propName: 'number?' - propName may be undefined or have type number
// propName: 'string{}' - propName must be defined, and is an object, every
// property of which must be a 'string'
// propName: 'number{}?' - propName may be undefined or must be an object
// every property of which must be a 'number'
var configSchema = (_configSchema = {
entityType: 'string'
}, defineProperty(_configSchema, idProp, 'string?'), defineProperty(_configSchema, targetProp, 'string?'), defineProperty(_configSchema, propertiesProp, 'string{}?'), _configSchema);
// Used to split the schema descriptions.
// E.g. 'abc{}?'.split(/(\{\})?(\??)$/) -> ["abc", "{}", "?", ""];
var schemaRE = /(\{\})?(\??)$/;
// Verifies that every member of an array matches the specified schema, see
// above.
var verifySchema = function verifySchema(arr, schema) {
return arr.every(function (obj) {
var keys = new Set([].concat(toConsumableArray(Object.keys(configSchema)), toConsumableArray(Object.keys(obj))));
return Array.from(keys).every(function (propName) {
var schemaValue = schema[propName];
if (schemaValue === undefined) return;
var _schemaValue$split = schemaValue.split(schemaRE),
_schemaValue$split2 = toArray(_schemaValue$split),
schemaType = _schemaValue$split2[0],
rest = _schemaValue$split2.slice(1);
var isOptional = rest.indexOf('?') > -1;
var isObject = rest.indexOf('{}') > -1;
var objValue = obj[propName];
var objType = typeof objValue === 'undefined' ? 'undefined' : _typeof(objValue);
if (isOptional && objValue === undefined) return true;
if (isObject) {
return objType === 'object' && Object.values(objValue).every(function (val) {
return (typeof val === 'undefined' ? 'undefined' : _typeof(val)) === schemaType;
});
}
return objValue !== undefined && (typeof objType === 'undefined' ? 'undefined' : _typeof(objType)) === schemaType;
});
});
};
var cachedRules = void 0;
var previousRules = void 0;
var previousLength = void 0;
function processRules(rules) {
if (cachedRules && rules === previousRules && rules.length === previousLength) {
return cachedRules;
}
if (!verifySchema(rules, configSchema)) {
throw 'Error: rules do not match schema';
}
cachedRules = rules.reduce(function (o, rule) {
return o[rule.entityType] = rule, o;
}, {});
previousRules = rules;
previousLength = rules.length;
return cachedRules;
}
/**
* Test if two arrays are the same, or contain the same contents
*/
function arrayEquals(arr1, arr2) {
return arr1 === arr2 || arr1 && arr2 && arr1.length === arr2.length && arr1.every(function (item, ii) {
return item === arr2[ii];
});
}
/**
* Build an array of all dependant entity types.
* Note: This doesn't handle cyclic dependencies. Please don't introduce any.
*/
function getEntityTypes(rules, type) {
var _resolveRule = resolveRule(rules, type),
rule = _resolveRule.rule,
entityType = _resolveRule.entityType;
var entityTypes = entityType && [entityType];
if (rule && rule[propertiesProp]) {
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = Object.values(rule[propertiesProp])[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var key = _step.value;
entityTypes = [].concat(toConsumableArray(entityTypes), toConsumableArray(getEntityTypes(rules, key)));
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
}
return entityTypes;
}
/**
* Creates a memoized selector which returns denormalized objects.
*