reselect-kit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +473 -0
- package/dist/cjs/index.js +6 -0
- package/dist/cjs/reselectKit.development.cjs +683 -0
- package/dist/cjs/reselectKit.development.cjs.map +1 -0
- package/dist/cjs/reselectKit.production.min.cjs +2 -0
- package/dist/cjs/reselectKit.production.min.cjs.map +1 -0
- package/dist/reselectKit.d.ts +248 -0
- package/dist/reselectKit.mjs +678 -0
- package/dist/reselectKit.mjs.map +1 -0
- package/package.json +70 -0
- package/src/__data__/state.ts +97 -0
- package/src/__tests__/createAdaptedSelector.test.ts +134 -0
- package/src/__tests__/createBoundSelector.test.ts +351 -0
- package/src/__tests__/createCachedSequenceSelector.test.ts +24 -0
- package/src/__tests__/createCachedStructuredSelector.test.ts +29 -0
- package/src/__tests__/createChainSelector.test.ts +404 -0
- package/src/__tests__/createEmptySelector.test.ts +99 -0
- package/src/__tests__/createPathSelector.test.ts +81 -0
- package/src/__tests__/createPropSelector.test.ts +36 -0
- package/src/__tests__/createSegmentSelector.test.ts +31 -0
- package/src/__tests__/createSequenceSelector.test.ts +62 -0
- package/src/_helpers/__tests__/arePathsEqual.test.ts +27 -0
- package/src/_helpers/__tests__/getObjectPaths.test.ts +56 -0
- package/src/_helpers/__tests__/temporaryAssign.test.ts +59 -0
- package/src/_helpers/arePathsEqual.ts +15 -0
- package/src/_helpers/defineDynamicSelectorName.ts +10 -0
- package/src/_helpers/generateSelectorKey.ts +15 -0
- package/src/_helpers/getObjectPaths.ts +13 -0
- package/src/_helpers/getSelectorName.ts +7 -0
- package/src/_helpers/isCachedSelector.ts +9 -0
- package/src/_helpers/isObject.ts +3 -0
- package/src/_helpers/stringifyFunction.ts +3 -0
- package/src/_helpers/temporaryAssign.ts +80 -0
- package/src/_hooks/__tests__/useMemoWith.test.ts +31 -0
- package/src/_hooks/__tests__/useSelector.test.ts +87 -0
- package/src/_hooks/useMemoWith.ts +11 -0
- package/src/_hooks/useSelector.ts +34 -0
- package/src/cache/TreeCache.ts +126 -0
- package/src/cache/__tests__/TreeCacheObject.test.ts +188 -0
- package/src/cache/__tests__/intervalMapCache.test.ts +133 -0
- package/src/cache/intervalMapCache.ts +97 -0
- package/src/createAdaptedSelector.ts +75 -0
- package/src/createBoundSelector.ts +197 -0
- package/src/createCachedSequenceSelector.ts +18 -0
- package/src/createCachedStructuredSelector.ts +44 -0
- package/src/createChainSelector.ts +230 -0
- package/src/createEmptySelector.ts +27 -0
- package/src/createPathSelector.ts +208 -0
- package/src/createPropSelector.ts +28 -0
- package/src/createSegmentSelector.ts +6 -0
- package/src/createSequenceSelector.ts +15 -0
- package/src/createStructuredSelector.ts +3 -0
- package/src/debug/__tests__/debug.test.ts +20 -0
- package/src/debug/debug.ts +7 -0
- package/src/debug/withDebugName.ts +28 -0
- package/src/index.ts +26 -0
- package/src/keys/__tests__/arrayComposeKeySelectors.test.ts +52 -0
- package/src/keys/__tests__/createKeySelectorCreator.test.ts +246 -0
- package/src/keys/__tests__/stringComposeKeySelectors.test.ts +52 -0
- package/src/keys/arrayComposeKeySelectors.ts +20 -0
- package/src/keys/createKeySelectorComposer.ts +68 -0
- package/src/keys/createKeySelectorCreator.ts +106 -0
- package/src/keys/defaultKeySelector.ts +1 -0
- package/src/keys/stringComposeKeySelectors.ts +15 -0
- package/src/types.ts +43 -0
- package/src/vitest-globals.d.ts +1 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Aleksandr Khizhnyi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
# reselect-kit
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/reselect-kit)
|
|
4
|
+
[](https://www.npmjs.com/package/reselect-kit)
|
|
5
|
+
[](LICENSE.md)
|
|
6
|
+
|
|
7
|
+
A powerful toolkit that extends the capabilities of @veksa/reselect and @veksa/re-reselect by providing utilities for advanced selector composition, safe property access, and optimized caching. This library helps you build more maintainable and efficient Redux selectors with enhanced type safety and functional programming patterns.
|
|
8
|
+
|
|
9
|
+
> **About this package**
|
|
10
|
+
>
|
|
11
|
+
> `reselect-kit` continues [sgrishchenko/reselect-utils](https://github.com/sgrishchenko/reselect-utils),
|
|
12
|
+
> keeping its full commit history. That project is no longer maintained, so the
|
|
13
|
+
> work moved here under a new name rather than staying on a fork of a dormant
|
|
14
|
+
> repository. The API is the same — only the package name changed, from
|
|
15
|
+
> `@veksa/reselect-utils` to `reselect-kit`.
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- **Chain Selector Pattern** - Build selector pipelines with fluent interfaces for complex data transformations
|
|
20
|
+
- **Powerful Path Selectors** - Safely traverse and select nested object properties with protection against null/undefined
|
|
21
|
+
- **Bound Selectors** - Create selectors with predefined parameters for reuse across components
|
|
22
|
+
- **Adapted Selectors** - Transform selector parameters to simplify composition and reuse
|
|
23
|
+
- **Enhanced Structured Selectors** - Better typed alternatives to createStructuredSelector with caching support
|
|
24
|
+
- **Key Selector Composition** - Compose key selectors for complex cache keys in re-reselect
|
|
25
|
+
- **Custom Caching** - Optimize performance with advanced garbage collection and cache strategies
|
|
26
|
+
- **TypeScript Support** - Full TypeScript type definitions with strict typing
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
reselect-kit requires **TypeScript 5.8 or later**.
|
|
31
|
+
|
|
32
|
+
### Using npm or yarn
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# npm
|
|
36
|
+
npm install reselect-kit
|
|
37
|
+
|
|
38
|
+
# yarn
|
|
39
|
+
yarn add reselect-kit
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Examples
|
|
43
|
+
|
|
44
|
+
### Comparing @veksa/reselect, @veksa/re-reselect, and reselect-kit
|
|
45
|
+
|
|
46
|
+
#### Problem: Working with optional nested data
|
|
47
|
+
|
|
48
|
+
Imagine we need to select a nested property from a state object that might not exist at every level.
|
|
49
|
+
|
|
50
|
+
#### @veksa/reselect implementation
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { createSelector } from '@veksa/reselect';
|
|
54
|
+
|
|
55
|
+
const getUser = (state) => state.user;
|
|
56
|
+
|
|
57
|
+
const getUserAddress = createSelector(getUser, (user) => {
|
|
58
|
+
// Need null checks at each level
|
|
59
|
+
if (user && user.contact && user.contact.address) {
|
|
60
|
+
return user.contact.address;
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Usage:
|
|
66
|
+
const address = getUserAddress(state); // May be undefined
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
With standard @veksa/reselect, you need to handle potential null/undefined values at each level with conditional checks.
|
|
70
|
+
|
|
71
|
+
#### @veksa/re-reselect solution
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { createCachedSelector } from '@veksa/re-reselect';
|
|
75
|
+
|
|
76
|
+
const getUser = (state) => state.user;
|
|
77
|
+
const getUserId = (state, userId) => userId;
|
|
78
|
+
|
|
79
|
+
// Using re-reselect for caching by userId
|
|
80
|
+
const getUserAddressByUserId = createCachedSelector(getUser, getUserId, (user, userId) => {
|
|
81
|
+
// Still need null checks for nested properties
|
|
82
|
+
const targetUser = user[userId];
|
|
83
|
+
if (targetUser && targetUser.contact && targetUser.contact.address) {
|
|
84
|
+
return targetUser.contact.address;
|
|
85
|
+
}
|
|
86
|
+
return undefined;
|
|
87
|
+
})((state, userId) => userId);
|
|
88
|
+
|
|
89
|
+
// Usage with better caching by userId:
|
|
90
|
+
const address = getUserAddressByUserId(state, '123');
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
@veksa/re-reselect adds caching benefits but still requires the same null checks for property access.
|
|
94
|
+
|
|
95
|
+
#### reselect-kit solution
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import { createPathSelector } from 'reselect-kit';
|
|
99
|
+
|
|
100
|
+
// Clean path selection with built-in null handling
|
|
101
|
+
const getUser = createPathSelector((state) => state.user);
|
|
102
|
+
|
|
103
|
+
// For parametric selectors
|
|
104
|
+
const getUserByUserId = createPathSelector((state, userId) => state.users?.[userId]);
|
|
105
|
+
|
|
106
|
+
// Usage:
|
|
107
|
+
const address = getUser(state).address(); // Safely returns undefined if path is broken
|
|
108
|
+
const userAddress = getUserByUserId(state, '123').address(); // With parameters
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
reselect-kit provides safer property access with path selectors that handle null/undefined values automatically.
|
|
112
|
+
|
|
113
|
+
### Basic Usage Patterns
|
|
114
|
+
|
|
115
|
+
#### Segment Selector
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { createSegmentSelector } from 'reselect-kit';
|
|
119
|
+
|
|
120
|
+
interface IUserStore {
|
|
121
|
+
address: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
interface IUserSegment {
|
|
125
|
+
user: IUserStore;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const defaultUser: IUserStore = { address: 'Default address' };
|
|
129
|
+
|
|
130
|
+
const getUserSegment = createSegmentSelector<IUserSegment, IUserStore>(
|
|
131
|
+
(state) => state.user,
|
|
132
|
+
defaultUser,
|
|
133
|
+
);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### Path Selector
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import { createPathSelector } from 'reselect-kit';
|
|
140
|
+
|
|
141
|
+
const state = {
|
|
142
|
+
user: {
|
|
143
|
+
address: 'Some user address',
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const getUserAddress = createPathSelector((state) => state.user).address();
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### Prop Selector
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import { createPropSelector } from 'reselect-kit';
|
|
154
|
+
|
|
155
|
+
const getUserIdFromProps = createPropSelector<{ userId: number }>().userId();
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### Bound Selector
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { createSelector } from '@veksa/reselect';
|
|
162
|
+
import { createBoundSelector } from 'reselect-kit';
|
|
163
|
+
|
|
164
|
+
const getUserByName = createSelector(
|
|
165
|
+
(state) => state.users,
|
|
166
|
+
(state, props) => props.userName,
|
|
167
|
+
(users, userName) => users[userName],
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const getAdmin = createBoundSelector(getUserByName, { userName: 'admin' });
|
|
171
|
+
|
|
172
|
+
// Usage:
|
|
173
|
+
const admin = getAdmin(state); // Same as getUserByName(state, {userName: 'admin'})
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
#### Adapted Selector
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
import { createSelector } from '@veksa/reselect';
|
|
180
|
+
import { createAdaptedSelector } from 'reselect-kit';
|
|
181
|
+
|
|
182
|
+
const getUserByNameAndRole = createSelector(
|
|
183
|
+
(state) => state.users,
|
|
184
|
+
(state, props) => props.userName,
|
|
185
|
+
(state, props) => props.userRole,
|
|
186
|
+
(users, userName, userRole) => users[userName][userRole],
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const getAdmin = createAdaptedSelector(getUserByNameAndRole, (props) => ({
|
|
190
|
+
userName: props.userName,
|
|
191
|
+
userRole: 'admin',
|
|
192
|
+
}));
|
|
193
|
+
|
|
194
|
+
// Usage:
|
|
195
|
+
const admin = getAdmin(state); // Same as getUserByName(state, {userName: 'admin'})
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
#### Chain Selectors
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
import { createChainSelector } from 'reselect-kit';
|
|
202
|
+
|
|
203
|
+
// Create a base selector
|
|
204
|
+
const getUserData = (state) => state.users;
|
|
205
|
+
|
|
206
|
+
// Build a selector chain
|
|
207
|
+
const getActiveUserEmails = createChainSelector(getUserData)
|
|
208
|
+
.map((users) => users.filter((user) => user.active))
|
|
209
|
+
.map((activeUsers) => activeUsers.map((user) => user.email))
|
|
210
|
+
.build();
|
|
211
|
+
|
|
212
|
+
// Usage:
|
|
213
|
+
const activeEmails = getActiveUserEmails(state); // ['alice@example.com', 'bob@example.com']
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Advanced Examples
|
|
217
|
+
|
|
218
|
+
### Using Structured Selectors
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
import { createCachedStructuredSelector } from 'reselect-kit';
|
|
222
|
+
|
|
223
|
+
const getUserProfile = createCachedStructuredSelector({
|
|
224
|
+
name: (state) => state.user.name,
|
|
225
|
+
email: (state) => state.user.email,
|
|
226
|
+
address: createPathSelector(
|
|
227
|
+
(state) => state.user,
|
|
228
|
+
(user) => user?.contact?.address,
|
|
229
|
+
),
|
|
230
|
+
})((state, userId) => userId);
|
|
231
|
+
|
|
232
|
+
// Usage:
|
|
233
|
+
const profile = getUserProfile(state, '123');
|
|
234
|
+
// Returns: { name: 'John', email: 'john@example.com', address: { ... } }
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Key Selector Composition
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
import { stringComposeKeySelectors } from 'reselect-kit';
|
|
241
|
+
|
|
242
|
+
// Compose several key selectors into a single one. `stringComposeKeySelectors`
|
|
243
|
+
// joins the composed keys with `:`.
|
|
244
|
+
const keySelector = stringComposeKeySelectors(
|
|
245
|
+
(state, props) => props.userId,
|
|
246
|
+
(state, props) => props.view,
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
// Usage:
|
|
250
|
+
// keySelector(state, { userId: '123', view: 'details' }) returns '123:details'
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## API Reference
|
|
254
|
+
|
|
255
|
+
### Core Functions
|
|
256
|
+
|
|
257
|
+
#### createChainSelector
|
|
258
|
+
|
|
259
|
+
Creates a selector that can be chained with map and chain methods.
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
createChainSelector(baseSelector).map(transformFn).build();
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
##### ChainSelector Methods
|
|
266
|
+
|
|
267
|
+
###### chain
|
|
268
|
+
|
|
269
|
+
Transforms the output of the previous selector by creating a new selector based on its result. This is where the real power of chain selectors comes in, allowing composition of selectors where the output of one becomes the context for creating the next.
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
chain<S2, R2>(fn: (result: R1) => Selector<S2, R2>, options?: ChainSelectorOptions): SelectorMonad
|
|
273
|
+
chain<S2, P2, R2>(fn: (result: R1) => Selector<S2, R2, [P2]>, options?: ChainSelectorOptions): SelectorMonad
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
- **fn** - Function that receives the previous selector's result and returns a new selector
|
|
277
|
+
- **options** - Optional configuration for caching and key selection
|
|
278
|
+
|
|
279
|
+
**Examples:**
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
// Basic chaining - transform one selector's result into another selector
|
|
283
|
+
const userWithDetails = createChainSelector((state) => state.users)
|
|
284
|
+
.chain((users) => (state) => {
|
|
285
|
+
// This function receives the users result and returns a new selector
|
|
286
|
+
const userIds = Object.keys(users);
|
|
287
|
+
return userIds.map((id) => state.userDetails[id]);
|
|
288
|
+
})
|
|
289
|
+
.build();
|
|
290
|
+
|
|
291
|
+
// Parametric selectors with chain
|
|
292
|
+
const getUserPostsById = createChainSelector(propSelector)
|
|
293
|
+
.chain((props) => {
|
|
294
|
+
// Use the props to create a targeted selector
|
|
295
|
+
return createSelector(
|
|
296
|
+
(state) => state.users[props.userId],
|
|
297
|
+
(state) => state.posts,
|
|
298
|
+
(user, posts) => posts.filter((post) => post.authorId === user.id),
|
|
299
|
+
);
|
|
300
|
+
})
|
|
301
|
+
.build();
|
|
302
|
+
|
|
303
|
+
// Multiple chains
|
|
304
|
+
const getUserStats = createChainSelector((state) => state.users)
|
|
305
|
+
.chain((users) => (state) => Object.values(users).filter((user) => user.active))
|
|
306
|
+
.chain((activeUsers) => (state) => ({
|
|
307
|
+
activeCount: activeUsers.length,
|
|
308
|
+
totalCount: Object.keys(state.users).length,
|
|
309
|
+
activeRatio: activeUsers.length / Object.keys(state.users).length,
|
|
310
|
+
}))
|
|
311
|
+
.build();
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
###### map
|
|
315
|
+
|
|
316
|
+
Transforms the output of the selector with a simple transformation function. Unlike `chain`, `map` doesn't create a new selector but just transforms the result of the current one.
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
map<R2>(fn: (result: R1) => R2, options?: ChainSelectorOptions): SelectorMonad
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
###### build
|
|
323
|
+
|
|
324
|
+
Completes the chain and returns the final selector function that can be used in components.
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
build(): Selector<S, R, Params>
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
#### createPathSelector
|
|
331
|
+
|
|
332
|
+
Creates a selector that safely accesses nested properties.
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
createPathSelector(sourceSelector, pathFn);
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
#### createBoundSelector
|
|
339
|
+
|
|
340
|
+
Creates a selector with predefined parameters.
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
createBoundSelector(selector, getParams);
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
#### createAdaptedSelector
|
|
347
|
+
|
|
348
|
+
Adapts a selector to work with a different parameter shape.
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
createAdaptedSelector(selector, paramsAdapter);
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
#### createCachedStructuredSelector
|
|
355
|
+
|
|
356
|
+
Creates a structured selector with caching support.
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
createCachedStructuredSelector(selectors)(keySelector);
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
#### createEmptySelector
|
|
363
|
+
|
|
364
|
+
Creates a selector that always returns `undefined` regardless of input. Useful as a placeholder or for conditional selection logic.
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
const emptySelector = createEmptySelector(baseSelector);
|
|
368
|
+
// Always returns undefined while maintaining the original selector's signature
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
#### createPropSelector
|
|
372
|
+
|
|
373
|
+
Creates a selector that returns the props passed to it, enabling strongly-typed access to props in selector chains.
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
const propSelector = createPropSelector();
|
|
377
|
+
// Usage: propSelector(state, props) returns props
|
|
378
|
+
|
|
379
|
+
// In a chain
|
|
380
|
+
const userByIdSelector = createChainSelector(propSelector)
|
|
381
|
+
.chain((props) =>
|
|
382
|
+
createPathSelector(
|
|
383
|
+
(state) => state.users[props.userId],
|
|
384
|
+
(user) => user,
|
|
385
|
+
),
|
|
386
|
+
)
|
|
387
|
+
.build();
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
#### createSegmentSelector
|
|
391
|
+
|
|
392
|
+
Creates a selector with a default/initial value when the selection returns null or undefined.
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
const getUserSettings = createSegmentSelector(
|
|
396
|
+
(state) => state.userSettings,
|
|
397
|
+
{ theme: 'light', notifications: true }, // Default value if userSettings is null/undefined
|
|
398
|
+
);
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
#### createSequenceSelector
|
|
402
|
+
|
|
403
|
+
Creates a selector that returns an array of results from multiple selectors.
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
const getUserStats = createSequenceSelector([
|
|
407
|
+
(state) => state.user.postsCount,
|
|
408
|
+
(state) => state.user.followersCount,
|
|
409
|
+
(state) => state.user.likesCount,
|
|
410
|
+
]);
|
|
411
|
+
|
|
412
|
+
// Returns [postsCount, followersCount, likesCount]
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Cache Objects
|
|
416
|
+
|
|
417
|
+
#### TreeCache
|
|
418
|
+
|
|
419
|
+
Implements a hierarchical cache structure for nested key support. Unlike flat cache objects, TreeCache allows for efficiently caching and retrieving selectors with complex, multi-part keys.
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
import { TreeCache } from 'reselect-kit';
|
|
423
|
+
|
|
424
|
+
// Create a TreeCache instance
|
|
425
|
+
const cache = new TreeCache({
|
|
426
|
+
cacheObjectCreator: () => new FlatObjectCache(), // Optional custom cache factory
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
// Use with complex keys (automatically normalized to arrays)
|
|
430
|
+
const complexKey = ['user', 123, 'profile'];
|
|
431
|
+
cache.set(complexKey, selectorInstance);
|
|
432
|
+
const selector = cache.get(complexKey);
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
#### IntervalMapCache and Garbage Collection
|
|
436
|
+
|
|
437
|
+
A time-based cache implementation with automatic garbage collection for unused selectors to prevent memory leaks.
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
import { IntervalMapCache, initGarbageCollector } from 'reselect-kit';
|
|
441
|
+
|
|
442
|
+
// Initialize garbage collector to clean up stale cache entries
|
|
443
|
+
initGarbageCollector();
|
|
444
|
+
|
|
445
|
+
// Create a cache that automatically manages memory
|
|
446
|
+
const cache = new IntervalMapCache();
|
|
447
|
+
|
|
448
|
+
// Items not accessed within the cache lifetime will be automatically purged
|
|
449
|
+
cache.set('key', selectorData);
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Comparing with @veksa/reselect and @veksa/re-reselect
|
|
453
|
+
|
|
454
|
+
| Feature | @veksa/reselect | @veksa/re-reselect | reselect-kit |
|
|
455
|
+
| --------------------------- | --------------- | ------------------ | -------------------- |
|
|
456
|
+
| Basic memoization | ✓ | ✓ | ✓ (via dependencies) |
|
|
457
|
+
| Parametric memoization | × | ✓ | ✓ (via dependencies) |
|
|
458
|
+
| Safe nested property access | × | × | ✓ |
|
|
459
|
+
| Fluent selector chains | × | × | ✓ |
|
|
460
|
+
| Parameter binding | × | × | ✓ |
|
|
461
|
+
| Parameter adaptation | × | × | ✓ |
|
|
462
|
+
| Advanced key composition | × | Limited | ✓ |
|
|
463
|
+
| Cache garbage collection | × | × | ✓ |
|
|
464
|
+
| Hierarchical caching | × | × | ✓ |
|
|
465
|
+
| Typescript support | ✓ | ✓ | ✓ (enhanced) |
|
|
466
|
+
|
|
467
|
+
## Contributing
|
|
468
|
+
|
|
469
|
+
This project welcomes contributions and suggestions. Feel free to submit a Pull Request.
|
|
470
|
+
|
|
471
|
+
## License
|
|
472
|
+
|
|
473
|
+
[MIT](LICENSE.md)
|