reselect-kit 1.0.1 → 1.1.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/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 +1 -1
- package/src/__tests__/createAdaptedSelector.test.ts +3 -4
- package/src/__tests__/createBoundSelector.test.ts +92 -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
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { CachedSelector, NamedParametricSelector } from './types';
|
|
1
|
+
import { CachedSelector, NamedSelector } from './types';
|
|
3
2
|
import { withDebugName } from './debug/withDebugName';
|
|
4
3
|
import { getSelectorName } from './_helpers/getSelectorName';
|
|
5
4
|
import { isCachedSelector } from './_helpers/isCachedSelector';
|
|
@@ -25,15 +24,20 @@ const generateMappingName = <P1 extends object, P2>(mapping: (props: P2) => P1)
|
|
|
25
24
|
|
|
26
25
|
const innerCreateAdaptedSelector =
|
|
27
26
|
<S, P1, P2, R>(
|
|
28
|
-
baseSelector:
|
|
27
|
+
baseSelector: (state: S, props: P1) => R,
|
|
29
28
|
mapping: (props: P2) => P1,
|
|
30
|
-
):
|
|
29
|
+
): NamedSelector<S, R, [P2]> =>
|
|
31
30
|
(state: S, props: P2) => {
|
|
32
31
|
return baseSelector(state, mapping(props));
|
|
33
32
|
};
|
|
34
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Rewrites the props a selector reads. Only the props argument changes: the
|
|
36
|
+
* adapted selector takes `[P2]` where the base took `[P1]`, which is the same
|
|
37
|
+
* argument tuple every other creator in the library carries.
|
|
38
|
+
*/
|
|
35
39
|
export const createAdaptedSelector = <S, P1 extends object, P2, R>(
|
|
36
|
-
baseSelector:
|
|
40
|
+
baseSelector: (state: S, props: P1) => R,
|
|
37
41
|
mapping: (props: P2) => P1,
|
|
38
42
|
) => {
|
|
39
43
|
const adaptedSelector = innerCreateAdaptedSelector(baseSelector, mapping);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { CachedSelector, NamedParametricSelector, NamedSelector } from './types';
|
|
1
|
+
import { CachedSelector, NamedSelector } from './types';
|
|
3
2
|
import { isComposedKeySelector, KeySelectorComposer } from './keys/createKeySelectorComposer';
|
|
4
3
|
import { isPropSelector } from './createPropSelector';
|
|
5
4
|
import { excludeDefaultSelectors } from './keys/createKeySelectorCreator';
|
|
@@ -28,47 +27,119 @@ const generateBindingName = <P extends object>(binding: P) => {
|
|
|
28
27
|
};
|
|
29
28
|
|
|
30
29
|
/**
|
|
31
|
-
* The
|
|
30
|
+
* The bound selector: what the binding did not supply is what is left to pass.
|
|
31
|
+
* A binding covering every prop leaves an empty argument tuple, which is what a
|
|
32
|
+
* plain selector is in the unified model — so one type covers both cases.
|
|
33
|
+
*
|
|
34
|
+
* The outer condition is the guard against binding a prop the base selector
|
|
35
|
+
* declares as optional with a value that is not.
|
|
32
36
|
*/
|
|
33
37
|
export type BoundSelector<S, P2, P1 extends Partial<P2>, R> =
|
|
34
38
|
P2 extends Pick<P1, keyof P2>
|
|
35
|
-
? Exclude<keyof P1, keyof P2> extends never
|
|
36
|
-
? NamedSelector<S, R>
|
|
37
|
-
: NamedParametricSelector<S, Omit<P1, keyof P2>, R>
|
|
39
|
+
? NamedSelector<S, R, Exclude<keyof P1, keyof P2> extends never ? [] : [Omit<P1, keyof P2>]>
|
|
38
40
|
: never;
|
|
39
41
|
|
|
40
42
|
export type BoundSelectorOptions<S, P2, P1 extends Partial<P2>, R> = {
|
|
41
43
|
bindingStrategy?: (
|
|
42
|
-
baseSelector:
|
|
44
|
+
baseSelector: (state: S, props: P1) => R,
|
|
43
45
|
binding: P2,
|
|
44
|
-
) =>
|
|
46
|
+
) => (state: S, props: Omit<P1, keyof P2>) => R;
|
|
45
47
|
keySelectorComposer?: KeySelectorComposer;
|
|
46
48
|
};
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
/**
|
|
51
|
+
* The default binding strategy: applies the binding to the props the caller
|
|
52
|
+
* passed, hands them to the base selector, and puts them back as they were.
|
|
53
|
+
*
|
|
54
|
+
* Almost every binding is a single key — `bound(selector, { positionId })` —
|
|
55
|
+
* and specialising it removes the two allocations the generic path costs per
|
|
56
|
+
* call: the `{ result, rollback }` object and the `rollback` closure. On a
|
|
57
|
+
* graph with a bound selector per row those are the calls that dominate.
|
|
58
|
+
*
|
|
59
|
+
* The `delete` in the restore stays deliberately. Leaving the key behind as
|
|
60
|
+
* `undefined` would be cheaper — it avoids putting the caller's props object
|
|
61
|
+
* into dictionary mode — but it also changes the object's shape, and
|
|
62
|
+
* `useSelector` memoizes props shallowly: a props object that has grown an
|
|
63
|
+
* extra key never again compares equal to a freshly built one, so the
|
|
64
|
+
* memoization the binding exists to protect would be defeated.
|
|
65
|
+
*/
|
|
66
|
+
const applyBinding = <S, P2, P1 extends Partial<P2>, R>(
|
|
67
|
+
baseSelector: (state: S, props: P1) => R,
|
|
50
68
|
binding: P2,
|
|
51
69
|
) => {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
70
|
+
const source = binding as unknown as Record<string, unknown>;
|
|
71
|
+
const keys = Object.keys(source);
|
|
72
|
+
|
|
73
|
+
if (keys.length === 1) {
|
|
74
|
+
const key = keys[0];
|
|
75
|
+
const value = source[key];
|
|
76
|
+
|
|
77
|
+
return (state: S, props: Omit<P1, keyof P2> | void) => {
|
|
78
|
+
const target = props as unknown as Record<string, unknown>;
|
|
79
|
+
|
|
80
|
+
// Nothing to merge into when the selector is read without props, which is
|
|
81
|
+
// what a fully bound selector is called like.
|
|
82
|
+
if (typeof target !== 'object' || target === null) {
|
|
83
|
+
return baseSelector(state, source as unknown as P1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const previous = target[key];
|
|
87
|
+
|
|
88
|
+
// A re-entrant call that finds the binding already applied has nothing to
|
|
89
|
+
// save and nothing to restore.
|
|
90
|
+
if (previous === value) {
|
|
91
|
+
return baseSelector(state, props as unknown as P1);
|
|
92
|
+
}
|
|
57
93
|
|
|
58
|
-
|
|
94
|
+
const had = Object.prototype.hasOwnProperty.call(target, key);
|
|
95
|
+
target[key] = value;
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
return baseSelector(state, props as unknown as P1);
|
|
99
|
+
} finally {
|
|
100
|
+
if (had) {
|
|
101
|
+
target[key] = previous;
|
|
102
|
+
} else {
|
|
103
|
+
delete target[key];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return (state: S, props: Omit<P1, keyof P2> | void) => {
|
|
110
|
+
// `temporaryAssign` falls back to the binding itself when there are no props
|
|
111
|
+
// to merge into, which is the read of a fully bound selector.
|
|
112
|
+
const { result: nextProps, rollback } = temporaryAssign(
|
|
113
|
+
props as unknown as Record<string, unknown>,
|
|
114
|
+
source,
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// Restored through `finally` for the same reason the single-key path does
|
|
118
|
+
// it: the props object belongs to the caller, and a selector that throws
|
|
119
|
+
// must not leave the binding written into it.
|
|
120
|
+
try {
|
|
121
|
+
return baseSelector(state, nextProps as unknown as P1);
|
|
122
|
+
} finally {
|
|
123
|
+
rollback();
|
|
124
|
+
}
|
|
125
|
+
};
|
|
59
126
|
};
|
|
60
127
|
|
|
61
|
-
const
|
|
62
|
-
baseSelector:
|
|
128
|
+
export const createBoundSelector = <S, P2 extends object, P1 extends Partial<P2>, R, OR extends R>(
|
|
129
|
+
baseSelector: (state: S, props: P1) => R,
|
|
63
130
|
binding: P2,
|
|
64
131
|
options: BoundSelectorOptions<S, P2, P1, OR> = {},
|
|
65
132
|
): BoundSelector<S, P2, P1, R> => {
|
|
66
|
-
const bindingStrategy =
|
|
67
|
-
(options.bindingStrategy as typeof innerCreateBoundSelector) ?? innerCreateBoundSelector;
|
|
133
|
+
const bindingStrategy = (options.bindingStrategy as typeof applyBinding) ?? applyBinding;
|
|
68
134
|
|
|
69
135
|
const keySelectorComposer = options.keySelectorComposer ?? stringComposeKeySelectors;
|
|
70
136
|
|
|
71
|
-
const boundSelector = bindingStrategy(baseSelector, binding)
|
|
137
|
+
const boundSelector = bindingStrategy(baseSelector, binding) as unknown as BoundSelector<
|
|
138
|
+
S,
|
|
139
|
+
P2,
|
|
140
|
+
P1,
|
|
141
|
+
R
|
|
142
|
+
>;
|
|
72
143
|
|
|
73
144
|
Object.assign(boundSelector, baseSelector);
|
|
74
145
|
boundSelector.dependencies = [baseSelector];
|
|
@@ -132,66 +203,3 @@ const createBoundInnerSelector = <S, P2 extends object, P1 extends Partial<P2>,
|
|
|
132
203
|
|
|
133
204
|
return boundSelector;
|
|
134
205
|
};
|
|
135
|
-
|
|
136
|
-
export const createBoundSelector: typeof createBoundInnerSelector = (baseSelector, binding) => {
|
|
137
|
-
return createBoundInnerSelector(baseSelector, binding, {
|
|
138
|
-
bindingStrategy: (selector, bindingProps) => {
|
|
139
|
-
const source = bindingProps as unknown as Record<string, unknown>;
|
|
140
|
-
const keys = Object.keys(source);
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Almost every binding is a single key — `bound(selector, { positionId })` —
|
|
144
|
-
* and specialising it removes the two allocations the generic path costs per
|
|
145
|
-
* call: the `{ result, rollback }` object and the `rollback` closure. On a
|
|
146
|
-
* graph with a bound selector per row those are the calls that dominate.
|
|
147
|
-
*
|
|
148
|
-
* The `delete` in the restore stays deliberately. Leaving the key behind as
|
|
149
|
-
* `undefined` would be cheaper — it avoids putting the caller's props object
|
|
150
|
-
* into dictionary mode — but it also changes the object's shape, and
|
|
151
|
-
* `useSelector` memoizes props shallowly: a props object that has grown an
|
|
152
|
-
* extra key never again compares equal to a freshly built one, so the
|
|
153
|
-
* memoization the binding exists to protect would be defeated.
|
|
154
|
-
*/
|
|
155
|
-
if (keys.length === 1) {
|
|
156
|
-
const key = keys[0];
|
|
157
|
-
const value = source[key];
|
|
158
|
-
|
|
159
|
-
return (state, props) => {
|
|
160
|
-
const target = props as unknown as Record<string, unknown>;
|
|
161
|
-
|
|
162
|
-
if (typeof target !== 'object' || target === null) {
|
|
163
|
-
return selector(state, source as Parameters<typeof baseSelector>[1]);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const previous = target[key];
|
|
167
|
-
|
|
168
|
-
// A re-entrant call that finds the binding already applied has nothing to
|
|
169
|
-
// save and nothing to restore.
|
|
170
|
-
if (previous === value) {
|
|
171
|
-
return selector(state, props as Parameters<typeof baseSelector>[1]);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const had = Object.prototype.hasOwnProperty.call(target, key);
|
|
175
|
-
target[key] = value;
|
|
176
|
-
|
|
177
|
-
try {
|
|
178
|
-
return selector(state, props as Parameters<typeof baseSelector>[1]);
|
|
179
|
-
} finally {
|
|
180
|
-
if (had) {
|
|
181
|
-
target[key] = previous;
|
|
182
|
-
} else {
|
|
183
|
-
delete target[key];
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return (state, props) => {
|
|
190
|
-
const { result: nextProps, rollback } = temporaryAssign(props, source);
|
|
191
|
-
const result = selector(state, nextProps as Parameters<typeof baseSelector>[1]);
|
|
192
|
-
rollback();
|
|
193
|
-
return result;
|
|
194
|
-
};
|
|
195
|
-
},
|
|
196
|
-
});
|
|
197
|
-
};
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { createSelector, Selector } from '@veksa/reselect';
|
|
1
|
+
import { createSelector, Selector, SelectorResultArray } from '@veksa/reselect';
|
|
2
2
|
import { createCachedSelector, PolymorphicCachedOptions } from '@veksa/re-reselect';
|
|
3
3
|
import { createSequenceSelector } from './createSequenceSelector';
|
|
4
|
+
import { AnySelector, MergedParams, MergedState } from './types';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
/**
|
|
7
|
+
* The cached counterpart of {@link createSequenceSelector}. The inferred tuple
|
|
8
|
+
* reaches `PolymorphicCachedOptions` as-is, so re-reselect types the caller's
|
|
9
|
+
* `keySelector` with the exact arguments of the group.
|
|
10
|
+
*/
|
|
11
|
+
export function createCachedSequenceSelector<Selectors extends readonly AnySelector[]>(
|
|
12
|
+
selectors: [...Selectors],
|
|
13
|
+
): (
|
|
14
|
+
options: PolymorphicCachedOptions<Selectors, SelectorResultArray<Selectors>>,
|
|
15
|
+
) => Selector<MergedState<Selectors>, SelectorResultArray<Selectors>, MergedParams<Selectors>>;
|
|
12
16
|
|
|
13
17
|
export function createCachedSequenceSelector(selectors: any): any {
|
|
14
18
|
return createSequenceSelector(
|
|
@@ -1,28 +1,19 @@
|
|
|
1
1
|
import { createSelector, Selector, SelectorResultsMap, SelectorsObject } from '@veksa/reselect';
|
|
2
2
|
import { createCachedSelector, PolymorphicCachedOptions } from '@veksa/re-reselect';
|
|
3
3
|
import { createStructuredSelector } from './createStructuredSelector';
|
|
4
|
-
import {
|
|
4
|
+
import { MergedParams, MergedState } from './types';
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
[K in keyof M]: Parameters<M[K]> extends [any, infer Props, ...any[]] ? Props : never;
|
|
14
|
-
}[keyof M];
|
|
15
|
-
|
|
16
|
-
/** The extra-arguments tuple: `[Props]` when any selector is parametric, else `[]`. */
|
|
17
|
-
type StructuredParams<M extends SelectorsObject> = [StructuredPropsUnion<M>] extends [never]
|
|
18
|
-
? []
|
|
19
|
-
: [UnionToIntersection<StructuredPropsUnion<M>>];
|
|
20
|
-
|
|
21
|
-
/** The input selectors as reselect sees them, used to type the cached options. */
|
|
6
|
+
/**
|
|
7
|
+
* The input selectors as re-reselect reads them when typing the caller's
|
|
8
|
+
* `keySelector`: it takes `state` and the extra arguments off the group, and
|
|
9
|
+
* both are already merged here. Putting the selectors back in key order would
|
|
10
|
+
* type the arguments no more precisely and would cost a type instantiation per
|
|
11
|
+
* key, capping the batch at roughly fifty fields.
|
|
12
|
+
*/
|
|
22
13
|
type StructuredInputs<M extends SelectorsObject> = Selector<
|
|
23
|
-
|
|
14
|
+
MergedState<M>,
|
|
24
15
|
unknown,
|
|
25
|
-
|
|
16
|
+
MergedParams<M>
|
|
26
17
|
>[];
|
|
27
18
|
|
|
28
19
|
/**
|
|
@@ -34,7 +25,7 @@ export function createCachedStructuredSelector<M extends SelectorsObject>(
|
|
|
34
25
|
selectors: M,
|
|
35
26
|
): (
|
|
36
27
|
options: PolymorphicCachedOptions<StructuredInputs<M>, SelectorResultsMap<M>>,
|
|
37
|
-
) => Selector<
|
|
28
|
+
) => Selector<MergedState<M>, SelectorResultsMap<M>, MergedParams<M>>;
|
|
38
29
|
|
|
39
30
|
export function createCachedStructuredSelector(selectors: any): any {
|
|
40
31
|
return createStructuredSelector(
|
|
@@ -1,30 +1,31 @@
|
|
|
1
|
-
import { createSelectorCreator, Selector } from '@veksa/reselect';
|
|
1
|
+
import { createSelector, createSelectorCreator, Selector } from '@veksa/reselect';
|
|
2
2
|
import { createCachedSelector, ICacheObject, KeySelector } from '@veksa/re-reselect';
|
|
3
3
|
import { KeySelectorComposer } from './keys/createKeySelectorComposer';
|
|
4
4
|
import { isCachedSelector } from './_helpers/isCachedSelector';
|
|
5
5
|
import { defaultKeySelector } from './keys/defaultKeySelector';
|
|
6
6
|
import { stringComposeKeySelectors } from './keys/stringComposeKeySelectors';
|
|
7
7
|
import { createKeySelectorCreator } from './keys/createKeySelectorCreator';
|
|
8
|
+
import { MergeParams } from './types';
|
|
8
9
|
import { runInDebug, withDebugName } from './debug/withDebugName';
|
|
9
10
|
import { defineDynamicSelectorName } from './_helpers/defineDynamicSelectorName';
|
|
10
11
|
import { getSelectorName } from './_helpers/getSelectorName';
|
|
11
12
|
import { generateSelectorKey } from './_helpers/generateSelectorKey';
|
|
12
13
|
import { stringifyFunction } from './_helpers/stringifyFunction';
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
/**
|
|
16
|
+
* A chain step: given the previous level's result, it returns the selector the
|
|
17
|
+
* next level reads. `Params` is empty for a step that only needs `state`, so a
|
|
18
|
+
* single type covers what used to be a plain and a parametric variant.
|
|
19
|
+
*/
|
|
20
|
+
export type SelectorChain<R1, S1, R2, Params extends readonly any[] = []> = (
|
|
21
|
+
result: R1,
|
|
22
|
+
) => Selector<S1, R2, Params>;
|
|
17
23
|
|
|
18
24
|
export type SelectorChainHierarchy<
|
|
19
|
-
C extends SelectorChain<any, any, any>,
|
|
25
|
+
C extends SelectorChain<any, any, any, any>,
|
|
20
26
|
H extends SelectorChainHierarchy<any, any>,
|
|
21
27
|
> = C & { parentChain?: H };
|
|
22
28
|
|
|
23
|
-
export type ParametricSelectorChainHierarchy<
|
|
24
|
-
C extends ParametricSelectorChain<any, any, any, any>,
|
|
25
|
-
H extends ParametricSelectorChainHierarchy<any, any>,
|
|
26
|
-
> = C & { parentChain?: H };
|
|
27
|
-
|
|
28
29
|
export type CreateSelectorOptions<SelectorCreator extends typeof createSelectorCreator> = {
|
|
29
30
|
selectorCreator: ReturnType<SelectorCreator>;
|
|
30
31
|
cacheObject: ICacheObject;
|
|
@@ -37,52 +38,46 @@ export type ChainSelectorOptions<
|
|
|
37
38
|
keySelectorComposer?: KeySelectorComposer;
|
|
38
39
|
};
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Every level of the chain carries the arguments its selector needs. A level
|
|
43
|
+
* that only reads `state` leaves `Params` empty, and one that reads props adds
|
|
44
|
+
* them — so a step introducing props makes them required for the whole built
|
|
45
|
+
* selector, whether or not the base selector had any.
|
|
46
|
+
*
|
|
47
|
+
* `Params` defaults to empty on the entry point, so that
|
|
48
|
+
* `createChainSelector<State, Result>(selector)` keeps working for callers that
|
|
49
|
+
* spell the type arguments out.
|
|
50
|
+
*/
|
|
51
|
+
interface ChainSelectorResult<S1, R1, Params extends readonly any[]> {
|
|
52
|
+
chain: <S2, R2, Params2 extends readonly any[]>(
|
|
53
|
+
fn: (result: R1) => (state: S2, ...params: Params2) => R2,
|
|
43
54
|
chainOptions?: ChainSelectorOptions,
|
|
44
|
-
) => ChainSelectorResult<S1 & S2, R2
|
|
55
|
+
) => ChainSelectorResult<S1 & S2, R2, MergeParams<Params, Params2>>;
|
|
45
56
|
map: <R2>(
|
|
46
57
|
fn: (result: R1) => R2,
|
|
47
58
|
mapOptions?: ChainSelectorOptions,
|
|
48
|
-
) => ChainSelectorResult<S1, R2>;
|
|
49
|
-
build: () => Selector<S1, R1,
|
|
59
|
+
) => ChainSelectorResult<S1, R2, Params>;
|
|
60
|
+
build: () => Selector<S1, R1, Params> & {
|
|
50
61
|
chainHierarchy: SelectorChainHierarchy<any, any> | undefined;
|
|
51
62
|
};
|
|
52
63
|
}
|
|
53
64
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
fn: ParametricSelectorChain<R1, S2, P2, R2>,
|
|
57
|
-
chainOptions?: ChainSelectorOptions,
|
|
58
|
-
) => ParametricChainSelectorResult<S1 & S2, P1 & P2, R2>;
|
|
59
|
-
map: <R2>(
|
|
60
|
-
fn: (result: R1) => R2,
|
|
61
|
-
mapOptions?: ChainSelectorOptions,
|
|
62
|
-
) => ParametricChainSelectorResult<S1, P1, R2>;
|
|
63
|
-
build: () => Selector<S1, R1, [P1]> & {
|
|
64
|
-
chainHierarchy: ParametricSelectorChainHierarchy<any, any> | undefined;
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function createChainSelector<S1, R1>(
|
|
69
|
-
selector: Selector<S1, R1>,
|
|
65
|
+
export function createChainSelector<S1, R1, Params extends readonly any[] = []>(
|
|
66
|
+
selector: (state: S1, ...params: Params) => R1,
|
|
70
67
|
options?: ChainSelectorOptions,
|
|
71
68
|
prevChain?: SelectorChainHierarchy<any, any>,
|
|
72
|
-
): ChainSelectorResult<S1, R1>;
|
|
69
|
+
): ChainSelectorResult<S1, R1, Params>;
|
|
73
70
|
|
|
74
|
-
export function createChainSelector
|
|
75
|
-
selector: Selector<S1, R1, [P1]>,
|
|
76
|
-
options?: ChainSelectorOptions,
|
|
77
|
-
prevChain?: ParametricSelectorChainHierarchy<any, any>,
|
|
78
|
-
): ParametricChainSelectorResult<S1, P1, R1>;
|
|
79
|
-
|
|
80
|
-
export function createChainSelector<S1, P1, R1>(
|
|
71
|
+
export function createChainSelector(
|
|
81
72
|
selector: any,
|
|
82
73
|
options?: ChainSelectorOptions,
|
|
83
74
|
prevChain?: any,
|
|
84
75
|
): any {
|
|
85
|
-
const
|
|
76
|
+
const createLevel = (
|
|
77
|
+
fn: any,
|
|
78
|
+
chainOptions: ChainSelectorOptions | undefined,
|
|
79
|
+
isDerivedKeyless: boolean,
|
|
80
|
+
) => {
|
|
86
81
|
const combinedOptions = {
|
|
87
82
|
...options,
|
|
88
83
|
...chainOptions,
|
|
@@ -95,13 +90,29 @@ export function createChainSelector<S1, P1, R1>(
|
|
|
95
90
|
|
|
96
91
|
const keySelectorCreator = createKeySelectorCreator(keySelectorComposer);
|
|
97
92
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Nothing below this level is keyed, so the cache would hold a single entry
|
|
95
|
+
* for the lifetime of the chain and every call would still pay for the key
|
|
96
|
+
* computation and the lookup that guard it. A plain memoized selector is the
|
|
97
|
+
* same mapping — re-reselect under a constant key is one `createSelector`
|
|
98
|
+
* instance — without the layer.
|
|
99
|
+
*/
|
|
100
|
+
const isKeyless = keySelector === defaultKeySelector;
|
|
101
|
+
|
|
102
|
+
const selectorOptions = createSelectorOptions() as Partial<CreateSelectorOptions<any>>;
|
|
103
|
+
|
|
104
|
+
const higherOrderSelector = isKeyless
|
|
105
|
+
? ((selectorOptions.selectorCreator ?? createSelector) as typeof createSelector)(
|
|
106
|
+
[selector],
|
|
107
|
+
fn,
|
|
108
|
+
)
|
|
109
|
+
: createCachedSelector(
|
|
110
|
+
[selector],
|
|
111
|
+
fn,
|
|
112
|
+
)({
|
|
113
|
+
...selectorOptions,
|
|
114
|
+
keySelector,
|
|
115
|
+
});
|
|
105
116
|
|
|
106
117
|
withDebugName(higherOrderSelector, () => {
|
|
107
118
|
const baseName = getSelectorName(selector);
|
|
@@ -109,13 +120,13 @@ export function createChainSelector<S1, P1, R1>(
|
|
|
109
120
|
return `higher order for ${baseName} (${stringifyFunction(fn)})`;
|
|
110
121
|
});
|
|
111
122
|
|
|
112
|
-
type CombineSelector = Selector<
|
|
123
|
+
type CombineSelector = Selector<any, any, [any]> & {
|
|
113
124
|
dependencies: any;
|
|
114
|
-
cache
|
|
115
|
-
keySelector?: KeySelector<
|
|
125
|
+
cache?: ICacheObject;
|
|
126
|
+
keySelector?: KeySelector<any>;
|
|
116
127
|
};
|
|
117
128
|
|
|
118
|
-
const combinedSelector: CombineSelector = (state:
|
|
129
|
+
const combinedSelector: CombineSelector = (state: any, props: any): any => {
|
|
119
130
|
const derivedSelector: any = (higherOrderSelector as any)(state, props);
|
|
120
131
|
|
|
121
132
|
runInDebug(() => {
|
|
@@ -147,40 +158,74 @@ export function createChainSelector<S1, P1, R1>(
|
|
|
147
158
|
};
|
|
148
159
|
|
|
149
160
|
combinedSelector.dependencies = [higherOrderSelector];
|
|
150
|
-
|
|
161
|
+
|
|
162
|
+
if (!isKeyless) {
|
|
163
|
+
combinedSelector.cache = (higherOrderSelector as unknown as { cache: ICacheObject }).cache;
|
|
164
|
+
}
|
|
151
165
|
|
|
152
166
|
/**
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* its own key computation, its own cache lookup, and its own evaluation of
|
|
158
|
-
* `higherOrderSelector` — to produce a value that depends on nothing but the
|
|
159
|
-
* derived selector. A `WeakMap` keyed on that selector is the same mapping with
|
|
160
|
-
* none of the layer, and it shares across cache keys rather than per key.
|
|
161
|
-
*
|
|
162
|
-
* Note what is deliberately *not* memoized: the `(state, props)` pair. `props`
|
|
163
|
-
* is mutated in place by `createBoundSelector`, so two calls can pass the same
|
|
164
|
-
* object reference with different contents, and a reference-keyed cache would
|
|
165
|
-
* hand back a stale selector.
|
|
167
|
+
* Assigned through a local and written to the selector once. Assigning the
|
|
168
|
+
* property in both branches instead reads as two expando declarations of
|
|
169
|
+
* `keySelector` to TypeScript before 7, which rejects it as a duplicate
|
|
170
|
+
* identifier.
|
|
166
171
|
*/
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
172
|
+
let levelKeySelector: KeySelector<any>;
|
|
173
|
+
|
|
174
|
+
if (isDerivedKeyless) {
|
|
175
|
+
/**
|
|
176
|
+
* A `map` level derives a plain `() => output`, never a cached selector, so
|
|
177
|
+
* it contributes nothing to the composed key and the whole composition is
|
|
178
|
+
* known at build time. It is the same call the wrapper below makes, minus
|
|
179
|
+
* the derived selector — so the key is composed exactly as before, once,
|
|
180
|
+
* instead of on every key computation of every level above.
|
|
181
|
+
*/
|
|
182
|
+
levelKeySelector = keySelectorCreator({
|
|
183
|
+
inputSelectors: [higherOrderSelector],
|
|
184
|
+
}) as KeySelector<any>;
|
|
185
|
+
} else {
|
|
186
|
+
/**
|
|
187
|
+
* The derived selector's key selector, memoized on the derived selector itself.
|
|
188
|
+
*
|
|
189
|
+
* This used to be a second `createCachedSelector` over `[higherOrderSelector]`,
|
|
190
|
+
* which meant every key computation paid a whole extra cached-selector layer —
|
|
191
|
+
* its own key computation, its own cache lookup, and its own evaluation of
|
|
192
|
+
* `higherOrderSelector` — to produce a value that depends on nothing but the
|
|
193
|
+
* derived selector. A `WeakMap` keyed on that selector is the same mapping with
|
|
194
|
+
* none of the layer, and it shares across cache keys rather than per key.
|
|
195
|
+
*
|
|
196
|
+
* Note what is deliberately *not* memoized: the `(state, props)` pair. `props`
|
|
197
|
+
* is mutated in place by `createBoundSelector`, so two calls can pass the same
|
|
198
|
+
* object reference with different contents, and a reference-keyed cache would
|
|
199
|
+
* hand back a stale selector.
|
|
200
|
+
*/
|
|
201
|
+
const derivedKeySelectors = new WeakMap<object, KeySelector<any>>();
|
|
202
|
+
|
|
203
|
+
levelKeySelector = (state: any, props: any) => {
|
|
204
|
+
const derivedSelector: any = (higherOrderSelector as any)(state, props);
|
|
205
|
+
|
|
206
|
+
let derivedKeySelector = derivedKeySelectors.get(derivedSelector);
|
|
207
|
+
|
|
208
|
+
if (derivedKeySelector === undefined) {
|
|
209
|
+
derivedKeySelector = keySelectorCreator({
|
|
210
|
+
inputSelectors: [higherOrderSelector, derivedSelector],
|
|
211
|
+
}) as KeySelector<any>;
|
|
212
|
+
|
|
213
|
+
derivedKeySelectors.set(derivedSelector, derivedKeySelector);
|
|
214
|
+
}
|
|
178
215
|
|
|
179
|
-
|
|
180
|
-
}
|
|
216
|
+
return (derivedKeySelector as any)(state, props);
|
|
217
|
+
};
|
|
218
|
+
}
|
|
181
219
|
|
|
182
|
-
|
|
183
|
-
|
|
220
|
+
/**
|
|
221
|
+
* When nothing keyed is left the composition yields the default key, and
|
|
222
|
+
* leaving the property off is what lets the next level skip its cache too.
|
|
223
|
+
* The wrapper above is a fresh closure, so only a `map` level can land here
|
|
224
|
+
* with the default.
|
|
225
|
+
*/
|
|
226
|
+
if (levelKeySelector !== defaultKeySelector) {
|
|
227
|
+
combinedSelector.keySelector = levelKeySelector;
|
|
228
|
+
}
|
|
184
229
|
|
|
185
230
|
withDebugName(combinedSelector, () => {
|
|
186
231
|
const baseName = getSelectorName(selector);
|
|
@@ -192,15 +237,15 @@ export function createChainSelector<S1, P1, R1>(
|
|
|
192
237
|
parentChain: prevChain,
|
|
193
238
|
});
|
|
194
239
|
|
|
195
|
-
return createChainSelector
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
);
|
|
240
|
+
return createChainSelector(combinedSelector, combinedOptions, nextPrevChain);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const chain = (fn: any, chainOptions?: ChainSelectorOptions) => {
|
|
244
|
+
return createLevel(fn, chainOptions, false);
|
|
200
245
|
};
|
|
201
246
|
|
|
202
|
-
const map =
|
|
203
|
-
const mapSelector = (result:
|
|
247
|
+
const map = (fn: (result: any) => any, mapOptions?: ChainSelectorOptions) => {
|
|
248
|
+
const mapSelector = (result: any) => {
|
|
204
249
|
const output = fn(result);
|
|
205
250
|
const mapSelector = () => output;
|
|
206
251
|
|
|
@@ -213,7 +258,7 @@ export function createChainSelector<S1, P1, R1>(
|
|
|
213
258
|
return mapSelector;
|
|
214
259
|
};
|
|
215
260
|
|
|
216
|
-
return
|
|
261
|
+
return createLevel(mapSelector, mapOptions, true);
|
|
217
262
|
};
|
|
218
263
|
|
|
219
264
|
const build = () => {
|