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,351 @@
1
+ import { createCachedSelector } from '@veksa/re-reselect';
2
+ import { commonState, State } from '../__data__/state';
3
+ import { createBoundSelector } from '../createBoundSelector';
4
+ import { createStructuredSelector } from '../createStructuredSelector';
5
+ import { NamedParametricSelector } from '../types';
6
+ import { createPropSelector } from '../createPropSelector';
7
+ import { isComposedKeySelector } from '../keys/createKeySelectorComposer';
8
+ import { stringComposeKeySelectors } from '../keys/stringComposeKeySelectors';
9
+ import { isCachedSelector } from '../_helpers/isCachedSelector';
10
+ import { defaultKeySelector } from '../keys/defaultKeySelector';
11
+
12
+ vi.mock('../debug/debug', async (importOriginal) => ({
13
+ ...(await importOriginal<object>()),
14
+ isDebugMode: vi.fn(() => true),
15
+ }));
16
+
17
+ describe('createBoundSelector', () => {
18
+ const personSelector = (state: State, props: { personId: number }) =>
19
+ state.persons.data[props.personId];
20
+ const messageSelector = (state: State, props: { messageId: number }) =>
21
+ state.messages.data[props.messageId];
22
+
23
+ const personAndMessageSelector = createStructuredSelector({
24
+ person: personSelector,
25
+ message: messageSelector,
26
+ });
27
+
28
+ test('should implement binding to partial props', () => {
29
+ const marryAndMessageSelector = createBoundSelector(personAndMessageSelector, {
30
+ personId: 1,
31
+ });
32
+
33
+ const marryAndMessage = marryAndMessageSelector(commonState, {
34
+ messageId: 100,
35
+ });
36
+
37
+ expect(marryAndMessage.person.firstName).toBe('Marry');
38
+ expect(marryAndMessage.message.text).toBe('Hello');
39
+
40
+ const personAndHelloSelector = createBoundSelector(personAndMessageSelector, {
41
+ messageId: 100,
42
+ });
43
+
44
+ const personAndHello = personAndHelloSelector(commonState, {
45
+ personId: 1,
46
+ });
47
+
48
+ expect(personAndHello.person.firstName).toBe('Marry');
49
+ expect(personAndHello.message.text).toBe('Hello');
50
+ });
51
+
52
+ test('should implement binding to full props', () => {
53
+ const marryAndHelloSelector = createBoundSelector(personAndMessageSelector, {
54
+ personId: 1,
55
+ messageId: 100,
56
+ });
57
+
58
+ const marryAndHello = marryAndHelloSelector(commonState);
59
+
60
+ expect(marryAndHello.person.firstName).toBe('Marry');
61
+ expect(marryAndHello.message.text).toBe('Hello');
62
+ });
63
+
64
+ test('should not support binding of optional values to non-optional props', () => {
65
+ type Props = {
66
+ first: string;
67
+ second?: string;
68
+ };
69
+
70
+ type WrongProps = {
71
+ first?: string;
72
+ };
73
+
74
+ const wrongProps: WrongProps = {};
75
+
76
+ const selector = (state: State, props: Props) => JSON.stringify(state) + JSON.stringify(props);
77
+
78
+ const boundSelector: never = createBoundSelector(selector, wrongProps);
79
+
80
+ // @ts-expect-error
81
+ boundSelector(commonState, {});
82
+ });
83
+
84
+ test('should support binding of non-optional values to optional props', () => {
85
+ type Props = {
86
+ first: string;
87
+ second?: string;
88
+ };
89
+
90
+ type RightProps = {
91
+ second: string;
92
+ };
93
+
94
+ const rightProps: RightProps = {
95
+ second: 'second',
96
+ };
97
+
98
+ const selector = (state: State, props: Props) => JSON.stringify(state) + JSON.stringify(props);
99
+
100
+ const boundSelector = createBoundSelector(selector, rightProps);
101
+
102
+ expect(boundSelector(commonState, { first: 'first' })).toBeDefined();
103
+ });
104
+
105
+ test('should generate selector name', () => {
106
+ const marrySelector = createBoundSelector(personSelector, {
107
+ personId: 1,
108
+ });
109
+ expect(marrySelector.selectorName).toMatchInlineSnapshot(`"personSelector (personId -> [*])"`);
110
+
111
+ const helloSelector = createBoundSelector(messageSelector, {
112
+ messageId: 100,
113
+ });
114
+ expect(helloSelector.selectorName).toMatchInlineSnapshot(
115
+ `"messageSelector (messageId -> [*])"`,
116
+ );
117
+
118
+ const personAndMessageNamedSelector = createStructuredSelector({
119
+ person: personSelector,
120
+ message: messageSelector,
121
+ });
122
+ (
123
+ personAndMessageNamedSelector as NamedParametricSelector<unknown, unknown, unknown>
124
+ ).selectorName = 'personAndMessageNamedSelector';
125
+
126
+ const marryAndHelloSelector = createBoundSelector(personAndMessageNamedSelector, {
127
+ personId: 1,
128
+ messageId: 100,
129
+ });
130
+
131
+ expect(marryAndHelloSelector.selectorName).toMatchInlineSnapshot(
132
+ `"personAndMessageNamedSelector (personId,messageId -> [*],[*])"`,
133
+ );
134
+ });
135
+
136
+ describe('integration with re-reselect', () => {
137
+ const fullNameCachedSelector = createCachedSelector(
138
+ [personSelector],
139
+ ({ firstName = '', secondName = '' }) => `${firstName} ${secondName}`,
140
+ )((_state, props) => props.personId);
141
+
142
+ test('should decorate getMatchingSelector and removeMatchingSelector of dependency', () => {
143
+ const marrySelector = createBoundSelector(fullNameCachedSelector, {
144
+ personId: 1,
145
+ });
146
+
147
+ if (!isCachedSelector(marrySelector)) {
148
+ throw new Error('marrySelector should be cached');
149
+ }
150
+
151
+ let selectorInstance = marrySelector.getMatchingSelector?.(commonState, expect.anything());
152
+ expect(selectorInstance).toBeUndefined();
153
+
154
+ marrySelector(commonState);
155
+ selectorInstance = marrySelector.getMatchingSelector?.(commonState, expect.anything());
156
+ expect(selectorInstance).toBeInstanceOf(Function);
157
+
158
+ marrySelector.removeMatchingSelector?.(commonState, expect.anything());
159
+ selectorInstance = marrySelector.getMatchingSelector?.(commonState, expect.anything());
160
+ expect(selectorInstance).toBeUndefined();
161
+
162
+ const key = marrySelector.keySelector(commonState, expect.anything()) as unknown;
163
+ expect(key).toBe(1);
164
+ });
165
+
166
+ test('should return key without keys which selectors are bounded', () => {
167
+ const firstPropSelector = createPropSelector<{
168
+ value1: number;
169
+ }>().value1();
170
+ const secondPropSelector = createPropSelector<{
171
+ value2: number;
172
+ }>().value2();
173
+ const thirdPropSelector = createPropSelector<{
174
+ value3: number;
175
+ }>().value3();
176
+ const fourthPropSelector = createPropSelector<{
177
+ value4: number;
178
+ }>().value4();
179
+
180
+ const selector = createCachedSelector([], () => {})({
181
+ keySelector: stringComposeKeySelectors(
182
+ firstPropSelector,
183
+ secondPropSelector,
184
+ thirdPropSelector,
185
+ fourthPropSelector,
186
+ ),
187
+ });
188
+
189
+ const boundSelector = createBoundSelector(selector, {
190
+ value1: 1,
191
+ value4: 4,
192
+ });
193
+
194
+ if (!isCachedSelector(boundSelector)) {
195
+ throw new Error('selector should be cached');
196
+ }
197
+
198
+ const { keySelector } = boundSelector;
199
+
200
+ const key = keySelector(commonState, {
201
+ value2: 2,
202
+ value3: 3,
203
+ }) as unknown;
204
+
205
+ expect(key).toBe('2:3');
206
+ });
207
+ });
208
+
209
+ test('should return default key selector if all selectors are bounded', () => {
210
+ const firstPropSelector = createPropSelector<{ value1: number }>().value1();
211
+ const secondPropSelector = createPropSelector<{
212
+ value2: number;
213
+ }>().value2();
214
+
215
+ const selector = createCachedSelector([], () => {})({
216
+ keySelector: stringComposeKeySelectors(firstPropSelector, secondPropSelector),
217
+ });
218
+
219
+ const boundSelector = createBoundSelector(selector, {
220
+ value1: 1,
221
+ value2: 2,
222
+ });
223
+
224
+ if (!isCachedSelector(boundSelector)) {
225
+ throw new Error('selector should be cached');
226
+ }
227
+
228
+ const { keySelector } = boundSelector;
229
+
230
+ expect(keySelector).toBe(defaultKeySelector);
231
+ });
232
+
233
+ test('should exclude default key selector from result key selector', () => {
234
+ const firstPropSelector = createPropSelector<{ value1: number }>().value1();
235
+ const secondPropSelector = createPropSelector<{
236
+ value2: number;
237
+ }>().value2();
238
+
239
+ const selector = createCachedSelector([], () => {})({
240
+ keySelector: stringComposeKeySelectors(
241
+ firstPropSelector,
242
+ secondPropSelector,
243
+ defaultKeySelector,
244
+ ),
245
+ });
246
+
247
+ const boundSelector = createBoundSelector(selector, {
248
+ value3: 3,
249
+ });
250
+
251
+ if (!isCachedSelector(boundSelector)) {
252
+ throw new Error('selector should be cached');
253
+ }
254
+
255
+ const { keySelector } = boundSelector;
256
+
257
+ const key = keySelector(commonState, {
258
+ value1: 1,
259
+ value2: 2,
260
+ }) as unknown;
261
+
262
+ expect(key).toBe('1:2');
263
+ });
264
+
265
+ test('should return not composed key selector if result key sector has only one dependency', () => {
266
+ const firstPropSelector = createPropSelector<{ value1: number }>().value1();
267
+ const secondPropSelector = createPropSelector<{
268
+ value2: number;
269
+ }>().value2();
270
+
271
+ const selector = createCachedSelector([], () => {})({
272
+ keySelector: stringComposeKeySelectors(firstPropSelector, secondPropSelector),
273
+ });
274
+
275
+ const boundSelector = createBoundSelector(selector, {
276
+ value1: 1,
277
+ });
278
+
279
+ if (!isCachedSelector(boundSelector)) {
280
+ throw new Error('selector should be cached');
281
+ }
282
+
283
+ const { keySelector } = boundSelector;
284
+
285
+ expect(isComposedKeySelector(keySelector)).toBeFalsy();
286
+ });
287
+
288
+ test('should return composed key selector if result key selector has more than one dependency', () => {
289
+ const firstPropSelector = createPropSelector<{ value1: number }>().value1();
290
+ const secondPropSelector = createPropSelector<{
291
+ value2: number;
292
+ }>().value2();
293
+ const thirdPropSelector = createPropSelector<{ value3: number }>().value3();
294
+
295
+ const selector = createCachedSelector([], () => {})({
296
+ keySelector: stringComposeKeySelectors(
297
+ firstPropSelector,
298
+ secondPropSelector,
299
+ thirdPropSelector,
300
+ ),
301
+ });
302
+
303
+ const boundSelector = createBoundSelector(selector, {
304
+ value1: 1,
305
+ });
306
+
307
+ if (!isCachedSelector(boundSelector)) {
308
+ throw new Error('selector should be cached');
309
+ }
310
+
311
+ const { keySelector } = boundSelector;
312
+
313
+ expect(isComposedKeySelector(keySelector)).toBeTruthy();
314
+ });
315
+
316
+ test('should bound exotic key selector without exclude', () => {
317
+ const firstPropSelector = (_state: State, props: { value1: number }) => props.value1;
318
+ const secondPropSelector = (_state: State, props: { value2: number }) => props.value2;
319
+ const thirdPropSelector = (_state: State, props: { value3: number }) => props.value3;
320
+
321
+ const selector = createCachedSelector(
322
+ [firstPropSelector, secondPropSelector, thirdPropSelector],
323
+ () => {},
324
+ )({
325
+ keySelector: stringComposeKeySelectors(
326
+ firstPropSelector,
327
+ secondPropSelector,
328
+ thirdPropSelector,
329
+ ),
330
+ });
331
+
332
+ const boundSelector = createBoundSelector(selector, {
333
+ value1: 1,
334
+ value3: 3,
335
+ });
336
+
337
+ if (!isCachedSelector(boundSelector)) {
338
+ throw new Error('selector should be cached');
339
+ }
340
+
341
+ const { keySelector } = boundSelector;
342
+
343
+ const key = keySelector(commonState, {
344
+ value1: 10,
345
+ value2: 20,
346
+ value3: 30,
347
+ }) as unknown;
348
+
349
+ expect(key).toBe('1:20:3');
350
+ });
351
+ });
@@ -0,0 +1,24 @@
1
+ import { createCachedSequenceSelector } from '../createCachedSequenceSelector';
2
+ import { commonState, State } from '../__data__/state';
3
+
4
+ describe('createCachedSequenceSelector', () => {
5
+ const personSelector = (state: State, props: { personId: number }) =>
6
+ state.persons.data[props.personId];
7
+
8
+ test('should cache result for different props', () => {
9
+ const selector = createCachedSequenceSelector([personSelector, personSelector])({
10
+ keySelector: (_state, props) => props.personId,
11
+ });
12
+
13
+ const result1 = selector(commonState, { personId: 1 });
14
+ const result2 = selector(commonState, { personId: 2 });
15
+ const result3 = selector(commonState, { personId: 1 });
16
+ const result4 = selector(commonState, { personId: 2 });
17
+
18
+ expect(result1.map((person) => person.firstName)).toEqual(['Marry', 'Marry']);
19
+ expect(result2.map((person) => person.firstName)).toEqual(['Harry', 'Harry']);
20
+
21
+ expect(result1).toBe(result3);
22
+ expect(result2).toBe(result4);
23
+ });
24
+ });
@@ -0,0 +1,29 @@
1
+ import { createCachedStructuredSelector } from '../createCachedStructuredSelector';
2
+ import { commonState, State } from '../__data__/state';
3
+
4
+ describe('createCachedSequenceSelector', () => {
5
+ const personSelector = (state: State, props: { personId: number }) =>
6
+ state.persons.data[props.personId];
7
+
8
+ test('should cache result for different props', () => {
9
+ const selector = createCachedStructuredSelector({
10
+ firstPerson: personSelector,
11
+ secondPerson: personSelector,
12
+ })({
13
+ keySelector: (_state, props) => props.personId,
14
+ });
15
+
16
+ const result1 = selector(commonState, { personId: 1 });
17
+ const result2 = selector(commonState, { personId: 2 });
18
+ const result3 = selector(commonState, { personId: 1 });
19
+ const result4 = selector(commonState, { personId: 2 });
20
+
21
+ expect(result1.firstPerson.firstName).toEqual('Marry');
22
+ expect(result1.secondPerson.firstName).toEqual('Marry');
23
+ expect(result2.firstPerson.firstName).toEqual('Harry');
24
+ expect(result2.secondPerson.firstName).toEqual('Harry');
25
+
26
+ expect(result1).toBe(result3);
27
+ expect(result2).toBe(result4);
28
+ });
29
+ });