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.
Files changed (67) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +473 -0
  3. package/dist/cjs/index.js +6 -0
  4. package/dist/cjs/reselectKit.development.cjs +683 -0
  5. package/dist/cjs/reselectKit.development.cjs.map +1 -0
  6. package/dist/cjs/reselectKit.production.min.cjs +2 -0
  7. package/dist/cjs/reselectKit.production.min.cjs.map +1 -0
  8. package/dist/reselectKit.d.ts +248 -0
  9. package/dist/reselectKit.mjs +678 -0
  10. package/dist/reselectKit.mjs.map +1 -0
  11. package/package.json +70 -0
  12. package/src/__data__/state.ts +97 -0
  13. package/src/__tests__/createAdaptedSelector.test.ts +134 -0
  14. package/src/__tests__/createBoundSelector.test.ts +351 -0
  15. package/src/__tests__/createCachedSequenceSelector.test.ts +24 -0
  16. package/src/__tests__/createCachedStructuredSelector.test.ts +29 -0
  17. package/src/__tests__/createChainSelector.test.ts +404 -0
  18. package/src/__tests__/createEmptySelector.test.ts +99 -0
  19. package/src/__tests__/createPathSelector.test.ts +81 -0
  20. package/src/__tests__/createPropSelector.test.ts +36 -0
  21. package/src/__tests__/createSegmentSelector.test.ts +31 -0
  22. package/src/__tests__/createSequenceSelector.test.ts +62 -0
  23. package/src/_helpers/__tests__/arePathsEqual.test.ts +27 -0
  24. package/src/_helpers/__tests__/getObjectPaths.test.ts +56 -0
  25. package/src/_helpers/__tests__/temporaryAssign.test.ts +59 -0
  26. package/src/_helpers/arePathsEqual.ts +15 -0
  27. package/src/_helpers/defineDynamicSelectorName.ts +10 -0
  28. package/src/_helpers/generateSelectorKey.ts +15 -0
  29. package/src/_helpers/getObjectPaths.ts +13 -0
  30. package/src/_helpers/getSelectorName.ts +7 -0
  31. package/src/_helpers/isCachedSelector.ts +9 -0
  32. package/src/_helpers/isObject.ts +3 -0
  33. package/src/_helpers/stringifyFunction.ts +3 -0
  34. package/src/_helpers/temporaryAssign.ts +80 -0
  35. package/src/_hooks/__tests__/useMemoWith.test.ts +31 -0
  36. package/src/_hooks/__tests__/useSelector.test.ts +87 -0
  37. package/src/_hooks/useMemoWith.ts +11 -0
  38. package/src/_hooks/useSelector.ts +34 -0
  39. package/src/cache/TreeCache.ts +126 -0
  40. package/src/cache/__tests__/TreeCacheObject.test.ts +188 -0
  41. package/src/cache/__tests__/intervalMapCache.test.ts +133 -0
  42. package/src/cache/intervalMapCache.ts +97 -0
  43. package/src/createAdaptedSelector.ts +75 -0
  44. package/src/createBoundSelector.ts +197 -0
  45. package/src/createCachedSequenceSelector.ts +18 -0
  46. package/src/createCachedStructuredSelector.ts +44 -0
  47. package/src/createChainSelector.ts +230 -0
  48. package/src/createEmptySelector.ts +27 -0
  49. package/src/createPathSelector.ts +208 -0
  50. package/src/createPropSelector.ts +28 -0
  51. package/src/createSegmentSelector.ts +6 -0
  52. package/src/createSequenceSelector.ts +15 -0
  53. package/src/createStructuredSelector.ts +3 -0
  54. package/src/debug/__tests__/debug.test.ts +20 -0
  55. package/src/debug/debug.ts +7 -0
  56. package/src/debug/withDebugName.ts +28 -0
  57. package/src/index.ts +26 -0
  58. package/src/keys/__tests__/arrayComposeKeySelectors.test.ts +52 -0
  59. package/src/keys/__tests__/createKeySelectorCreator.test.ts +246 -0
  60. package/src/keys/__tests__/stringComposeKeySelectors.test.ts +52 -0
  61. package/src/keys/arrayComposeKeySelectors.ts +20 -0
  62. package/src/keys/createKeySelectorComposer.ts +68 -0
  63. package/src/keys/createKeySelectorCreator.ts +106 -0
  64. package/src/keys/defaultKeySelector.ts +1 -0
  65. package/src/keys/stringComposeKeySelectors.ts +15 -0
  66. package/src/types.ts +43 -0
  67. 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,6 @@
1
+ export function createSegmentSelector<State, SelectedSegment extends State | State[keyof State]>(
2
+ fn: (state: State) => SelectedSegment,
3
+ initial: SelectedSegment,
4
+ ): (state: State) => SelectedSegment {
5
+ return (state: State) => fn(state) ?? initial;
6
+ }
@@ -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,3 @@
1
+ import { createStructuredSelector as reselectStructuredSelector } from '@veksa/reselect';
2
+
3
+ export const createStructuredSelector = reselectStructuredSelector;
@@ -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,7 @@
1
+ let debugMode = false;
2
+
3
+ export const isDebugMode = () => debugMode;
4
+
5
+ export const setDebugMode = (value: boolean) => {
6
+ debugMode = value;
7
+ };
@@ -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';