Gathering detailed insights and metrics for @veksa/re-reselect
Gathering detailed insights and metrics for @veksa/re-reselect
Gathering detailed insights and metrics for @veksa/re-reselect
Gathering detailed insights and metrics for @veksa/re-reselect
Enhance Reselect selectors with deeper memoization and cache management.
npm install @veksa/re-reselect
Typescript
Module System
Node Version
NPM Version
74.5
Supply Chain
97.2
Quality
87.1
Maintenance
100
Vulnerability
100
License
TypeScript (84.51%)
JavaScript (15.49%)
Total Downloads
1,974
Last Day
2
Last Week
2
Last Month
259
Last Year
1,974
MIT License
1,091 Stars
503 Commits
47 Forks
7 Watchers
5 Branches
17 Contributors
Updated on Jul 02, 2025
Minified
Minified + Gzipped
Latest Version
5.1.1-p8
Package Id
@veksa/re-reselect@5.1.1-p8
Unpacked Size
422.24 kB
Size
33.96 kB
File Count
11
NPM Version
9.6.6
Node Version
22.13.0
Published on
Jun 18, 2025
Cumulative downloads
Total Downloads
Last Day
0%
2
Compared to previous day
Last Week
-95.2%
2
Compared to previous week
Last Month
-20.1%
259
Compared to previous month
Last Year
0%
1,974
Compared to previous year
@veksa/re-reselect
is a lightweight wrapper around Reselect that enhances selectors with deeper memoization and sophisticated cache management. This package is a fork of the original re-reselect
maintained for compatibility with @veksa/reselect
v5+ and modern TypeScript environments.
Standard @veksa/reselect
selectors have a cache limit of one, causing cache invalidation when switching between different arguments. @veksa/re-reselect
solves this by maintaining a cache of selectors, preserving computed values across multiple selector calls with different parameters.
@veksa/re-reselect requires TypeScript 5.8 or later.
1# npm 2npm install @veksa/reselect @veksa/re-reselect 3 4# yarn 5yarn add @veksa/reselect @veksa/re-reselect
Let's compare how we would implement a data selector with both libraries. In this example, we're retrieving user data filtered by a specific criteria and library.
Imagine we need to select users who belong to a particular library and match a given filter. With standard selectors, we would implement this as follows:
1import { createSelector } from '@veksa/reselect'; 2 3const getUsers = (state) => state.users; 4const getLibraryId = (state, libraryId) => libraryId; 5const getFilter = (state, libraryId, filter) => filter; 6 7const getUsersByLibraryAndFilter = createSelector( 8 getUsers, 9 getLibraryId, 10 getFilter, 11 (users, libraryId, filter) => { 12 console.log('Expensive computation running!'); 13 return users.filter(user => 14 user.libraryId === libraryId && 15 user.name.includes(filter) 16 ); 17 } 18); 19 20// Usage: 21const state = { users: [/* ... user data ... */] }; 22 23// First call with 'react' library and 'john' filter 24const reactUsersJohn = getUsersByLibraryAndFilter(state, 'react', 'john'); 25 26// Second call with 'vue' library and 'smith' filter 27const vueUsersSmith = getUsersByLibraryAndFilter(state, 'vue', 'smith'); 28 29// Third call with 'react' library and 'john' filter again 30// Despite having the same parameters as the first call, 31// the expensive computation runs again because the second call invalidated the cache 32const reactUsersJohnAgain = getUsersByLibraryAndFilter(state, 'react', 'john'); 33// Console: 'Expensive computation running!' (3 times)
With a standard @veksa/reselect selector, each time you call it with different arguments, the memoization cache is invalidated. This means that even when you return to previous argument combinations, the expensive computation runs again.
1import { createCachedSelector } from '@veksa/re-reselect'; 2 3const getUsers = (state) => state.users; 4const getLibraryId = (state, libraryId) => libraryId; 5const getFilter = (state, libraryId, filter) => filter; 6 7const getUsersByLibraryAndFilter = createCachedSelector( 8 getUsers, 9 getLibraryId, 10 getFilter, 11 (users, libraryId, filter) => { 12 console.log('Expensive computation running!'); 13 return users.filter(user => 14 user.libraryId === libraryId && 15 user.name.includes(filter) 16 ); 17 } 18)( 19 // The keySelector function creates a cache key from the arguments 20 // In this case, we use a combination of libraryId and filter 21 (state, libraryId, filter) => `${libraryId}:${filter}` 22); 23 24// Usage: 25const state = { users: [/* ... user data ... */] }; 26 27// First call with 'react' library and 'john' filter 28const reactUsersJohn = getUsersByLibraryAndFilter(state, 'react', 'john'); 29// Console: 'Expensive computation running!' (1st time) 30 31// Second call with 'vue' library and 'smith' filter 32const vueUsersSmith = getUsersByLibraryAndFilter(state, 'vue', 'smith'); 33// Console: 'Expensive computation running!' (2nd time, different key) 34 35// Third call with 'react' library and 'john' filter again 36// This time, it uses the CACHED selector for 'react:john' 37const reactUsersJohnAgain = getUsersByLibraryAndFilter(state, 'react', 'john'); 38// No console output - cached result is used!
When a cached selector is called, @veksa/re-reselect does the following:
cacheKey
by executing the keySelector
function with the current argumentsThe API is identical from the user's perspective, but @veksa/re-reselect maintains a cache of selectors keyed by the values you care about, preventing unnecessary recalculations.
When working with selectors that need parameters, several approaches are possible:
1// Pattern 1: Multiple discrete selectors (doesn't scale) 2const getProduct1Stats = createSelector( 3 getProducts, 4 products => computeProductStatistics(products, 'product-1') 5); 6 7// Pattern 2: Parameterized selector (cache invalidation problem) 8const getProductStats = createSelector( 9 getProducts, 10 (state, productId) => productId, 11 (products, productId) => computeProductStatistics(products, productId) 12); 13 14// Pattern 3: Selector factory (complex lifecycle management) 15const makeGetProductStats = (productId) => createSelector(/* ... */) 16 17// Pattern 4: Cached selector (best solution) 18const getProductStats = createCachedSelector( 19 getProducts, 20 (state, productId) => productId, 21 (products, productId) => computeProductStatistics(products, productId) 22)( 23 (state, productId) => productId // Cache key 24);
Use cached selectors to share selectors with props across multiple components:
1// selectors.js 2import { createCachedSelector } from '@veksa/re-reselect'; 3 4const getVisibleTodos = createCachedSelector( 5 [(state, props) => state.todoLists[props.listId].visibilityFilter, 6 (state, props) => state.todoLists[props.listId].todos], 7 (visibilityFilter, todos) => { 8 switch (visibilityFilter) { 9 case 'SHOW_COMPLETED': return todos.filter(t => t.completed); 10 case 'SHOW_ACTIVE': return todos.filter(t => !t.completed); 11 default: return todos; 12 } 13 } 14)( 15 (state, props) => props.listId // Cache by listId 16); 17 18// In component - no factory pattern needed 19const mapStateToProps = (state, props) => ({ 20 todos: getVisibleTodos(state, props) 21});
The keySelector
function determines how selectors are cached. Here are common patterns:
1// 1. Simple parameter as key 2const getUserPosts = createCachedSelector( 3 getUser, getPosts, 4 (user, allPosts) => allPosts.filter(post => post.userId === user.id) 5)( 6 (state, userId) => userId // Simple key 7); 8 9// 2. Composite keys from multiple parameters 10const getFilteredItems = createCachedSelector( 11 /* input selectors */ 12)( 13 (state, categoryId, statusFilter) => `${categoryId}::${statusFilter || 'all'}` 14); 15 16// 3. Object property extraction 17const getChartData = createCachedSelector( 18 /* input selectors */ 19)( 20 (state, options) => `${options.timeRange}::${options.aggregation}` 21); 22 23// 4. JSON serialization for complex objects 24const getComplexCalculation = createCachedSelector( 25 /* input selectors */ 26)( 27 (state, complexConfig) => JSON.stringify(complexConfig) 28);
For more complex scenarios, you can create dynamic cache keys that automatically adapt as your selectors evolve.
1// Create a utility that combines key selectors 2function keySelectorCombiner({ inputSelectors = [] } = {}) { 3 // Find input selectors with keySelector property 4 const keySelectors = inputSelectors 5 .map(selector => selector.keySelector) 6 .filter(value => Boolean(value)); 7 8 return (...args) => { 9 if (keySelectors.length === 0) { 10 return args[1]; 11 } 12 13 // Join all key parts with a separator 14 return keySelectors 15 .map(selector => selector(...args)) 16 .join('::'); 17 }; 18}
1// Input selectors with attached key information 2const getUserById = (state, userId) => state.users[userId]; 3userById.keySelector = (state, userId) => `user:${userId}`; 4 5const getTeamById = (state, userId) => { 6 const user = getUserById(state, userId); 7 return state.teams[user.teamId]; 8}; 9getTeamById.keySelector = (state, userId) => { 10 const user = getUserById(state, userId); 11 return `team:${user.teamId}`; 12}; 13 14// Regular input selector that doesn't affect caching 15const getGlobalSettings = state => state.settings; 16 17// Combined selector with dynamic key composition 18const getUserDashboardData = createCachedSelector( 19 [ 20 getUserById, // Has keySelector 21 getTeamById, // Has keySelector 22 getGlobalSettings // No keySelector, won't affect cache key 23 ], 24 (user, team, settings) => computeDashboardData(user, team, settings) 25)(keySelectorCombiner); // Automatically creates composite key
At runtime, the code above effectively creates this key selector:
1// Equivalent key selector generated at runtime 2(state, userId) => `user:${userId}::team:${getUser(state, userId).teamId}`
Cached selectors support both standard testing approaches and advanced cache-specific testing methods:
1// Basic result function testing 2test('should filter users by ID', () => { 3 const users = [{ id: 1 }, { id: 2 }]; 4 const userId = 1; 5 6 // Direct access to the result function 7 const result = getUsersById.resultFunc(users, userId); 8 9 expect(result).toEqual([{ id: 1 }]); 10}); 11 12// Testing structured selectors 13test('should gather dashboard data', () => { 14 const user = { id: 1, name: 'Test' }; 15 const metrics = { visits: 10 }; 16 const notifications = ['Message 1']; 17 18 // Direct call to resultFunc with expected inputs 19 const result = getDashboardData.resultFunc(user, metrics, notifications); 20 21 expect(result).toEqual({ 22 user, 23 metrics, 24 notifications 25 }); 26}); 27 28// Testing cache hits 29test('should return cached result for same filter', () => { 30 const state = { users: [/* data */] }; 31 const filter = 'test'; 32 33 const result1 = getFilteredUsers(state, filter); 34 const result2 = getFilteredUsers(state, filter); 35 36 // Same reference = cache hit 37 expect(result1).toBe(result2); 38 expect(getFilteredUsers.recomputations()).toBe(1); // Only computed once 39}); 40 41// Testing cache invalidation 42test('should invalidate cache for different filters', () => { 43 const state = { users: [/* data */] }; 44 45 getFilteredUsers(state, 'a'); 46 expect(getFilteredUsers.recomputations()).toBe(1); 47 48 // Different key should compute again 49 getFilteredUsers(state, 'b'); 50 expect(getFilteredUsers.recomputations()).toBe(2); 51}); 52 53// Testing cache manipulation methods 54test('should expose cache methods', () => { 55 const state = { users: [/* data */] }; 56 57 // Call with filter 'a' 58 getFilteredUsers(state, 'a'); 59 60 // Get underlying selector for key 'a' 61 const underlyingSelector = getFilteredUsers.getMatchingSelector(state, 'a'); 62 expect(underlyingSelector).toBeDefined(); 63 64 // Remove specific key from cache 65 getFilteredUsers.removeMatchingSelector(state, 'a'); 66 expect(getFilteredUsers.getMatchingSelector(state, 'a')).toBeUndefined(); 67 68 // Clear entire cache 69 getFilteredUsers(state, 'a'); 70 getFilteredUsers(state, 'b'); 71 getFilteredUsers.clearCache(); 72 expect(getFilteredUsers.cache.size).toBe(0); 73});
1import {createCachedSelector} from '@veksa/re-reselect'; 2 3const myCachedSelector = createCachedSelector( 4 // Input selectors (same as @veksa/reselect) 5 inputSelector1, 6 inputSelector2, 7 // Result function 8 (input1, input2) => computeResult(input1, input2) 9)( 10 // Either a simple keySelector function: 11 (state, arg) => arg, 12 // Or an options object: 13 { 14 keySelector: (state, arg) => arg, 15 cacheObject: new LruObjectCache({cacheSize: 10}), 16 // Other options... 17 } 18);
Creates a memoized selector with cache behavior based on a key selector function.
1import {createStructuredCachedSelector} from '@veksa/re-reselect'; 2 3const structuredSelector = createStructuredCachedSelector({ 4 value1: selector1, 5 value2: selector2, 6 // More key-selector pairs... 7})( 8 (state, arg) => arg // Key selector 9);
Creates a structured cached selector that returns an object composed of the results of each input selector.
Type: function
Default: undefined
A function that determines the cache key for a selector call:
1// Simple key selector using a single parameter 2(state, userId) => userId 3 4// Composite key using multiple parameters 5(state, categoryId, statusFilter) => `${categoryId}::${statusFilter}`
Type: object
Default: FlatObjectCache
@veksa/re-reselect
provides comprehensive cache management through the cacheObject
option:
1import { createCachedSelector, LruObjectCache } from '@veksa/re-reselect'; 2 3const categoryProductsSelector = createCachedSelector( 4 getAllProducts, 5 (state, categoryId) => categoryId, 6 (products, categoryId) => products.filter(p => p.categoryId === categoryId) 7)({ 8 keySelector: (state, categoryId) => categoryId, 9 cacheObject: new LruObjectCache({ cacheSize: 10 }) 10});
@veksa/re-reselect
includes six ready-to-use cache implementations:
Cache Strategy | Key Types | Eviction Policy | Storage | Use Case |
---|---|---|---|---|
FlatObjectCache | string/number | Unlimited | JS object | Simple selectors with few parameters |
FifoObjectCache | string/number | First-in-first-out | JS object | When memory constraints exist |
LruObjectCache | string/number | Least-recently-used | JS object | Most common use case (memory efficient) |
FlatMapCache | any | Unlimited | Map | When keys are objects or other complex types |
FifoMapCache | any | First-in-first-out | Map | Complex keys with memory constraints |
LruMapCache | any | Least-recently-used | Map | Best overall for complex keys |
FIFO and LRU cache strategies accept a cacheSize
parameter to control memory usage:
1// Limit to 5 most recently used items 2const lruCache = new LruObjectCache({ cacheSize: 5 }); 3 4// Limit to 10 most recently added items 5const fifoCache = new FifoMapCache({ cacheSize: 10 });
*ObjectCache
strategies convert number keys to strings (JS object limitation)*MapCache
strategies support any key type but may require a polyfill in older browsersFlatObjectCache
/FlatMapCache
have no size limits and should be used carefullyYou can create your own cache object by implementing this interface:
1interface ICacheObject { 2 // Store a selector function with the given key 3 set(key: any, selectorFn: any): void; 4 5 // Retrieve a selector function by key 6 get(key: any): any; 7 8 // Remove a specific selector from cache 9 remove(key: any): void; 10 11 // Clear the entire cache 12 clear(): void; 13 14 // Optional: Validate if a key is acceptable 15 isValidCacheKey?(key: any): boolean; 16}
This enables custom caching behaviors like time-based expiration, hybrid eviction policies, or integration with external caching systems.
Type: function
Default: undefined
Dynamically generates a key selector function based on the provided inputs:
1type keySelectorCreator = (selectorInputs: { 2 inputSelectors: InputSelector[]; 3 resultFunc: ResultFunc; 4 keySelector: KeySelector; 5}) => KeySelector;
Type: function
Default: createSelector
from @veksa/reselect
An alternative implementation of createSelector
to be used internally.
Cached selectors expose these methods:
dependencies
: Get array of input selectorsresultFunc
: Access the result function (useful for testing)recomputations()
: Count how many times the selector has recalculated its valueresetRecomputations()
: Reset the recomputation countergetMatchingSelector(selectorArguments)
: Get the underlying cached selector for specific argumentsremoveMatchingSelector(selectorArguments)
: Remove a specific selector from cachecache
: Access the cache object for advanced operationsclearCache()
: Clear the entire selector cachekeySelector
: Access the key selector functionThis project welcomes contributions and suggestions.
No vulnerabilities found.
Reason
16 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
Reason
no binaries found in the repo
Reason
no dangerous workflow patterns detected
Reason
license file detected
Details
Reason
3 existing vulnerabilities detected
Details
Reason
dependency not pinned by hash detected -- score normalized to 2
Details
Reason
detected GitHub workflow tokens with excessive permissions
Details
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
Reason
project is not fuzzed
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Score
Last Scanned on 2025-06-30
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More