1 | import type {
|
2 | Selector,
|
3 | GetParamsFromSelectors,
|
4 | OutputSelector,
|
5 | SelectorArray,
|
6 | SelectorResultArray,
|
7 | DropFirst,
|
8 | MergeParameters,
|
9 | Expand,
|
10 | ObjValueTuple,
|
11 | Head,
|
12 | Tail
|
13 | } from './types'
|
14 |
|
15 | export type {
|
16 | Selector,
|
17 | GetParamsFromSelectors,
|
18 | GetStateFromSelectors,
|
19 | OutputSelector,
|
20 | EqualityFn,
|
21 | SelectorArray,
|
22 | SelectorResultArray,
|
23 | ParametricSelector,
|
24 | OutputParametricSelector,
|
25 | OutputSelectorFields
|
26 | } from './types'
|
27 |
|
28 | import {
|
29 | defaultMemoize,
|
30 | defaultEqualityCheck,
|
31 | DefaultMemoizeOptions
|
32 | } from './defaultMemoize'
|
33 |
|
34 | export { defaultMemoize, defaultEqualityCheck }
|
35 |
|
36 | export type { DefaultMemoizeOptions }
|
37 |
|
38 | function getDependencies(funcs: unknown[]) {
|
39 | const dependencies = Array.isArray(funcs[0]) ? funcs[0] : funcs
|
40 |
|
41 | if (!dependencies.every(dep => typeof dep === 'function')) {
|
42 | const dependencyTypes = dependencies
|
43 | .map(dep =>
|
44 | typeof dep === 'function'
|
45 | ? `function ${dep.name || 'unnamed'}()`
|
46 | : typeof dep
|
47 | )
|
48 | .join(', ')
|
49 |
|
50 | throw new Error(
|
51 | `createSelector expects all input-selectors to be functions, but received the following types: [${dependencyTypes}]`
|
52 | )
|
53 | }
|
54 |
|
55 | return dependencies as SelectorArray
|
56 | }
|
57 |
|
58 | export function createSelectorCreator<
|
59 | /** Selectors will eventually accept some function to be memoized */
|
60 | F extends (...args: unknown[]) => unknown,
|
61 | /** A memoizer such as defaultMemoize that accepts a function + some possible options */
|
62 | MemoizeFunction extends (func: F, ...options: any[]) => F,
|
63 | /** The additional options arguments to the memoizer */
|
64 | MemoizeOptions extends unknown[] = DropFirst<Parameters<MemoizeFunction>>
|
65 | >(
|
66 | memoize: MemoizeFunction,
|
67 | ...memoizeOptionsFromArgs: DropFirst<Parameters<MemoizeFunction>>
|
68 | ) {
|
69 | const createSelector = (...funcs: Function[]) => {
|
70 | let recomputations = 0
|
71 | let lastResult: unknown
|
72 |
|
73 | // Due to the intricacies of rest params, we can't do an optional arg after `...funcs`.
|
74 | // So, start by declaring the default value here.
|
75 | // (And yes, the words 'memoize' and 'options' appear too many times in this next sequence.)
|
76 | let directlyPassedOptions: CreateSelectorOptions<MemoizeOptions> = {
|
77 | memoizeOptions: undefined
|
78 | }
|
79 |
|
80 |
|
81 | let resultFunc = funcs.pop()
|
82 |
|
83 |
|
84 | if (typeof resultFunc === 'object') {
|
85 | directlyPassedOptions = resultFunc as any
|
86 |
|
87 | resultFunc = funcs.pop()
|
88 | }
|
89 |
|
90 | if (typeof resultFunc !== 'function') {
|
91 | throw new Error(
|
92 | `createSelector expects an output function after the inputs, but received: [${typeof resultFunc}]`
|
93 | )
|
94 | }
|
95 |
|
96 |
|
97 |
|
98 | const { memoizeOptions = memoizeOptionsFromArgs } = directlyPassedOptions
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 | const finalMemoizeOptions = Array.isArray(memoizeOptions)
|
106 | ? memoizeOptions
|
107 | : ([memoizeOptions] as MemoizeOptions)
|
108 |
|
109 | const dependencies = getDependencies(funcs)
|
110 |
|
111 | const memoizedResultFunc = memoize(
|
112 | function recomputationWrapper() {
|
113 | recomputations++
|
114 |
|
115 | return resultFunc!.apply(null, arguments)
|
116 | } as F,
|
117 | ...finalMemoizeOptions
|
118 | )
|
119 |
|
120 |
|
121 | const selector = memoize(function dependenciesChecker() {
|
122 | const params = []
|
123 | const length = dependencies.length
|
124 |
|
125 | for (let i = 0; i < length; i++) {
|
126 |
|
127 |
|
128 | params.push(dependencies[i].apply(null, arguments))
|
129 | }
|
130 |
|
131 |
|
132 | lastResult = memoizedResultFunc.apply(null, params)
|
133 | return lastResult
|
134 | } as F)
|
135 |
|
136 | Object.assign(selector, {
|
137 | resultFunc,
|
138 | memoizedResultFunc,
|
139 | dependencies,
|
140 | lastResult: () => lastResult,
|
141 | recomputations: () => recomputations,
|
142 | resetRecomputations: () => (recomputations = 0)
|
143 | })
|
144 |
|
145 | return selector
|
146 | }
|
147 |
|
148 | return createSelector as CreateSelectorFunction<
|
149 | F,
|
150 | MemoizeFunction,
|
151 | MemoizeOptions
|
152 | >
|
153 | }
|
154 |
|
155 | export interface CreateSelectorOptions<MemoizeOptions extends unknown[]> {
|
156 | memoizeOptions: MemoizeOptions[0] | MemoizeOptions
|
157 | }
|
158 |
|
159 |
|
160 |
|
161 |
|
162 | export interface CreateSelectorFunction<
|
163 | F extends (...args: unknown[]) => unknown,
|
164 | MemoizeFunction extends (func: F, ...options: any[]) => F,
|
165 | MemoizeOptions extends unknown[] = DropFirst<Parameters<MemoizeFunction>>,
|
166 | Keys = Expand<
|
167 | Pick<ReturnType<MemoizeFunction>, keyof ReturnType<MemoizeFunction>>
|
168 | >
|
169 | > {
|
170 |
|
171 | <Selectors extends SelectorArray, Result>(
|
172 | ...items: [
|
173 | ...Selectors,
|
174 | (...args: SelectorResultArray<Selectors>) => Result
|
175 | ]
|
176 | ): OutputSelector<
|
177 | Selectors,
|
178 | Result,
|
179 | (...args: SelectorResultArray<Selectors>) => Result,
|
180 | GetParamsFromSelectors<Selectors>,
|
181 | Keys
|
182 | > &
|
183 | Keys
|
184 |
|
185 |
|
186 | <Selectors extends SelectorArray, Result>(
|
187 | ...items: [
|
188 | ...Selectors,
|
189 | (...args: SelectorResultArray<Selectors>) => Result,
|
190 | CreateSelectorOptions<MemoizeOptions>
|
191 | ]
|
192 | ): OutputSelector<
|
193 | Selectors,
|
194 | Result,
|
195 | ((...args: SelectorResultArray<Selectors>) => Result),
|
196 | GetParamsFromSelectors<Selectors>,
|
197 | Keys
|
198 | > &
|
199 | Keys
|
200 |
|
201 |
|
202 | <Selectors extends SelectorArray, Result>(
|
203 | selectors: [...Selectors],
|
204 | combiner: (...args: SelectorResultArray<Selectors>) => Result,
|
205 | options?: CreateSelectorOptions<MemoizeOptions>
|
206 | ): OutputSelector<
|
207 | Selectors,
|
208 | Result,
|
209 | (...args: SelectorResultArray<Selectors>) => Result,
|
210 | GetParamsFromSelectors<Selectors>,
|
211 | Keys
|
212 | > &
|
213 | Keys
|
214 | }
|
215 |
|
216 | export const createSelector =
|
217 | createSelectorCreator(defaultMemoize)
|
218 |
|
219 | type SelectorsObject = { [key: string]: (...args: any[]) => any }
|
220 |
|
221 | export interface StructuredSelectorCreator {
|
222 | <
|
223 | SelectorMap extends SelectorsObject,
|
224 | SelectorParams = MergeParameters<ObjValueTuple<SelectorMap>>
|
225 | >(
|
226 | selectorMap: SelectorMap,
|
227 | selectorCreator?: CreateSelectorFunction<any, any, any>
|
228 | ): (
|
229 |
|
230 |
|
231 |
|
232 |
|
233 | state: Head<SelectorParams>,
|
234 | ...params: Tail<SelectorParams>
|
235 | ) => {
|
236 | [Key in keyof SelectorMap]: ReturnType<SelectorMap[Key]>
|
237 | }
|
238 |
|
239 | <State, Result = State>(
|
240 | selectors: { [K in keyof Result]: Selector<State, Result[K], never> },
|
241 | selectorCreator?: CreateSelectorFunction<any, any, any>
|
242 | ): Selector<State, Result, never>
|
243 | }
|
244 |
|
245 |
|
246 | export const createStructuredSelector = ((
|
247 | selectors: SelectorsObject,
|
248 | selectorCreator = createSelector
|
249 | ) => {
|
250 | if (typeof selectors !== 'object') {
|
251 | throw new Error(
|
252 | 'createStructuredSelector expects first argument to be an object ' +
|
253 | `where each property is a selector, instead received a ${typeof selectors}`
|
254 | )
|
255 | }
|
256 | const objectKeys = Object.keys(selectors)
|
257 | const resultSelector = selectorCreator(
|
258 |
|
259 | objectKeys.map(key => selectors[key]),
|
260 | (...values: any[]) => {
|
261 | return values.reduce((composition, value, index) => {
|
262 | composition[objectKeys[index]] = value
|
263 | return composition
|
264 | }, {})
|
265 | }
|
266 | )
|
267 | return resultSelector
|
268 | }) as unknown as StructuredSelectorCreator
|