import type { GridState } from '../reducer' /* Typescript approach adapted from https://stackoverflow.com/a/53612254 */ /* One of the known top level keys of state. Will map to combineReducers call object */ type StateKey = keyof GridState /* Generic selector */ type Selector = ( state: TState, ...args: TArgs ) => TResult /* Global selector aware of all state */ type GlobalizedSelector = Selector< GridState, TArgs, TResult > /* Slice selector, aware of just one slice and its key */ type SliceSelector< TKey extends StateKey, TArgs extends any[], TResult, > = Selector, TArgs, TResult> /* One slice's state */ type StateSlice = GridState[TKey] /* This properly maps a local selector to a global selector without losing type safety */ const globalizeSelector = ( sliceKey: TKey, sliceSelector: Selector, TArgs, Result> ): GlobalizedSelector => (state, ...args) => sliceSelector(state[sliceKey], ...args) /* This takes a collection of selectors for a specific slice and makes them global selectors */ export const globalizeSelectors = < TKey extends StateKey, T extends { /* This special syntax is present so parameter/argument types are not lost during mapping */ [P in keyof T]: T[P] extends ( state: GridState[TKey], ...args: infer TArguments ) => infer Result ? SliceSelector : never }, >( sliceKey: TKey, sliceSelectors: T ): { /* This special syntax is present so parameter/argument types are not lost during mapping */ [P in keyof T]: T[P] extends ( state: GridState[TKey], ...args: infer TArguments ) => infer Result ? GlobalizedSelector : never } => Object.keys(sliceSelectors).reduce>( (memo, selectorName) => { const selectorNameTyped = selectorName as keyof T memo[selectorNameTyped] = globalizeSelector( sliceKey, sliceSelectors[selectorNameTyped] ) return memo }, {} as any ) as any /** * This is only needed because of `default` and `generateSelectors` exports from the reducer files. * * The TypeScript logic doesn't map 1:1 to the code. Anything that starts with `select` will be returned, * but TypeScript will only exclude items in the Exclude phrase below. */ export const prepare = >( sliceSelectors: T ): { [P in keyof T as Exclude]: T[P] } => { const obj = {} as any for (const [key, selector] of Object.entries(sliceSelectors)) { if (key.startsWith('select')) { obj[key as keyof typeof obj] = selector } } return obj }