import { Action, AnyAction, Reducer } from 'redux' import { DeepReadonly } from '../../types' import { ActionReducerMapBuilder, executeReducerBuilderCallback } from './mapBuilders' import { NoInfer } from './tsHelpers' export type CaseReducer = ( state: S, action: A ) => S | void export interface ActionMatcher { (action: AnyAction): action is A } /** * Defines a mapping from action types to corresponding action object shapes. * * @deprecated This should not be used manually - it is only used for internal * inference purposes and should not have any further value. * It might be removed in the future. * @public */ export type Actions = Record /** * A mapping from action types to case reducers for `createReducer()`. * * @deprecated This should not be used manually - it is only used * for internal inference purposes and using it manually * would lead to type erasure. * It might be removed in the future. * @public */ export type CaseReducers = { [T in keyof AS]: AS[T] extends Action ? CaseReducer : void } export type ActionMatcherDescription = { matcher: ActionMatcher reducer: CaseReducer> } export type ActionMatcherDescriptionCollection = Array< ActionMatcherDescription > export type NotFunction = T extends Function ? never : T export type ReducerWithInitialState> = Reducer> & { getInitialState: () => DeepReadonly } export type ReadonlyActionMatcherDescriptionCollection = ReadonlyArray< ActionMatcherDescription > function isStateFunction(x: unknown): x is () => S { return typeof x === 'function' } export function createReducer>( initialState: S | (() => S), builderCallback: (builder: ActionReducerMapBuilder) => void ): ReducerWithInitialState export function createReducer< S extends NotFunction, CR extends CaseReducers = CaseReducers >( initialState: S | (() => S), actionsMap: CR, actionMatchers?: ActionMatcherDescriptionCollection, defaultCaseReducer?: CaseReducer ): ReducerWithInitialState export function createReducer>( initialState: S | (() => S), mapOrBuilderCallback: | CaseReducers | ((builder: ActionReducerMapBuilder) => void), actionMatchers: ReadonlyActionMatcherDescriptionCollection = [], defaultCaseReducer?: CaseReducer ): ReducerWithInitialState { let [actionsMap, finalActionMatchers, finalDefaultCaseReducer] = typeof mapOrBuilderCallback === 'function' ? executeReducerBuilderCallback(mapOrBuilderCallback) : [mapOrBuilderCallback, actionMatchers, defaultCaseReducer] // Ensure the initial state gets frozen either way let getInitialState: () => S if (isStateFunction(initialState)) { getInitialState = () => initialState() } else { getInitialState = () => initialState } function reducer(state = getInitialState(), action: any): S { let caseReducers = [ actionsMap[action.type], ...finalActionMatchers .filter(({ matcher }) => matcher(action)) .map(({ reducer }) => reducer), ] if (caseReducers.filter((cr) => !!cr).length === 0) { caseReducers = [finalDefaultCaseReducer] } return caseReducers.reduce((previousState, caseReducer): S => { if (caseReducer) { return caseReducer(previousState, action) as S } return previousState }, state) } reducer.getInitialState = getInitialState return reducer as ReducerWithInitialState }