/* eslint-disable @typescript-eslint/no-explicit-any */ /** * @file Utilities for defining typed action creators and reducer handler maps without external dependencies. */ export type AnyAction = { type: string }; export type AnyActionCreator = { (...args: any[]): AnyAction; readonly type: string; }; type PayloadCreator = (...args: any[]) => any; type ActionFromPayload = ReturnType extends undefined | void ? { type: Type } : { type: Type; payload: Exclude, undefined> }; /** * Creates a typed action creator with an attached literal `type` property. */ export function createAction(type: Type): (() => { type: Type }) & { readonly type: Type }; export function createAction( type: Type, payloadCreator: Creator, ): ((...args: Parameters) => ActionFromPayload) & { readonly type: Type }; export function createAction( type: Type, payloadCreator?: Creator, ) { if (!payloadCreator) { const actionCreator = (() => ({ type } as const)) as (() => { type: Type }) & { readonly type: Type }; Object.defineProperty(actionCreator, "type", { value: type, writable: false, enumerable: true, }); return actionCreator; } const actionCreator = ((...args: Parameters) => { const payload = payloadCreator(...args); if (typeof payload === "undefined") { return { type } as const; } return { type, payload } as const; }) as ((...args: Parameters) => ActionFromPayload) & { readonly type: Type }; Object.defineProperty(actionCreator, "type", { value: type, writable: false, enumerable: true, }); return actionCreator; } export type ActionCreatorsMap = Record; export type ActionUnion = ReturnType; export type CaseReducer = (state: State, action: Action, extra: Extra) => State; export type BoundActionCreators = { [Key in keyof Creators]: (...args: Parameters) => ReturnType; }; export const bindActionCreators = ( creators: Creators, dispatch: (action: ActionUnion) => unknown, ): BoundActionCreators => { const bound = {} as BoundActionCreators; (Object.keys(creators) as Array).forEach((key) => { const creator = creators[key]; bound[key] = ((...args: Parameters) => { const action = creator(...args); dispatch(action as ActionUnion); return action; }) as BoundActionCreators[keyof Creators]; }); return bound; }; export const createActionHandlerMap = < State, Creators extends ActionCreatorsMap, Extra = undefined, >( creators: Creators, handlers: { [Key in keyof Creators]?: CaseReducer, Extra>; }, ): Record, Extra>> => { const map: Record, Extra>> = {}; (Object.keys(handlers) as Array).forEach((key) => { const handler = handlers[key]; if (!handler) { return; } const creator = creators[key]; if (!creator) { throw new Error(`Missing action creator for key "${String(key)}"`); } map[creator.type] = handler as CaseReducer, Extra>; }); return map; };