import type { ThunkAction, ThunkDispatch } from 'redux-thunk'; import type { ActionCreatorFactory, AnyAction, AsyncActionCreators, Meta, } from 'typescript-fsa'; /** * This interface can be augmented by users to add default types for the root state when * using `typescript-fsa-redux-thunk`. * Use module augmentation to append your own type definition in a your_custom_type.d.ts file. * https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation */ // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface DefaultRootState {} /** * It's either a promise, or it isn't */ export type MaybePromise = T | Promise; /** * A redux-thunk with the params as the first argument. You don't have to * return a promise; but, the result of the dispatch will be one. */ export type AsyncWorker< InputType, ReturnType, State = DefaultRootState, Extra = unknown, > = ( params: InputType, dispatch: ThunkDispatch, getState: () => State, extraArgument: Extra, ) => MaybePromise; /** Workaround for typescript-fsa issue #77 */ export type ThunkReturnType = T extends void ? unknown : T extends Promise ? Promise : T; type SmartThunkFunction = unknown extends InputType ? ThunkFunctionWithoutParams< ThunkReturnType, State, Error, Extra > : ThunkFunction< InputType, ThunkReturnType, State, Error, Extra >; /** * Factory function to easily create a thunk * @param factory typescript-fsa action creator factory * @returns an function that takes * - the `type` of the action, * - the your worker thunk function * And returns object with the async actions and the thunk itself */ export const asyncFactory = ( factory: ActionCreatorFactory, resolve: () => Promise = Promise.resolve.bind(Promise), ) => ( type: string, worker: AsyncWorker, State, Extra>, commonMeta?: Meta, ): SmartThunkFunction => { type Procedure = ThunkFunction< InputType, ThunkReturnType, State, Error, Extra >; const async = factory.async, Error>( type, commonMeta, ); const fn: Procedure = (params) => (dispatch, getState, extraArgument) => resolve() .then(() => { dispatch(async.started(params)); }) .then(() => worker(params, dispatch, getState, extraArgument)) .then( (result) => { dispatch(async.done({ params, result })); return result; }, (error) => { dispatch(async.failed({ params, error })); throw error; }, ); fn.action = (params) => fn(params); fn.async = async; return fn as unknown as SmartThunkFunction< State, InputType, ReturnType, Error, Extra >; }; /* eslint-enable @typescript-eslint/explicit-module-boundary-types */ export type ThunkFunctionAction< ReturnType, State = DefaultRootState, Extra = unknown, > = ( dispatch: ThunkDispatch, getState: () => State, extraArgument: Extra, ) => Promise; export interface ThunkFunction< InputType, ReturnType, State = DefaultRootState, Error = unknown, Extra = unknown, > { (params: InputType): ThunkFunctionAction; action(params: InputType): ThunkFunctionAction; async: AsyncActionCreators; } export interface ThunkFunctionWithoutParams< ReturnType, State = DefaultRootState, Error = unknown, Extra = unknown, > { (): ThunkFunctionAction; action(): ThunkFunctionAction; async: AsyncActionCreators; } /** Utility type for a function that takes paras and returns a redux-thunk */ export type ThunkCreator = ( params?: InputType, ) => ThunkAction, State, unknown, AnyAction>; /** The result type for thunkToAction below */ export type ThunkFn = ( params?: InputType, ) => Promise; /** * Passing the result of this to bindActionCreators and then calling the result * is equivalent to calling `store.dispatch(thunkCreator(params))`. Useful * for when you pass it to `connect()` in an action creators map object. * @param thunkCreator The thunk action creator * @returns thunkAction as if it was bound */ export function thunkToAction( thunkCreator: ThunkCreator, ): ThunkFn { return thunkCreator as unknown as ThunkFn; } export default asyncFactory;