UNPKG

redux-resource

Version:
1,248 lines (1,026 loc) 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.resourceReduc