UNPKG

redux-resource

Version:
1,259 lines 50.3 kB
(function (global, factory) {
	typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
	typeof define === 'function' && define.amd ? define(['exports'], factory) :
	(factory((global.ReduxResource = {})));
}(this, (function (exports) { 'use strict';

var codeCache = {};

function warning(message, code) {
  // This ensures that each warning type is only logged out one time
  if (code) {
    if (codeCache[code]) {
      return;
    }

    codeCache[code] = true;
  }

  if (typeof console !== 'undefined' && typeof console.error === 'function') {
    console.error(message);
  }

  try {
    // This error was thrown as a convenience so that if you enable
    // "break on all exceptions" in your console,
    // it would pause the execution at this line.
    throw new Error(message);
  } catch (e) {
    // Intentionally blank
  }
}

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

// Add or update resources
function upsertResources(resources, newResources, mergeResources) {
  if (!newResources) {
    return resources;
  }

  var mergeResourcesOption = typeof mergeResources !== 'undefined' ? mergeResources : true;
  var shallowClone = Object.assign({}, resources);

  if (Array.isArray(newResources)) {
    newResources.forEach(function (resource) {
      var resourceIsObject = (typeof resource === 'undefined' ? 'undefined' : _typeof(resource)) === 'object';
      var id = resourceIsObject ? resource.id : resource;

      // If a resource doesn't have an ID, then it cannot be tracked
      if (!id && id !== 0) {
        {
          warning('You attempted to update or add a resource without an ID attribute. ' + 'Redux Resource requires that all resources have an ID. You should ' + 'double-check your Action Creators to make sure that all entries in ' + 'are either an ID or an object with an "id" attribute. ' + 'For more information, refer to the documentation on resource objects at: ' + 'https://redux-resource.js.org/docs/resources/resource-objects.html', 'MISSING_ID_UPSERT');
        }

        return;
      }

      var resourceObj = resourceIsObject ? resource : { id: resource };

      var resourceAlreadyExists = Boolean(resources[id]);

      // If there is no existing resource, we just add it to the resources object
      if (!resourceAlreadyExists) {
        shallowClone[id] = resourceObj;
        return shallowClone;
      }

      var resourceToInsert = void 0;
      if (mergeResourcesOption) {
        var currentResource = shallowClone[id];
        resourceToInsert = Object.assign({}, currentResource, resourceObj);
      } else {
        resourceToInsert = resourceObj;
      }

      shallowClone[id] = resourceToInsert;
    });
  } else {
    for (var id in newResources) {
      var resource = newResources[id];

      var resourceAlreadyExists = Boolean(resources[id]);

      // If there is no existing resource, we just add it to the resources object
      if (!resourceAlreadyExists) {
        shallowClone[id] = resource;
        continue;
      }

      var resourceToInsert = void 0;
      if (mergeResourcesOption) {
        var currentResource = shallowClone[id];
        resourceToInsert = Object.assign({}, currentResource, resource);
      } else {
        resourceToInsert = _extends({}, resource);
      }

      shallowClone[id] = resourceToInsert;
    }
  }

  return shallowClone;
}

// Add or update meta
function upsertMeta(meta, newMeta, mergeMeta) {
  if (!newMeta) {
    return meta;
  }

  var mergeMetaOption = typeof mergeMeta !== 'undefined' ? mergeMeta : true;
  var shallowClone = Object.assign({}, meta);

  for (var id in newMeta) {
    var resource = newMeta[id];

    var resourceAlreadyExists = Boolean(meta[id]);

    // If there is no existing resource, we just add it to the meta object
    if (!resourceAlreadyExists) {
      shallowClone[id] = resource;
      continue;
    }

    var resourceToInsert = void 0;
    if (mergeMetaOption) {
      var currentResource = shallowClone[id];
      resourceToInsert = Object.assign({}, currentResource, resource);
    } else {
      resourceToInsert = resource;
    }

    shallowClone[id] = resourceToInsert;
  }

  return shallowClone;
}

var requestStatuses = {
  IDLE: 'IDLE',
  PENDING: 'PENDING',
  FAILED: 'FAILED',
  SUCCEEDED: 'SUCCEEDED'
};

{
  Object.defineProperty(requestStatuses, 'NULL', {
    get: function get() {
      warning('You attempted to access the NULL request status from the requestStatus object ' + 'that is exported from Redux Resource. The NULL request status ' + 'has been renamed to IDLE in Redux Resource v3. Please update your ' + 'application to use the new request status. Typically, this can be ' + 'done by doing a find and replace within your source code to ' + 'replace the string "NULL" with "IDLE". For more information, refer to ' + 'the documentation for the request statuses at: ' + 'https://redux-resource.js.org/docs/api-reference/request-statuses.html\n\n' + 'Also, the migration guide to Redux Resource v3 can be found at: ' + 'https://github.com/jamesplease/redux-resource/blob/master/packages/redux-resource/docs/migration-guides/2-to-3.md', 'NULL_REQUEST_STATUS_ACCESSED');
    }
  });
}

var initialResourceMetaState = {
  createStatus: requestStatuses.IDLE,
  readStatus: requestStatuses.IDLE,
  updateStatus: requestStatuses.IDLE,
  deleteStatus: requestStatuses.IDLE
};

var updateResourcesPlugin = (function (resourceType, _ref) {
  var initialResourceMeta = _ref.initialResourceMeta;
  return function (state, action) {
    if (action.type !== 'UPDATE_RESOURCES' && action.type !== 'DELETE_RESOURCES') {
      return state;
    }

    var naiveNewResources = action.resources && action.resources[resourceType];
    var naiveNewMeta = action.meta && action.meta[resourceType];
    var additionalLists = action.lists && action.lists[resourceType];

    if (action.type === 'UPDATE_RESOURCES') {
      {
        if (typeof action.mergeListIds !== 'undefined') {
          warning('You passed the "mergeListId" properties to an UPDATE_RESOURCES action. ' + 'This property only works for the request action types (such as ' + 'READ_RESOURCES_PENDING). When using UPDATE_RESOURCES, you must modify the ' + 'list yourself, and then pass the new list to the action creator. ' + 'For more information, refer to the documentation on this action at: ' + 'https://redux-resource.js.org/docs/resources/modifying-resources.html', 'MERGE_LIST_ID_UPDATE_RESOURCES');
        }

        if (!action.resources && !action.meta && !action.lists) {
          warning('You dispatched an UPDATE_RESOURCES action without any resources, meta, ' + 'or lists, so the store will not be updated. ' + 'For more information, refer to the documentation on this action at: ' + 'https://redux-resource.js.org/docs/resources/modifying-resources.html', 'UPDATE_RESOURCES_NO_OP');
        }
      }

      var mergeResources = void 0;
      if (typeof action.mergeResources === 'boolean') {
        mergeResources = action.mergeResources;
      } else if (_typeof(action.mergeResources) === 'object') {
        mergeResources = action.mergeResources[resourceType];
      } else {
        mergeResources = true;
      }

      var mergeMeta = void 0;
      if (typeof action.mergeMeta === 'boolean') {
        mergeMeta = action.mergeMeta;
      } else if (_typeof(action.mergeMeta) === 'object') {
        mergeMeta = action.mergeMeta[resourceType];
      } else {
        mergeMeta = true;
      }

      var newResources = upsertResources(state.resources, naiveNewResources, mergeResources);

      var newMeta = void 0;
      if (!Array.isArray(naiveNewMeta)) {
        newMeta = upsertMeta(state.meta, naiveNewMeta, mergeMeta);
      } else {
        newMeta = state.meta;
      }

      var newLists = state.lists;

      if (additionalLists) {
        newLists = _extends({}, state.lists, additionalLists);
      }

      return _extends({}, state, action.resourceSliceAttributes, {
        resources: newResources,
        meta: newMeta,
        lists: newLists
      });
    } else {
      var _ret = function () {
        {
          if (!action.resources && !action.meta) {
            warning('You dispatched a DELETE_RESOURCES action without any resources ' + 'or meta, so the store will not be updated. ' + 'For more information, refer to the documentation on this action at: ' + 'https://redux-resource.js.org/docs/resources/modifying-resources.html', 'DELETE_RESOURCES_NO_OP');
          }
        }

        var idList = void 0;
        if (naiveNewResources && naiveNewResources.map) {
          idList = naiveNewResources.map(function (r) {
            if ((typeof r === 'undefined' ? 'undefined' : _typeof(r)) === 'object') {
              {
                if (!r.id && r.id !== 0 || typeof r.id !== 'string' && typeof r.id !== 'number') {
                  warning('A resource without an ID was passed to an action with type ' + (action.type + '. Every resource must have an ID that is either ') + 'a number of a string. You should check your action creators to ' + 'make sure that an ID is always included in your resources. ' + 'For more information, refer to the documentation on resource objects at: ' + 'https://redux-resource.js.org/docs/resources/resource-objects.html', 'NO_RESOURCE_ID');
                }
              }
              return r.id;
            } else {
              {
                if (typeof r !== 'string' && typeof r !== 'number') {
                  warning('A resource without an ID was passed to an action with type ' + (action.type + '. Every resource must have an ID that is either ') + 'a number of a string. You should check your action creators to ' + 'make sure that an ID is always included in your resources. ' + 'For more information, refer to the documentation on resource objects at: ' + 'https://redux-resource.js.org/docs/resources/resource-objects.html', 'NO_RESOURCE_ID');
                }
              }
              return r;
            }
          });
        } else {
          idList = Object.keys(naiveNewResources || {});
        }

        var hasIds = idList && idList.length;
        if (!hasIds) {
          return {
            v: state
          };
        }

        var newMeta = void 0;
        var newLists = {};
        var meta = state.meta;
        var lists = state.lists;

        for (var resourceList in lists) {
          var existingList = lists[resourceList];
          newLists[resourceList] = existingList.filter(function (r) {
            return !idList.includes(r);
          });
        }

        var nullMeta = idList.reduce(function (memo, id) {
          var newMeta = (naiveNewMeta || {})[id];
          memo[id] = _extends({}, initialResourceMetaState, initialResourceMeta, newMeta, {
            deleteStatus: requestStatuses.SUCCEEDED
          });
          return memo;
        }, {});

        newMeta = _extends({}, meta, nullMeta);

        // Shallow clone the existing resource object, nulling any deleted resources
        var newResources = Object.assign({}, state.resources);
        if (hasIds) {
          idList.forEach(function (id) {
            delete newResources[id];
          });
        }

        return {
          v: _extends({}, state, {
            meta: newMeta,
            lists: newLists,
            resources: newResources
          })
        };
      }();

      if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v;
    }
  };
});

// Update the meta for `resources`
function setResourceMeta(options) {
  var resources = options.resources,
      newMeta = options.newMeta,
      meta = options.meta,
      mergeMeta = options.mergeMeta,
      _options$initialResou = options.initialResourceMeta,
      initialResourceMeta = _options$initialResou === undefined ? {} : _options$initialResou;


  var next = _extends({}, meta);

  if (!resources) {
    return meta;
  }

  var mergeMetaOption = typeof mergeMeta !== 'undefined' ? mergeMeta : true;
  var resourcesArray = Array.isArray(resources) ? resources : Object.values(resources);

  if (!resourcesArray.length) {
    return next;
  }

  resourcesArray.forEach(function (resource) {
    var id = (typeof resource === 'undefined' ? 'undefined' : _typeof(resource)) === 'object' ? resource.id : resource;

    // If we have no ID for this resource, or if its not a number or string,
    // then we bail. This currently isn't logging so that we don't double-blast
    // the user with meta **and** attribute update problems. If the ID check
    // is moved into the success reducers directly, then we may be able to
    // remove these typeof checks for efficiency.
    if (!id && id !== 0 || typeof id !== 'string' && typeof id !== 'number') {
      return;
    }

    var currentMeta = next[id];

    var startMeta = void 0;
    if (currentMeta) {
      startMeta = _extends({}, initialResourceMeta, currentMeta);
    } else {
      startMeta = initialResourceMeta;
    }

    var baseMeta = mergeMetaOption ? startMeta : initialResourceMeta;

    next[id] = _extends({}, baseMeta, newMeta);
  });

  return next;
}

// This helper is used to simplify non-success reducers. Because non-success
// reducers don't modify the data – ever – it simplifies the scope of what can
// change.
// Basically, two things can change:
//
// 1. Request status for resource IDs in `meta`, if IDs are passed in
// 2. Request status for a named request
//
// A named request's IDs don't change, and neither does the resource. Consequently,
// this helper completely defines all of the ways in which the non-success reducers
// can change the state.
var reducerGenerator = function (crudAction, requestStatus) {
  return function (state, action) {
    var _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {},
        initialResourceMeta = _ref.initialResourceMeta;

    var resources = action.resources;
    var mergeMeta = action.mergeMeta;

    var requestKey = void 0,
        requestName = void 0;
    if (action.request && typeof action.request === 'string') {
      requestKey = requestName = action.request;
    }
    if (action.requestKey && typeof action.requestKey === 'string') {
      requestKey = action.requestKey;
    }
    if (action.requestName && typeof action.requestName === 'string') {
      requestName = action.requestName;
    }

    var idList = void 0;
    if (resources) {
      idList = resources.map(function (r) {
        if ((typeof r === 'undefined' ? 'undefined' : _typeof(r)) === 'object') {
          return r.id;
        } else {
          return r;
        }
      });
    } else {
      idList = [];
    }

    var statusAttribute = crudAction + 'Status';
    var newRequests = void 0,
        newMeta = void 0,
        newLists = void 0;

    if (!requestKey && !idList.length) {
      {
        warning('A Redux Resource action of type ' + action.type + ' was dispatched ' + 'without a "requestKey" or "resources" array. Without one of these ' + 'values, Redux Resource cannot track the request status for this ' + 'CRUD operation. You should check your Action Creators. Read more about ' + 'request tracking at: https://redux-resource.js.org/docs/other-guides/tracking-request-statuses.html', 'NO_OP_NON_SUCCESS_ACTION');
      }

      return state;
    }

    if (requestKey) {
      var existingRequest = state.requests[requestKey] || {};

      var newRequest = _extends({}, existingRequest, action.requestProperties, {
        requestKey: requestKey,
        resourceType: action.resourceType || action.resourceName,
        status: requestStatus
      });

      if (requestName) {
        newRequest.requestName = requestName;
      }

      newRequests = _extends({}, state.requests, defineProperty({}, requestKey, newRequest));
    } else {
      newRequests = state.requests;
    }

    // Lists only change when a request succeeds
    newLists = _extends({}, state.lists);

    if (idList.length) {
      newMeta = setResourceMeta({
        meta: state.meta,
        newMeta: defineProperty({}, statusAttribute, requestStatus),
        resources: idList,
        mergeMeta: mergeMeta,
        initialResourceMeta: _extends({}, initialResourceMetaState, initialResourceMeta)
      });
    } else {
      newMeta = state.meta;
    }

    return _extends({}, state, {
      requests: newRequests,
      lists: newLists,
      meta: newMeta
    });
  };
};

// This reducer helper handles the "CRU" in "CRUD".

var cruReducerHelper = function (state, action, _ref, updatedMeta) {
  var initialResourceMeta = _ref.initialResourceMeta;

  var resources = action.resources;
  var resourcesIsUndefined = typeof resources === 'undefined';
  var hasResources = resources && resources.length;

  var requestKey = void 0,
      requestName = void 0;
  if (action.request && typeof action.request === 'string') {
    requestKey = requestName = action.request;
  }
  if (action.requestKey && typeof action.requestKey === 'string') {
    requestKey = action.requestKey;
  }
  if (action.requestName && typeof action.requestName === 'string') {
    requestName = action.requestName;
  }

  var list = void 0;
  if (action.list && typeof action.list === 'string') {
    list = action.list;
  }

  {
    if (!resources) {
      warning('A \'resources\' array was not included in a Redux Resource ' + ('"success" action with type "' + action.type + '. Without a \'resources\' ') + 'Array, Redux Resource will not be able to track which resources ' + 'were affected by this CRUD operation. You should check your Action ' + 'Creators to make sure that they always include a \'resources\' array. ' + 'For more information, refer to the request action documentation at: ' + 'https://redux-resource.js.org/docs/requests/request-actions.html', 'SUCCESS_NO_RESOURCES');
    } else if (!Array.isArray(resources)) {
      warning('A non-array \'resources\' value was passed to a Redux Resource ' + ('"success" action with type "' + action.type + '". \'resources\' must be an ') + 'array. If your backend returned a single object, be sure to wrap it ' + 'inside of an array. If you\'re using the Redux Resource XHR ' + 'library, you can do this using the "transformData" option. ' + 'For more information, refer to the request action documentation at: ' + 'https://redux-resource.js.org/docs/requests/request-actions.html', 'NON_ARRAY_RESOURCES');
    }
  }

  // Without resources, a list, or a request key, there is nothing to update
  if (!hasResources && !requestKey && !list) {
    return state;
  }

  var newResources = upsertResources(state.resources, resources, action.mergeResources);
  var newMeta = setResourceMeta({
    resources: resources,
    meta: state.meta,
    newMeta: updatedMeta,
    mergeMeta: action.mergeMeta,
    initialResourceMeta: initialResourceMeta
  });

  var newRequests = void 0;
  if (requestKey) {
    var existingRequest = state.requests[requestKey] || {};
    var newRequest = _extends({}, existingRequest, action.requestProperties, {
      resourceType: action.resourceType || action.resourceName,
      requestKey: requestKey,
      status: requestStatuses.SUCCEEDED
    });

    if (requestName) {
      newRequest.requestName = requestName;
    }

    var newRequestIds = void 0;
    if (hasResources) {
      newRequestIds = resources.map(function (resource) {
        return (typeof resource === 'undefined' ? 'undefined' : _typeof(resource)) === 'object' ? resource.id : resource;
      });
    }

    newRequest.ids = newRequestIds || [];

    newRequests = _extends({}, state.requests, defineProperty({}, requestKey, newRequest));
  } else {
    newRequests = state.requests;
  }

  var newLists = void 0;
  if (list) {
    var currentList = state.lists[list] || [];
    var newList = void 0;

    if (action.mergeListIds === false) {
      if (hasResources) {
        newList = resources.map(function (resource) {
          return (typeof resource === 'undefined' ? 'undefined' : _typeof(resource)) === 'object' ? resource.id : resource;
        });
      } else if (!resourcesIsUndefined) {
        newList = [];
      }
    } else if (hasResources) {
      newList = Array.prototype.slice.call(currentList);

      resources.forEach(function (resource) {
        var id = (typeof resource === 'undefined' ? 'undefined' : _typeof(resource)) === 'object' ? resource.id : resource;
        if (!newList.includes(id)) {
          newList.push(id);
        }
      });
    }

    newLists = _extends({}, state.lists, defineProperty({}, list, newList || currentList));
  } else {
    newLists = state.lists;
  }

  return _extends({}, state, {
    resources: newResources,
    meta: newMeta,
    requests: newRequests,
    lists: newLists
  });
};

var read = reducerGenerator('read', requestStatuses.PENDING);
var readFail = reducerGenerator('read', requestStatuses.FAILED);
var readIdle = reducerGenerator('read', requestStatuses.IDLE);

function readSucceed(state, action, options) {
  return cruReducerHelper(state, action, options, {
    readStatus: requestStatuses.SUCCEEDED
  });
}

var create = reducerGenerator('create', requestStatuses.PENDING);
var createFail = reducerGenerator('create', requestStatuses.FAILED);
var createIdle = reducerGenerator('create', requestStatuses.IDLE);

function createSucceed(state, action, options) {
  return cruReducerHelper(state, action, options, {
    readStatus: requestStatuses.SUCCEEDED,
    createStatus: requestStatuses.SUCCEEDED
  });
}

var update = reducerGenerator('update', requestStatuses.PENDING);
var updateFail = reducerGenerator('update', requestStatuses.FAILED);
var updateIdle = reducerGenerator('update', requestStatuses.IDLE);

function updateSucceed(state, action, options) {
  return cruReducerHelper(state, action, options, {
    updateStatus: requestStatuses.SUCCEEDED
  });
}

var del = reducerGenerator('delete', requestStatuses.PENDING);
var delFail = reducerGenerator('delete', requestStatuses.FAILED);
var delIdle = reducerGenerator('delete', requestStatuses.IDLE);

function delSucceed(state, action, _ref) {
  var initialResourceMeta = _ref.initialResourceMeta;

  var resources = action.resources;

  var requestKey = void 0,
      requestName = void 0;
  if (action.request && typeof action.request === 'string') {
    requestKey = requestName = action.request;
  }
  if (action.requestKey && typeof action.requestKey === 'string') {
    requestKey = action.requestKey;
  }
  if (action.requestName && typeof action.requestName === 'string') {
    requestName = action.requestName;
  }

  {
    if (!resources) {
      warning('A \'resources\' array was not included in a Redux Resource ' + ('"success" action with type "' + action.type + '. Without a \'resources\' ') + 'Array, Redux Resource will not be able to track which resources ' + 'were affected by this CRUD operation. You should check your Action ' + 'Creators to make sure that they always include a \'resources\' array. ' + 'For more information, refer to the request action documentation at: ' + 'https://redux-resource.js.org/docs/requests/request-actions.html', 'SUCCESS_NO_RESOURCES');
    } else if (!Array.isArray(resources)) {
      warning('A non-array \'resources\' value was passed to a Redux Resource ' + ('"success" action with type "' + action.type + '". \'resources\' must be an ') + 'array. If your backend returned a single object, be sure to wrap it ' + 'inside of an array. If you\'re using the Redux Resource XHR ' + 'library, you can do this using the "transformData" option. ' + 'For more information, refer to the request action documentation at: ' + 'https://redux-resource.js.org/docs/requests/request-actions.html', 'NON_ARRAY_RESOURCES');
    }

    if (action.list) {
      warning('You included a "list" in a delete action. You don\'t need to do this, ' + 'because successful deletes remove the deleted resources from all lists. ' + 'For more information, refer to the documentation on deleting resources at: ' + 'https://redux-resource.js.org/docs/requests/deleting-resources.html', 'DELETE_LISTS');
    }
  }

  // Find the list of IDs affected by this action
  var idList = void 0;
  if (resources && resources.map) {
    idList = resources.map(function (r) {
      if ((typeof r === 'undefined' ? 'undefined' : _typeof(r)) === 'object') {
        {
          if (!r.id && r.id !== 0 || typeof r.id !== 'string' && typeof r.id !== 'number') {
            warning('A resource without an ID was passed to an action with type ' + (action.type + '. Every resource must have an ID that is either ') + 'a number of a string. You should check your action creators to ' + 'make sure that an ID is always included in your resources. ' + 'For more information, refer to the documentation on resource objects at: ' + 'https://redux-resource.js.org/docs/resources/resource-objects.html', 'NO_RESOURCE_ID');
          }
        }
        return r.id;
      } else {
        {
          if (typeof r !== 'string' && typeof r !== 'number') {
            warning('A resource without an ID was passed to an action with type ' + (action.type + '. Every resource must have an ID that is either ') + 'a number of a string. You should check your action creators to ' + 'make sure that an ID is always included in your resources. ' + 'For more information, refer to the documentation on resource objects at: ' + 'https://redux-resource.js.org/docs/resources/resource-objects.html', 'NO_RESOURCE_ID');
          }
        }
        return r;
      }
    });
  }

  var hasIds = idList && idList.length;

  // If we have no IDs nor requestKey, then there is nothing to update
  if (!hasIds && !requestKey) {
    return state;
  }

  var newMeta = void 0;
  var newLists = {};
  var newRequests = {};
  var meta = state.meta;
  var requests = state.requests;
  var lists = state.lists;

  if (requestKey) {
    var existingRequest = requests[requestKey] || {};
    var newRequest = _extends({}, existingRequest, action.requestProperties, {
      requestKey: requestKey,
      resourceType: action.resourceType || action.resourceName,
      status: requestStatuses.SUCCEEDED,
      ids: idList || []
    });

    if (requestName) {
      newRequest.requestName = requestName;
    }
    newRequests = _extends({}, requests, defineProperty({}, requestKey, newRequest));
  } else {
    newRequests = requests;
  }

  for (var resourceList in lists) {
    var existingList = lists[resourceList];

    var newList = void 0;
    if (hasIds && existingList) {
      newList = existingList.filter(function (r) {
        return !idList.includes(r);
      });
    } else if (existingList) {
      newList = existingList;
    }

    newLists[resourceList] = newList;
  }

  if (hasIds) {
    var nullMeta = idList.reduce(function (memo, id) {
      memo[id] = _extends({}, initialResourceMetaState, initialResourceMeta, {
        deleteStatus: requestStatuses.SUCCEEDED
      });
      return memo;
    }, {});

    newMeta = _extends({}, meta, nullMeta);
  } else {
    newMeta = meta;
  }

  // Shallow clone the existing resource object, nulling any deleted resources
  var newResources = Object.assign({}, state.resources);
  if (hasIds) {
    idList.forEach(function (id) {
      delete newResources[id];
    });
  }

  return _extends({}, state, {
    meta: newMeta,
    lists: newLists,
    requests: newRequests,
    resources: newResources
  });
}

var actionReducersMap = {
  CREATE_RESOURCES_PENDING: create,
  CREATE_RESOURCES_FAILED: createFail,
  CREATE_RESOURCES_SUCCEEDED: createSucceed,
  CREATE_RESOURCES_IDLE: createIdle,

  READ_RESOURCES_PENDING: read,
  READ_RESOURCES_FAILED: readFail,
  READ_RESOURCES_SUCCEEDED: readSucceed,
  READ_RESOURCES_IDLE: readIdle,

  UPDATE_RESOURCES_PENDING: update,
  UPDATE_RESOURCES_FAILED: updateFail,
  UPDATE_RESOURCES_SUCCEEDED: updateSucceed,
  UPDATE_RESOURCES_IDLE: updateIdle,

  DELETE_RESOURCES_PENDING: del,
  DELETE_RESOURCES_FAILED: delFail,
  DELETE_RESOURCES_SUCCEEDED: delSucceed,
  DELETE_RESOURCES_IDLE: delIdle
};

function requestStatusesPlugin(resourceType) {
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

  var customInitialMeta = options.initialResourceMeta || {};
  var optionsToSend = {
    initialResourceMeta: _extends({}, initialResourceMetaState, customInitialMeta)
  };

  return function (state, action) {
    var reducer = actionReducersMap[action.type];

    {
      if (reducer && !action.resourceName && !action.resourceType) {
        warning('A resourceType was not included in an action with type ' + ('"' + action.type + '". Without a resourceType, Redux Resource will ') + 'not be able to update a slice of your store. For more information, refer to ' + 'the guide on Request Actions: ' + 'https://redux-resource.js.org/docs/requests/request-actions.html', 'MISSING_RESOURCE_TYPE');
      }
    }

    var actionResourceType = action.resourceType || action.resourceName;

    if (actionResourceType !== resourceType) {
      return state;
    }

    var callActionReducer = reducer && actionResourceType === resourceType;
    return callActionReducer ? reducer(state, action, optionsToSend) : state;
  };
}

function generateDefaultInitialState() {
  return {
    // These is a complete collection of all of the resources that the server has sent
    // back, for all requests. The keys of this Object are the resource's ID. There's
    // no ordering here: use `lists` for that.
    resources: {},
    // This is metadata about _specific_ resources. For instance, if a DELETE
    // is in flight for a book with ID 24, then you could find that here.
    meta: {},
    // Named requests are used to track the statuses of requests that aren't
    // associated with a resource ID
    requests: {},
    // Lists are ordered collections of resources
    lists: {}
  };
}

function composeReducers(reducers) {
  return function (state, action) {
    return reducers.reduceRight(function (prevState, reducer) {
      return reducer(prevState, action);
    }, state);
  };
}

// Create a resource reducer.
//
// `resourceType`: the kind of resource that this slice represents. For instance, "books".
// `options`: pass options to change the behavior of the reducer. See the docs
//   for more information on the available options.
function resourceReducer(resourceType) {
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  var _options$plugins = options.plugins,
      plugins = _options$plugins === undefined ? [] : _options$plugins,
      _options$initialState = options.initialState,
      initialState = _options$initialState === undefined ? {} : _options$initialState;

  var defaultInitialState = generateDefaultInitialState();
  var initial = _extends({}, defaultInitialState, initialState, {
    resourceType: resourceType
  });

  {
    if (typeof resourceType !== 'string') {
      warning('The value of "resourceType" that you passed to resourceReducer was ' + 'not a string. The resource name must be a string. You should check ' + 'your reducer configuration. ' + 'For more information, refer to the documentation at: ' + 'https://redux-resource.js.org/docs/requests/request-actions.html', 'INVALID_RESOURCE_TYPE_PASSED');
    }
  }

  var allPlugins = plugins.concat(requestStatusesPlugin, updateResourcesPlugin);

  var computedPlugins = allPlugins.map(function (plugin) {
    var result = plugin(resourceType, options);
    {
      if (typeof result !== 'function') {
        warning('A plugin was initialized that did not return a function. Plugins ' + 'should return a function with the same signature as a reducer. ' + 'For more information, refer to the documentation on plugins: ' + 'https://redux-resource.js.org/docs/other-guides/plugins.html', 'BAD_PLUGIN_INITIALIZED');
      }
    }
    return result;
  });

  return function reducer() {
    var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initial;
    var action = arguments[1];

    {
      if (action.type === 'REQUEST_PENDING' || action.type === 'REQUEST_IDLE' || action.type === 'REQUEST_FAILED' || action.type === 'REQUEST_SUCCEEDED') {
        warning('You dispatched an action with type ' + action.type + '. This is a reserved ' + 'action type that will be used in a future version of Redux Resource. ' + 'We recommend that you use a different type to avoid conflict. ' + 'For more information, refer to the documentation at: ' + 'https://redux-resource.js.org/docs/api-reference/action-types.html#reserved-action-types', 'RESERVED_ACTION_TYPE_USED');
      }

      if (action.resourceName && typeof action.resourceName === 'string') {
        warning('You dispatched an action of type ' + action.type + ' with a "resourceName" property. This property has been deprecated in ' + 'favor of a new property, "resourceType." This new property serves ' + 'the same function; it has simply been renamed. The old property ' + 'will continue to work until the next major release of Redux Resource (v4). ' + 'Please update your action creators. For more information, refer to ' + 'the request action documentation at: ' + 'https://redux-resource.js.org/docs/requests/request-actions.html\n\n' + 'Also, the migration guide to Redux Resource v3 can be found at: ' + 'https://github.com/jamesplease/redux-resource/blob/master/packages/redux-resource/docs/migration-guides/2-to-3.md', 'DEPRECATED_RESOURCE_NAME_SPECIFIED');
      }

      if (action.request && typeof action.request !== 'string') {
        warning('An invalid request property was included in an action with type ' + ('"' + action.type + '". The request property must be a string. ') + 'For more information, refer to the documentation at: ' + 'https://redux-resource.js.org/docs/requests/request-actions.html', 'INVALID_REQUEST_NAME_PASSED');
      }

      if (action.requestKey && typeof action.requestKey !== 'string') {
        warning('An invalid requestKey property was included in an action with type ' + ('"' + action.type + '". The requestKey property must be a string. ') + 'For more information, refer to the documentation at: ' + 'https://redux-resource.js.org/docs/requests/request-keys.html', 'INVALID_REQUEST_KEY_PASSED');
      }

      if (action.requestName && typeof action.requestName !== 'string') {
        warning('An invalid requestName property was included in an action with type ' + ('"' + action.type + '". The requestName property must be a string. ') + 'For more information, refer to the documentation at: ' + 'https://redux-resource.js.org/docs/requests/request-names.html', 'INVALID_REQUEST_NAME_PASSED');
      }

      if (action.list && typeof action.list !== 'string') {
        warning('An invalid list was included in an action with type ' + ('"' + action.type + '". Lists must be strings.') + 'For more information, refer to the documentation at: ' + 'https://redux-resource.js.org/docs/resources/lists.html', 'INVALID_LIST_NAME_PASSED');
      }
    }

    return composeReducers(computedPlugins)(state, action);
  };
}

var actionTypes = {
  UPDATE_RESOURCES: 'UPDATE_RESOURCES',
  DELETE_RESOURCES: 'DELETE_RESOURCES'

  // These will be used in Redux Resource v3.1.0. For now,
  // they are reserved action types.
  // REQUEST_IDLE: 'REQUEST_IDLE',
  // REQUEST_PENDING: 'REQUEST_PENDING',
  // REQUEST_FAILED: 'REQUEST_FAILED',
  // REQUEST_SUCCEEDED: 'REQUEST_SUCCEEDED',
};

// This function generates the five statuses from a single CRUD action.
// For instance, you'd probably pass "CREATE", "READ", "UPDATE", or "DELETE"
// as `crudAction`.
//
// These are deprecated in favor of the simpler alternatives listed in
// `./action-types`. Those work the _exact_ same way. Because there are
// fewer of them, they should be easier to use.
var mapConstant = function mapConstant(crudAction) {
  var _ref;

  return _ref = {}, defineProperty(_ref, crudAction + '_RESOURCES_PENDING', crudAction + '_RESOURCES_PENDING'), defineProperty(_ref, crudAction + '_RESOURCES_SUCCEEDED', crudAction + '_RESOURCES_SUCCEEDED'), defineProperty(_ref, crudAction + '_RESOURCES_FAILED', crudAction + '_RESOURCES_FAILED'), defineProperty(_ref, crudAction + '_RESOURCES_IDLE', crudAction + '_RESOURCES_IDLE'), _ref;
};

var createTypes = mapConstant('CREATE');
var readTypes = mapConstant('READ');
var updateTypes = mapConstant('UPDATE');
var deleteTypes = mapConstant('DELETE');

var deprecated = _extends({}, createTypes, readTypes, updateTypes, deleteTypes);

var allTypes = _extends({}, deprecated, actionTypes);

{
  // eslint-disable-next-line
  var warn = function warn(propName) {
    var newPropName = propName.split('_').slice(0, 2).concat('IDLE').join('_');

    warning('You attempted to access the Redux Resource action type: ' + propName + '. ' + ('This action type has been renamed to ' + newPropName + ' ') + 'in Redux Resource v3. Please update your application to ' + 'use the new action type. For more information, refer to the action types ' + 'documentation at: ' + 'https://redux-resource.js.org/docs/api-reference/action-types.html\n\n' + 'Also, the migration guide to Redux Resource v3 can be found at: ' + 'https://github.com/jamesplease/redux-resource/blob/master/packages/redux-resource/docs/migration-guides/2-to-3.md', 'INVALID_PROP_' + propName + '_ACCESSED');
  };

  Object.defineProperties(allTypes, {
    READ_RESOURCES_NULL: {
      get: function get$$1() {
        warn('READ_RESOURCES_NULL');
      }
    },
    CREATE_RESOURCES_NULL: {
      get: function get$$1() {
        warn('READ_RESOURCES_NULL');
      }
    },
    UPDATE_RESOURCES_NULL: {
      get: function get$$1() {
        warn('READ_RESOURCES_NULL');
      }
    },
    DELETE_RESOURCES_NULL: {
      get: function get$$1() {
        warn('READ_RESOURCES_NULL');
      }
    }
  });
}

var index = _extends({}, deprecated, actionTypes);

// Given a path, such as "meta[24].readStatus", this will return an array of "path parts." In
// that example, the path parts would be ['meta', '24', 'readStatus'].
// This method supports dot notation as well as bracket notation.
function getPathParts(path) {
  var parts = [];
  var i = 0;
  var nextDot = void 0,
      nextOpenBracket = void 0,
      openQuote = void 0,
      nextCloseBracket = void 0;

  while (i < path.length) {
    nextDot = path.indexOf('.', i);
    nextOpenBracket = path.indexOf('[', i);

    // When there are no dots nor opening brackets ahead of the
    // current index, then we've reached the final path part.
    if (nextDot === -1 && nextOpenBracket === -1) {
      parts.push(path.slice(i, path.length));
      i = path.length;
    } else if (nextOpenBracket === -1 || nextDot !== -1 && nextDot < nextOpenBracket) {
      // This handles dots. When there are no more open brackets, or the next dot is before
      // the next open bracket, then we simply add the path part.
      parts.push(path.slice(i, nextDot));
      i = nextDot + 1;
    } else {
      // If neither of the above two conditions are met, then we're dealing with a bracket.
      if (nextOpenBracket > i) {
        parts.push(path.slice(i, nextOpenBracket));
        i = nextOpenBracket;
      }

      openQuote = path.slice(nextOpenBracket + 1, nextOpenBracket + 2);

      // This handles the situation when we do not have quotes. For instance, [24] or [asdf]
      if (openQuote !== '"' && openQuote !== "'") {
        nextCloseBracket = path.indexOf(']', nextOpenBracket);
        if (nextCloseBracket === -1) {
          nextCloseBracket = path.length;
        }
        parts.push(path.slice(i + 1, nextCloseBracket));
        i = path.slice(nextCloseBracket + 1, nextCloseBracket + 2) === '.' ? nextCloseBracket + 2 : nextCloseBracket + 1;
      } else {
        // This handles brackets that are wrapped in quotes. For instance, ["hello"] or ['24']
        nextCloseBracket = path.indexOf(openQuote + ']', nextOpenBracket);
        if (nextCloseBracket === -1) {
          nextCloseBracket = path.length;
        }
        parts.push(path.slice(i + 2, nextCloseBracket));
        i = path.slice(nextCloseBracket + 2, nextCloseBracket + 3) === '.' ? nextCloseBracket + 3 : nextCloseBracket + 2;
      }
    }
  }

  return parts;
}

function getSingleStatus(state, statusLocation, treatIdleAsPending) {
  var splitPath = getPathParts(statusLocation);

  var status = void 0;
  var currentVal = state;
  for (var i = 0; i < splitPath.length; i++) {
    var pathValue = currentVal[splitPath[i]];
    if (typeof pathValue === 'undefined') {
      status = requestStatuses.IDLE;
      break;
    } else if (i === splitPath.length - 1) {
      status = pathValue;
    }

    currentVal = pathValue;
  }

  {
    var isStatus = status === requestStatuses.IDLE || status === requestStatuses.PENDING || status === requestStatuses.FAILED || status === requestStatuses.SUCCEEDED;
    if (!isStatus) {
      warning('You called "getStatus" with path "' + statusLocation + '", which resolved ' + 'to a value that is not a valid resource status. You may want to ' + 'check that this path is correct. ' + 'Read more about getStatus on the documentation page: ' + 'https://redux-resource.js.org/docs/api-reference/get-status.html', 'GET_STATUS_PATH');
    }

    if (status === 'NULL') {
      warning('You called "getStatus" with path "' + statusLocation + '", which resolved ' + 'to a value of NULL. The NULL request status was renamed to IDLE in ' + 'Redux Resource v3.0.0. You may need to verify that your code is ' + 'compatible with Redux Resource v3.0.0. ' + 'For more information, refer to the documentation for request statuses at: ' + 'https://redux-resource.js.org/docs/api-reference/request-statuses.html\n\n' + 'Also, the migration guide to Redux Resource v3 can be found at: ' + 'https://github.com/jamesplease/redux-resource/blob/master/packages/redux-resource/docs/migration-guides/2-to-3.md', 'INVALID_REQUEST_STATUS_GET_STATUS');
    }
  }

  var isPending = status === requestStatuses.PENDING;
  var isIdle = status === requestStatuses.IDLE;
  var treatIdleAsPendingBool = Boolean(treatIdleAsPending);

  return {
    idle: isIdle && !treatIdleAsPendingBool,
    pending: isPending || isIdle && treatIdleAsPendingBool,
    failed: status === requestStatuses.FAILED,
    succeeded: status === requestStatuses.SUCCEEDED
  };
}

// Returns the status of a particular CRUD action based on `statusLocation`.
//
// `state`: A piece of the Redux store containing the relevant resources
// `action`: The CRUD action in question
// `statusLocation`: A location of the meta resource (see `find-meta.js` for more)
// `treatIdleAsPending`: Whether or not to count a status of `IDLE` as pending.
//
// Returns an Object with the following properties:
//
// {
//   idle: false,
//   failed: false,
//   pending: false,
//   succeeded: true,
// }
//
// Note that at most _one_ of those properties will be true. It is
// possible for them to all be false.
function getStatus(state, statusLocations, treatIdleAsPending) {
  if (!Array.isArray(statusLocations)) {
    var _status = getSingleStatus(state, statusLocations, treatIdleAsPending);

    {
      Object.defineProperty(_status, 'null', {
        get: function get() {
          warning('You attempted to access a property named "null" from the object returned by ' + 'the getStatus method from Redux Resource. This property has been renamed to "idle" ' + 'in Redux Resource v3. Please update your application to ' + 'use the "idle" rather than "null". For more information, refer to the ' + 'documentation for getStatus at: ' + 'https://redux-resource.js.org/docs/api-reference/get-status.html\n\n' + 'Also, the migration guide to Redux Resource v3 can be found at: ' + 'https://github.com/jamesplease/redux-resource/blob/master/packages/redux-resource/docs/migration-guides/2-to-3.md', 'NULL_GET_STATUS_VALUE_ACCESSED');
        }
      });
    }

    return _status;
  }

  var statusValues = statusLocations.map(function (loc) {
    return getSingleStatus(state, loc, treatIdleAsPending);
  });

  var idleValue = true;
  var pending = false;
  var failed = false;
  var succeeded = false;

  var successCount = 0;
  var pendingCount = 0;
  for (var i = 0; i < statusValues.length; i++) {
    var _status2 = statusValues[i];
    if (_status2.failed) {
      idleValue = false;
      failed = true;
      break;
    } else if (_status2.pending) {
      pendingCount++;
    } else if (_status2.succeeded) {
      successCount++;
    }
  }

  if (!failed && pendingCount > 0) {
    idleValue = false;
    pending = true;
  } else if (successCount === statusValues.length) {
    idleValue = false;
    succeeded = true;
  }

  var status = { idle: idleValue, pending: pending, failed: failed, succeeded: succeeded };

  {
    Object.defineProperty(status, 'null', {
      get: function get() {
        warning('You attempted to access a property named "null" from the object returned by ' + 'the getStatus method from Redux Resource. This property has been renamed to "idle" ' + 'in Redux Resource v3. Please update your application to ' + 'use the "idle" rather than "null". For more information, refer to the ' + 'documentation for getStatus at: ' + 'https://redux-resource.js.org/docs/api-reference/get-status.html\n\n' + 'Also, the migration guide to Redux Resource v3 can be found at: ' + 'https://github.com/jamesplease/redux-resource/blob/master/packages/redux-resource/docs/migration-guides/2-to-3.md', 'NULL_GET_STATUS_VALUE_ACCESSED');
      }
    });
  }

  return status;
}

function resourceArrayToObject(arr) {
  return arr.reduce(function (memo, r) {
    memo[r.id] = r;
    return memo;
  }, {});
}

// Returns a list of resources by IDs or list name
var getResources = function (resourceSlice, filter, options) {
  {
    // We use duck typing to try and differentiate between a user passing the entire state tree
    // in as `resourceSlice` (which was the original getResources API).
    // eslint-disable-next-line no-inner-declarations
    var validateSlice = function validateSlice() {
      var slice = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

      return slice.hasOwnProperty('resources') && slice.hasOwnProperty('meta') && slice.hasOwnProperty('requests') && slice.hasOwnProperty('lists');
    };

    if (arguments.length === 3 && !validateSlice(resourceSlice) &&
    // We assume that undefined means that a slice is undefined, rather than the entire state tree being undefined.
    typeof resourceSlice !== 'undefined') {
      warning('You called getResources with an argument signature that was removed in ' + 'v3.0.0 of Redux Resource. The old signature accepted three arguments, with the first being the all of the store\'s ' + 'state. The new signature of this function requires passing a resource slice as the first argument. Please ' + 'update your code to use the new signature. For more information, reference the documentation at ' + 'https://redux-resource.js.org/docs/api-reference/get-resources.html\n\n' + 'Also, the migration guide to Redux Resource v3 can be found at: ' + 'https://github.com/jamesplease/redux-resource/blob/master/packages/redux-resource/docs/migration-guides/2-to-3.md', 'DEPRECATED_GET_RESOURCES_SIGNATURE');
    }
  }

  var byId = void 0;
  if (options && options.byId) {
    byId = options.byId;
  }

  var noResultsReturn = byId ? {} : [];

  if (!resourceSlice) {
    return noResultsReturn;
  }

  var resources = resourceSlice.resources;
  var idsList = void 0;

  if (typeof filter === 'function' || !filter) {
    var appliedFilter = filter ? filter : function () {
      return true;
    };
    var _resourcesList = Object.values(resources).filter(function (resource) {
      return appliedFilter(resource, resourceSlice.meta[resource.id], resourceSlice);
    });

    return byId ? resourceArrayToObject(_resourcesList) : _resourcesList;
  } else if (typeof filter === 'string') {
    // This conditional handles the situation where `filter` is an list name
    var list = resourceSlice.lists[filter];
    if (!list) {
      return noResultsReturn;
    }

    idsList = list;
  } else {
    idsList = filter;
  }

  if (!(idsList && idsList.length)) {
    return noResultsReturn;
  }

  var resourcesList = idsList.map(function (id) {
    return resources[id];
  }).filter(Boolean);

  return byId ? resourceArrayToObject(resourcesList) : resourcesList;
};

/* eslint-disable no-empty-function */
/*
* This is a dummy function to check if the function name has been altered by minification.
* If the function has been minified and NODE_ENV !== 'production', warn the user.
*/
function isCrushed() {}
/* eslint-disable no-empty-function */

if ("development" !== 'production' && typeof isCrushed.name === 'string' && isCrushed.name !== 'isCrushed') {
  warning("You are currently using minified code outside of NODE_ENV === 'production'. " + 'This means that you are running a slower development build of Redux Resource. ' + 'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' + 'or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' + 'to ensure you have the correct code for your production build.', 'MINIFIED');
}

exports.resourceReducer = resourceReducer;
exports.actionTypes = index;
exports.requestStatuses = requestStatuses;
exports.setResourceMeta = setResourceMeta;
exports.upsertResources = upsertResources;
exports.getStatus = getStatus;
exports.getResources = getResources;

Object.defineProperty(exports, '__esModule', { value: true });

})));