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
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createSelector, Selector } from '@veksa/reselect';
|
|
2
|
+
import { createCachedSelector, PolymorphicCachedOptions } from '@veksa/re-reselect';
|
|
3
|
+
import { createSequenceSelector } from './createSequenceSelector';
|
|
4
|
+
|
|
5
|
+
export function createCachedSequenceSelector<S, R>(
|
|
6
|
+
selectors: Selector<S, R>[],
|
|
7
|
+
): (options: PolymorphicCachedOptions<Selector<S, R>[], R[]>) => Selector<S, R[]>;
|
|
8
|
+
|
|
9
|
+
export function createCachedSequenceSelector<S, P, R>(
|
|
10
|
+
selectors: Selector<S, R, [P]>[],
|
|
11
|
+
): (options: PolymorphicCachedOptions<Selector<S, R, [P]>[], R[]>) => Selector<S, R[], [P]>;
|
|
12
|
+
|
|
13
|
+
export function createCachedSequenceSelector(selectors: any): any {
|
|
14
|
+
return createSequenceSelector(
|
|
15
|
+
selectors,
|
|
16
|
+
createCachedSelector as unknown as typeof createSelector,
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { createSelector, Selector, SelectorResultsMap, SelectorsObject } from '@veksa/reselect';
|
|
2
|
+
import { createCachedSelector, PolymorphicCachedOptions } from '@veksa/re-reselect';
|
|
3
|
+
import { createStructuredSelector } from './createStructuredSelector';
|
|
4
|
+
import { UnionToIntersection } from './types';
|
|
5
|
+
|
|
6
|
+
/** Intersection of the `state` argument of every selector in the object. */
|
|
7
|
+
type StructuredState<M extends SelectorsObject> = UnionToIntersection<
|
|
8
|
+
{ [K in keyof M]: Parameters<M[K]>[0] }[keyof M]
|
|
9
|
+
>;
|
|
10
|
+
|
|
11
|
+
/** Union of the `props` argument (position 1) of every parametric selector. */
|
|
12
|
+
type StructuredPropsUnion<M extends SelectorsObject> = {
|
|
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. */
|
|
22
|
+
type StructuredInputs<M extends SelectorsObject> = Selector<
|
|
23
|
+
StructuredState<M>,
|
|
24
|
+
unknown,
|
|
25
|
+
StructuredParams<M>
|
|
26
|
+
>[];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A single variadic signature replaces the former `Selector` / `ParametricSelector`
|
|
30
|
+
* conditional: `state` and `props` are derived from the selectors object, so a
|
|
31
|
+
* parametric input keeps its props type without a separate overload.
|
|
32
|
+
*/
|
|
33
|
+
export function createCachedStructuredSelector<M extends SelectorsObject>(
|
|
34
|
+
selectors: M,
|
|
35
|
+
): (
|
|
36
|
+
options: PolymorphicCachedOptions<StructuredInputs<M>, SelectorResultsMap<M>>,
|
|
37
|
+
) => Selector<StructuredState<M>, SelectorResultsMap<M>, StructuredParams<M>>;
|
|
38
|
+
|
|
39
|
+
export function createCachedStructuredSelector(selectors: any): any {
|
|
40
|
+
return createStructuredSelector(
|
|
41
|
+
selectors,
|
|
42
|
+
createCachedSelector as unknown as typeof createSelector,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { createSelectorCreator, Selector } from '@veksa/reselect';
|
|
2
|
+
import { createCachedSelector, ICacheObject, KeySelector } from '@veksa/re-reselect';
|
|
3
|
+
import { KeySelectorComposer } from './keys/createKeySelectorComposer';
|
|
4
|
+
import { isCachedSelector } from './_helpers/isCachedSelector';
|
|
5
|
+
import { defaultKeySelector } from './keys/defaultKeySelector';
|
|
6
|
+
import { stringComposeKeySelectors } from './keys/stringComposeKeySelectors';
|
|
7
|
+
import { createKeySelectorCreator } from './keys/createKeySelectorCreator';
|
|
8
|
+
import { runInDebug, withDebugName } from './debug/withDebugName';
|
|
9
|
+
import { defineDynamicSelectorName } from './_helpers/defineDynamicSelectorName';
|
|
10
|
+
import { getSelectorName } from './_helpers/getSelectorName';
|
|
11
|
+
import { generateSelectorKey } from './_helpers/generateSelectorKey';
|
|
12
|
+
import { stringifyFunction } from './_helpers/stringifyFunction';
|
|
13
|
+
|
|
14
|
+
export type SelectorChain<R1, S1, R2> = (result: R1) => Selector<S1, R2>;
|
|
15
|
+
|
|
16
|
+
export type ParametricSelectorChain<R1, S1, P1, R2> = (result: R1) => Selector<S1, R2, [P1]>;
|
|
17
|
+
|
|
18
|
+
export type SelectorChainHierarchy<
|
|
19
|
+
C extends SelectorChain<any, any, any>,
|
|
20
|
+
H extends SelectorChainHierarchy<any, any>,
|
|
21
|
+
> = C & { parentChain?: H };
|
|
22
|
+
|
|
23
|
+
export type ParametricSelectorChainHierarchy<
|
|
24
|
+
C extends ParametricSelectorChain<any, any, any, any>,
|
|
25
|
+
H extends ParametricSelectorChainHierarchy<any, any>,
|
|
26
|
+
> = C & { parentChain?: H };
|
|
27
|
+
|
|
28
|
+
export type CreateSelectorOptions<SelectorCreator extends typeof createSelectorCreator> = {
|
|
29
|
+
selectorCreator: ReturnType<SelectorCreator>;
|
|
30
|
+
cacheObject: ICacheObject;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type ChainSelectorOptions<
|
|
34
|
+
SelectorCreator extends typeof createSelectorCreator = typeof createSelectorCreator,
|
|
35
|
+
> = {
|
|
36
|
+
createSelectorOptions?: () => CreateSelectorOptions<SelectorCreator>;
|
|
37
|
+
keySelectorComposer?: KeySelectorComposer;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
interface ChainSelectorResult<S1, R1> {
|
|
41
|
+
chain: <S2, R2 = any>(
|
|
42
|
+
fn: SelectorChain<R1, S2, R2>,
|
|
43
|
+
chainOptions?: ChainSelectorOptions,
|
|
44
|
+
) => ChainSelectorResult<S1 & S2, R2>;
|
|
45
|
+
map: <R2>(
|
|
46
|
+
fn: (result: R1) => R2,
|
|
47
|
+
mapOptions?: ChainSelectorOptions,
|
|
48
|
+
) => ChainSelectorResult<S1, R2>;
|
|
49
|
+
build: () => Selector<S1, R1, []> & {
|
|
50
|
+
chainHierarchy: SelectorChainHierarchy<any, any> | undefined;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface ParametricChainSelectorResult<S1, P1, R1> {
|
|
55
|
+
chain: <S2, P2 = unknown, R2 = any>(
|
|
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>,
|
|
70
|
+
options?: ChainSelectorOptions,
|
|
71
|
+
prevChain?: SelectorChainHierarchy<any, any>,
|
|
72
|
+
): ChainSelectorResult<S1, R1>;
|
|
73
|
+
|
|
74
|
+
export function createChainSelector<S1, P1, R1>(
|
|
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>(
|
|
81
|
+
selector: any,
|
|
82
|
+
options?: ChainSelectorOptions,
|
|
83
|
+
prevChain?: any,
|
|
84
|
+
): any {
|
|
85
|
+
const chain = <S2, P2, R2>(fn: any, chainOptions?: ChainSelectorOptions) => {
|
|
86
|
+
const combinedOptions = {
|
|
87
|
+
...options,
|
|
88
|
+
...chainOptions,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const keySelector = isCachedSelector(selector) ? selector.keySelector : defaultKeySelector;
|
|
92
|
+
|
|
93
|
+
const { createSelectorOptions = () => ({}), keySelectorComposer = stringComposeKeySelectors } =
|
|
94
|
+
combinedOptions;
|
|
95
|
+
|
|
96
|
+
const keySelectorCreator = createKeySelectorCreator(keySelectorComposer);
|
|
97
|
+
|
|
98
|
+
const higherOrderSelector = createCachedSelector(
|
|
99
|
+
[selector],
|
|
100
|
+
fn,
|
|
101
|
+
)({
|
|
102
|
+
...createSelectorOptions(),
|
|
103
|
+
keySelector,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
withDebugName(higherOrderSelector, () => {
|
|
107
|
+
const baseName = getSelectorName(selector);
|
|
108
|
+
|
|
109
|
+
return `higher order for ${baseName} (${stringifyFunction(fn)})`;
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
type CombineSelector = Selector<S1 & S2, R1 & R2, [P1 & P2]> & {
|
|
113
|
+
dependencies: any;
|
|
114
|
+
cache: ICacheObject;
|
|
115
|
+
keySelector?: KeySelector<S1 & S2>;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const combinedSelector: CombineSelector = (state: S1 & S2, props: P1 & P2): R1 & R2 => {
|
|
119
|
+
const derivedSelector: any = (higherOrderSelector as any)(state, props);
|
|
120
|
+
|
|
121
|
+
runInDebug(() => {
|
|
122
|
+
// Debug-only: shows the branch this call took. Nothing outside the debug
|
|
123
|
+
// tooling reads it, so in production it was an array allocated per call and
|
|
124
|
+
// abandoned. The static `[higherOrderSelector]` below still stands.
|
|
125
|
+
combinedSelector.dependencies = [higherOrderSelector, derivedSelector];
|
|
126
|
+
|
|
127
|
+
const derivedSelectorName = getSelectorName(derivedSelector);
|
|
128
|
+
|
|
129
|
+
if (!derivedSelectorName) {
|
|
130
|
+
defineDynamicSelectorName(derivedSelector, () => {
|
|
131
|
+
const baseName = getSelectorName(selector);
|
|
132
|
+
const derivedSelectorKey = generateSelectorKey(derivedSelector);
|
|
133
|
+
|
|
134
|
+
return `derived from ${baseName} (${derivedSelectorKey})`;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
defineDynamicSelectorName(combinedSelector, () => {
|
|
139
|
+
const baseName = getSelectorName(selector);
|
|
140
|
+
const dependencyName = getSelectorName(derivedSelector);
|
|
141
|
+
|
|
142
|
+
return `${baseName} (chained by ${dependencyName})`;
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return derivedSelector(state, props);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
combinedSelector.dependencies = [higherOrderSelector];
|
|
150
|
+
combinedSelector.cache = higherOrderSelector.cache;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The derived selector's key selector, memoized on the derived selector itself.
|
|
154
|
+
*
|
|
155
|
+
* This used to be a second `createCachedSelector` over `[higherOrderSelector]`,
|
|
156
|
+
* which meant every key computation paid a whole extra cached-selector layer —
|
|
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.
|
|
166
|
+
*/
|
|
167
|
+
const derivedKeySelectors = new WeakMap<object, KeySelector<S1 & S2>>();
|
|
168
|
+
|
|
169
|
+
combinedSelector.keySelector = (state: S1 & S2, props: P1 & P2) => {
|
|
170
|
+
const derivedSelector: any = (higherOrderSelector as any)(state, props);
|
|
171
|
+
|
|
172
|
+
let derivedKeySelector = derivedKeySelectors.get(derivedSelector);
|
|
173
|
+
|
|
174
|
+
if (derivedKeySelector === undefined) {
|
|
175
|
+
derivedKeySelector = keySelectorCreator({
|
|
176
|
+
inputSelectors: [higherOrderSelector, derivedSelector],
|
|
177
|
+
}) as KeySelector<S1 & S2>;
|
|
178
|
+
|
|
179
|
+
derivedKeySelectors.set(derivedSelector, derivedKeySelector);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return (derivedKeySelector as any)(state, props);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
withDebugName(combinedSelector, () => {
|
|
186
|
+
const baseName = getSelectorName(selector);
|
|
187
|
+
|
|
188
|
+
return `${baseName} (will be chained ${stringifyFunction(fn)})`;
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const nextPrevChain = Object.assign(fn, {
|
|
192
|
+
parentChain: prevChain,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
return createChainSelector<S1 & S2, P1 & P2, R1 & R2>(
|
|
196
|
+
combinedSelector,
|
|
197
|
+
combinedOptions,
|
|
198
|
+
nextPrevChain,
|
|
199
|
+
);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const map = <R2>(fn: (result: R1) => R2, mapOptions?: ChainSelectorOptions) => {
|
|
203
|
+
const mapSelector = (result: R1) => {
|
|
204
|
+
const output = fn(result);
|
|
205
|
+
const mapSelector = () => output;
|
|
206
|
+
|
|
207
|
+
withDebugName(mapSelector, () => {
|
|
208
|
+
const baseName = getSelectorName(selector);
|
|
209
|
+
|
|
210
|
+
return `mapped from ${baseName} (${stringifyFunction(fn)})`;
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
return mapSelector;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
return chain(mapSelector, mapOptions);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const build = () => {
|
|
220
|
+
return Object.assign(selector, {
|
|
221
|
+
chainHierarchy: prevChain,
|
|
222
|
+
});
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
chain,
|
|
227
|
+
map,
|
|
228
|
+
build,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Selector } from '@veksa/reselect';
|
|
2
|
+
import { CachedSelector } from './types';
|
|
3
|
+
import { isCachedSelector } from './_helpers/isCachedSelector';
|
|
4
|
+
import { defaultKeySelector } from './keys/defaultKeySelector';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates a selector that always resolves to `undefined`. Used as the "no link"
|
|
8
|
+
* branch of a chain (e.g. an optional relation that isn't present).
|
|
9
|
+
*
|
|
10
|
+
* When the base selector is cached, the empty selector is tagged with
|
|
11
|
+
* {@link defaultKeySelector} so that it still passes {@link isCachedSelector}
|
|
12
|
+
* and takes part in key-selector composition. The default key is deliberately
|
|
13
|
+
* the only cache-related metadata copied: an empty selector has no result to
|
|
14
|
+
* memoize, so it needs no `cache` / `getMatchingSelector` of its own, and the
|
|
15
|
+
* default key is dropped by `excludeDefaultSelectors` during composition.
|
|
16
|
+
*/
|
|
17
|
+
export const createEmptySelector = <S, R>(
|
|
18
|
+
baseSelector: Selector<S, R, any[]>,
|
|
19
|
+
): Selector<S, R | undefined> => {
|
|
20
|
+
const emptySelector = () => undefined;
|
|
21
|
+
|
|
22
|
+
if (isCachedSelector(baseSelector)) {
|
|
23
|
+
(emptySelector as unknown as CachedSelector).keySelector = defaultKeySelector;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return emptySelector;
|
|
27
|
+
};
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { Selector } from '@veksa/reselect';
|
|
2
|
+
import { NamedParametricSelector, NamedSelector, Path } from './types';
|
|
3
|
+
import { isObject } from './_helpers/isObject';
|
|
4
|
+
import { withDebugName } from './debug/withDebugName';
|
|
5
|
+
import { getSelectorName } from './_helpers/getSelectorName';
|
|
6
|
+
|
|
7
|
+
export type Defined<T> = Exclude<T, undefined>;
|
|
8
|
+
|
|
9
|
+
export type IsOptional<T> = undefined extends T ? true : null extends T ? true : false;
|
|
10
|
+
|
|
11
|
+
export type IsObject<T> = T extends object ? true : false;
|
|
12
|
+
|
|
13
|
+
export type PathSelector<S, R, D> = NamedSelector<S, R, [], D> & {
|
|
14
|
+
path: Path;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type PathParametricSelector<S, P, R, D> = NamedParametricSelector<S, P, R, D> & {
|
|
18
|
+
path: Path;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type Required<T> = { [P in keyof T]-?: Exclude<T[P], undefined | null> };
|
|
22
|
+
|
|
23
|
+
export type RequiredSelectorBuilder<S, R, D> = R extends object
|
|
24
|
+
? { [K in keyof R]: () => PathSelector<S, NonNullable<R[K]>, D> }
|
|
25
|
+
: {};
|
|
26
|
+
|
|
27
|
+
export type OptionalSelectorBuilder<S, R, D> = ((
|
|
28
|
+
defaultValue?: R,
|
|
29
|
+
) => PathSelector<S, R extends undefined ? Defined<R> : R, D>) &
|
|
30
|
+
(R extends object
|
|
31
|
+
? { [K in keyof Required<R>]: () => PathSelector<S, NonNullable<R[K]>, D> }
|
|
32
|
+
: {});
|
|
33
|
+
|
|
34
|
+
export type RequiredParametricSelectorBuilder<S, P, R, D> = R extends object
|
|
35
|
+
? { [K in keyof R]: () => PathParametricSelector<S, P, NonNullable<R[K]>, D> }
|
|
36
|
+
: {};
|
|
37
|
+
|
|
38
|
+
export type OptionalParametricSelectorBuilder<S, P, R, D> = ((
|
|
39
|
+
defaultValue?: R,
|
|
40
|
+
) => PathParametricSelector<S, P, R extends undefined ? Defined<R> : R, D>) &
|
|
41
|
+
(R extends object
|
|
42
|
+
? { [K in keyof Required<R>]: () => PathParametricSelector<S, P, NonNullable<R[K]>, D> }
|
|
43
|
+
: {});
|
|
44
|
+
|
|
45
|
+
export type RequiredObjectSelectorWrapper<S, R, D> = {
|
|
46
|
+
[K in keyof R]-?: IsOptional<R[K]> extends true
|
|
47
|
+
? OptionalPathSelectorType<S, R[K], D>
|
|
48
|
+
: RequiredPathSelectorType<S, R[K], D>;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type OptionalObjectSelectorWrapper<S, R, D> = {
|
|
52
|
+
[K in keyof R]-?: OptionalPathSelectorType<S, R[K], D>;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type RequiredObjectParametricSelectorWrapper<S, P, R, D> = {
|
|
56
|
+
[K in keyof R]-?: IsOptional<R[K]> extends true
|
|
57
|
+
? OptionalPathParametricSelectorType<S, P, R[K], D>
|
|
58
|
+
: RequiredPathParametricSelectorType<S, P, R[K], D>;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export type OptionalObjectParametricSelectorWrapper<S, P, R, D> = {
|
|
62
|
+
[K in keyof R]-?: OptionalPathParametricSelectorType<S, P, R[K], D>;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export type RequiredArraySelectorWrapper<S, R, D> = {
|
|
66
|
+
length: RequiredPathSelectorType<S, number, D>;
|
|
67
|
+
|
|
68
|
+
[K: number]: IsOptional<R> extends true
|
|
69
|
+
? OptionalPathSelectorType<S, R, D>
|
|
70
|
+
: RequiredPathSelectorType<S, R, D>;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export type OptionalArraySelectorWrapper<S, R, D> = {
|
|
74
|
+
length: RequiredPathSelectorType<S, number, D>;
|
|
75
|
+
|
|
76
|
+
[K: number]: OptionalPathSelectorType<S, R, D>;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export type RequiredArrayParametricSelectorWrapper<S, P, R, D> = {
|
|
80
|
+
length: RequiredPathParametricSelectorType<S, P, number, D>;
|
|
81
|
+
|
|
82
|
+
[K: number]: IsOptional<R> extends true
|
|
83
|
+
? OptionalPathParametricSelectorType<S, P, R, D>
|
|
84
|
+
: RequiredPathParametricSelectorType<S, P, R, D>;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export type OptionalArrayParametricSelectorWrapper<S, P, R, D> = {
|
|
88
|
+
length: RequiredPathParametricSelectorType<S, P, number, D>;
|
|
89
|
+
|
|
90
|
+
[K: number]: OptionalPathParametricSelectorType<S, P, R, D>;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export type RequiredDataSelectorWrapper<S, R, D> = R extends unknown[]
|
|
94
|
+
? RequiredArraySelectorWrapper<S, R[number], D>
|
|
95
|
+
: IsObject<R> extends true
|
|
96
|
+
? RequiredObjectSelectorWrapper<S, R, D>
|
|
97
|
+
: RequiredSelectorBuilder<S, R, D>;
|
|
98
|
+
|
|
99
|
+
export type OptionalDataSelectorWrapper<S, R, D> = R extends unknown[]
|
|
100
|
+
? OptionalArraySelectorWrapper<S, R[number], D>
|
|
101
|
+
: IsObject<R> extends true
|
|
102
|
+
? OptionalObjectSelectorWrapper<S, R, D>
|
|
103
|
+
: OptionalSelectorBuilder<S, R, D>;
|
|
104
|
+
|
|
105
|
+
export type RequiredDataParametricSelectorWrapper<S, P, R, D> = R extends unknown[]
|
|
106
|
+
? RequiredArrayParametricSelectorWrapper<S, P, R[number], D>
|
|
107
|
+
: IsObject<R> extends true
|
|
108
|
+
? RequiredObjectParametricSelectorWrapper<S, P, R, D>
|
|
109
|
+
: RequiredParametricSelectorBuilder<S, P, R, D>;
|
|
110
|
+
|
|
111
|
+
export type OptionalDataParametricSelectorWrapper<S, P, R, D> = R extends unknown[]
|
|
112
|
+
? OptionalArrayParametricSelectorWrapper<S, P, R[number], D>
|
|
113
|
+
: IsObject<R> extends true
|
|
114
|
+
? OptionalObjectParametricSelectorWrapper<S, P, R, D>
|
|
115
|
+
: OptionalParametricSelectorBuilder<S, P, R, D>;
|
|
116
|
+
|
|
117
|
+
export type RequiredPathSelectorType<S, R, D> = RequiredSelectorBuilder<S, R, D> &
|
|
118
|
+
RequiredDataSelectorWrapper<S, NonNullable<R>, D>;
|
|
119
|
+
|
|
120
|
+
export type OptionalPathSelectorType<S, R, D> = OptionalSelectorBuilder<S, R, D> &
|
|
121
|
+
OptionalDataSelectorWrapper<S, NonNullable<R>, D>;
|
|
122
|
+
|
|
123
|
+
export type RequiredPathParametricSelectorType<S, P, R, D> = RequiredParametricSelectorBuilder<
|
|
124
|
+
S,
|
|
125
|
+
P,
|
|
126
|
+
R,
|
|
127
|
+
D
|
|
128
|
+
> &
|
|
129
|
+
RequiredDataParametricSelectorWrapper<S, P, NonNullable<R>, D>;
|
|
130
|
+
|
|
131
|
+
export type OptionalPathParametricSelectorType<S, P, R, D> = OptionalParametricSelectorBuilder<
|
|
132
|
+
S,
|
|
133
|
+
P,
|
|
134
|
+
R,
|
|
135
|
+
D
|
|
136
|
+
> &
|
|
137
|
+
OptionalDataParametricSelectorWrapper<S, P, NonNullable<R>, D>;
|
|
138
|
+
|
|
139
|
+
const defaultVoidFunction = () => {
|
|
140
|
+
return;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/** @internal */
|
|
144
|
+
export const innerCreatePathSelector = (
|
|
145
|
+
baseSelector: (...args: unknown[]) => unknown,
|
|
146
|
+
path: Path = [],
|
|
147
|
+
applyMeta: (selector: unknown) => void = defaultVoidFunction,
|
|
148
|
+
): unknown => {
|
|
149
|
+
const pathLength = path.length;
|
|
150
|
+
|
|
151
|
+
const proxyTarget = (defaultValue?: unknown) => {
|
|
152
|
+
function resultSelector(state?: unknown, props?: unknown) {
|
|
153
|
+
// Selectors are almost always reached as `(state)` or `(state, props)`, and
|
|
154
|
+
// dispatching those two arities directly keeps `arguments` out of the hot
|
|
155
|
+
// path. The arity is matched exactly rather than always passing two: reselect
|
|
156
|
+
// memoizes on the argument list, so handing a one-argument selector a second
|
|
157
|
+
// `undefined` would change its cache key.
|
|
158
|
+
let result: unknown;
|
|
159
|
+
|
|
160
|
+
if (arguments.length === 2) {
|
|
161
|
+
result = baseSelector(state, props);
|
|
162
|
+
} else if (arguments.length === 1) {
|
|
163
|
+
result = baseSelector(state);
|
|
164
|
+
} else {
|
|
165
|
+
result = baseSelector.apply(null, arguments as unknown as unknown[]);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
for (let i = 0; i < pathLength && isObject(result); i += 1) {
|
|
169
|
+
result = result[path[i]];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// For optional properties, we want to exclude undefined from the return type
|
|
173
|
+
// but still return undefined if the property doesn't exist
|
|
174
|
+
return result === undefined ? defaultValue : result;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
Object.assign(resultSelector, baseSelector, { path });
|
|
178
|
+
resultSelector.dependencies = [baseSelector];
|
|
179
|
+
|
|
180
|
+
applyMeta(resultSelector);
|
|
181
|
+
|
|
182
|
+
withDebugName(resultSelector, () => {
|
|
183
|
+
const baseName = getSelectorName(baseSelector);
|
|
184
|
+
|
|
185
|
+
return [baseName, ...path].join('.');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
return resultSelector;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
return new Proxy(proxyTarget, {
|
|
192
|
+
get: (_target, key) => {
|
|
193
|
+
return innerCreatePathSelector(baseSelector, [...path, String(key)], applyMeta);
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
export function createPathSelector<S, R, P = void>(
|
|
199
|
+
baseSelector: P extends void ? Selector<S, R> : Selector<S, R, [P]>,
|
|
200
|
+
): P extends void
|
|
201
|
+
? IsOptional<R> extends true
|
|
202
|
+
? OptionalPathSelectorType<S, R, [Selector<S, R>]>
|
|
203
|
+
: RequiredPathSelectorType<S, R, [Selector<S, R>]>
|
|
204
|
+
: IsOptional<R> extends true
|
|
205
|
+
? OptionalPathParametricSelectorType<S, P, R, [Selector<S, R, [P]>]>
|
|
206
|
+
: RequiredPathParametricSelectorType<S, P, R, [Selector<S, R, [P]>]> {
|
|
207
|
+
return innerCreatePathSelector(baseSelector as (...args: unknown[]) => unknown) as any;
|
|
208
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Selector } from '@veksa/reselect';
|
|
2
|
+
import { innerCreatePathSelector, RequiredPathParametricSelectorType } from './createPathSelector';
|
|
3
|
+
import { Path } from './types';
|
|
4
|
+
|
|
5
|
+
const propSelectorSymbol = Symbol.for('PropSelector');
|
|
6
|
+
|
|
7
|
+
export const isPropSelector = (selector: unknown): selector is { path: Path } => {
|
|
8
|
+
return selector instanceof Object && propSelectorSymbol in selector;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function createPropSelector<P>(): RequiredPathParametricSelectorType<
|
|
12
|
+
unknown,
|
|
13
|
+
P,
|
|
14
|
+
P,
|
|
15
|
+
[Selector<unknown, P, [P]>]
|
|
16
|
+
> {
|
|
17
|
+
const propsSelector = (_state: unknown, props: unknown) => props;
|
|
18
|
+
|
|
19
|
+
const applyMeta = (selector: unknown) => {
|
|
20
|
+
Object.defineProperty(selector, propSelectorSymbol, { value: true });
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
return innerCreatePathSelector(
|
|
24
|
+
propsSelector,
|
|
25
|
+
[],
|
|
26
|
+
applyMeta,
|
|
27
|
+
) as RequiredPathParametricSelectorType<unknown, P, P, [Selector<unknown, P, [P]>]>;
|
|
28
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createSelector, Selector } from '@veksa/reselect';
|
|
2
|
+
|
|
3
|
+
export function createSequenceSelector<S, R>(
|
|
4
|
+
selectors: Selector<S, R>[],
|
|
5
|
+
selectorCreator?: typeof createSelector,
|
|
6
|
+
): Selector<S, R[]>;
|
|
7
|
+
|
|
8
|
+
export function createSequenceSelector<S, P, R>(
|
|
9
|
+
selectors: Selector<S, R, [P]>[],
|
|
10
|
+
selectorCreator?: typeof createSelector,
|
|
11
|
+
): Selector<S, R[], [P]>;
|
|
12
|
+
|
|
13
|
+
export function createSequenceSelector(selectors: any, selectorCreator = createSelector) {
|
|
14
|
+
return selectorCreator(selectors, (...results: unknown[]) => results);
|
|
15
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from 'vitest';
|
|
2
|
+
import { isDebugMode, setDebugMode } from '../debug';
|
|
3
|
+
|
|
4
|
+
describe('debug mode', () => {
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
setDebugMode(false);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test('is disabled by default', () => {
|
|
10
|
+
expect(isDebugMode()).toBe(false);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('reflects the value set via setDebugMode', () => {
|
|
14
|
+
setDebugMode(true);
|
|
15
|
+
expect(isDebugMode()).toBe(true);
|
|
16
|
+
|
|
17
|
+
setDebugMode(false);
|
|
18
|
+
expect(isDebugMode()).toBe(false);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { defineDynamicSelectorName } from '../_helpers/defineDynamicSelectorName';
|
|
2
|
+
import { isDebugMode } from './debug';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Runs `callback` only outside production and only while debug mode is enabled.
|
|
6
|
+
* Centralizes the `NODE_ENV` / `isDebugMode` guard that the selector factories
|
|
7
|
+
* would otherwise repeat around every debug-name assignment.
|
|
8
|
+
*/
|
|
9
|
+
export const runInDebug = (callback: () => void) => {
|
|
10
|
+
/* istanbul ignore else */
|
|
11
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
12
|
+
/* istanbul ignore else */
|
|
13
|
+
if (isDebugMode()) {
|
|
14
|
+
callback();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Assigns a lazily-computed debug name to `selector`, guarded by
|
|
21
|
+
* {@link runInDebug}. Covers the common single-name case; use `runInDebug`
|
|
22
|
+
* directly when a block needs to name several selectors at once.
|
|
23
|
+
*/
|
|
24
|
+
export const withDebugName = (selector: unknown, selectorNameGetter: () => string) => {
|
|
25
|
+
runInDebug(() => {
|
|
26
|
+
defineDynamicSelectorName(selector, selectorNameGetter);
|
|
27
|
+
});
|
|
28
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/* istanbul ignore file */
|
|
2
|
+
/* Helpers */
|
|
3
|
+
export { createSegmentSelector } from './createSegmentSelector';
|
|
4
|
+
export { createPathSelector } from './createPathSelector';
|
|
5
|
+
export { createPropSelector } from './createPropSelector';
|
|
6
|
+
export { createBoundSelector } from './createBoundSelector';
|
|
7
|
+
export { createEmptySelector } from './createEmptySelector';
|
|
8
|
+
export { createAdaptedSelector } from './createAdaptedSelector';
|
|
9
|
+
export { createChainSelector, type ChainSelectorOptions } from './createChainSelector';
|
|
10
|
+
export { createCachedSequenceSelector } from './createCachedSequenceSelector';
|
|
11
|
+
export { createCachedStructuredSelector } from './createCachedStructuredSelector';
|
|
12
|
+
|
|
13
|
+
/* Key Selector Composition */
|
|
14
|
+
export { defaultKeySelector } from './keys/defaultKeySelector';
|
|
15
|
+
export { createKeySelectorComposer } from './keys/createKeySelectorComposer';
|
|
16
|
+
export { stringComposeKeySelectors } from './keys/stringComposeKeySelectors';
|
|
17
|
+
export { arrayComposeKeySelectors } from './keys/arrayComposeKeySelectors';
|
|
18
|
+
export { createKeySelectorCreator } from './keys/createKeySelectorCreator';
|
|
19
|
+
|
|
20
|
+
export { setDebugMode, isDebugMode } from './debug/debug';
|
|
21
|
+
|
|
22
|
+
/* Cache */
|
|
23
|
+
export { initGarbageCollector, IntervalMapCache } from './cache/intervalMapCache';
|
|
24
|
+
export { TreeCache } from './cache/TreeCache';
|
|
25
|
+
|
|
26
|
+
export { useSelector } from './_hooks/useSelector';
|