reselect-kit 1.0.1 → 1.1.1
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/README.md +307 -36
- package/dist/cjs/reselectKit.development.cjs +256 -95
- package/dist/cjs/reselectKit.development.cjs.map +1 -1
- package/dist/cjs/reselectKit.production.min.cjs +1 -1
- package/dist/cjs/reselectKit.production.min.cjs.map +1 -1
- package/dist/reselectKit.d.ts +201 -92
- package/dist/reselectKit.mjs +257 -97
- package/dist/reselectKit.mjs.map +1 -1
- package/package.json +6 -6
- package/src/__tests__/createAdaptedSelector.test.ts +3 -4
- package/src/__tests__/createBoundSelector.test.ts +98 -5
- package/src/__tests__/createChainSelector.bench.ts +117 -0
- package/src/__tests__/createChainSelector.test.ts +162 -6
- package/src/__tests__/createPathSelector.test.ts +14 -0
- package/src/__tests__/createSequenceSelector.test.ts +10 -14
- package/src/_helpers/__tests__/generateSelectorKey.test.ts +24 -0
- package/src/_helpers/__tests__/getSelectorName.test.ts +30 -0
- package/src/_helpers/unfreezeProps.ts +14 -0
- package/src/_hooks/__tests__/useSelector.bench.ts +133 -0
- package/src/_hooks/__tests__/useSelector.test.ts +69 -0
- package/src/_hooks/__tests__/useSelectors.bench.ts +160 -0
- package/src/_hooks/__tests__/useSelectors.test.ts +219 -0
- package/src/_hooks/selectorOptions.ts +11 -0
- package/src/_hooks/useSelector.ts +68 -26
- package/src/_hooks/useSelectors.ts +162 -0
- package/src/cache/__tests__/intervalMapCache.test.ts +11 -0
- package/src/createAdaptedSelector.ts +9 -5
- package/src/createBoundSelector.ts +92 -84
- package/src/createCachedSequenceSelector.ts +12 -8
- package/src/createCachedStructuredSelector.ts +11 -20
- package/src/createChainSelector.ts +134 -89
- package/src/createPathSelector.ts +71 -104
- package/src/createPropSelector.ts +9 -8
- package/src/createSequenceSelector.ts +14 -9
- package/src/index.ts +2 -0
- package/src/keys/createKeySelectorComposer.ts +12 -23
- package/src/types.ts +115 -7
package/README.md
CHANGED
|
@@ -21,13 +21,15 @@ A powerful toolkit that extends the capabilities of @veksa/reselect and @veksa/r
|
|
|
21
21
|
- **Bound Selectors** - Create selectors with predefined parameters for reuse across components
|
|
22
22
|
- **Adapted Selectors** - Transform selector parameters to simplify composition and reuse
|
|
23
23
|
- **Enhanced Structured Selectors** - Better typed alternatives to createStructuredSelector with caching support
|
|
24
|
+
- **Batched Store Reads** - Read a whole group of selectors through a single store subscription with `useSelectors`
|
|
24
25
|
- **Key Selector Composition** - Compose key selectors for complex cache keys in re-reselect
|
|
25
26
|
- **Custom Caching** - Optimize performance with advanced garbage collection and cache strategies
|
|
26
27
|
- **TypeScript Support** - Full TypeScript type definitions with strict typing
|
|
27
28
|
|
|
28
29
|
## Installation
|
|
29
30
|
|
|
30
|
-
reselect-kit requires **TypeScript 5.
|
|
31
|
+
reselect-kit requires **TypeScript 5.4 or later** — the floor the type tests run
|
|
32
|
+
against on every commit, together with every minor up to 7.0.
|
|
31
33
|
|
|
32
34
|
### Using npm or yarn
|
|
33
35
|
|
|
@@ -39,6 +41,37 @@ npm install reselect-kit
|
|
|
39
41
|
yarn add reselect-kit
|
|
40
42
|
```
|
|
41
43
|
|
|
44
|
+
### Upgrading from 1.0.x
|
|
45
|
+
|
|
46
|
+
Props are no longer a type parameter of their own: a selector is typed by the
|
|
47
|
+
tuple of arguments it takes after `state`, and a parametric selector is simply
|
|
48
|
+
one whose tuple is not empty. Call sites that let the types be inferred are
|
|
49
|
+
unaffected. Ones that spell the type arguments out move the props into the tuple:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// before
|
|
53
|
+
useSelector<State, PersonProps, Person>(getPerson, { personId: 1 });
|
|
54
|
+
createPathSelector<State, Person, PersonProps>(getPerson);
|
|
55
|
+
createChainSelector<State, PersonProps, Person>(getPerson);
|
|
56
|
+
|
|
57
|
+
// after
|
|
58
|
+
useSelector<State, Person, [PersonProps]>(getPerson, { personId: 1 });
|
|
59
|
+
createPathSelector<State, Person, [PersonProps]>(getPerson);
|
|
60
|
+
createChainSelector<State, Person, [PersonProps]>(getPerson);
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`createCachedSequenceSelector` infers everything from the array it is given, so
|
|
64
|
+
its type arguments cannot be written out at all — annotate `state` in the
|
|
65
|
+
selectors instead.
|
|
66
|
+
|
|
67
|
+
Two more consequences of the same change:
|
|
68
|
+
|
|
69
|
+
- a sequence returns a tuple of the individual result types rather than `R[]`,
|
|
70
|
+
which is what makes each position keep its own type;
|
|
71
|
+
- a chain step that reads props now makes them required for the whole built
|
|
72
|
+
selector even when the base selector had none. That type used to be dropped,
|
|
73
|
+
so this surfaces as a new error on code that was already passing the props.
|
|
74
|
+
|
|
42
75
|
## Examples
|
|
43
76
|
|
|
44
77
|
### Comparing @veksa/reselect, @veksa/re-reselect, and reselect-kit
|
|
@@ -101,11 +134,11 @@ import { createPathSelector } from 'reselect-kit';
|
|
|
101
134
|
const getUser = createPathSelector((state) => state.user);
|
|
102
135
|
|
|
103
136
|
// For parametric selectors
|
|
104
|
-
const getUserByUserId = createPathSelector((state,
|
|
137
|
+
const getUserByUserId = createPathSelector((state, props) => state.users?.[props.userId]);
|
|
105
138
|
|
|
106
|
-
//
|
|
107
|
-
const address = getUser
|
|
108
|
-
const userAddress = getUserByUserId(state, '123')
|
|
139
|
+
// Walking the path builds a selector; calling it reads the store
|
|
140
|
+
const address = getUser.address()(state); // Safely returns undefined if path is broken
|
|
141
|
+
const userAddress = getUserByUserId.address()(state, { userId: '123' }); // With parameters
|
|
109
142
|
```
|
|
110
143
|
|
|
111
144
|
reselect-kit provides safer property access with path selectors that handle null/undefined values automatically.
|
|
@@ -186,13 +219,14 @@ const getUserByNameAndRole = createSelector(
|
|
|
186
219
|
(users, userName, userRole) => users[userName][userRole],
|
|
187
220
|
);
|
|
188
221
|
|
|
189
|
-
const getAdmin = createAdaptedSelector(getUserByNameAndRole, (props) => ({
|
|
190
|
-
userName: props.
|
|
222
|
+
const getAdmin = createAdaptedSelector(getUserByNameAndRole, (props: { name: string }) => ({
|
|
223
|
+
userName: props.name,
|
|
191
224
|
userRole: 'admin',
|
|
192
225
|
}));
|
|
193
226
|
|
|
194
|
-
// Usage:
|
|
195
|
-
const admin = getAdmin(state
|
|
227
|
+
// Usage: the adapted selector takes the props the mapping accepts
|
|
228
|
+
const admin = getAdmin(state, { name: 'alice' });
|
|
229
|
+
// Same as getUserByNameAndRole(state, {userName: 'alice', userRole: 'admin'})
|
|
196
230
|
```
|
|
197
231
|
|
|
198
232
|
#### Chain Selectors
|
|
@@ -223,17 +257,46 @@ import { createCachedStructuredSelector } from 'reselect-kit';
|
|
|
223
257
|
const getUserProfile = createCachedStructuredSelector({
|
|
224
258
|
name: (state) => state.user.name,
|
|
225
259
|
email: (state) => state.user.email,
|
|
226
|
-
address: createPathSelector(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
})((state, userId) => userId);
|
|
260
|
+
address: createPathSelector((state) => state.user).contact.address(),
|
|
261
|
+
})({
|
|
262
|
+
keySelector: (state, props) => props.userId,
|
|
263
|
+
});
|
|
231
264
|
|
|
232
265
|
// Usage:
|
|
233
|
-
const profile = getUserProfile(state, '123');
|
|
266
|
+
const profile = getUserProfile(state, { userId: '123' });
|
|
234
267
|
// Returns: { name: 'John', email: 'john@example.com', address: { ... } }
|
|
235
268
|
```
|
|
236
269
|
|
|
270
|
+
### Reading a Batch of Selectors in a Component
|
|
271
|
+
|
|
272
|
+
The same structural idea at the call site: `useSelectors` takes an object of
|
|
273
|
+
selectors and reads them all through one store subscription.
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
import { useSelectors } from 'reselect-kit';
|
|
277
|
+
|
|
278
|
+
const QuoteRow = ({ symbol }) => {
|
|
279
|
+
const { bid, ask, volume } = useSelectors({ bid: getBid, ask: getAsk, volume: getVolume }, { symbol });
|
|
280
|
+
|
|
281
|
+
return <Row bid={bid} ask={ask} volume={volume} />;
|
|
282
|
+
};
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Which of the two to use is a question of where the memoization should live, not
|
|
286
|
+
of speed — they perform the same:
|
|
287
|
+
|
|
288
|
+
| | `createCachedStructuredSelector` | `useSelectors` |
|
|
289
|
+
| ------------ | --------------------------------------------- | -------------------------------- |
|
|
290
|
+
| What it is | a selector: composable, usable outside React | a read at the call site only |
|
|
291
|
+
| Memoization | one module-level cache, keyed | one slot per component instance |
|
|
292
|
+
| Cache key | a key selector is required | none |
|
|
293
|
+
| Cache sizing | must hold every live row, or it thrashes | dies with the component |
|
|
294
|
+
| Sharing | two components with the same key compute once | every component computes its own |
|
|
295
|
+
|
|
296
|
+
Reuse the batch elsewhere, or feed it into another selector — build a structured
|
|
297
|
+
selector. If the batch is simply "what this component reads", reach for the hook
|
|
298
|
+
and skip the key selector and the cache sizing.
|
|
299
|
+
|
|
237
300
|
### Key Selector Composition
|
|
238
301
|
|
|
239
302
|
```typescript
|
|
@@ -269,13 +332,19 @@ createChainSelector(baseSelector).map(transformFn).build();
|
|
|
269
332
|
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
333
|
|
|
271
334
|
```typescript
|
|
272
|
-
chain<S2, R2>(
|
|
273
|
-
|
|
335
|
+
chain<S2, R2, Params2>(
|
|
336
|
+
fn: (result: R1) => (state: S2, ...params: Params2) => R2,
|
|
337
|
+
options?: ChainSelectorOptions,
|
|
338
|
+
): ChainSelectorResult
|
|
274
339
|
```
|
|
275
340
|
|
|
276
341
|
- **fn** - Function that receives the previous selector's result and returns a new selector
|
|
277
342
|
- **options** - Optional configuration for caching and key selection
|
|
278
343
|
|
|
344
|
+
A step reading props adds them to what the built selector requires, whether or
|
|
345
|
+
not the base selector had any: the arguments of every level are merged rather
|
|
346
|
+
than the level having to match the base.
|
|
347
|
+
|
|
279
348
|
**Examples:**
|
|
280
349
|
|
|
281
350
|
```typescript
|
|
@@ -319,6 +388,29 @@ Transforms the output of the selector with a simple transformation function. Unl
|
|
|
319
388
|
map<R2>(fn: (result: R1) => R2, options?: ChainSelectorOptions): SelectorMonad
|
|
320
389
|
```
|
|
321
390
|
|
|
391
|
+
###### Keyed levels
|
|
392
|
+
|
|
393
|
+
A level only gets a keyed cache when its key can actually vary. Where the key is
|
|
394
|
+
a constant, the cache would hold a single entry for the lifetime of the chain
|
|
395
|
+
while every call still paid for the key computation and the lookup guarding it,
|
|
396
|
+
so the level is built as a plain memoized selector instead — the same mapping
|
|
397
|
+
without the layer.
|
|
398
|
+
|
|
399
|
+
What each level knows about its key differs:
|
|
400
|
+
|
|
401
|
+
- a `map` level derives a plain `() => output`, never a cached selector, so its
|
|
402
|
+
key is its base's own and is composed once at build time. A chain of `map`s
|
|
403
|
+
over a plain (uncached) selector composes to the default key, and the built
|
|
404
|
+
selector then carries neither `keySelector` nor `cache`;
|
|
405
|
+
- a `chain` level cannot know its key at build time, because the selector it
|
|
406
|
+
derives exists only per call and may itself be cached — chaining a bound
|
|
407
|
+
cached selector off a plain base is the ordinary case. So it always carries a
|
|
408
|
+
`keySelector` that resolves the derived selector's key. Over an uncached base
|
|
409
|
+
that key evaluates to the default, but it is not the default _selector_, so a
|
|
410
|
+
level built on top of it does take the keyed path and holds a single entry.
|
|
411
|
+
|
|
412
|
+
Chaining off a cached selector is where those caches earn their keep.
|
|
413
|
+
|
|
322
414
|
###### build
|
|
323
415
|
|
|
324
416
|
Completes the chain and returns the final selector function that can be used in components.
|
|
@@ -329,43 +421,56 @@ build(): Selector<S, R, Params>
|
|
|
329
421
|
|
|
330
422
|
#### createPathSelector
|
|
331
423
|
|
|
332
|
-
Creates a selector that safely accesses nested properties.
|
|
424
|
+
Creates a selector that safely accesses nested properties. Property access walks
|
|
425
|
+
the path and the call terminates it, producing the selector.
|
|
333
426
|
|
|
334
427
|
```typescript
|
|
335
|
-
createPathSelector(sourceSelector
|
|
428
|
+
createPathSelector(sourceSelector).some.nested.path();
|
|
336
429
|
```
|
|
337
430
|
|
|
338
431
|
#### createBoundSelector
|
|
339
432
|
|
|
340
|
-
Creates a selector with predefined
|
|
433
|
+
Creates a selector with predefined props. Whatever the binding does not supply
|
|
434
|
+
stays required; a binding covering every prop leaves a plain selector.
|
|
341
435
|
|
|
342
436
|
```typescript
|
|
343
|
-
createBoundSelector(selector,
|
|
437
|
+
createBoundSelector(selector, binding);
|
|
344
438
|
```
|
|
345
439
|
|
|
346
440
|
#### createAdaptedSelector
|
|
347
441
|
|
|
348
|
-
Adapts a selector to work with a different
|
|
442
|
+
Adapts a selector to work with a different props shape. The adapted selector
|
|
443
|
+
takes the props the mapping accepts, in place of the ones the base selector reads.
|
|
349
444
|
|
|
350
445
|
```typescript
|
|
351
|
-
createAdaptedSelector(selector,
|
|
446
|
+
createAdaptedSelector(selector, propsAdapter);
|
|
352
447
|
```
|
|
353
448
|
|
|
354
449
|
#### createCachedStructuredSelector
|
|
355
450
|
|
|
356
|
-
Creates a structured selector with caching support.
|
|
451
|
+
Creates a structured selector with caching support. The second call takes either
|
|
452
|
+
re-reselect's options object or the key selector on its own.
|
|
357
453
|
|
|
358
454
|
```typescript
|
|
359
|
-
createCachedStructuredSelector(selectors)(keySelector);
|
|
455
|
+
createCachedStructuredSelector(selectors)({ keySelector });
|
|
360
456
|
```
|
|
361
457
|
|
|
458
|
+
The key selector is typed from the batch, so its `state` and props are the ones
|
|
459
|
+
the batch reads.
|
|
460
|
+
|
|
461
|
+
The second call needs a way to derive the key: a `keySelector`, given on its own
|
|
462
|
+
or inside the options, or a `keySelectorCreator` that produces one. re-reselect
|
|
463
|
+
types both as optional, so an options object carrying neither compiles and then
|
|
464
|
+
throws on the first read.
|
|
465
|
+
|
|
362
466
|
#### createEmptySelector
|
|
363
467
|
|
|
364
468
|
Creates a selector that always returns `undefined` regardless of input. Useful as a placeholder or for conditional selection logic.
|
|
365
469
|
|
|
366
470
|
```typescript
|
|
367
471
|
const emptySelector = createEmptySelector(baseSelector);
|
|
368
|
-
// Always returns undefined
|
|
472
|
+
// Always returns undefined; the result widens to `R | undefined` and the
|
|
473
|
+
// selector takes whatever arguments it is called with
|
|
369
474
|
```
|
|
370
475
|
|
|
371
476
|
#### createPropSelector
|
|
@@ -378,12 +483,7 @@ const propSelector = createPropSelector();
|
|
|
378
483
|
|
|
379
484
|
// In a chain
|
|
380
485
|
const userByIdSelector = createChainSelector(propSelector)
|
|
381
|
-
.chain((props) =>
|
|
382
|
-
createPathSelector(
|
|
383
|
-
(state) => state.users[props.userId],
|
|
384
|
-
(user) => user,
|
|
385
|
-
),
|
|
386
|
-
)
|
|
486
|
+
.chain((props) => createPathSelector((state) => state.users[props.userId]).profile())
|
|
387
487
|
.build();
|
|
388
488
|
```
|
|
389
489
|
|
|
@@ -400,7 +500,10 @@ const getUserSettings = createSegmentSelector(
|
|
|
400
500
|
|
|
401
501
|
#### createSequenceSelector
|
|
402
502
|
|
|
403
|
-
|
|
503
|
+
Runs a group of selectors and collects their results positionally. The result is
|
|
504
|
+
a tuple of the individual result types, and `state` and the extra arguments are
|
|
505
|
+
merged across the group, so a single parametric member makes props required for
|
|
506
|
+
the whole sequence.
|
|
404
507
|
|
|
405
508
|
```typescript
|
|
406
509
|
const getUserStats = createSequenceSelector([
|
|
@@ -409,7 +512,163 @@ const getUserStats = createSequenceSelector([
|
|
|
409
512
|
(state) => state.user.likesCount,
|
|
410
513
|
]);
|
|
411
514
|
|
|
412
|
-
//
|
|
515
|
+
// getUserStats(state) returns [postsCount, followersCount, likesCount]
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
#### createCachedSequenceSelector
|
|
519
|
+
|
|
520
|
+
The cached counterpart of `createSequenceSelector`: the second call takes
|
|
521
|
+
re-reselect's options, with the key selector typed from the group.
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
const getPostStats = createCachedSequenceSelector([
|
|
525
|
+
(state, props) => state.posts[props.postId].likes,
|
|
526
|
+
(state, props) => state.posts[props.postId].comments,
|
|
527
|
+
])({
|
|
528
|
+
keySelector: (state, props) => props.postId,
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
// getPostStats(state, { postId: 1 }) returns [likes, comments]
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### Hooks
|
|
535
|
+
|
|
536
|
+
#### useSelector
|
|
537
|
+
|
|
538
|
+
Reads one selector from the store. Props are compared shallowly, so an inline
|
|
539
|
+
object literal does not invalidate the selector on every render.
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
const price = useSelector(getPrice, { symbol });
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
#### useSelectors
|
|
546
|
+
|
|
547
|
+
Reads a batch of selectors through a **single** store subscription and returns
|
|
548
|
+
their results as one object.
|
|
549
|
+
|
|
550
|
+
```typescript
|
|
551
|
+
const { symbol, bid, ask } = useSelectors(
|
|
552
|
+
{ symbol: getSymbol, bid: getBid, ask: getAsk },
|
|
553
|
+
{ symbol },
|
|
554
|
+
);
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
The props argument follows the batch: it is rejected when no selector takes
|
|
558
|
+
props, required as soon as one requires them, and optional when every selector
|
|
559
|
+
that reads props declares them optional. The props of the batch are intersected,
|
|
560
|
+
so one object satisfies every member.
|
|
561
|
+
|
|
562
|
+
Every member is read as `selector(state, props)`, so a selector that requires a
|
|
563
|
+
third argument does not belong in a batch — the batch has nothing to pass for it
|
|
564
|
+
and it arrives as `undefined`.
|
|
565
|
+
|
|
566
|
+
Seven `useSelector` calls in a row component mean seven store listeners, seven
|
|
567
|
+
`useSyncExternalStore` instances and seven snapshot comparisons per dispatch;
|
|
568
|
+
`useSelectors` collapses them into one of each. The result object keeps its
|
|
569
|
+
reference while every field is unchanged, so an update that touches nothing the
|
|
570
|
+
row reads does not re-render it.
|
|
571
|
+
|
|
572
|
+
The trade-off against separate hooks is granularity: any tracked state segment
|
|
573
|
+
changing re-runs the whole batch, which for memoized selectors is a cache hit
|
|
574
|
+
per field. See [Reading a Batch of Selectors in a Component](#reading-a-batch-of-selectors-in-a-component)
|
|
575
|
+
for how this compares with a structured selector.
|
|
576
|
+
|
|
577
|
+
### Key Selectors
|
|
578
|
+
|
|
579
|
+
#### stringComposeKeySelectors
|
|
580
|
+
|
|
581
|
+
Composes several key selectors into one that joins their keys with `:`. The
|
|
582
|
+
result carries its inputs as `dependencies`, which is what lets
|
|
583
|
+
`createBoundSelector` drop the key selectors whose props it has bound.
|
|
584
|
+
|
|
585
|
+
```typescript
|
|
586
|
+
const keySelector = stringComposeKeySelectors(
|
|
587
|
+
(state, props) => props.userId,
|
|
588
|
+
(state, props) => props.view,
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
// keySelector(state, { userId: '123', view: 'details' }) returns '123:details'
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
#### arrayComposeKeySelectors
|
|
595
|
+
|
|
596
|
+
The same composition, producing an array key instead of a string — for cache
|
|
597
|
+
objects that take multi-part keys, such as [TreeCache](#treecache). Nested array
|
|
598
|
+
keys are flattened.
|
|
599
|
+
|
|
600
|
+
```typescript
|
|
601
|
+
const keySelector = arrayComposeKeySelectors(
|
|
602
|
+
(state, props) => props.userId,
|
|
603
|
+
(state, props) => props.view,
|
|
604
|
+
);
|
|
605
|
+
|
|
606
|
+
// keySelector(state, { userId: '123', view: 'details' }) returns ['123', 'details']
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
#### createKeySelectorComposer
|
|
610
|
+
|
|
611
|
+
Builds a composer of your own from a function that reduces N key selectors into
|
|
612
|
+
one. Both composers above are built with it.
|
|
613
|
+
|
|
614
|
+
```typescript
|
|
615
|
+
const commaComposeKeySelectors = createKeySelectorComposer(
|
|
616
|
+
(...keySelectors) =>
|
|
617
|
+
(state, props) =>
|
|
618
|
+
keySelectors.map((keySelector) => keySelector(state, props)).join(),
|
|
619
|
+
);
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
#### createKeySelectorCreator
|
|
623
|
+
|
|
624
|
+
Builds the `keySelectorCreator` that re-reselect calls to derive a selector's key
|
|
625
|
+
from its inputs: it collects the key selectors of every cached input, flattens
|
|
626
|
+
already-composed ones, drops duplicates and the default key, and composes what is
|
|
627
|
+
left with the given composer.
|
|
628
|
+
|
|
629
|
+
```typescript
|
|
630
|
+
const fullNameSelector = createCachedSelector(
|
|
631
|
+
[personByMessageIdSelector],
|
|
632
|
+
toFullName,
|
|
633
|
+
)({
|
|
634
|
+
keySelectorCreator: createKeySelectorCreator(stringComposeKeySelectors),
|
|
635
|
+
});
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
A chain builds its own creator from the `keySelectorComposer` it was given, so
|
|
639
|
+
inside a chain the composer is what you configure, not the creator.
|
|
640
|
+
|
|
641
|
+
#### defaultKeySelector
|
|
642
|
+
|
|
643
|
+
The key of a selector that has nothing to key on — a single constant. Key
|
|
644
|
+
composition drops it, so a chain whose key cannot vary carries no cache at all.
|
|
645
|
+
|
|
646
|
+
#### ChainSelectorOptions
|
|
647
|
+
|
|
648
|
+
The options every chain level accepts, either on `createChainSelector` itself or
|
|
649
|
+
per `chain` / `map` call. A per-level object overrides what the chain was created
|
|
650
|
+
with, and the merged result carries to the levels after it.
|
|
651
|
+
|
|
652
|
+
```typescript
|
|
653
|
+
type ChainSelectorOptions = {
|
|
654
|
+
// called per level, so each one gets its own cache object
|
|
655
|
+
createSelectorOptions?: () => { selectorCreator; cacheObject };
|
|
656
|
+
keySelectorComposer?: KeySelectorComposer;
|
|
657
|
+
};
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
### Debugging
|
|
661
|
+
|
|
662
|
+
#### setDebugMode / isDebugMode
|
|
663
|
+
|
|
664
|
+
Debug mode makes every creator tag the selectors it builds with a readable
|
|
665
|
+
`selectorName`, composed from the names of what they were built from. It is off
|
|
666
|
+
by default, since the names cost string work on every build.
|
|
667
|
+
|
|
668
|
+
```typescript
|
|
669
|
+
import { setDebugMode } from 'reselect-kit';
|
|
670
|
+
|
|
671
|
+
setDebugMode(process.env.NODE_ENV !== 'production');
|
|
413
672
|
```
|
|
414
673
|
|
|
415
674
|
### Cache Objects
|
|
@@ -418,12 +677,17 @@ const getUserStats = createSequenceSelector([
|
|
|
418
677
|
|
|
419
678
|
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
679
|
|
|
680
|
+
Pair it with [arrayComposeKeySelectors](#arraycomposekeyselectors), which is what
|
|
681
|
+
produces the multi-part keys it exists for.
|
|
682
|
+
|
|
421
683
|
```typescript
|
|
684
|
+
import { FlatObjectCache } from '@veksa/re-reselect';
|
|
422
685
|
import { TreeCache } from 'reselect-kit';
|
|
423
686
|
|
|
424
|
-
//
|
|
687
|
+
// The options object is required; `cacheObjectCreator` inside it is not, and
|
|
688
|
+
// defaults to a FlatObjectCache per level
|
|
425
689
|
const cache = new TreeCache({
|
|
426
|
-
cacheObjectCreator: () => new FlatObjectCache(),
|
|
690
|
+
cacheObjectCreator: () => new FlatObjectCache(),
|
|
427
691
|
});
|
|
428
692
|
|
|
429
693
|
// Use with complex keys (automatically normalized to arrays)
|
|
@@ -449,6 +713,11 @@ const cache = new IntervalMapCache();
|
|
|
449
713
|
cache.set('key', selectorData);
|
|
450
714
|
```
|
|
451
715
|
|
|
716
|
+
The sweep runs on a 10-second interval and evicts entries untouched for that
|
|
717
|
+
long; reading an entry refreshes it. `initGarbageCollector` schedules through
|
|
718
|
+
`window`, so it is a no-op where there is none — on the server the cache simply
|
|
719
|
+
keeps what it is given. Calling it more than once does not stack collectors.
|
|
720
|
+
|
|
452
721
|
### Comparing with @veksa/reselect and @veksa/re-reselect
|
|
453
722
|
|
|
454
723
|
| Feature | @veksa/reselect | @veksa/re-reselect | reselect-kit |
|
|
@@ -462,6 +731,8 @@ cache.set('key', selectorData);
|
|
|
462
731
|
| Advanced key composition | × | Limited | ✓ |
|
|
463
732
|
| Cache garbage collection | × | × | ✓ |
|
|
464
733
|
| Hierarchical caching | × | × | ✓ |
|
|
734
|
+
| Props-aware React hooks | × | × | ✓ |
|
|
735
|
+
| Batched store subscription | × | × | ✓ |
|
|
465
736
|
| Typescript support | ✓ | ✓ | ✓ (enhanced) |
|
|
466
737
|
|
|
467
738
|
## Contributing
|