UNPKG

selectorator

Version:

Simplified generator of reselect selectors

190 lines (184 loc) 6.7 kB
'use strict'; var identitate = require('identitate'); var pathington = require('pathington'); var reselect = require('reselect'); const INVALID_ARRAY_PATHS_MESSAGE = 'You have not provided any values for paths, so no values can be retrieved from state.'; const INVALID_PATHS_MESSAGE = [ 'First parameter passed must be either an array or a plain object.', 'If you are creating a standard selector, pass an array of either', 'properties on the state to retrieve, or custom selector functions.', 'If creating a structured selector, pass a plain object with source', 'and destination properties, where source is an array of properties', 'or custom selector functions, and destination is an array of property', 'names to assign the values from source to.', ].join(' '); const INVALID_OBJECT_PATH_MESSAGE = 'When providing an object path, you must provide the `path` property.'; const INVALID_PATH_MESSAGE = ` Path provided is of invalid type. It can be any one of the following values: * Dot-bracket notation, e.g. "foo.bar" or "bar[0].baz" * Number index, e.g. 0 * Object {path, argIndex}, e.g. {path: "foo.bar", argIndex: 1} * Selector function `.trim(); // eslint-disable-next-line @typescript-eslint/unbound-method const hasOwnProperty = Object.prototype.hasOwnProperty; /** * Whether the path is a functon */ function isManualSelector(path) { return typeof path === 'function'; } /** * Whether the path is an object. */ function isObjectPath(path) { return !!path && typeof path === 'object' && !Array.isArray(path); } function isPathItem(path) { return path != null && (typeof path === 'string' || typeof path === 'number' || typeof path === 'symbol'); } /** * Create the identity selector function based on the path passed. If the path item is a function then it is used directly, * otherwise the function is created based on its type. */ function createIdentitySelector(path) { if (isManualSelector(path)) { return path; } if (isObjectPath(path)) { if (hasOwnProperty.call(path, 'path')) { const { argIndex = 0, path: objectPath } = path; const selectorIdentity = identitate.createIdentity(argIndex); const parsedPath = isPathItem(objectPath) ? pathington.parse(objectPath) : Array.isArray(objectPath) && objectPath.every(isPathItem) ? pathington.parse(objectPath) : null; if (parsedPath != null) { return function (...args) { return getDeep(parsedPath, selectorIdentity(...args)); }; } } throw new ReferenceError(INVALID_OBJECT_PATH_MESSAGE); } const parsedPath = isPathItem(path) ? pathington.parse(path) : Array.isArray(path) && path.every(isPathItem) ? pathington.parse(path) : null; if (parsedPath != null) { return (state) => getDeep(parsedPath, state); } throw new TypeError(INVALID_PATH_MESSAGE); } /** * Get the value at the deep location expected from the path. If no match is found, return `undefined`. */ function getDeep(path, state) { if (state == null) { return; } let value = state; for (let index = 0, length = path.length; index < length; ++index) { const pathItem = path[index]; value = value[pathItem]; if (value == null && index < length - 1) { return; } } return value; } /** * Get the creator function to use when generating the selector. */ function getSelectorCreator({ argsMemoize, argsMemoizeOptions, devModeChecks, memoize, memoizeOptions, }) { return reselect.createSelectorCreator({ argsMemoize, argsMemoizeOptions, devModeChecks, memoize: memoize !== null && memoize !== void 0 ? memoize : reselect.weakMapMemoize, memoizeOptions, }); } /** * Get a standard selector based on the paths and getComputedValue provided. */ function getStandardSelector(paths, selectorCreator, getComputedValue) { return selectorCreator(paths.map(createIdentitySelector), getComputedValue); } /** * Get an object of property => selected value pairs based on paths. */ function getStructuredSelector(paths, selectorCreator, getComputedValue) { const selectors = Object.keys(paths).map((key) => createIdentitySelector( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion paths[key])); return selectorCreator(selectors, getComputedValue); } /** * Get an object of property => selected value pairs based on paths. */ function getStructuredIdentitySelector(paths) { const properties = Object.keys(paths); return function structuredObject(...values) { return properties.reduce((structuredObject, property, index) => { structuredObject[property] = values[index]; return structuredObject; }, {}); }; } /** * Create a selector without any boilerplate code * * @example * import { createSelector } from 'selectorator'; * * interface Item { * name: string; * value: number; * } * * interface State { * items: Item[]; * filter: { * value: string; * } * } * * const getFilteredItems = createSelector<State>()( * ['items', 'filter.value'], * (items, filterValue) => items.filter((item) => item.includes(filterValue)), * ); * * const state = { * items: ['foo', 'bar', 'foo-bar'], * filter: { * value: 'foo' * } * }; * * console.log(getFilteredItems(state)); // ['foo', 'foo-bar']; * console.log(getFilteredItems(state)); // ['foo', 'foo-bar'], pulled from cache; */ function createSelector(options = {}) { const selectorCreator = getSelectorCreator(options); // implementation function selector(paths, getComputedValue) { if (Array.isArray(paths)) { if (!paths.length) { throw new ReferenceError(INVALID_ARRAY_PATHS_MESSAGE); } return getStandardSelector(paths, selectorCreator, getComputedValue !== null && getComputedValue !== void 0 ? getComputedValue : identitate.identity); } if (typeof paths === 'object' // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition && paths != null) { return getStructuredSelector(paths, selectorCreator, getComputedValue !== null && getComputedValue !== void 0 ? getComputedValue : getStructuredIdentitySelector(paths)); } throw new TypeError(INVALID_PATHS_MESSAGE); } return selector; } exports.createSelector = createSelector; //# sourceMappingURL=index.cjs.map