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,678 @@
|
|
|
1
|
+
import { FlatObjectCache, createCachedSelector } from "@veksa/re-reselect";
|
|
2
|
+
import { createSelector, createStructuredSelector } from "@veksa/reselect";
|
|
3
|
+
import { shallowEqual, useSelector as useSelector$1 } from "@veksa/react-redux";
|
|
4
|
+
import { useCallback, useMemo, useRef } from "react";
|
|
5
|
+
//#region src/createSegmentSelector.ts
|
|
6
|
+
function createSegmentSelector(fn, initial) {
|
|
7
|
+
return (state) => {
|
|
8
|
+
var _fn;
|
|
9
|
+
return (_fn = fn(state)) !== null && _fn !== void 0 ? _fn : initial;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/_helpers/isObject.ts
|
|
14
|
+
const isObject = (value) => {
|
|
15
|
+
return value !== null && typeof value === "object";
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/_helpers/defineDynamicSelectorName.ts
|
|
19
|
+
const defineDynamicSelectorName = (selector, selectorNameGetter) => {
|
|
20
|
+
let overriddenSelectorName;
|
|
21
|
+
Object.defineProperty(selector, "selectorName", {
|
|
22
|
+
configurable: true,
|
|
23
|
+
get: () => {
|
|
24
|
+
var _overriddenSelectorNa;
|
|
25
|
+
return (_overriddenSelectorNa = overriddenSelectorName) !== null && _overriddenSelectorNa !== void 0 ? _overriddenSelectorNa : selectorNameGetter();
|
|
26
|
+
},
|
|
27
|
+
set: (value) => {
|
|
28
|
+
overriddenSelectorName = value;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/debug/debug.ts
|
|
34
|
+
let debugMode = false;
|
|
35
|
+
const isDebugMode = () => debugMode;
|
|
36
|
+
const setDebugMode = (value) => {
|
|
37
|
+
debugMode = value;
|
|
38
|
+
};
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/debug/withDebugName.ts
|
|
41
|
+
/**
|
|
42
|
+
* Runs `callback` only outside production and only while debug mode is enabled.
|
|
43
|
+
* Centralizes the `NODE_ENV` / `isDebugMode` guard that the selector factories
|
|
44
|
+
* would otherwise repeat around every debug-name assignment.
|
|
45
|
+
*/
|
|
46
|
+
const runInDebug = (callback) => {
|
|
47
|
+
/* istanbul ignore else */
|
|
48
|
+
if (process.env.NODE_ENV !== "production") {
|
|
49
|
+
/* istanbul ignore else */
|
|
50
|
+
if (isDebugMode()) callback();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Assigns a lazily-computed debug name to `selector`, guarded by
|
|
55
|
+
* {@link runInDebug}. Covers the common single-name case; use `runInDebug`
|
|
56
|
+
* directly when a block needs to name several selectors at once.
|
|
57
|
+
*/
|
|
58
|
+
const withDebugName = (selector, selectorNameGetter) => {
|
|
59
|
+
runInDebug(() => {
|
|
60
|
+
defineDynamicSelectorName(selector, selectorNameGetter);
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/_helpers/getSelectorName.ts
|
|
65
|
+
const getSelectorName = (selector) => {
|
|
66
|
+
if ("selectorName" in selector && selector.selectorName) return selector.selectorName;
|
|
67
|
+
return selector.name;
|
|
68
|
+
};
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/createPathSelector.ts
|
|
71
|
+
const defaultVoidFunction = () => {};
|
|
72
|
+
/** @internal */
|
|
73
|
+
const innerCreatePathSelector = (baseSelector, path = [], applyMeta = defaultVoidFunction) => {
|
|
74
|
+
const pathLength = path.length;
|
|
75
|
+
const proxyTarget = (defaultValue) => {
|
|
76
|
+
function resultSelector(state, props) {
|
|
77
|
+
let result;
|
|
78
|
+
if (arguments.length === 2) result = baseSelector(state, props);
|
|
79
|
+
else if (arguments.length === 1) result = baseSelector(state);
|
|
80
|
+
else result = baseSelector.apply(null, arguments);
|
|
81
|
+
for (let i = 0; i < pathLength && isObject(result); i += 1) result = result[path[i]];
|
|
82
|
+
return result === void 0 ? defaultValue : result;
|
|
83
|
+
}
|
|
84
|
+
Object.assign(resultSelector, baseSelector, { path });
|
|
85
|
+
resultSelector.dependencies = [baseSelector];
|
|
86
|
+
applyMeta(resultSelector);
|
|
87
|
+
withDebugName(resultSelector, () => {
|
|
88
|
+
return [getSelectorName(baseSelector), ...path].join(".");
|
|
89
|
+
});
|
|
90
|
+
return resultSelector;
|
|
91
|
+
};
|
|
92
|
+
return new Proxy(proxyTarget, { get: (_target, key) => {
|
|
93
|
+
return innerCreatePathSelector(baseSelector, [...path, String(key)], applyMeta);
|
|
94
|
+
} });
|
|
95
|
+
};
|
|
96
|
+
function createPathSelector(baseSelector) {
|
|
97
|
+
return innerCreatePathSelector(baseSelector);
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/createPropSelector.ts
|
|
101
|
+
const propSelectorSymbol = Symbol.for("PropSelector");
|
|
102
|
+
const isPropSelector = (selector) => {
|
|
103
|
+
return selector instanceof Object && propSelectorSymbol in selector;
|
|
104
|
+
};
|
|
105
|
+
function createPropSelector() {
|
|
106
|
+
const propsSelector = (_state, props) => props;
|
|
107
|
+
const applyMeta = (selector) => {
|
|
108
|
+
Object.defineProperty(selector, propSelectorSymbol, { value: true });
|
|
109
|
+
};
|
|
110
|
+
return innerCreatePathSelector(propsSelector, [], applyMeta);
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/keys/createKeySelectorComposer.ts
|
|
114
|
+
const composedKeySelectorSymbol = Symbol.for("ComposedKeySelector");
|
|
115
|
+
function isComposedKeySelector(keySelector) {
|
|
116
|
+
return "dependencies" in keySelector && composedKeySelectorSymbol in keySelector;
|
|
117
|
+
}
|
|
118
|
+
function createKeySelectorComposer(baseKeySelectorComposer) {
|
|
119
|
+
return ((...keySelectors) => {
|
|
120
|
+
const resultSelector = baseKeySelectorComposer(...keySelectors);
|
|
121
|
+
resultSelector.dependencies = keySelectors;
|
|
122
|
+
Object.defineProperty(resultSelector, composedKeySelectorSymbol, { value: true });
|
|
123
|
+
return resultSelector;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
//#endregion
|
|
127
|
+
//#region src/_helpers/arePathsEqual.ts
|
|
128
|
+
const arePathsEqual = (path, anotherPath) => {
|
|
129
|
+
if (path.length !== anotherPath.length) return false;
|
|
130
|
+
for (let i = 0; i < path.length; i += 1) if (path[i] !== anotherPath[i]) return false;
|
|
131
|
+
return true;
|
|
132
|
+
};
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/keys/defaultKeySelector.ts
|
|
135
|
+
const defaultKeySelector = () => "<DefaultKey>";
|
|
136
|
+
//#endregion
|
|
137
|
+
//#region src/_helpers/isCachedSelector.ts
|
|
138
|
+
const isCachedSelector = (selector) => {
|
|
139
|
+
return selector instanceof Object && "keySelector" in selector && typeof selector.keySelector === "function";
|
|
140
|
+
};
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region src/keys/createKeySelectorCreator.ts
|
|
143
|
+
const areSelectorsEqual = (selector, another) => {
|
|
144
|
+
if (selector === another) return true;
|
|
145
|
+
if (isPropSelector(selector) && isPropSelector(another)) return arePathsEqual(selector.path, another.path);
|
|
146
|
+
return false;
|
|
147
|
+
};
|
|
148
|
+
const flatKeySelectors = (keySelectors) => {
|
|
149
|
+
const result = [];
|
|
150
|
+
for (let i = 0; i < keySelectors.length; i += 1) {
|
|
151
|
+
const keySelector = keySelectors[i];
|
|
152
|
+
if (isComposedKeySelector(keySelector)) result.push(...flatKeySelectors(keySelector.dependencies));
|
|
153
|
+
else result.push(keySelector);
|
|
154
|
+
}
|
|
155
|
+
return result;
|
|
156
|
+
};
|
|
157
|
+
const uniqKeySelectors = (keySelectors) => {
|
|
158
|
+
const result = [];
|
|
159
|
+
for (let i = 0; i < keySelectors.length; i += 1) {
|
|
160
|
+
const keySelector = keySelectors[i];
|
|
161
|
+
if (!result.some((resultKeySelector) => areSelectorsEqual(keySelector, resultKeySelector))) result.push(keySelector);
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
};
|
|
165
|
+
const excludeDefaultSelectors = (keySelectors) => {
|
|
166
|
+
const result = [];
|
|
167
|
+
for (let i = 0; i < keySelectors.length; i += 1) {
|
|
168
|
+
const keySelector = keySelectors[i];
|
|
169
|
+
if (keySelector !== defaultKeySelector) result.push(keySelector);
|
|
170
|
+
}
|
|
171
|
+
return result;
|
|
172
|
+
};
|
|
173
|
+
function createKeySelectorCreator(keySelectorComposer) {
|
|
174
|
+
return ({ inputSelectors, keySelector }) => {
|
|
175
|
+
let keySelectors = [];
|
|
176
|
+
if (keySelector) keySelectors.push(keySelector);
|
|
177
|
+
inputSelectors.forEach((selector) => {
|
|
178
|
+
if (isCachedSelector(selector)) keySelectors.push(selector.keySelector);
|
|
179
|
+
});
|
|
180
|
+
keySelectors = flatKeySelectors(keySelectors);
|
|
181
|
+
keySelectors = uniqKeySelectors(keySelectors);
|
|
182
|
+
keySelectors = excludeDefaultSelectors(keySelectors);
|
|
183
|
+
if (keySelectors.length === 0) return defaultKeySelector;
|
|
184
|
+
if (keySelectors.length === 1) {
|
|
185
|
+
const [resultSelector] = keySelectors;
|
|
186
|
+
return resultSelector;
|
|
187
|
+
}
|
|
188
|
+
return keySelectorComposer(...keySelectors);
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
//#endregion
|
|
192
|
+
//#region src/keys/stringComposeKeySelectors.ts
|
|
193
|
+
const stringComposeKeySelectors = createKeySelectorComposer((...keySelectors) => (state, props) => {
|
|
194
|
+
let key = keySelectors[0](state, props);
|
|
195
|
+
for (let i = 1; i < keySelectors.length; i += 1) {
|
|
196
|
+
key += ":";
|
|
197
|
+
key += keySelectors[i](state, props);
|
|
198
|
+
}
|
|
199
|
+
return key;
|
|
200
|
+
});
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region src/_helpers/temporaryAssign.ts
|
|
203
|
+
const noop = () => void 0;
|
|
204
|
+
function temporaryAssign(target, source) {
|
|
205
|
+
if (typeof target !== "object" || target === null) return {
|
|
206
|
+
result: source,
|
|
207
|
+
rollback: noop
|
|
208
|
+
};
|
|
209
|
+
let temporary = null;
|
|
210
|
+
let deleteProps = null;
|
|
211
|
+
for (const prop in source) if (Object.prototype.hasOwnProperty.call(source, prop)) {
|
|
212
|
+
const targetProp = target[prop];
|
|
213
|
+
const sourceProp = source[prop];
|
|
214
|
+
if (targetProp !== sourceProp) {
|
|
215
|
+
if (Object.prototype.hasOwnProperty.call(target, prop)) {
|
|
216
|
+
if (temporary === null) temporary = {};
|
|
217
|
+
temporary[prop] = targetProp;
|
|
218
|
+
} else {
|
|
219
|
+
if (deleteProps === null) deleteProps = [];
|
|
220
|
+
deleteProps.push(prop);
|
|
221
|
+
}
|
|
222
|
+
target[prop] = sourceProp;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (temporary === null && deleteProps === null) return {
|
|
226
|
+
result: target,
|
|
227
|
+
rollback: noop
|
|
228
|
+
};
|
|
229
|
+
const restore = temporary;
|
|
230
|
+
const remove = deleteProps;
|
|
231
|
+
const rollback = () => {
|
|
232
|
+
if (restore !== null) {
|
|
233
|
+
for (const prop in restore) if (Object.prototype.hasOwnProperty.call(restore, prop)) target[prop] = restore[prop];
|
|
234
|
+
}
|
|
235
|
+
if (remove !== null) {
|
|
236
|
+
for (const prop of remove) if (Object.prototype.hasOwnProperty.call(target, prop)) delete target[prop];
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
return {
|
|
240
|
+
result: target,
|
|
241
|
+
rollback
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
//#endregion
|
|
245
|
+
//#region src/_helpers/getObjectPaths.ts
|
|
246
|
+
const getObjectPaths = (value, parentPath = []) => {
|
|
247
|
+
if (!isObject(value)) return [parentPath];
|
|
248
|
+
return Object.keys(value).reduce((result, key) => {
|
|
249
|
+
const paths = getObjectPaths(value[key], parentPath.concat([key]));
|
|
250
|
+
return result.concat(paths);
|
|
251
|
+
}, []);
|
|
252
|
+
};
|
|
253
|
+
//#endregion
|
|
254
|
+
//#region src/createBoundSelector.ts
|
|
255
|
+
const generateBindingName = (binding) => {
|
|
256
|
+
const structure = Object.keys(binding).reduce((result, key) => ({
|
|
257
|
+
...result,
|
|
258
|
+
[key]: "[*]"
|
|
259
|
+
}), {});
|
|
260
|
+
return `${Object.keys(structure).join()} -> ${Object.values(structure).join()}`;
|
|
261
|
+
};
|
|
262
|
+
const innerCreateBoundSelector = (baseSelector, binding) => {
|
|
263
|
+
const boundSelector = (state, props) => baseSelector(state, {
|
|
264
|
+
...props,
|
|
265
|
+
...binding
|
|
266
|
+
});
|
|
267
|
+
return boundSelector;
|
|
268
|
+
};
|
|
269
|
+
const createBoundInnerSelector = (baseSelector, binding, options = {}) => {
|
|
270
|
+
var _ref, _options$keySelectorC;
|
|
271
|
+
const bindingStrategy = (_ref = options.bindingStrategy) !== null && _ref !== void 0 ? _ref : innerCreateBoundSelector;
|
|
272
|
+
const keySelectorComposer = (_options$keySelectorC = options.keySelectorComposer) !== null && _options$keySelectorC !== void 0 ? _options$keySelectorC : stringComposeKeySelectors;
|
|
273
|
+
const boundSelector = bindingStrategy(baseSelector, binding);
|
|
274
|
+
Object.assign(boundSelector, baseSelector);
|
|
275
|
+
boundSelector.dependencies = [baseSelector];
|
|
276
|
+
withDebugName(boundSelector, () => {
|
|
277
|
+
return `${getSelectorName(baseSelector)} (${generateBindingName(binding)})`;
|
|
278
|
+
});
|
|
279
|
+
if (isCachedSelector(baseSelector)) {
|
|
280
|
+
const cachedBoundSelector = boundSelector;
|
|
281
|
+
if (baseSelector.getMatchingSelector) cachedBoundSelector.getMatchingSelector = bindingStrategy(baseSelector.getMatchingSelector, binding);
|
|
282
|
+
if (baseSelector.removeMatchingSelector) cachedBoundSelector.removeMatchingSelector = bindingStrategy(baseSelector.removeMatchingSelector, binding);
|
|
283
|
+
const baseKeySelector = baseSelector.keySelector;
|
|
284
|
+
if (isObject(binding)) {
|
|
285
|
+
const paths = getObjectPaths(binding);
|
|
286
|
+
let keySelectors = isComposedKeySelector(baseKeySelector) ? baseKeySelector.dependencies : [baseKeySelector];
|
|
287
|
+
keySelectors = excludeDefaultSelectors(keySelectors);
|
|
288
|
+
keySelectors = keySelectors.filter((keySelector) => !isPropSelector(keySelector) || !paths.some((path) => arePathsEqual(keySelector.path, path))).map((keySelector) => isPropSelector(keySelector) ? keySelector : bindingStrategy(keySelector, binding));
|
|
289
|
+
if (keySelectors.length === 0) cachedBoundSelector.keySelector = defaultKeySelector;
|
|
290
|
+
else if (keySelectors.length === 1) {
|
|
291
|
+
const [keySelector] = keySelectors;
|
|
292
|
+
cachedBoundSelector.keySelector = keySelector;
|
|
293
|
+
} else cachedBoundSelector.keySelector = keySelectorComposer(...keySelectors);
|
|
294
|
+
} else cachedBoundSelector.keySelector = bindingStrategy(baseKeySelector, binding);
|
|
295
|
+
}
|
|
296
|
+
return boundSelector;
|
|
297
|
+
};
|
|
298
|
+
const createBoundSelector = (baseSelector, binding) => {
|
|
299
|
+
return createBoundInnerSelector(baseSelector, binding, { bindingStrategy: (selector, bindingProps) => {
|
|
300
|
+
const source = bindingProps;
|
|
301
|
+
const keys = Object.keys(source);
|
|
302
|
+
/**
|
|
303
|
+
* Almost every binding is a single key — `bound(selector, { positionId })` —
|
|
304
|
+
* and specialising it removes the two allocations the generic path costs per
|
|
305
|
+
* call: the `{ result, rollback }` object and the `rollback` closure. On a
|
|
306
|
+
* graph with a bound selector per row those are the calls that dominate.
|
|
307
|
+
*
|
|
308
|
+
* The `delete` in the restore stays deliberately. Leaving the key behind as
|
|
309
|
+
* `undefined` would be cheaper — it avoids putting the caller's props object
|
|
310
|
+
* into dictionary mode — but it also changes the object's shape, and
|
|
311
|
+
* `useSelector` memoizes props shallowly: a props object that has grown an
|
|
312
|
+
* extra key never again compares equal to a freshly built one, so the
|
|
313
|
+
* memoization the binding exists to protect would be defeated.
|
|
314
|
+
*/
|
|
315
|
+
if (keys.length === 1) {
|
|
316
|
+
const key = keys[0];
|
|
317
|
+
const value = source[key];
|
|
318
|
+
return (state, props) => {
|
|
319
|
+
const target = props;
|
|
320
|
+
if (typeof target !== "object" || target === null) return selector(state, source);
|
|
321
|
+
const previous = target[key];
|
|
322
|
+
if (previous === value) return selector(state, props);
|
|
323
|
+
const had = Object.prototype.hasOwnProperty.call(target, key);
|
|
324
|
+
target[key] = value;
|
|
325
|
+
try {
|
|
326
|
+
return selector(state, props);
|
|
327
|
+
} finally {
|
|
328
|
+
if (had) target[key] = previous;
|
|
329
|
+
else delete target[key];
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
return (state, props) => {
|
|
334
|
+
const { result: nextProps, rollback } = temporaryAssign(props, source);
|
|
335
|
+
const result = selector(state, nextProps);
|
|
336
|
+
rollback();
|
|
337
|
+
return result;
|
|
338
|
+
};
|
|
339
|
+
} });
|
|
340
|
+
};
|
|
341
|
+
//#endregion
|
|
342
|
+
//#region src/createEmptySelector.ts
|
|
343
|
+
/**
|
|
344
|
+
* Creates a selector that always resolves to `undefined`. Used as the "no link"
|
|
345
|
+
* branch of a chain (e.g. an optional relation that isn't present).
|
|
346
|
+
*
|
|
347
|
+
* When the base selector is cached, the empty selector is tagged with
|
|
348
|
+
* {@link defaultKeySelector} so that it still passes {@link isCachedSelector}
|
|
349
|
+
* and takes part in key-selector composition. The default key is deliberately
|
|
350
|
+
* the only cache-related metadata copied: an empty selector has no result to
|
|
351
|
+
* memoize, so it needs no `cache` / `getMatchingSelector` of its own, and the
|
|
352
|
+
* default key is dropped by `excludeDefaultSelectors` during composition.
|
|
353
|
+
*/
|
|
354
|
+
const createEmptySelector = (baseSelector) => {
|
|
355
|
+
const emptySelector = () => void 0;
|
|
356
|
+
if (isCachedSelector(baseSelector)) emptySelector.keySelector = defaultKeySelector;
|
|
357
|
+
return emptySelector;
|
|
358
|
+
};
|
|
359
|
+
//#endregion
|
|
360
|
+
//#region src/createAdaptedSelector.ts
|
|
361
|
+
const generateMappingName = (mapping) => {
|
|
362
|
+
if (mapping.name) return mapping.name;
|
|
363
|
+
const structure = mapping(new Proxy({}, { get: (_target, key) => key }));
|
|
364
|
+
return `${Object.keys(structure).join()} -> ${Object.values(structure).join()}`;
|
|
365
|
+
};
|
|
366
|
+
const innerCreateAdaptedSelector = (baseSelector, mapping) => (state, props) => {
|
|
367
|
+
return baseSelector(state, mapping(props));
|
|
368
|
+
};
|
|
369
|
+
const createAdaptedSelector = (baseSelector, mapping) => {
|
|
370
|
+
const adaptedSelector = innerCreateAdaptedSelector(baseSelector, mapping);
|
|
371
|
+
Object.assign(adaptedSelector, baseSelector);
|
|
372
|
+
adaptedSelector.dependencies = [baseSelector];
|
|
373
|
+
withDebugName(adaptedSelector, () => {
|
|
374
|
+
return `${getSelectorName(baseSelector)} (${generateMappingName(mapping)})`;
|
|
375
|
+
});
|
|
376
|
+
if (isCachedSelector(baseSelector)) {
|
|
377
|
+
const cachedAdaptedSelector = adaptedSelector;
|
|
378
|
+
if (baseSelector.getMatchingSelector) cachedAdaptedSelector.getMatchingSelector = innerCreateAdaptedSelector(baseSelector.getMatchingSelector, mapping);
|
|
379
|
+
if (baseSelector.removeMatchingSelector) cachedAdaptedSelector.removeMatchingSelector = innerCreateAdaptedSelector(baseSelector.removeMatchingSelector, mapping);
|
|
380
|
+
cachedAdaptedSelector.keySelector = innerCreateAdaptedSelector(baseSelector.keySelector, mapping);
|
|
381
|
+
}
|
|
382
|
+
return adaptedSelector;
|
|
383
|
+
};
|
|
384
|
+
//#endregion
|
|
385
|
+
//#region src/_helpers/stringifyFunction.ts
|
|
386
|
+
const stringifyFunction = (source) => {
|
|
387
|
+
return String(source).replace(/\s+/g, " ").trim();
|
|
388
|
+
};
|
|
389
|
+
//#endregion
|
|
390
|
+
//#region src/_helpers/generateSelectorKey.ts
|
|
391
|
+
const generateSelectorKey = (selector) => {
|
|
392
|
+
var _dependencies;
|
|
393
|
+
const dependencies = (_dependencies = selector.dependencies) !== null && _dependencies !== void 0 ? _dependencies : [];
|
|
394
|
+
let result = stringifyFunction(selector);
|
|
395
|
+
for (let i = dependencies.length - 1; i >= 0; i -= 1) {
|
|
396
|
+
const dependency = dependencies[i];
|
|
397
|
+
result += stringifyFunction(dependency);
|
|
398
|
+
}
|
|
399
|
+
return result;
|
|
400
|
+
};
|
|
401
|
+
//#endregion
|
|
402
|
+
//#region src/createChainSelector.ts
|
|
403
|
+
function createChainSelector(selector, options, prevChain) {
|
|
404
|
+
const chain = (fn, chainOptions) => {
|
|
405
|
+
const combinedOptions = {
|
|
406
|
+
...options,
|
|
407
|
+
...chainOptions
|
|
408
|
+
};
|
|
409
|
+
const keySelector = isCachedSelector(selector) ? selector.keySelector : defaultKeySelector;
|
|
410
|
+
const { createSelectorOptions = () => ({}), keySelectorComposer = stringComposeKeySelectors } = combinedOptions;
|
|
411
|
+
const keySelectorCreator = createKeySelectorCreator(keySelectorComposer);
|
|
412
|
+
const higherOrderSelector = createCachedSelector([selector], fn)({
|
|
413
|
+
...createSelectorOptions(),
|
|
414
|
+
keySelector
|
|
415
|
+
});
|
|
416
|
+
withDebugName(higherOrderSelector, () => {
|
|
417
|
+
return `higher order for ${getSelectorName(selector)} (${stringifyFunction(fn)})`;
|
|
418
|
+
});
|
|
419
|
+
const combinedSelector = (state, props) => {
|
|
420
|
+
const derivedSelector = higherOrderSelector(state, props);
|
|
421
|
+
runInDebug(() => {
|
|
422
|
+
combinedSelector.dependencies = [higherOrderSelector, derivedSelector];
|
|
423
|
+
if (!getSelectorName(derivedSelector)) defineDynamicSelectorName(derivedSelector, () => {
|
|
424
|
+
return `derived from ${getSelectorName(selector)} (${generateSelectorKey(derivedSelector)})`;
|
|
425
|
+
});
|
|
426
|
+
defineDynamicSelectorName(combinedSelector, () => {
|
|
427
|
+
return `${getSelectorName(selector)} (chained by ${getSelectorName(derivedSelector)})`;
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
return derivedSelector(state, props);
|
|
431
|
+
};
|
|
432
|
+
combinedSelector.dependencies = [higherOrderSelector];
|
|
433
|
+
combinedSelector.cache = higherOrderSelector.cache;
|
|
434
|
+
/**
|
|
435
|
+
* The derived selector's key selector, memoized on the derived selector itself.
|
|
436
|
+
*
|
|
437
|
+
* This used to be a second `createCachedSelector` over `[higherOrderSelector]`,
|
|
438
|
+
* which meant every key computation paid a whole extra cached-selector layer —
|
|
439
|
+
* its own key computation, its own cache lookup, and its own evaluation of
|
|
440
|
+
* `higherOrderSelector` — to produce a value that depends on nothing but the
|
|
441
|
+
* derived selector. A `WeakMap` keyed on that selector is the same mapping with
|
|
442
|
+
* none of the layer, and it shares across cache keys rather than per key.
|
|
443
|
+
*
|
|
444
|
+
* Note what is deliberately *not* memoized: the `(state, props)` pair. `props`
|
|
445
|
+
* is mutated in place by `createBoundSelector`, so two calls can pass the same
|
|
446
|
+
* object reference with different contents, and a reference-keyed cache would
|
|
447
|
+
* hand back a stale selector.
|
|
448
|
+
*/
|
|
449
|
+
const derivedKeySelectors = /* @__PURE__ */ new WeakMap();
|
|
450
|
+
combinedSelector.keySelector = (state, props) => {
|
|
451
|
+
const derivedSelector = higherOrderSelector(state, props);
|
|
452
|
+
let derivedKeySelector = derivedKeySelectors.get(derivedSelector);
|
|
453
|
+
if (derivedKeySelector === void 0) {
|
|
454
|
+
derivedKeySelector = keySelectorCreator({ inputSelectors: [higherOrderSelector, derivedSelector] });
|
|
455
|
+
derivedKeySelectors.set(derivedSelector, derivedKeySelector);
|
|
456
|
+
}
|
|
457
|
+
return derivedKeySelector(state, props);
|
|
458
|
+
};
|
|
459
|
+
withDebugName(combinedSelector, () => {
|
|
460
|
+
return `${getSelectorName(selector)} (will be chained ${stringifyFunction(fn)})`;
|
|
461
|
+
});
|
|
462
|
+
return createChainSelector(combinedSelector, combinedOptions, Object.assign(fn, { parentChain: prevChain }));
|
|
463
|
+
};
|
|
464
|
+
const map = (fn, mapOptions) => {
|
|
465
|
+
const mapSelector = (result) => {
|
|
466
|
+
const output = fn(result);
|
|
467
|
+
const mapSelector = () => output;
|
|
468
|
+
withDebugName(mapSelector, () => {
|
|
469
|
+
return `mapped from ${getSelectorName(selector)} (${stringifyFunction(fn)})`;
|
|
470
|
+
});
|
|
471
|
+
return mapSelector;
|
|
472
|
+
};
|
|
473
|
+
return chain(mapSelector, mapOptions);
|
|
474
|
+
};
|
|
475
|
+
const build = () => {
|
|
476
|
+
return Object.assign(selector, { chainHierarchy: prevChain });
|
|
477
|
+
};
|
|
478
|
+
return {
|
|
479
|
+
chain,
|
|
480
|
+
map,
|
|
481
|
+
build
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
//#endregion
|
|
485
|
+
//#region src/createSequenceSelector.ts
|
|
486
|
+
function createSequenceSelector(selectors, selectorCreator = createSelector) {
|
|
487
|
+
return selectorCreator(selectors, (...results) => results);
|
|
488
|
+
}
|
|
489
|
+
//#endregion
|
|
490
|
+
//#region src/createCachedSequenceSelector.ts
|
|
491
|
+
function createCachedSequenceSelector(selectors) {
|
|
492
|
+
return createSequenceSelector(selectors, createCachedSelector);
|
|
493
|
+
}
|
|
494
|
+
//#endregion
|
|
495
|
+
//#region src/createStructuredSelector.ts
|
|
496
|
+
const createStructuredSelector$1 = createStructuredSelector;
|
|
497
|
+
//#endregion
|
|
498
|
+
//#region src/createCachedStructuredSelector.ts
|
|
499
|
+
function createCachedStructuredSelector(selectors) {
|
|
500
|
+
return createStructuredSelector$1(selectors, createCachedSelector);
|
|
501
|
+
}
|
|
502
|
+
//#endregion
|
|
503
|
+
//#region src/keys/arrayComposeKeySelectors.ts
|
|
504
|
+
const arrayComposeKeySelectors = createKeySelectorComposer((...keySelectors) => (state, props) => {
|
|
505
|
+
let keyPath = [];
|
|
506
|
+
for (let i = 0; i < keySelectors.length; i += 1) {
|
|
507
|
+
const key = keySelectors[i](state, props);
|
|
508
|
+
if (Array.isArray(key)) keyPath = keyPath.concat(key);
|
|
509
|
+
else keyPath.push(key);
|
|
510
|
+
}
|
|
511
|
+
return keyPath;
|
|
512
|
+
});
|
|
513
|
+
//#endregion
|
|
514
|
+
//#region src/cache/intervalMapCache.ts
|
|
515
|
+
/**
|
|
516
|
+
* Buckets used to live in a module-level `Record<string, …>` keyed by an
|
|
517
|
+
* incrementing numeric id, which meant every `get` and `set` coerced that number to
|
|
518
|
+
* a string and re-read the global record — four times per cache hit in `get`.
|
|
519
|
+
* Holding the entries on the instance removes both, and a `Map` keeps non-string
|
|
520
|
+
* keys as they are instead of stringifying them.
|
|
521
|
+
*/
|
|
522
|
+
const buckets = /* @__PURE__ */ new Set();
|
|
523
|
+
const cacheLifetime = 1e4;
|
|
524
|
+
let garbageCollectorStarted = false;
|
|
525
|
+
const runGarbageCollector = () => {
|
|
526
|
+
const currentTime = Date.now();
|
|
527
|
+
buckets.forEach((bucket) => {
|
|
528
|
+
if (bucket.sweep(currentTime)) buckets.delete(bucket);
|
|
529
|
+
});
|
|
530
|
+
window.setTimeout(runGarbageCollector, cacheLifetime);
|
|
531
|
+
};
|
|
532
|
+
const initGarbageCollector = () => {
|
|
533
|
+
if (garbageCollectorStarted) return;
|
|
534
|
+
if (typeof window !== "undefined") {
|
|
535
|
+
garbageCollectorStarted = true;
|
|
536
|
+
window.setTimeout(runGarbageCollector, cacheLifetime);
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
var IntervalMapCache = class {
|
|
540
|
+
constructor() {
|
|
541
|
+
this.entries = /* @__PURE__ */ new Map();
|
|
542
|
+
}
|
|
543
|
+
set(key, data) {
|
|
544
|
+
const entry = this.entries.get(key);
|
|
545
|
+
if (entry === void 0) {
|
|
546
|
+
buckets.add(this);
|
|
547
|
+
this.entries.set(key, {
|
|
548
|
+
data,
|
|
549
|
+
time: Date.now()
|
|
550
|
+
});
|
|
551
|
+
} else {
|
|
552
|
+
entry.data = data;
|
|
553
|
+
entry.time = Date.now();
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
get(key) {
|
|
557
|
+
const entry = this.entries.get(key);
|
|
558
|
+
if (entry === void 0) return;
|
|
559
|
+
entry.time = Date.now();
|
|
560
|
+
return entry.data;
|
|
561
|
+
}
|
|
562
|
+
remove(key) {
|
|
563
|
+
this.entries.delete(key);
|
|
564
|
+
}
|
|
565
|
+
clear() {
|
|
566
|
+
this.entries.clear();
|
|
567
|
+
buckets.delete(this);
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Drops entries untouched for longer than the lifetime. Returns whether the
|
|
571
|
+
* bucket is now empty, which is what lets the collector unregister it.
|
|
572
|
+
*/
|
|
573
|
+
sweep(currentTime) {
|
|
574
|
+
this.entries.forEach((entry, key) => {
|
|
575
|
+
if (currentTime - entry.time > cacheLifetime) this.entries.delete(key);
|
|
576
|
+
});
|
|
577
|
+
return this.entries.size === 0;
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
//#endregion
|
|
581
|
+
//#region src/cache/TreeCache.ts
|
|
582
|
+
const normalizeKey = (key) => Array.isArray(key) ? key : [key];
|
|
583
|
+
var TreeCacheNode = class {};
|
|
584
|
+
var TreeCache = class {
|
|
585
|
+
constructor(options) {
|
|
586
|
+
var _options$cacheObjectC;
|
|
587
|
+
this.cacheObjectCreator = (_options$cacheObjectC = options.cacheObjectCreator) !== null && _options$cacheObjectC !== void 0 ? _options$cacheObjectC : (() => new FlatObjectCache());
|
|
588
|
+
const root = new TreeCacheNode();
|
|
589
|
+
root.cache = this.cacheObjectCreator();
|
|
590
|
+
this.root = root;
|
|
591
|
+
}
|
|
592
|
+
clear() {
|
|
593
|
+
const root = new TreeCacheNode();
|
|
594
|
+
root.cache = this.cacheObjectCreator();
|
|
595
|
+
this.root = root;
|
|
596
|
+
}
|
|
597
|
+
get(key) {
|
|
598
|
+
if (!Array.isArray(key)) {
|
|
599
|
+
var _this$root$cache;
|
|
600
|
+
const cacheResponse = (_this$root$cache = this.root.cache) === null || _this$root$cache === void 0 ? void 0 : _this$root$cache.get(key);
|
|
601
|
+
return cacheResponse instanceof TreeCacheNode ? cacheResponse.selectorFn : void 0;
|
|
602
|
+
}
|
|
603
|
+
let currentNode = this.root;
|
|
604
|
+
for (let i = 0; i < key.length; i += 1) {
|
|
605
|
+
var _currentNode$cache;
|
|
606
|
+
const item = key[i];
|
|
607
|
+
const cacheResponse = (_currentNode$cache = currentNode.cache) === null || _currentNode$cache === void 0 ? void 0 : _currentNode$cache.get(item);
|
|
608
|
+
if (cacheResponse instanceof TreeCacheNode) currentNode = cacheResponse;
|
|
609
|
+
else return;
|
|
610
|
+
}
|
|
611
|
+
return currentNode.selectorFn;
|
|
612
|
+
}
|
|
613
|
+
set(key, selectorFn) {
|
|
614
|
+
const keyPath = normalizeKey(key);
|
|
615
|
+
let currentNode = this.root;
|
|
616
|
+
for (let i = 0; i < keyPath.length; i += 1) {
|
|
617
|
+
const item = keyPath[i];
|
|
618
|
+
if (!currentNode.cache) currentNode.cache = this.cacheObjectCreator();
|
|
619
|
+
const cacheResponse = currentNode.cache.get(item);
|
|
620
|
+
if (cacheResponse instanceof TreeCacheNode) currentNode = cacheResponse;
|
|
621
|
+
else {
|
|
622
|
+
const node = new TreeCacheNode();
|
|
623
|
+
currentNode.cache.set(item, node);
|
|
624
|
+
currentNode = node;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
currentNode.selectorFn = selectorFn;
|
|
628
|
+
}
|
|
629
|
+
remove(key) {
|
|
630
|
+
const keyPath = normalizeKey(key);
|
|
631
|
+
let currentNode = this.root;
|
|
632
|
+
for (let i = 0; i < keyPath.length; i += 1) {
|
|
633
|
+
var _currentNode$cache2;
|
|
634
|
+
const item = keyPath[i];
|
|
635
|
+
const cacheResponse = (_currentNode$cache2 = currentNode.cache) === null || _currentNode$cache2 === void 0 ? void 0 : _currentNode$cache2.get(item);
|
|
636
|
+
if (cacheResponse instanceof TreeCacheNode) currentNode = cacheResponse;
|
|
637
|
+
else return;
|
|
638
|
+
}
|
|
639
|
+
currentNode.selectorFn = void 0;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Called by re-reselect on every cache operation, so neither the wrapping array
|
|
643
|
+
* nor the `every` callback is allocated here.
|
|
644
|
+
*/
|
|
645
|
+
isValidCacheKey(key) {
|
|
646
|
+
const rootCache = this.root.cache;
|
|
647
|
+
if ((rootCache === null || rootCache === void 0 ? void 0 : rootCache.isValidCacheKey) === void 0) return true;
|
|
648
|
+
if (!Array.isArray(key)) return rootCache.isValidCacheKey(key);
|
|
649
|
+
for (let i = 0; i < key.length; i += 1) if (!rootCache.isValidCacheKey(key[i])) return false;
|
|
650
|
+
return true;
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
//#endregion
|
|
654
|
+
//#region src/_hooks/useMemoWith.ts
|
|
655
|
+
const useMemoWith = (value, comparator) => {
|
|
656
|
+
const ref = useRef(value);
|
|
657
|
+
if (!comparator(value, ref.current)) ref.current = value;
|
|
658
|
+
return ref.current;
|
|
659
|
+
};
|
|
660
|
+
//#endregion
|
|
661
|
+
//#region src/_hooks/useSelector.ts
|
|
662
|
+
function useSelector(selector, ...params) {
|
|
663
|
+
const props = params[0];
|
|
664
|
+
const memoizedProps = useMemoWith(props, shallowEqual);
|
|
665
|
+
const unfrozenProps = useMemo(() => {
|
|
666
|
+
return Object.isFrozen(memoizedProps) ? { ...memoizedProps } : memoizedProps;
|
|
667
|
+
}, [memoizedProps]);
|
|
668
|
+
return useSelector$1(useCallback((state) => {
|
|
669
|
+
return selector(state, unfrozenProps);
|
|
670
|
+
}, [selector, unfrozenProps]), { devModeChecks: {
|
|
671
|
+
stabilityCheck: "never",
|
|
672
|
+
identityFunctionCheck: "never"
|
|
673
|
+
} });
|
|
674
|
+
}
|
|
675
|
+
//#endregion
|
|
676
|
+
export { IntervalMapCache, TreeCache, arrayComposeKeySelectors, createAdaptedSelector, createBoundSelector, createCachedSequenceSelector, createCachedStructuredSelector, createChainSelector, createEmptySelector, createKeySelectorComposer, createKeySelectorCreator, createPathSelector, createPropSelector, createSegmentSelector, defaultKeySelector, initGarbageCollector, isDebugMode, setDebugMode, stringComposeKeySelectors, useSelector };
|
|
677
|
+
|
|
678
|
+
//# sourceMappingURL=reselectKit.mjs.map
|