type Selector = (state: State, ...args: any[]) => unknown; type Fn = (...args: any[]) => any; /** * The NoOptionalParams type is a utility type that checks if a function has optional or default parameters. * If the function has optional or default parameters, it returns a string literal type with an error message. * Otherwise, it returns the original function type. * * This is used to enforce that the combiner function passed to createSelector does not have optional or default parameters, * as memoization relies on the Function.length property, which does not account for optional or default parameters. */ type NoOptionalParams = Parameters extends Required> ? F : 'Combiner cannot have optional or default parameters because memoization relies on Function.length'; export type CreateSelectorFunction = < const Args extends any[], const Selectors extends ReadonlyArray>, const Combiner extends (...args: readonly [...ReturnTypes, ...Args]) => any, >( ...items: [...Selectors, NoOptionalParams] ) => ( ...args: Selectors['length'] extends 0 ? MergeParams, Parameters> : [ StateFromSelectorList, ...MergeParams, Parameters>, ] ) => ReturnType; type StateFromSelectorList = Selectors extends [ f: infer F, ...other: infer R, ] ? StateFromSelector extends StateFromSelectorList ? StateFromSelector : StateFromSelectorList : {}; type StateFromSelector = T extends (first: infer F, ...args: any[]) => any ? F : never; type DropFirst = T extends [any, ...infer Xs] ? Xs : []; type ReturnTypes = { [Index in keyof FunctionsArray]: FunctionsArray[Index] extends FunctionsArray[number] ? ReturnType : never; }; type MergeParams< STypes extends readonly unknown[], CTypes extends readonly unknown[], > = STypes['length'] extends 0 ? CTypes : MergeParams, DropFirst>; /** * Creates a selector function that can be used to derive values from the store's state. * * The combiner function can have up to three additional parameters, but it **cannot have optional or default parameters**. * * This function accepts up to six functions and combines them into a single selector function. * The resulting selector will take the state from the combined selectors and any additional parameters required by the combiner. * * The return type of the resulting selector is determined by the return type of the combiner function. * * @example * const selector = createSelector( * (state) => state.disabled * ); * * @example * const selector = createSelector( * (state) => state.disabled, * (state) => state.open, * (disabled, open) => ({ disabled, open }) * ); */ /* eslint-disable id-denylist */ export const createSelector = (( a: Function, b?: Function, c?: Function, d?: Function, e?: Function, f?: Function, ...other: any[] ) => { if (other.length > 0) { throw new Error('Unsupported number of selectors'); } let selector: any; if (a && b && c && d && e && f) { selector = (state: any, a1: any, a2: any, a3: any) => { const va = a(state, a1, a2, a3); const vb = b(state, a1, a2, a3); const vc = c(state, a1, a2, a3); const vd = d(state, a1, a2, a3); const ve = e(state, a1, a2, a3); return f(va, vb, vc, vd, ve, a1, a2, a3); }; } else if (a && b && c && d && e) { selector = (state: any, a1: any, a2: any, a3: any) => { const va = a(state, a1, a2, a3); const vb = b(state, a1, a2, a3); const vc = c(state, a1, a2, a3); const vd = d(state, a1, a2, a3); return e(va, vb, vc, vd, a1, a2, a3); }; } else if (a && b && c && d) { selector = (state: any, a1: any, a2: any, a3: any) => { const va = a(state, a1, a2, a3); const vb = b(state, a1, a2, a3); const vc = c(state, a1, a2, a3); return d(va, vb, vc, a1, a2, a3); }; } else if (a && b && c) { selector = (state: any, a1: any, a2: any, a3: any) => { const va = a(state, a1, a2, a3); const vb = b(state, a1, a2, a3); return c(va, vb, a1, a2, a3); }; } else if (a && b) { selector = (state: any, a1: any, a2: any, a3: any) => { const va = a(state, a1, a2, a3); return b(va, a1, a2, a3); }; } else if (a) { selector = a; } else { throw new Error('Missing arguments'); } return selector; }) as unknown as CreateSelectorFunction; /* eslint-enable id-denylist */