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); +}