diff --git a/README.md b/README.md
index 01e49e4e..26eac363 100644
--- a/README.md
+++ b/README.md
@@ -368,7 +368,7 @@ A custom function receiving the same arguments as your selectors (and `inputSele
`cacheKey` is **by default a `string` or `number`** but can be anything depending on the chosen cache strategy (see [`cacheObject` option](#optionscacheobject)).
-The `keySelector` idea comes from [Lodash's .memoize][lodash-memoize].
+The `keySelector` idea comes from [Lodash's .memoize resolver][lodash-memoize].
### options
@@ -386,6 +386,23 @@ Default: `reselect`'s [`createSelector`][reselect-create-selector]
An optional function describing a [custom version of createSelector][reselect-create-selector-creator].
+#### keySelectorCreator
+
+Type: `function`
+Default: `undefined`
+
+An optional function with the following signature returning the [`keySelector`](#keyselector) used by the cached selector.
+
+```typescript
+export type keySelectorCreator = (selectorInputs: {
+ inputSelectors: InputSelector[];
+ resultFunc: ResultFunc;
+ keySelector: KeySelector;
+}) => KeySelector;
+```
+
+This allows to dynamically **generate `keySelectors` on runtime** based on provided `inputSelectors`/`resultFunc` and support [**key selectors composition**](https://github.com/toomuchdesign/re-reselect/pull/73).
+
### re-reselect selector instance
`createCachedSelector` and `createStructuredCachedSelector` return a **selector instance** which extends the API of a **standard reselect selector**.
diff --git a/src/__tests__/createCachedSelector.spec.js b/src/__tests__/createCachedSelector.spec.js
index 1b6f145b..496eeef4 100644
--- a/src/__tests__/createCachedSelector.spec.js
+++ b/src/__tests__/createCachedSelector.spec.js
@@ -1,4 +1,3 @@
-/* eslint comma-dangle: 0 */
import * as reselect from 'reselect';
import createCachedSelector, {FlatObjectCache} from '../../src/index';
@@ -20,232 +19,266 @@ function selectorWithMockedResultFunc() {
}
describe('createCachedSelector', () => {
- describe('"recomputations" property and cached selectors', () => {
- describe('keySelector returns the same value', () => {
- it('Should create and use the same cached selector', () => {
- const cachedSelector = selectorWithMockedResultFunc();
- const firstCall = cachedSelector('foo', 'bar');
- const secondCallWithSamekeySelector = cachedSelector('foo', 'bar');
-
- expect(createSelectorSpy).toHaveBeenCalledTimes(1);
- expect(cachedSelector.recomputations()).toBe(1);
- });
- });
-
- describe('keySelector returns 2 different values', () => {
- it('Should create 2 selectors only and produce 2 recomputations', () => {
- const cachedSelector = selectorWithMockedResultFunc();
- const firstCallResult = cachedSelector('foo', 'bar');
- const secondCallResult = cachedSelector('foo', 'moo');
- const thirdCallResult = cachedSelector('foo', 'bar');
- const fourthCallResult = cachedSelector('foo', 'moo');
-
- expect(createSelectorSpy).toHaveBeenCalledTimes(2);
- expect(cachedSelector.recomputations()).toBe(2);
- });
- });
- });
+ describe('options', () => {
+ it('accepts cacheObject and selectorCreator options', () => {
+ const cachedSelector = createCachedSelector(resultFuncMock)(
+ (arg1, arg2) => arg2,
+ {
+ cacheObject: new FlatObjectCache(),
+ selectorCreator: reselect.createSelector,
+ }
+ );
- describe('cacheKey validity check', () => {
- describe('cacheObject.isValidCacheKey not available', () => {
- it('Should accept any value', () => {
- const cacheObjectMock = {
- get: jest.fn(() => () => 'foo'),
- };
- const values = [{}, [], null, undefined, 12, 'bar'];
-
- const cachedSelector = createCachedSelector(resultFuncMock)(
- arg1 => arg1, // cacheKey
- {
- cacheObject: cacheObjectMock,
- }
- );
-
- values.forEach((value, index) => {
- cachedSelector(value);
- expect(cacheObjectMock.get).toHaveBeenCalledTimes(index + 1);
- expect(cacheObjectMock.get).toHaveBeenLastCalledWith(value);
- });
- });
+ expect(cachedSelector.recomputations()).toBe(0);
+ cachedSelector('foo', 'bar');
+ cachedSelector('foo', 'bar');
+ expect(cachedSelector.recomputations()).toBe(1);
});
- describe('cacheObject.isValidCacheKey returns "true"', () => {
- it('Should call cache.get method', () => {
- const cacheObjectMock = new FlatObjectCache();
- cacheObjectMock.isValidCacheKey = jest.fn(() => true);
- cacheObjectMock.get = jest.fn();
+ it('accepts selectorCreator option', () => {
+ const inputSelector = () => {};
+ const resultFunc = () => {};
+ const keySelector = () => {};
+ const generatedKeySelector = () => {};
+ const keySelectorCreatorMock = jest.fn(() => generatedKeySelector);
- const cachedSelector = createCachedSelector(resultFuncMock)(
- arg1 => arg1,
- {
- cacheObject: cacheObjectMock,
- }
- );
-
- cachedSelector('foo');
+ const cachedSelector = createCachedSelector(inputSelector, resultFunc)(
+ keySelector,
+ {
+ keySelectorCreator: keySelectorCreatorMock,
+ }
+ );
- expect(cacheObjectMock.get).toHaveBeenCalledTimes(1);
- // Receive cacheKey and reselect selector as arguments
- expect(cacheObjectMock.get).toHaveBeenCalledWith('foo');
+ expect(keySelectorCreatorMock).toHaveBeenCalledWith({
+ inputSelectors: [inputSelector],
+ resultFunc: resultFunc,
+ keySelector: keySelector,
});
+ expect(cachedSelector.keySelector).toBe(generatedKeySelector);
});
+ });
- describe('cacheObject.isValidCacheKey returns "false"', () => {
- it('Should return "undefined" and call "console.warn"', () => {
- const cacheObjectMock = new FlatObjectCache();
- cacheObjectMock.isValidCacheKey = jest.fn(() => false);
- cacheObjectMock.get = jest.fn();
+ describe('created selector', () => {
+ describe('cache retention', () => {
+ describe('calls producing identical cacheKey', () => {
+ it('creates and use the same cached selector', () => {
+ const cachedSelector = selectorWithMockedResultFunc();
+ cachedSelector('foo', 'bar');
+ cachedSelector('foo', 'bar');
- const cachedSelector = createCachedSelector(resultFuncMock)(
- arg1 => arg1,
- {
- cacheObject: cacheObjectMock,
- }
- );
+ expect(createSelectorSpy).toHaveBeenCalledTimes(1);
+ expect(cachedSelector.recomputations()).toBe(1);
+ });
+ });
- const actual = cachedSelector('foo');
+ describe('calls producing 2 different cacheKey', () => {
+ it('creates 2 selectors only and produce 2 recomputations', () => {
+ const cachedSelector = selectorWithMockedResultFunc();
+ cachedSelector('foo', 'bar');
+ cachedSelector('foo', 'moo');
+ cachedSelector('foo', 'bar');
+ cachedSelector('foo', 'moo');
- expect(actual).toBe(undefined);
- expect(cacheObjectMock.get).not.toHaveBeenCalled();
- expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
+ expect(createSelectorSpy).toHaveBeenCalledTimes(2);
+ expect(cachedSelector.recomputations()).toBe(2);
+ });
});
});
- });
- it('Should throw an error when a function is provided as 2° argument', () => {
- expect(() => {
- const cachedSelector = createCachedSelector(resultFuncMock)(() => {},
- reselect.createSelector);
- }).toThrow(/Second argument "options" must be an object/);
- });
+ describe('cacheKey validation', () => {
+ describe('cacheObject.isValidCacheKey', () => {
+ describe("doesn't exist", () => {
+ it('accepts any value', () => {
+ const cacheObjectMock = {
+ get: jest.fn(() => () => 'foo'),
+ };
+ const values = [{}, [], null, undefined, 12, 'bar'];
+
+ const cachedSelector = createCachedSelector(resultFuncMock)(
+ arg1 => arg1,
+ {
+ cacheObject: cacheObjectMock,
+ }
+ );
+
+ values.forEach((value, index) => {
+ cachedSelector(value);
+ expect(cacheObjectMock.get).toHaveBeenCalledTimes(index + 1);
+ expect(cacheObjectMock.get).toHaveBeenLastCalledWith(value);
+ });
+ });
+ });
- it('Should accept an options object', () => {
- const cachedSelector = createCachedSelector(resultFuncMock)(
- (arg1, arg2) => arg2,
- {
- cacheObject: new FlatObjectCache(),
- selectorCreator: reselect.createSelector,
- }
- );
-
- expect(cachedSelector.recomputations()).toBe(0);
- cachedSelector('foo', 'bar');
- cachedSelector('foo', 'bar');
- expect(cachedSelector.recomputations()).toBe(1);
- });
+ describe('returns true', () => {
+ it('calls cache.get method', () => {
+ const cacheObjectMock = new FlatObjectCache();
+ cacheObjectMock.isValidCacheKey = () => true;
+ cacheObjectMock.get = jest.fn();
- describe('getMatchingSelector()', () => {
- it('Should return underlying reselect selector for a given cache key', () => {
- const cachedSelector = createCachedSelector(() => {})(
- (arg1, arg2) => arg2
- );
+ const cachedSelector = createCachedSelector(resultFuncMock)(
+ arg1 => arg1,
+ {
+ cacheObject: cacheObjectMock,
+ }
+ );
- // Retrieve result from re-reselect cached selector
- const actualResult = cachedSelector('foo', 1);
+ cachedSelector('foo');
- // Retrieve result directly calling underlying reselect selector
- const reselectSelector = cachedSelector.getMatchingSelector('foo', 1);
- const expectedResultFromSelector = reselectSelector('foo', 1);
+ expect(cacheObjectMock.get).toHaveBeenCalledTimes(1);
+ expect(cacheObjectMock.get).toHaveBeenCalledWith('foo');
+ });
+ });
- expect(actualResult).toBe(expectedResultFromSelector);
+ describe('returns false', () => {
+ it('returns "undefined" and call "console.warn"', () => {
+ const cacheObjectMock = new FlatObjectCache();
+ cacheObjectMock.isValidCacheKey = () => false;
+ cacheObjectMock.get = jest.fn();
+
+ const cachedSelector = createCachedSelector(resultFuncMock)(
+ arg1 => arg1,
+ {
+ cacheObject: cacheObjectMock,
+ }
+ );
+
+ const actual = cachedSelector('foo');
+
+ expect(actual).toBe(undefined);
+ expect(cacheObjectMock.get).not.toHaveBeenCalled();
+ expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
+ });
+ });
+ });
});
- it('Should return "undefined" when given cache key doesn\'t match any cache entry', () => {
- const cachedSelector = selectorWithMockedResultFunc();
+ describe('available methods', () => {
+ describe('getMatchingSelector()', () => {
+ it('returns underlying reselect selector for a given cache key', () => {
+ const cachedSelector = createCachedSelector(() => {})(
+ (arg1, arg2) => arg2
+ );
- const actual = cachedSelector.getMatchingSelector('foo', 1);
- const expected = undefined;
+ // Retrieve result from re-reselect cached selector
+ const actualResult = cachedSelector('foo', 1);
- expect(actual).toEqual(expected);
- });
- });
+ // Retrieve result directly calling underlying reselect selector
+ const reselectSelector = cachedSelector.getMatchingSelector('foo', 1);
+ const expectedResultFromSelector = reselectSelector('foo', 1);
- describe('removeMatchingSelector()', () => {
- it('Should set the matching cache entry to "undefined"', () => {
- const cachedSelector = selectorWithMockedResultFunc();
+ expect(actualResult).toBe(expectedResultFromSelector);
+ });
- cachedSelector('foo', 1); // add to cache
- cachedSelector('foo', 2); // add to cache
- cachedSelector.removeMatchingSelector('foo', 1); // remove key from chache
+ it('returns "undefined" when given cache key doesn\'t match any cache entry', () => {
+ const cachedSelector = selectorWithMockedResultFunc();
- const firstSelectorActual = cachedSelector.getMatchingSelector('foo', 1);
- const secondSelectorActual = cachedSelector.getMatchingSelector('foo', 2);
+ const actual = cachedSelector.getMatchingSelector('foo', 1);
+ const expected = undefined;
- expect(firstSelectorActual).toBe(undefined);
- expect(secondSelectorActual).not.toBe(undefined);
- });
- });
+ expect(actual).toEqual(expected);
+ });
+ });
- describe('clearCache()', () => {
- it('Should reset cache', () => {
- const cachedSelector = selectorWithMockedResultFunc();
+ describe('removeMatchingSelector()', () => {
+ it('sets the matching cache entry to "undefined"', () => {
+ const cachedSelector = selectorWithMockedResultFunc();
+
+ cachedSelector('foo', 1); // add to cache
+ cachedSelector('foo', 2); // add to cache
+ cachedSelector.removeMatchingSelector('foo', 1);
+
+ const firstSelectorActual = cachedSelector.getMatchingSelector(
+ 'foo',
+ 1
+ );
+ const secondSelectorActual = cachedSelector.getMatchingSelector(
+ 'foo',
+ 2
+ );
+
+ expect(firstSelectorActual).toBe(undefined);
+ expect(secondSelectorActual).not.toBe(undefined);
+ });
+ });
- cachedSelector('foo', 1); // add to cache
- cachedSelector.clearCache(); // clear cache
- const actual = cachedSelector.getMatchingSelector('foo', 1);
+ describe('clearCache()', () => {
+ it('resets cache', () => {
+ const cachedSelector = selectorWithMockedResultFunc();
- expect(actual).toBe(undefined);
- });
- });
+ cachedSelector('foo', 1); // add to cache
+ cachedSelector.clearCache();
+ const actual = cachedSelector.getMatchingSelector('foo', 1);
- describe('resetRecomputations()', () => {
- it('Should reset recomputations', () => {
- const cachedSelector = selectorWithMockedResultFunc();
- const firstCallResult = cachedSelector('foo', 'bar');
+ expect(actual).toBe(undefined);
+ });
+ });
- expect(cachedSelector.recomputations()).toBe(1);
- cachedSelector.resetRecomputations();
- expect(cachedSelector.recomputations()).toBe(0);
- });
- });
+ describe('resetRecomputations()', () => {
+ it('resets recomputations', () => {
+ const cachedSelector = selectorWithMockedResultFunc();
+ cachedSelector('foo', 'bar');
- describe('"dependencies" property', () => {
- it('Should export an array containing provided inputSelectors', () => {
- const dependency1 = state => state.a;
- const dependency2 = state => state.a;
+ expect(cachedSelector.recomputations()).toBe(1);
+ cachedSelector.resetRecomputations();
+ expect(cachedSelector.recomputations()).toBe(0);
+ });
+ });
- const cachedSelector = createCachedSelector(
- dependency1,
- dependency2,
- () => {}
- )(arg1 => arg1);
+ describe('"dependencies" property', () => {
+ it('exports an array containing provided inputSelectors', () => {
+ const dependency1 = state => state.a;
+ const dependency2 = state => state.a;
- const actual = cachedSelector.dependencies;
- const expected = [dependency1, dependency2];
- expect(actual).toEqual(expected);
- });
- });
+ const cachedSelector = createCachedSelector(
+ dependency1,
+ dependency2,
+ () => {}
+ )(arg1 => arg1);
- describe('"resultFunc" property', () => {
- it('Should point to provided result function', () => {
- const cachedSelector = createCachedSelector(() => {}, resultFuncMock)(
- (arg1, arg2) => arg2
- );
- expect(cachedSelector.resultFunc).toBe(resultFuncMock);
- });
- });
+ const actual = cachedSelector.dependencies;
+ const expected = [dependency1, dependency2];
+ expect(actual).toEqual(expected);
+ });
+ });
- describe('"cache" property', () => {
- it('Should point to currently used cacheObject', () => {
- const currentCacheObject = new FlatObjectCache();
- const cachedSelector = createCachedSelector(resultFuncMock)(
- arg1 => arg1,
- {
- cacheObject: currentCacheObject,
- }
- );
+ describe('"resultFunc" property', () => {
+ it('points to provided result function', () => {
+ const cachedSelector = createCachedSelector(() => {}, resultFuncMock)(
+ (arg1, arg2) => arg2
+ );
+ expect(cachedSelector.resultFunc).toBe(resultFuncMock);
+ });
+ });
- expect(cachedSelector.cache).toBe(currentCacheObject);
+ describe('"cache" property', () => {
+ it('points to currently used cacheObject', () => {
+ const currentCacheObject = new FlatObjectCache();
+ const cachedSelector = createCachedSelector(resultFuncMock)(
+ arg1 => arg1,
+ {
+ cacheObject: currentCacheObject,
+ }
+ );
+
+ expect(cachedSelector.cache).toBe(currentCacheObject);
+ });
+ });
+
+ describe('"keySelector" property', () => {
+ it('points to provided keySelector', () => {
+ const keySelector = (arg1, arg2) => arg2;
+ const cachedSelector = createCachedSelector(() => {}, resultFuncMock)(
+ keySelector
+ );
+ expect(cachedSelector.keySelector).toBe(keySelector);
+ });
+ });
});
});
- describe('"keySelector" property', () => {
- it('Should point to provided keySelector', () => {
- const keySelector = (arg1, arg2) => arg2;
- const cachedSelector = createCachedSelector(() => {}, resultFuncMock)(
- keySelector
- );
- expect(cachedSelector.keySelector).toBe(keySelector);
- });
+ it('throws an error when a function is provided as 2° argument', () => {
+ expect(() => {
+ createCachedSelector(resultFuncMock)(() => {}, reselect.createSelector);
+ }).toThrow(/Second argument "options" must be an object/);
});
});
diff --git a/src/cache/__tests__/deprecated/FifoCacheObject.js b/src/cache/__tests__/deprecated/FifoCacheObject.js
index 8b76c572..095279f7 100644
--- a/src/cache/__tests__/deprecated/FifoCacheObject.js
+++ b/src/cache/__tests__/deprecated/FifoCacheObject.js
@@ -1,7 +1,7 @@
import {FifoCacheObject as CacheObject} from '../../../../src/index';
describe('FifoCacheObject (deprecated)', () => {
- it('Should return "FifoObjectCache" class', () => {
+ it('returns "FifoObjectCache" class', () => {
expect(CacheObject.name).toBe('FifoObjectCache');
});
});
diff --git a/src/cache/__tests__/deprecated/FlatCacheObject.js b/src/cache/__tests__/deprecated/FlatCacheObject.js
index 7818eb80..46432581 100644
--- a/src/cache/__tests__/deprecated/FlatCacheObject.js
+++ b/src/cache/__tests__/deprecated/FlatCacheObject.js
@@ -1,7 +1,7 @@
import {FlatCacheObject as CacheObject} from '../../../../src/index';
describe('FlatCacheObject (deprecated)', () => {
- it('Should return "FlatObjectCache" class', () => {
+ it('returns "FlatObjectCache" class', () => {
expect(CacheObject.name).toBe('FlatObjectCache');
});
});
diff --git a/src/cache/__tests__/deprecated/LruCacheObject.js b/src/cache/__tests__/deprecated/LruCacheObject.js
index a66582e4..ffe4b259 100644
--- a/src/cache/__tests__/deprecated/LruCacheObject.js
+++ b/src/cache/__tests__/deprecated/LruCacheObject.js
@@ -1,7 +1,7 @@
import {LruCacheObject as CacheObject} from '../../../../src/index';
describe('LruCacheObject (deprecated)', () => {
- it('Should return "LruMapCache" class', () => {
+ it('returns "LruMapCache" class', () => {
expect(CacheObject.name).toBe('LruMapCache');
});
});
diff --git a/src/cache/__util__/testBasicBehavior.js b/src/cache/__util__/testBasicBehavior.js
index 27db5a38..34d1284f 100644
--- a/src/cache/__util__/testBasicBehavior.js
+++ b/src/cache/__util__/testBasicBehavior.js
@@ -2,7 +2,7 @@ import fillCacheWith from './fillCacheWith';
function testBasicBehavior(CacheObject, options) {
describe('Cache basic behavior', () => {
- it('Should return cached value', () => {
+ it('returns cached value', () => {
const cache = new CacheObject(options);
const actual = () => {};
@@ -12,7 +12,7 @@ function testBasicBehavior(CacheObject, options) {
expect(actual).toBe(expected);
});
- it('Should remove a single item', () => {
+ it('removes a single item', () => {
const cache = new CacheObject(options);
const entries = [1, 2, 3, 4, 5];
fillCacheWith(cache, entries);
@@ -25,7 +25,7 @@ function testBasicBehavior(CacheObject, options) {
});
});
- it('Should clear the cache', () => {
+ it('clears the cache', () => {
const cache = new CacheObject(options);
const entries = [1, 2, 3, 4, 5];
fillCacheWith(cache, entries);
diff --git a/src/cache/__util__/testCacheSizeOptionValidation.js b/src/cache/__util__/testCacheSizeOptionValidation.js
index c537fb80..d59d18c5 100644
--- a/src/cache/__util__/testCacheSizeOptionValidation.js
+++ b/src/cache/__util__/testCacheSizeOptionValidation.js
@@ -1,12 +1,12 @@
function testCacheSizeOptionValidation(CacheObject) {
describe('cacheSize option validation', () => {
- it('Should throw error if not defined', () => {
+ it('throws error if not defined', () => {
expect(() => {
const cache = new CacheObject();
}).toThrow(/Missing/);
});
- it('Should throw error if not a positive integer', () => {
+ it('throws error if not a positive integer', () => {
const wrongValues = [2.5, -12, 0];
wrongValues.forEach(value => {
@@ -16,7 +16,7 @@ function testCacheSizeOptionValidation(CacheObject) {
});
});
- it('Should not throw if a positive integer', () => {
+ it("doesn't throw if a positive integer", () => {
expect(() => {
const cache = new CacheObject({cacheSize: 22});
}).not.toThrow();
diff --git a/src/cache/__util__/testFifoBehavior.js b/src/cache/__util__/testFifoBehavior.js
index 309f4815..95bdc0fc 100644
--- a/src/cache/__util__/testFifoBehavior.js
+++ b/src/cache/__util__/testFifoBehavior.js
@@ -2,7 +2,7 @@ import fillCacheWith from './fillCacheWith';
function testFifoBehavior(CacheObject) {
describe('FIFO cache behavior', () => {
- it('Should limit cache queue by removing the first added items', () => {
+ it('limits cache queue by removing the first added items', () => {
const cache = new CacheObject({cacheSize: 5});
const entries = [1, 2, 3, 4];
const newEntries = [5, 6, 7];
@@ -17,7 +17,7 @@ function testFifoBehavior(CacheObject) {
});
});
- it('Should mantain cache updated after removing extraneous entry', () => {
+ it('mantains cache updated after removing extraneous entry', () => {
const cache = new CacheObject({cacheSize: 5});
const entries = [1, 2, 3, 4, 5];
fillCacheWith(cache, entries);
diff --git a/src/cache/__util__/testLruBehavior.js b/src/cache/__util__/testLruBehavior.js
index cb392a31..963466fe 100644
--- a/src/cache/__util__/testLruBehavior.js
+++ b/src/cache/__util__/testLruBehavior.js
@@ -2,7 +2,7 @@ import fillCacheWith from './fillCacheWith';
function testLruBehavior(CacheObject) {
describe('LRU cache behavior', () => {
- it('Should remove an item and update cache ordering when another is added', () => {
+ it('removes an item and update cache ordering when another is added', () => {
const cache = new CacheObject({cacheSize: 5});
const entries = [1, 2, 3, 4, 5];
fillCacheWith(cache, entries);
@@ -16,7 +16,7 @@ function testLruBehavior(CacheObject) {
});
});
- it('Should limit cache queue by removing the least recently used item', () => {
+ it('limits cache queue by removing the least recently used item', () => {
const cache = new CacheObject({cacheSize: 5});
const entries = [0, 1, 2];
diff --git a/src/cache/__util__/testMapCacheKeyBehavior.js b/src/cache/__util__/testMapCacheKeyBehavior.js
index 61609d99..d07e3485 100644
--- a/src/cache/__util__/testMapCacheKeyBehavior.js
+++ b/src/cache/__util__/testMapCacheKeyBehavior.js
@@ -3,13 +3,13 @@ import fillCacheWith from './fillCacheWith';
function testMapCacheKeyBehavior(CacheObject, options) {
describe('cacheKey', () => {
describe('isValidCacheKey method', () => {
- it('Should not exist', () => {
+ it("doesn't not exist", () => {
const cache = new CacheObject(options);
expect(cache.isValidCacheKey).toBe(undefined);
});
});
- it('Any kind of value should work as cache key', () => {
+ it('any kind of value works as cache key', () => {
const cache = new CacheObject(options);
const entries = new Set([1, {}, 3, [], null]);
diff --git a/src/cache/__util__/testObjectCacheKeyBehavior.js b/src/cache/__util__/testObjectCacheKeyBehavior.js
index 92292c5f..0d8b9f63 100644
--- a/src/cache/__util__/testObjectCacheKeyBehavior.js
+++ b/src/cache/__util__/testObjectCacheKeyBehavior.js
@@ -1,6 +1,6 @@
function testObjectCacheKeyBehavior(CacheObject, options) {
describe('isValidCacheKey method', () => {
- it('Should accept only numbers and string', () => {
+ it('accepts only numbers and string', () => {
const cache = new CacheObject(options);
const validValues = [1, 1.2, -5, 'foo', '12'];
const invalidValues = [{}, [], null, undefined, new Map()];
diff --git a/src/createCachedSelector.js b/src/createCachedSelector.js
index a566edc7..d598fdbb 100644
--- a/src/createCachedSelector.js
+++ b/src/createCachedSelector.js
@@ -28,6 +28,14 @@ function createCachedSelector(...funcs) {
const selectorCreator = options.selectorCreator || createSelector;
const isValidCacheKey = cache.isValidCacheKey || defaultCacheKeyValidator;
+ if (options.keySelectorCreator) {
+ keySelector = options.keySelectorCreator({
+ inputSelectors: dependencies,
+ resultFunc,
+ keySelector,
+ });
+ }
+
// Application receives this function
const selector = function(...args) {
const cacheKey = keySelector(...args);
diff --git a/src/index.d.ts b/src/index.d.ts
index 38f79396..92689f12 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -34,20 +34,25 @@ export type OutputParametricSelector = ParametricSelector<
export type CreateSelectorInstance = typeof createSelector;
-type Options =
+type Options =
| {
selectorCreator?: CreateSelectorInstance;
- cacheObject: ICacheObject;
+ cacheObject?: ICacheObject;
+ keySelectorCreator?: KeySelectorCreator;
}
+ | CreateSelectorInstance;
+
+type ParametricOptions =
| {
- selectorCreator: CreateSelectorInstance;
+ selectorCreator?: CreateSelectorInstance;
cacheObject?: ICacheObject;
+ keySelectorCreator?: ParametricKeySelectorCreator;
}
| CreateSelectorInstance;
export type OutputCachedSelector = (
keySelector: KeySelector,
- optionsOrSelectorCreator?: Options
+ optionsOrSelectorCreator?: Options
) => OutputSelector & {
getMatchingSelector: (state: S, ...args: any[]) => OutputSelector;
removeMatchingSelector: (state: S, ...args: any[]) => void;
@@ -58,7 +63,7 @@ export type OutputCachedSelector = (
export type OutputParametricCachedSelector = (
keySelector: ParametricKeySelector,
- optionsOrSelectorCreator?: Options
+ optionsOrSelectorCreator?: ParametricOptions
) => OutputParametricSelector & {
getMatchingSelector: (
state: S,
@@ -4447,3 +4452,18 @@ export class LruMapCache implements ICacheObject {
remove(key: any): void;
clear(): void;
}
+
+/*
+ * Key selector creators
+ */
+export type KeySelectorCreator = (selectorInputs: {
+ inputSelectors: D;
+ resultFunc: C;
+ keySelector: KeySelector;
+}) => KeySelector;
+
+export type ParametricKeySelectorCreator = (selectorInputs: {
+ inputSelectors: D;
+ resultFunc: C;
+ keySelector: ParametricKeySelector;
+}) => ParametricKeySelector;
diff --git a/typescript_test/test.ts b/typescript_test/createCachedSelector.ts
similarity index 92%
rename from typescript_test/test.ts
rename to typescript_test/createCachedSelector.ts
index aaafa2f2..85e51c9c 100644
--- a/typescript_test/test.ts
+++ b/typescript_test/createCachedSelector.ts
@@ -330,7 +330,7 @@ function testArrayArgument() {
}
}
-function testResolver() {
+function testKeySelector() {
type State = {foo: string; obj: {bar: string}};
const selector = createCachedSelector(
@@ -348,7 +348,7 @@ function testResolver() {
)((state: never, obj) => obj);
}
-function testCustomSelectorCreator() {
+function testSelectorCreatorOption() {
type State = {foo: string};
const selector1 = createCachedSelector(
@@ -369,3 +369,29 @@ function testCustomSelectorCreator() {
foo => foo
)((state: State) => state.foo, (): void => {});
}
+
+function testKeySelectorCreatorOption() {
+ type State = {foo: string};
+ const state = {foo: 'bar'};
+ const inputSelector = (state: State) => state.foo;
+ const resultFunc = (input: string) => input;
+ const keySelector = (state: State) => state.foo;
+
+ const selector = createCachedSelector(
+ inputSelector,
+ inputSelector,
+ resultFunc
+ )(keySelector, {
+ keySelectorCreator: ({inputSelectors, resultFunc, keySelector}) => {
+ const input1 = inputSelectors[0](state);
+ const input2 = inputSelectors[1](state);
+ // typings:expect-error
+ inputSelectors[2];
+
+ const result: string = resultFunc(input1, input2);
+ return keySelector;
+ },
+ });
+
+ const result: string = selector(state);
+}