redux-resource
Version:
Resource management for Redux.
1,248 lines (1,026 loc) • 50.3 kB
JavaScript
(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.resourceReduc