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,52 @@
|
|
|
1
|
+
import { arrayComposeKeySelectors } from '../arrayComposeKeySelectors';
|
|
2
|
+
import { createPropSelector } from '../../createPropSelector';
|
|
3
|
+
import { isComposedKeySelector } from '../createKeySelectorComposer';
|
|
4
|
+
|
|
5
|
+
describe('arrayComposeKeySelectors', () => {
|
|
6
|
+
const firstPropSelector = createPropSelector<{ value1: number }>().value1();
|
|
7
|
+
const secondPropSelector = createPropSelector<{
|
|
8
|
+
value2: number;
|
|
9
|
+
}>().value2();
|
|
10
|
+
const thirdPropSelector = createPropSelector<{ value3: number }>().value3();
|
|
11
|
+
|
|
12
|
+
test('should return composed key selector', () => {
|
|
13
|
+
const keySelector = arrayComposeKeySelectors(
|
|
14
|
+
firstPropSelector,
|
|
15
|
+
secondPropSelector,
|
|
16
|
+
thirdPropSelector,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
expect(isComposedKeySelector(keySelector)).toBeTruthy();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('should return array of results from input selectors', () => {
|
|
23
|
+
const keySelector = arrayComposeKeySelectors(
|
|
24
|
+
firstPropSelector,
|
|
25
|
+
secondPropSelector,
|
|
26
|
+
thirdPropSelector,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const key: unknown = keySelector(expect.anything(), {
|
|
30
|
+
value1: 1,
|
|
31
|
+
value2: 2,
|
|
32
|
+
value3: 3,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(key).toEqual([1, 2, 3]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('should return flat array', () => {
|
|
39
|
+
const keySelector = arrayComposeKeySelectors(
|
|
40
|
+
firstPropSelector,
|
|
41
|
+
arrayComposeKeySelectors(secondPropSelector, thirdPropSelector),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const key: unknown = keySelector(expect.anything(), {
|
|
45
|
+
value1: 1,
|
|
46
|
+
value2: 2,
|
|
47
|
+
value3: 3,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
expect(key).toEqual([1, 2, 3]);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { createCachedSelector } from '@veksa/re-reselect';
|
|
2
|
+
import { createPropSelector } from '../../createPropSelector';
|
|
3
|
+
import { createKeySelectorCreator } from '../createKeySelectorCreator';
|
|
4
|
+
import { createPathSelector } from '../../createPathSelector';
|
|
5
|
+
import { createAdaptedSelector } from '../../createAdaptedSelector';
|
|
6
|
+
import { createChainSelector } from '../../createChainSelector';
|
|
7
|
+
import { stringComposeKeySelectors } from '../stringComposeKeySelectors';
|
|
8
|
+
|
|
9
|
+
describe('createKeySelectorCreator', () => {
|
|
10
|
+
const keySelectorCreator = createKeySelectorCreator(stringComposeKeySelectors);
|
|
11
|
+
|
|
12
|
+
const firstKeySelector = createPropSelector<{
|
|
13
|
+
firstProp: string;
|
|
14
|
+
}>().firstProp();
|
|
15
|
+
|
|
16
|
+
const secondKeySelector = createPropSelector<{
|
|
17
|
+
secondProp: string;
|
|
18
|
+
}>().secondProp();
|
|
19
|
+
|
|
20
|
+
const thirdKeySelector = createPropSelector<{
|
|
21
|
+
thirdProp: { value: string };
|
|
22
|
+
}>().thirdProp.value();
|
|
23
|
+
|
|
24
|
+
const firstSelector = createCachedSelector(
|
|
25
|
+
firstKeySelector,
|
|
26
|
+
() => undefined,
|
|
27
|
+
)({
|
|
28
|
+
keySelector: firstKeySelector,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const secondSelector = createCachedSelector(secondKeySelector, () => ({
|
|
32
|
+
someValue: 'someValue',
|
|
33
|
+
}))({
|
|
34
|
+
keySelector: secondKeySelector,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('should compose result selector key with delimiter', () => {
|
|
38
|
+
const keySelector = keySelectorCreator({
|
|
39
|
+
inputSelectors: [firstSelector, secondSelector],
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const firstProp = 'firstProp';
|
|
43
|
+
const secondProp = 'secondProp';
|
|
44
|
+
|
|
45
|
+
const key = keySelector(expect.anything(), {
|
|
46
|
+
firstProp,
|
|
47
|
+
secondProp,
|
|
48
|
+
}) as unknown;
|
|
49
|
+
|
|
50
|
+
expect(key).toBe(`${firstProp}:${secondProp}`);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('should use own keySelector in composition', () => {
|
|
54
|
+
const keySelector = keySelectorCreator({
|
|
55
|
+
keySelector: firstKeySelector,
|
|
56
|
+
inputSelectors: [secondSelector],
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const firstProp = 'firstProp';
|
|
60
|
+
const secondProp = 'secondProp';
|
|
61
|
+
|
|
62
|
+
const key = keySelector(expect.anything(), {
|
|
63
|
+
firstProp,
|
|
64
|
+
secondProp,
|
|
65
|
+
} as { firstProp: string }) as unknown;
|
|
66
|
+
|
|
67
|
+
expect(key).toBe(`${firstProp}:${secondProp}`);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('should compose any number of selectors', () => {
|
|
71
|
+
const keySelector = keySelectorCreator({
|
|
72
|
+
keySelector: thirdKeySelector,
|
|
73
|
+
inputSelectors: [firstSelector, secondSelector],
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const firstProp = 'firstProp';
|
|
77
|
+
const secondProp = 'secondProp';
|
|
78
|
+
const thirdProp = { value: 'thirdProp' };
|
|
79
|
+
|
|
80
|
+
const key = keySelector(expect.anything(), {
|
|
81
|
+
firstProp,
|
|
82
|
+
secondProp,
|
|
83
|
+
thirdProp,
|
|
84
|
+
} as { thirdProp: { value: string } }) as unknown;
|
|
85
|
+
|
|
86
|
+
expect(key).toBe(`${thirdProp.value}:${firstProp}:${secondProp}`);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('should not duplicate selectors if they are equal', () => {
|
|
90
|
+
const keySelector = keySelectorCreator({
|
|
91
|
+
keySelector: firstKeySelector,
|
|
92
|
+
inputSelectors: [firstSelector, firstSelector, firstSelector],
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const firstProp = 'firstProp';
|
|
96
|
+
|
|
97
|
+
const key = keySelector(expect.anything(), {
|
|
98
|
+
firstProp,
|
|
99
|
+
}) as unknown;
|
|
100
|
+
|
|
101
|
+
expect(key).toBe(firstProp);
|
|
102
|
+
expect(keySelector).toBe(firstKeySelector);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test(
|
|
106
|
+
'should be able extract keySelector from wrapped dependencies ' +
|
|
107
|
+
'(adaptedSelector, boundSelector and pathSelector)',
|
|
108
|
+
() => {
|
|
109
|
+
const inputSelector = createPathSelector(
|
|
110
|
+
createAdaptedSelector(secondSelector, (props: { altSecondProp: string }) => ({
|
|
111
|
+
secondProp: `alt ${props.altSecondProp}`,
|
|
112
|
+
})),
|
|
113
|
+
).someValue();
|
|
114
|
+
|
|
115
|
+
const keySelector = keySelectorCreator({
|
|
116
|
+
keySelector: firstKeySelector,
|
|
117
|
+
inputSelectors: [inputSelector],
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const firstProp = 'firstProp';
|
|
121
|
+
const altSecondProp = 'secondProp';
|
|
122
|
+
|
|
123
|
+
const key = keySelector(expect.anything(), {
|
|
124
|
+
firstProp,
|
|
125
|
+
altSecondProp,
|
|
126
|
+
} as { firstProp: string }) as unknown;
|
|
127
|
+
|
|
128
|
+
expect(key).toBe(`${firstProp}:alt ${altSecondProp}`);
|
|
129
|
+
},
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
test('should be able extract keySelector from chainSelector', () => {
|
|
133
|
+
const inputSelector = createChainSelector(firstSelector)
|
|
134
|
+
.chain(() => secondSelector)
|
|
135
|
+
.build();
|
|
136
|
+
|
|
137
|
+
const keySelector = keySelectorCreator({
|
|
138
|
+
keySelector: thirdKeySelector,
|
|
139
|
+
inputSelectors: [inputSelector],
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const firstProp = 'firstProp';
|
|
143
|
+
const secondProp = 'secondProp';
|
|
144
|
+
const thirdProp = { value: 'thirdProp' };
|
|
145
|
+
|
|
146
|
+
const key = keySelector(expect.anything(), {
|
|
147
|
+
firstProp,
|
|
148
|
+
secondProp,
|
|
149
|
+
thirdProp,
|
|
150
|
+
} as { thirdProp: { value: string } }) as unknown;
|
|
151
|
+
|
|
152
|
+
expect(key).toBe(`${thirdProp.value}:${firstProp}:${secondProp}`);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('should can be used as real keySelectorCreator', () => {
|
|
156
|
+
const cachedSelector = createCachedSelector(
|
|
157
|
+
[firstSelector, secondSelector],
|
|
158
|
+
() => undefined,
|
|
159
|
+
)({
|
|
160
|
+
keySelectorCreator,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const { keySelector } = cachedSelector;
|
|
164
|
+
const firstProp = 'firstProp';
|
|
165
|
+
const secondProp = 'secondProp';
|
|
166
|
+
|
|
167
|
+
const key = keySelector(expect.anything(), {
|
|
168
|
+
firstProp,
|
|
169
|
+
secondProp,
|
|
170
|
+
}) as unknown;
|
|
171
|
+
|
|
172
|
+
expect(key).toBe(`${firstProp}:${secondProp}`);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test('should flat output key selector', () => {
|
|
176
|
+
const inputKeySelector = createPropSelector<{
|
|
177
|
+
prop: string;
|
|
178
|
+
}>().prop();
|
|
179
|
+
|
|
180
|
+
const keySelector = keySelectorCreator({
|
|
181
|
+
keySelector: stringComposeKeySelectors(inputKeySelector, inputKeySelector),
|
|
182
|
+
inputSelectors: [
|
|
183
|
+
createCachedSelector(
|
|
184
|
+
inputKeySelector,
|
|
185
|
+
() => undefined,
|
|
186
|
+
)({
|
|
187
|
+
keySelector: stringComposeKeySelectors(inputKeySelector, inputKeySelector),
|
|
188
|
+
}),
|
|
189
|
+
createCachedSelector(
|
|
190
|
+
inputKeySelector,
|
|
191
|
+
() => undefined,
|
|
192
|
+
)({
|
|
193
|
+
keySelector: stringComposeKeySelectors(inputKeySelector, inputKeySelector),
|
|
194
|
+
}),
|
|
195
|
+
],
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const prop = 'prop';
|
|
199
|
+
|
|
200
|
+
const key = keySelector(expect.anything(), {
|
|
201
|
+
prop,
|
|
202
|
+
}) as unknown;
|
|
203
|
+
|
|
204
|
+
expect(key).toBe(`${prop}`);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test(
|
|
208
|
+
'should flat output key selector ' + '(even if prop selectors are declared independently)',
|
|
209
|
+
() => {
|
|
210
|
+
const keySelector = keySelectorCreator({
|
|
211
|
+
keySelector: stringComposeKeySelectors(
|
|
212
|
+
createPropSelector<{ prop: string }>().prop(),
|
|
213
|
+
createPropSelector<{ prop: string }>().prop(),
|
|
214
|
+
),
|
|
215
|
+
inputSelectors: [
|
|
216
|
+
createCachedSelector(
|
|
217
|
+
createPropSelector<{ prop: string }>().prop(),
|
|
218
|
+
() => undefined,
|
|
219
|
+
)({
|
|
220
|
+
keySelector: stringComposeKeySelectors(
|
|
221
|
+
createPropSelector<{ prop: string }>().prop(),
|
|
222
|
+
createPropSelector<{ prop: string }>().prop(),
|
|
223
|
+
),
|
|
224
|
+
}),
|
|
225
|
+
createCachedSelector(
|
|
226
|
+
createPropSelector<{ prop: string }>().prop(),
|
|
227
|
+
() => undefined,
|
|
228
|
+
)({
|
|
229
|
+
keySelector: stringComposeKeySelectors(
|
|
230
|
+
createPropSelector<{ prop: string }>().prop(),
|
|
231
|
+
createPropSelector<{ prop: string }>().prop(),
|
|
232
|
+
),
|
|
233
|
+
}),
|
|
234
|
+
],
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const prop = 'prop';
|
|
238
|
+
|
|
239
|
+
const key = keySelector(expect.anything(), {
|
|
240
|
+
prop,
|
|
241
|
+
}) as unknown;
|
|
242
|
+
|
|
243
|
+
expect(key).toBe(`${prop}`);
|
|
244
|
+
},
|
|
245
|
+
);
|
|
246
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { stringComposeKeySelectors } from '../stringComposeKeySelectors';
|
|
2
|
+
import { createPropSelector } from '../../createPropSelector';
|
|
3
|
+
import { isComposedKeySelector } from '../createKeySelectorComposer';
|
|
4
|
+
|
|
5
|
+
describe('stringComposeKeySelectors', () => {
|
|
6
|
+
const firstPropSelector = createPropSelector<{ value1: number }>().value1();
|
|
7
|
+
const secondPropSelector = createPropSelector<{
|
|
8
|
+
value2: number;
|
|
9
|
+
}>().value2();
|
|
10
|
+
const thirdPropSelector = createPropSelector<{ value3: number }>().value3();
|
|
11
|
+
|
|
12
|
+
test('should return composed key selector', () => {
|
|
13
|
+
const keySelector = stringComposeKeySelectors(
|
|
14
|
+
firstPropSelector,
|
|
15
|
+
secondPropSelector,
|
|
16
|
+
thirdPropSelector,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
expect(isComposedKeySelector(keySelector)).toBeTruthy();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('should return string with results from input selectors', () => {
|
|
23
|
+
const keySelector = stringComposeKeySelectors(
|
|
24
|
+
firstPropSelector,
|
|
25
|
+
secondPropSelector,
|
|
26
|
+
thirdPropSelector,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const key: unknown = keySelector(expect.anything(), {
|
|
30
|
+
value1: 1,
|
|
31
|
+
value2: 2,
|
|
32
|
+
value3: 3,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(key).toEqual('1:2:3');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('should return flat string', () => {
|
|
39
|
+
const keySelector = stringComposeKeySelectors(
|
|
40
|
+
firstPropSelector,
|
|
41
|
+
stringComposeKeySelectors(secondPropSelector, thirdPropSelector),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const key: unknown = keySelector(expect.anything(), {
|
|
45
|
+
value1: 1,
|
|
46
|
+
value2: 2,
|
|
47
|
+
value3: 3,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
expect(key).toEqual('1:2:3');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createKeySelectorComposer } from './createKeySelectorComposer';
|
|
2
|
+
|
|
3
|
+
export const arrayComposeKeySelectors = createKeySelectorComposer(
|
|
4
|
+
(...keySelectors) =>
|
|
5
|
+
(state, props) => {
|
|
6
|
+
let keyPath: unknown[] = [];
|
|
7
|
+
|
|
8
|
+
for (let i = 0; i < keySelectors.length; i += 1) {
|
|
9
|
+
const key: unknown = keySelectors[i](state, props);
|
|
10
|
+
|
|
11
|
+
if (Array.isArray(key)) {
|
|
12
|
+
keyPath = keyPath.concat(key);
|
|
13
|
+
} else {
|
|
14
|
+
keyPath.push(key);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return keyPath;
|
|
19
|
+
},
|
|
20
|
+
);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { KeySelector } from '@veksa/re-reselect';
|
|
2
|
+
import { UnionToIntersection } from '../types';
|
|
3
|
+
|
|
4
|
+
export const composedKeySelectorSymbol = Symbol.for('ComposedKeySelector');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The loosest shape a key selector can take: a function of `state` plus any
|
|
8
|
+
* number of extra arguments (props, ...). Used for variadic tuple inference.
|
|
9
|
+
*/
|
|
10
|
+
type AnyKeySelector = KeySelector<any>;
|
|
11
|
+
|
|
12
|
+
/** Intersection of every input key selector's `state` argument. */
|
|
13
|
+
type ComposedState<Ks extends readonly AnyKeySelector[]> = UnionToIntersection<
|
|
14
|
+
{ [Index in keyof Ks]: Parameters<Ks[Index]>[0] }[number]
|
|
15
|
+
>;
|
|
16
|
+
|
|
17
|
+
/** Union of every input key selector's `props` argument (position 1), if any. */
|
|
18
|
+
type PropsUnion<Ks extends readonly AnyKeySelector[]> = {
|
|
19
|
+
[Index in keyof Ks]: Parameters<Ks[Index]> extends [any, infer Props, ...any[]] ? Props : never;
|
|
20
|
+
}[number];
|
|
21
|
+
|
|
22
|
+
/** Intersection of every input key selector's `props`, or `never` if none are parametric. */
|
|
23
|
+
type ComposedProps<Ks extends readonly AnyKeySelector[]> = [PropsUnion<Ks>] extends [never]
|
|
24
|
+
? never
|
|
25
|
+
: UnionToIntersection<PropsUnion<Ks>>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The key selector produced by a {@link KeySelectorComposer}: a function that
|
|
29
|
+
* intersects the `state` (and `props`, when any input is parametric) of every
|
|
30
|
+
* composed key selector, tagged with the original inputs as `dependencies`.
|
|
31
|
+
*/
|
|
32
|
+
export type OutputKeySelector<S, P, D extends readonly AnyKeySelector[]> = ([P] extends [never]
|
|
33
|
+
? (state: S) => unknown
|
|
34
|
+
: (state: S, props: P) => unknown) & {
|
|
35
|
+
dependencies: D;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Composes N key selectors into a single one. A single variadic signature with
|
|
40
|
+
* tuple inference replaces the former per-arity overload set.
|
|
41
|
+
*/
|
|
42
|
+
export type KeySelectorComposer = <Ks extends readonly AnyKeySelector[]>(
|
|
43
|
+
...keySelectors: Ks
|
|
44
|
+
) => OutputKeySelector<ComposedState<Ks>, ComposedProps<Ks>, Ks>;
|
|
45
|
+
|
|
46
|
+
export function isComposedKeySelector(
|
|
47
|
+
keySelector: AnyKeySelector,
|
|
48
|
+
): keySelector is AnyKeySelector & { dependencies: AnyKeySelector[] } {
|
|
49
|
+
return 'dependencies' in keySelector && composedKeySelectorSymbol in keySelector;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function createKeySelectorComposer(
|
|
53
|
+
baseKeySelectorComposer: (...keySelectors: AnyKeySelector[]) => AnyKeySelector,
|
|
54
|
+
): KeySelectorComposer {
|
|
55
|
+
return ((...keySelectors: AnyKeySelector[]) => {
|
|
56
|
+
const resultSelector = baseKeySelectorComposer(...keySelectors) as AnyKeySelector & {
|
|
57
|
+
dependencies: AnyKeySelector[];
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
resultSelector.dependencies = keySelectors;
|
|
61
|
+
|
|
62
|
+
Object.defineProperty(resultSelector, composedKeySelectorSymbol, {
|
|
63
|
+
value: true,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return resultSelector;
|
|
67
|
+
}) as KeySelectorComposer;
|
|
68
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { KeySelector } from '@veksa/re-reselect';
|
|
2
|
+
import { isPropSelector } from '../createPropSelector';
|
|
3
|
+
import { isComposedKeySelector, KeySelectorComposer } from './createKeySelectorComposer';
|
|
4
|
+
import { arePathsEqual } from '../_helpers/arePathsEqual';
|
|
5
|
+
import { defaultKeySelector } from './defaultKeySelector';
|
|
6
|
+
import { isCachedSelector } from '../_helpers/isCachedSelector';
|
|
7
|
+
|
|
8
|
+
const areSelectorsEqual = (selector: unknown, another: unknown) => {
|
|
9
|
+
if (selector === another) {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (isPropSelector(selector) && isPropSelector(another)) {
|
|
14
|
+
return arePathsEqual(selector.path, another.path);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return false;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const flatKeySelectors = <S>(keySelectors: KeySelector<S>[]) => {
|
|
21
|
+
const result: typeof keySelectors = [];
|
|
22
|
+
|
|
23
|
+
for (let i = 0; i < keySelectors.length; i += 1) {
|
|
24
|
+
const keySelector = keySelectors[i];
|
|
25
|
+
|
|
26
|
+
if (isComposedKeySelector(keySelector)) {
|
|
27
|
+
result.push(...flatKeySelectors(keySelector.dependencies));
|
|
28
|
+
} else {
|
|
29
|
+
result.push(keySelector);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const uniqKeySelectors = <S>(keySelectors: KeySelector<S>[]) => {
|
|
37
|
+
const result: typeof keySelectors = [];
|
|
38
|
+
|
|
39
|
+
for (let i = 0; i < keySelectors.length; i += 1) {
|
|
40
|
+
const keySelector = keySelectors[i];
|
|
41
|
+
|
|
42
|
+
const isKeySelectorAdded = result.some((resultKeySelector) =>
|
|
43
|
+
areSelectorsEqual(keySelector, resultKeySelector),
|
|
44
|
+
);
|
|
45
|
+
if (!isKeySelectorAdded) {
|
|
46
|
+
result.push(keySelector);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return result;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const excludeDefaultSelectors = <S>(keySelectors: KeySelector<S>[]) => {
|
|
54
|
+
const result: typeof keySelectors = [];
|
|
55
|
+
|
|
56
|
+
for (let i = 0; i < keySelectors.length; i += 1) {
|
|
57
|
+
const keySelector = keySelectors[i];
|
|
58
|
+
|
|
59
|
+
if (keySelector !== defaultKeySelector) {
|
|
60
|
+
result.push(keySelector);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return result;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export function createKeySelectorCreator(
|
|
68
|
+
keySelectorComposer: KeySelectorComposer,
|
|
69
|
+
): <S, D>(selectorInputs: { inputSelectors: D; keySelector?: KeySelector<S> }) => KeySelector<S> {
|
|
70
|
+
return <S>({
|
|
71
|
+
inputSelectors,
|
|
72
|
+
keySelector,
|
|
73
|
+
}: {
|
|
74
|
+
inputSelectors: unknown;
|
|
75
|
+
keySelector?: KeySelector<S>;
|
|
76
|
+
}) => {
|
|
77
|
+
let keySelectors: KeySelector<S>[] = [];
|
|
78
|
+
|
|
79
|
+
if (keySelector) {
|
|
80
|
+
keySelectors.push(keySelector);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
(inputSelectors as unknown[]).forEach((selector) => {
|
|
84
|
+
if (isCachedSelector(selector)) {
|
|
85
|
+
keySelectors.push(selector.keySelector);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
keySelectors = flatKeySelectors(keySelectors);
|
|
90
|
+
keySelectors = uniqKeySelectors(keySelectors);
|
|
91
|
+
keySelectors = excludeDefaultSelectors(keySelectors);
|
|
92
|
+
|
|
93
|
+
if (keySelectors.length === 0) {
|
|
94
|
+
return defaultKeySelector;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (keySelectors.length === 1) {
|
|
98
|
+
const [resultSelector] = keySelectors;
|
|
99
|
+
return resultSelector;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// The composer infers a `UnionToIntersection` state from the runtime array,
|
|
103
|
+
// which TS can't see as identical to `S`; narrow it back for the caller.
|
|
104
|
+
return keySelectorComposer(...keySelectors) as KeySelector<S>;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const defaultKeySelector = () => '<DefaultKey>';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createKeySelectorComposer } from './createKeySelectorComposer';
|
|
2
|
+
|
|
3
|
+
export const stringComposeKeySelectors = createKeySelectorComposer(
|
|
4
|
+
(...keySelectors) =>
|
|
5
|
+
(state, props) => {
|
|
6
|
+
let key: unknown = keySelectors[0](state, props);
|
|
7
|
+
|
|
8
|
+
for (let i = 1; i < keySelectors.length; i += 1) {
|
|
9
|
+
key += ':';
|
|
10
|
+
key += keySelectors[i](state, props);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return key;
|
|
14
|
+
},
|
|
15
|
+
);
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Selector, SelectorArray } from '@veksa/reselect';
|
|
2
|
+
import { OutputCachedSelector } from '@veksa/re-reselect';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A selector carrying optional debug metadata. In the unified model a
|
|
6
|
+
* "parametric" selector is simply a selector whose `Params` tuple is non-empty,
|
|
7
|
+
* so a single type covers both cases.
|
|
8
|
+
*
|
|
9
|
+
* @template Params - Extra arguments beyond `state` (e.g. `[Props]`).
|
|
10
|
+
* @template D - The selector's dependency tuple, surfaced for debug tooling.
|
|
11
|
+
*/
|
|
12
|
+
export type NamedSelector<S, R, Params extends readonly any[] = [], D = unknown[]> = Selector<
|
|
13
|
+
S,
|
|
14
|
+
R,
|
|
15
|
+
Params
|
|
16
|
+
> & {
|
|
17
|
+
selectorName?: string;
|
|
18
|
+
dependencies?: D;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Back-compat alias: a parametric selector is a {@link NamedSelector} whose
|
|
23
|
+
* single extra argument is `Props`.
|
|
24
|
+
*/
|
|
25
|
+
export type NamedParametricSelector<S, P, R, D = unknown[]> = NamedSelector<S, R, [P], D>;
|
|
26
|
+
|
|
27
|
+
export type ReReselectSelector = OutputCachedSelector<SelectorArray, unknown>;
|
|
28
|
+
|
|
29
|
+
export type CachedSelector = Pick<ReReselectSelector, 'cache' | 'keySelector'> &
|
|
30
|
+
Partial<ReReselectSelector>;
|
|
31
|
+
|
|
32
|
+
export type Path = string[];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Turns a union `A | B | C` into the intersection `A & B & C`. Not exported by
|
|
36
|
+
* reselect, so kept here for the selector-composition helpers that need to
|
|
37
|
+
* intersect the `state`/`props` of several selectors.
|
|
38
|
+
*/
|
|
39
|
+
export type UnionToIntersection<Union> = (
|
|
40
|
+
Union extends unknown ? (distributed: Union) => void : never
|
|
41
|
+
) extends (merged: infer Intersection) => void
|
|
42
|
+
? Intersection
|
|
43
|
+
: never;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vitest/globals" />
|