reselect
Version:
Selectors for Redux.
743 lines (732 loc) • 22 kB
JavaScript
// src/devModeChecks/identityFunctionCheck.ts
var runIdentityFunctionCheck = (resultFunc, inputSelectorsResults, outputSelectorResult) => {
if (inputSelectorsResults.length === 1 && inputSelectorsResults[0] === outputSelectorResult) {
let isInputSameAsOutput = false;
try {
const emptyObject = {};
if (resultFunc(emptyObject) === emptyObject)
isInputSameAsOutput = true;
} catch {
}
if (isInputSameAsOutput) {
let stack = void 0;
try {
throw new Error();
} catch (e) {
;
({ stack } = e);
}
console.warn(
"The result function returned its own inputs without modification. e.g\n`createSelector([state => state.todos], todos => todos)`\nThis could lead to inefficient memoization and unnecessary re-renders.\nEnsure transformation logic is in the result function, and extraction logic is in the input selectors.",
{ stack }
);
}
}
};
// src/devModeChecks/inputStabilityCheck.ts
var runInputStabilityCheck = (inputSelectorResultsObject, options, inputSelectorArgs) => {
const { memoize, memoizeOptions } = options;
const { inputSelectorResults, inputSelectorResultsCopy } = inputSelectorResultsObject;
const createAnEmptyObject = memoize(() => ({}), ...memoizeOptions);
const areInputSelectorResultsEqual = createAnEmptyObject.apply(null, inputSelectorResults) === createAnEmptyObject.apply(null, inputSelectorResultsCopy);
if (!areInputSelectorResultsEqual) {
let stack = void 0;
try {
throw new Error();
} catch (e) {
;
({ stack } = e);
}
console.warn(
"An input selector returned a different result when passed same arguments.\nThis means your output selector will likely run more frequently than intended.\nAvoid returning a new reference inside your input selector, e.g.\n`createSelector([state => state.todos.map(todo => todo.id)], todoIds => todoIds.length)`",
{
arguments: inputSelectorArgs,
firstInputs: inputSelectorResults,
secondInputs: inputSelectorResultsCopy,
stack
}
);
}
};
// src/devModeChecks/setGlobalDevModeChecks.ts
var globalDevModeChecks = {
inputStabilityCheck: "once",
identityFunctionCheck: "once"
};
var setGlobalDevModeChecks = (devModeChecks) => {
Object.assign(globalDevModeChecks, devModeChecks);
};
// src/utils.ts
var NOT_FOUND = /* @__PURE__ */ Symbol("NOT_FOUND");
function assertIsFunction(func, errorMessage = `expected a function, instead received ${typeof func}`) {
if (typeof func !== "function") {
throw new TypeError(errorMessage);
}
}
function assertIsObject(object, errorMessage = `expected an object, instead received ${typeof object}`) {
if (typeof object !== "object") {
throw new TypeError(errorMessage);
}
}
function assertIsArrayOfFunctions(array, errorMessage = `expected all items to be functions, instead received the following types: `) {
if (!array.every((item) => typeof item === "function")) {
const itemTypes = array.map(
(item) => typeof item === "function" ? `function ${item.name || "unnamed"}()` : typeof item
).join(", ");
throw new TypeError(`${errorMessage}[${itemTypes}]`);
}
}
var ensureIsArray = (item) => {
return Array.isArray(item) ? item : [item];
};
function getDependencies(createSelectorArgs) {
const dependencies = Array.isArray(createSelectorArgs[0]) ? createSelectorArgs[0] : createSelectorArgs;
assertIsArrayOfFunctions(
dependencies,
`createSelector expects all input-selectors to be functions, but received the following types: `
);
return dependencies;
}
function collectInputSelectorResults(dependencies, inputSelectorArgs) {
const inputSelectorResults = [];
const { length } = dependencies;
for (let i = 0; i < length; i++) {
inputSelectorResults.push(dependencies[i].apply(null, inputSelectorArgs));
}
return inputSelectorResults;
}
var getDevModeChecksExecutionInfo = (firstRun, devModeChecks) => {
const { identityFunctionCheck, inputStabilityCheck } = {
...globalDevModeChecks,
...devModeChecks
};
return {
identityFunctionCheck: {
shouldRun: identityFunctionCheck === "always" || identityFunctionCheck === "once" && firstRun,
run: runIdentityFunctionCheck
},
inputStabilityCheck: {
shouldRun: inputStabilityCheck === "always" || inputStabilityCheck === "once" && firstRun,
run: runInputStabilityCheck
}
};
};
// src/autotrackMemoize/autotracking.ts
var $REVISION = 0;
var CURRENT_TRACKER = null;
var Cell = class {
revision = $REVISION;
_value;
_lastValue;
_isEqual = tripleEq;
constructor(initialValue, isEqual = tripleEq) {
this._value = this._lastValue = initialValue;
this._isEqual = isEqual;
}
// Whenever a storage value is read, it'll add itself to the current tracker if
// one exists, entangling its state with that cache.
get value() {
CURRENT_TRACKER?.add(this);
return this._value;
}
// Whenever a storage value is updated, we bump the global revision clock,
// assign the revision for this storage to the new value, _and_ we schedule a
// rerender. This is important, and it's what makes autotracking _pull_
// based. We don't actively tell the caches which depend on the storage that
// anything has happened. Instead, we recompute the caches when needed.
set value(newValue) {
if (this.value === newValue)
return;
this._value = newValue;
this.revision = ++$REVISION;
}
};
function tripleEq(a, b) {
return a === b;
}
var TrackingCache = class {
_cachedValue;
_cachedRevision = -1;
_deps = [];
hits = 0;
fn;
constructor(fn) {
this.fn = fn;
}
clear() {
this._cachedValue = void 0;
this._cachedRevision = -1;
this._deps = [];
this.hits = 0;
}
get value() {
if (this.revision > this._cachedRevision) {
const { fn } = this;
const currentTracker = /* @__PURE__ */ new Set();
const prevTracker = CURRENT_TRACKER;
CURRENT_TRACKER = currentTracker;
this._cachedValue = fn();
CURRENT_TRACKER = prevTracker;
this.hits++;
this._deps = Array.from(currentTracker);
this._cachedRevision = this.revision;
}
CURRENT_TRACKER?.add(this);
return this._cachedValue;
}
get revision() {
return Math.max(...this._deps.map((d) => d.revision), 0);
}
};
function getValue(cell) {
if (!(cell instanceof Cell)) {
console.warn("Not a valid cell! ", cell);
}
return cell.value;
}
function setValue(storage, value) {
if (!(storage instanceof Cell)) {
throw new TypeError(
"setValue must be passed a tracked store created with `createStorage`."
);
}
storage.value = storage._lastValue = value;
}
function createCell(initialValue, isEqual = tripleEq) {
return new Cell(initialValue, isEqual);
}
function createCache(fn) {
assertIsFunction(
fn,
"the first parameter to `createCache` must be a function"
);
return new TrackingCache(fn);
}
// src/autotrackMemoize/tracking.ts
var neverEq = (a, b) => false;
function createTag() {
return createCell(null, neverEq);
}
function dirtyTag(tag, value) {
setValue(tag, value);
}
var consumeCollection = (node) => {
let tag = node.collectionTag;
if (tag === null) {
tag = node.collectionTag = createTag();
}
getValue(tag);
};
var dirtyCollection = (node) => {
const tag = node.collectionTag;
if (tag !== null) {
dirtyTag(tag, null);
}
};
// src/autotrackMemoize/proxy.ts
var REDUX_PROXY_LABEL = Symbol();
var nextId = 0;
var proto = Object.getPrototypeOf({});
var ObjectTreeNode = class {
constructor(value) {
this.value = value;
this.value = value;
this.tag.value = value;
}
proxy = new Proxy(this, objectProxyHandler);
tag = createTag();
tags = {};
children = {};
collectionTag = null;
id = nextId++;
};
var objectProxyHandler = {
get(node, key) {
function calculateResult() {
const { value } = node;
const childValue = Reflect.get(value, key);
if (typeof key === "symbol") {
return childValue;
}
if (key in proto) {
return childValue;
}
if (typeof childValue === "object" && childValue !== null) {
let childNode = node.children[key];
if (childNode === void 0) {
childNode = node.children[key] = createNode(childValue);
}
if (childNode.tag) {
getValue(childNode.tag);
}
return childNode.proxy;
} else {
let tag = node.tags[key];
if (tag === void 0) {
tag = node.tags[key] = createTag();
tag.value = childValue;
}
getValue(tag);
return childValue;
}
}
const res = calculateResult();
return res;
},
ownKeys(node) {
consumeCollection(node);
return Reflect.ownKeys(node.value);
},
getOwnPropertyDescriptor(node, prop) {
return Reflect.getOwnPropertyDescriptor(node.value, prop);
},
has(node, prop) {
return Reflect.has(node.value, prop);
}
};
var ArrayTreeNode = class {
constructor(value) {
this.value = value;
this.value = value;
this.tag.value = value;
}
proxy = new Proxy([this], arrayProxyHandler);
tag = createTag();
tags = {};
children = {};
collectionTag = null;
id = nextId++;
};
var arrayProxyHandler = {
get([node], key) {
if (key === "length") {
consumeCollection(node);
}
return objectProxyHandler.get(node, key);
},
ownKeys([node]) {
return objectProxyHandler.ownKeys(node);
},
getOwnPropertyDescriptor([node], prop) {
return objectProxyHandler.getOwnPropertyDescriptor(node, prop);
},
has([node], prop) {
return objectProxyHandler.has(node, prop);
}
};
function createNode(value) {
if (Array.isArray(value)) {
return new ArrayTreeNode(value);
}
return new ObjectTreeNode(value);
}
function updateNode(node, newValue) {
const { value, tags, children } = node;
node.value = newValue;
if (Array.isArray(value) && Array.isArray(newValue) && value.length !== newValue.length) {
dirtyCollection(node);
} else {
if (value !== newValue) {
let oldKeysSize = 0;
let newKeysSize = 0;
let anyKeysAdded = false;
for (const _key in value) {
oldKeysSize++;
}
for (const key in newValue) {
newKeysSize++;
if (!(key in value)) {
anyKeysAdded = true;
break;
}
}
const isDifferent = anyKeysAdded || oldKeysSize !== newKeysSize;
if (isDifferent) {
dirtyCollection(node);
}
}
}
for (const key in tags) {
const childValue = value[key];
const newChildValue = newValue[key];
if (childValue !== newChildValue) {
dirtyCollection(node);
dirtyTag(tags[key], newChildValue);
}
if (typeof newChildValue === "object" && newChildValue !== null) {
delete tags[key];
}
}
for (const key in children) {
const childNode = children[key];
const newChildValue = newValue[key];
const childValue = childNode.value;
if (childValue === newChildValue) {
continue;
} else if (typeof newChildValue === "object" && newChildValue !== null) {
updateNode(childNode, newChildValue);
} else {
deleteNode(childNode);
delete children[key];
}
}
}
function deleteNode(node) {
if (node.tag) {
dirtyTag(node.tag, null);
}
dirtyCollection(node);
for (const key in node.tags) {
dirtyTag(node.tags[key], null);
}
for (const key in node.children) {
deleteNode(node.children[key]);
}
}
// src/lruMemoize.ts
function createSingletonCache(equals) {
let entry;
return {
get(key) {
if (entry && equals(entry.key, key)) {
return entry.value;
}
return NOT_FOUND;
},
put(key, value) {
entry = { key, value };
},
getEntries() {
return entry ? [entry] : [];
},
clear() {
entry = void 0;
}
};
}
function createLruCache(maxSize, equals) {
let entries = [];
function get(key) {
const cacheIndex = entries.findIndex((entry) => equals(key, entry.key));
if (cacheIndex > -1) {
const entry = entries[cacheIndex];
if (cacheIndex > 0) {
entries.splice(cacheIndex, 1);
entries.unshift(entry);
}
return entry.value;
}
return NOT_FOUND;
}
function put(key, value) {
if (get(key) === NOT_FOUND) {
entries.unshift({ key, value });
if (entries.length > maxSize) {
entries.pop();
}
}
}
function getEntries() {
return entries;
}
function clear() {
entries = [];
}
return { get, put, getEntries, clear };
}
var referenceEqualityCheck = (a, b) => a === b;
function createCacheKeyComparator(equalityCheck) {
return function areArgumentsShallowlyEqual(prev, next) {
if (prev === null || next === null || prev.length !== next.length) {
return false;
}
const { length } = prev;
for (let i = 0; i < length; i++) {
if (!equalityCheck(prev[i], next[i])) {
return false;
}
}
return true;
};
}
function lruMemoize(func, equalityCheckOrOptions) {
const providedOptions = typeof equalityCheckOrOptions === "object" ? equalityCheckOrOptions : { equalityCheck: equalityCheckOrOptions };
const {
equalityCheck = referenceEqualityCheck,
maxSize = 1,
resultEqualityCheck
} = providedOptions;
const comparator = createCacheKeyComparator(equalityCheck);
let resultsCount = 0;
const cache = maxSize <= 1 ? createSingletonCache(comparator) : createLruCache(maxSize, comparator);
function memoized() {
let value = cache.get(arguments);
if (value === NOT_FOUND) {
value = func.apply(null, arguments);
resultsCount++;
if (resultEqualityCheck) {
const entries = cache.getEntries();
const matchingEntry = entries.find(
(entry) => resultEqualityCheck(entry.value, value)
);
if (matchingEntry) {
value = matchingEntry.value;
resultsCount !== 0 && resultsCount--;
}
}
cache.put(arguments, value);
}
return value;
}
memoized.clearCache = () => {
cache.clear();
memoized.resetResultsCount();
};
memoized.resultsCount = () => resultsCount;
memoized.resetResultsCount = () => {
resultsCount = 0;
};
return memoized;
}
// src/autotrackMemoize/autotrackMemoize.ts
function autotrackMemoize(func) {
const node = createNode(
[]
);
let lastArgs = null;
const shallowEqual = createCacheKeyComparator(referenceEqualityCheck);
const cache = createCache(() => {
const res = func.apply(null, node.proxy);
return res;
});
function memoized() {
if (!shallowEqual(lastArgs, arguments)) {
updateNode(node, arguments);
lastArgs = arguments;
}
return cache.value;
}
memoized.clearCache = () => {
return cache.clear();
};
return memoized;
}
// src/weakMapMemoize.ts
var StrongRef = class {
constructor(value) {
this.value = value;
}
deref() {
return this.value;
}
};
var Ref = typeof WeakRef !== "undefined" ? WeakRef : StrongRef;
var UNTERMINATED = 0;
var TERMINATED = 1;
function createCacheNode() {
return {
s: UNTERMINATED,
v: void 0,
o: null,
p: null
};
}
function weakMapMemoize(func, options = {}) {
let fnNode = createCacheNode();
const { resultEqualityCheck } = options;
let lastResult;
let resultsCount = 0;
function memoized() {
let cacheNode = fnNode;
const { length } = arguments;
for (let i = 0, l = length; i < l; i++) {
const arg = arguments[i];
if (typeof arg === "function" || typeof arg === "object" && arg !== null) {
let objectCache = cacheNode.o;
if (objectCache === null) {
cacheNode.o = objectCache = /* @__PURE__ */ new WeakMap();
}
const objectNode = objectCache.get(arg);
if (objectNode === void 0) {
cacheNode = createCacheNode();
objectCache.set(arg, cacheNode);
} else {
cacheNode = objectNode;
}
} else {
let primitiveCache = cacheNode.p;
if (primitiveCache === null) {
cacheNode.p = primitiveCache = /* @__PURE__ */ new Map();
}
const primitiveNode = primitiveCache.get(arg);
if (primitiveNode === void 0) {
cacheNode = createCacheNode();
primitiveCache.set(arg, cacheNode);
} else {
cacheNode = primitiveNode;
}
}
}
const terminatedNode = cacheNode;
let result;
if (cacheNode.s === TERMINATED) {
result = cacheNode.v;
} else {
result = func.apply(null, arguments);
resultsCount++;
if (resultEqualityCheck) {
const lastResultValue = lastResult?.deref?.() ?? lastResult;
if (lastResultValue != null && resultEqualityCheck(lastResultValue, result)) {
result = lastResultValue;
resultsCount !== 0 && resultsCount--;
}
const needsWeakRef = typeof result === "object" && result !== null || typeof result === "function";
lastResult = needsWeakRef ? new Ref(result) : result;
}
}
terminatedNode.s = TERMINATED;
terminatedNode.v = result;
return result;
}
memoized.clearCache = () => {
fnNode = createCacheNode();
memoized.resetResultsCount();
};
memoized.resultsCount = () => resultsCount;
memoized.resetResultsCount = () => {
resultsCount = 0;
};
return memoized;
}
// src/createSelectorCreator.ts
function createSelectorCreator(memoizeOrOptions, ...memoizeOptionsFromArgs) {
const createSelectorCreatorOptions = typeof memoizeOrOptions === "function" ? {
memoize: memoizeOrOptions,
memoizeOptions: memoizeOptionsFromArgs
} : memoizeOrOptions;
const createSelector2 = (...createSelectorArgs) => {
let recomputations = 0;
let dependencyRecomputations = 0;
let lastResult;
let directlyPassedOptions = {};
let resultFunc = createSelectorArgs.pop();
if (typeof resultFunc === "object") {
directlyPassedOptions = resultFunc;
resultFunc = createSelectorArgs.pop();
}
assertIsFunction(
resultFunc,
`createSelector expects an output function after the inputs, but received: [${typeof resultFunc}]`
);
const combinedOptions = {
...createSelectorCreatorOptions,
...directlyPassedOptions
};
const {
memoize,
memoizeOptions = [],
argsMemoize = weakMapMemoize,
argsMemoizeOptions = [],
devModeChecks = {}
} = combinedOptions;
const finalMemoizeOptions = ensureIsArray(memoizeOptions);
const finalArgsMemoizeOptions = ensureIsArray(argsMemoizeOptions);
const dependencies = getDependencies(createSelectorArgs);
const memoizedResultFunc = memoize(function recomputationWrapper() {
recomputations++;
return resultFunc.apply(
null,
arguments
);
}, ...finalMemoizeOptions);
let firstRun = true;
const selector = argsMemoize(function dependenciesChecker() {
dependencyRecomputations++;
const inputSelectorResults = collectInputSelectorResults(
dependencies,
arguments
);
lastResult = memoizedResultFunc.apply(null, inputSelectorResults);
if (process.env.NODE_ENV !== "production") {
const { identityFunctionCheck, inputStabilityCheck } = getDevModeChecksExecutionInfo(firstRun, devModeChecks);
if (identityFunctionCheck.shouldRun) {
identityFunctionCheck.run(
resultFunc,
inputSelectorResults,
lastResult
);
}
if (inputStabilityCheck.shouldRun) {
const inputSelectorResultsCopy = collectInputSelectorResults(
dependencies,
arguments
);
inputStabilityCheck.run(
{ inputSelectorResults, inputSelectorResultsCopy },
{ memoize, memoizeOptions: finalMemoizeOptions },
arguments
);
}
if (firstRun)
firstRun = false;
}
return lastResult;
}, ...finalArgsMemoizeOptions);
return Object.assign(selector, {
resultFunc,
memoizedResultFunc,
dependencies,
dependencyRecomputations: () => dependencyRecomputations,
resetDependencyRecomputations: () => {
dependencyRecomputations = 0;
},
lastResult: () => lastResult,
recomputations: () => recomputations,
resetRecomputations: () => {
recomputations = 0;
},
memoize,
argsMemoize
});
};
Object.assign(createSelector2, {
withTypes: () => createSelector2
});
return createSelector2;
}
var createSelector = /* @__PURE__ */ createSelectorCreator(weakMapMemoize);
// src/createStructuredSelector.ts
var createStructuredSelector = Object.assign(
(inputSelectorsObject, selectorCreator = createSelector) => {
assertIsObject(
inputSelectorsObject,
`createStructuredSelector expects first argument to be an object where each property is a selector, instead received a ${typeof inputSelectorsObject}`
);
const inputSelectorKeys = Object.keys(inputSelectorsObject);
const dependencies = inputSelectorKeys.map(
(key) => inputSelectorsObject[key]
);
const structuredSelector = selectorCreator(
dependencies,
(...inputSelectorResults) => {
return inputSelectorResults.reduce((composition, value, index) => {
composition[inputSelectorKeys[index]] = value;
return composition;
}, {});
}
);
return structuredSelector;
},
{ withTypes: () => createStructuredSelector }
);
export {
createSelector,
createSelectorCreator,
createStructuredSelector,
lruMemoize,
referenceEqualityCheck,
setGlobalDevModeChecks,
autotrackMemoize as unstable_autotrackMemoize,
weakMapMemoize
};
//# sourceMappingURL=reselect.mjs.map