import { Action, AnyAction } from "redux" import { CaseReducer } from "./createReducer" import { ActionMatcher, ActionMatcherDescriptionCollection, CaseReducers } from "./createReducer" export interface TypedActionCreator { (...args: any[]): Action type: Type } /** * A builder for an action <-> reducer map. * * @public */ export interface ActionReducerMapBuilder { /** * Adds a case reducer to handle a single exact action type. * @remarks * All calls to `builder.addCase` must come before any calls to `builder.addMatcher` or `builder.addDefaultCase`. * @param actionCreator - Either a plain action type string, or an action creator generated by [`createAction`](./createAction) that can be used to determine the action type. * @param reducer - The actual case reducer function. */ addCase>( actionCreator: ActionCreator, reducer: CaseReducer> ): ActionReducerMapBuilder /** * Adds a case reducer to handle a single exact action type. * @remarks * All calls to `builder.addCase` must come before any calls to `builder.addMatcher` or `builder.addDefaultCase`. * @param actionCreator - Either a plain action type string, or an action creator generated by [`createAction`](./createAction) that can be used to determine the action type. * @param reducer - The actual case reducer function. */ addCase>( type: Type, reducer: CaseReducer ): ActionReducerMapBuilder addMatcher( matcher: ActionMatcher | ((action: AnyAction) => boolean), reducer: CaseReducer ): Omit, 'addCase'> addDefaultCase(reducer: CaseReducer): {} } export function executeReducerBuilderCallback( builderCallback: (builder: ActionReducerMapBuilder) => void ): [ CaseReducers, ActionMatcherDescriptionCollection, CaseReducer | undefined ] { const actionsMap: CaseReducers = {} const actionMatchers: ActionMatcherDescriptionCollection = [] let defaultCaseReducer: CaseReducer | undefined const builder = { addCase( typeOrActionCreator: string | TypedActionCreator, reducer: CaseReducer ) { if (process.env.NODE_ENV !== 'production') { /* to keep the definition by the user in line with actual behavior, we enforce `addCase` to always be called before calling `addMatcher` as matching cases take precedence over matchers */ if (actionMatchers.length > 0) { throw new Error( '`builder.addCase` should only be called before calling `builder.addMatcher`' ) } if (defaultCaseReducer) { throw new Error( '`builder.addCase` should only be called before calling `builder.addDefaultCase`' ) } } const type = typeof typeOrActionCreator === 'string' ? typeOrActionCreator : typeOrActionCreator.type if (type in actionsMap) { throw new Error( 'addCase cannot be called with two reducers for the same action type' ) } actionsMap[type] = reducer return builder }, addMatcher( matcher: ActionMatcher, reducer: CaseReducer ) { if (process.env.NODE_ENV !== 'production') { if (defaultCaseReducer) { throw new Error( '`builder.addMatcher` should only be called before calling `builder.addDefaultCase`' ) } } actionMatchers.push({ matcher, reducer }) return builder }, addDefaultCase(reducer: CaseReducer) { if (process.env.NODE_ENV !== 'production') { if (defaultCaseReducer) { throw new Error('`builder.addDefaultCase` can only be called once') } } defaultCaseReducer = reducer return builder }, } builderCallback(builder) return [actionsMap, actionMatchers, defaultCaseReducer] }