redux-loop
Version:
Sequence your effects naturally and purely by returning them from your reducers.
264 lines (218 loc) • 7.29 kB
TypeScript
import { Action, ActionCreator, AnyAction, StoreEnhancer, Store } from 'redux';
export type UnknownAction = AnyAction | never;
export interface StoreCreator {
<S, A extends Action>(
reducer: LoopReducer<S, A>,
preloadedState: S | undefined,
enhancer: StoreEnhancer<S>
): Store<S>;
}
type WithDefaultActionHandling<T extends AnyAction> =
| T
| Action<'@@REDUX_LOOP/ENFORCE_DEFAULT_HANDLING'>;
export type Loop<S> = [S, CmdType];
export interface LoopReducer<S, A extends Action = AnyAction> {
(state: S | undefined, action: WithDefaultActionHandling<A>, ...args: any[]):
| S
| Loop<S>;
}
export interface LoopReducerWithDefinedState<S, A extends Action = AnyAction> {
(state: S, action: WithDefaultActionHandling<A>, ...args: any[]): S | Loop<S>;
}
export interface LiftedLoopReducer<S, A extends Action = AnyAction> {
(
state: S | undefined,
action: WithDefaultActionHandling<A>,
...args: any[]
): Loop<S>;
}
export type CmdSimulation = {
result: any;
success: boolean;
};
export interface MultiCmdSimulation {
[index: number]: CmdSimulation | MultiCmdSimulation;
}
export interface NoneCmd {
readonly type: 'NONE';
simulate(): null;
}
export interface ListCmd {
readonly type: 'LIST';
readonly cmds: CmdType[];
readonly sequence?: boolean;
readonly batch?: boolean;
simulate(simulations: MultiCmdSimulation): AnyAction[];
}
export interface ActionCmd<A extends Action> {
readonly type: 'ACTION';
readonly actionToDispatch: A;
simulate(): A;
}
export interface SetTimeoutCmd<A extends Action = never> {
readonly type: 'SET_TIMEOUT';
readonly nestedCmd: CmdType;
readonly delayMs: number;
readonly scheduledActionCreator?: (timerId: number) => A;
simulate(timerId: number, nestedSimulation?: CmdSimulation | MultiCmdSimulation): A[] | A | null
}
export interface SetIntervalCmd<A extends Action = never> {
readonly type: 'SET_INTERVAL';
readonly nestedCmd: CmdType;
readonly delayMs: number;
readonly scheduledActionCreator?: (timerId: number) => A;
simulate(timerId: number, nestedSimulation?: CmdSimulation | MultiCmdSimulation): A[] | A | null
}
export interface MapCmd<A extends Action = never> {
readonly type: 'MAP';
readonly tagger: ActionCreator<A>;
readonly nestedCmd: CmdType;
readonly args: any[];
simulate(simulations?: CmdSimulation | MultiCmdSimulation): A[] | A | null;
}
export interface RunCmd<
SuccessAction extends Action = never,
FailAction extends Action = never
> {
readonly type: 'RUN';
readonly func: (...args: any[]) => any;
readonly args?: any[];
readonly failActionCreator?: ActionCreator<FailAction>;
readonly successActionCreator?: ActionCreator<SuccessAction>;
readonly forceSync?: boolean;
simulate(simulation: CmdSimulation): SuccessAction | FailAction;
}
export type CmdType =
| ActionCmd<UnknownAction>
| SetTimeoutCmd<UnknownAction>
| SetIntervalCmd<UnknownAction>
| ListCmd
| MapCmd<UnknownAction>
| NoneCmd
| RunCmd<UnknownAction, UnknownAction>;
export interface LoopConfig {
readonly DONT_LOG_ERRORS_ON_HANDLED_FAILURES?: boolean;
readonly ENABLE_THUNK_MIGRATION?: boolean;
}
export function install<S>(config?: LoopConfig): StoreEnhancer<S>;
export function loop<S>(state: S, cmd: CmdType): Loop<S>;
export namespace Cmd {
export const dispatch: unique symbol;
export const getState: unique symbol;
export const none: NoneCmd;
export type Dispatch = <A extends Action>(a: A) => Promise<A>;
export type GetState = <S>() => S;
export function action<A extends Action>(action: A): ActionCmd<A>;
export type ListOptions = {
batch?: boolean;
sequence?: boolean;
testInvariants?: boolean;
};
export function list(cmds: CmdType[], options?: ListOptions): ListCmd;
export function clearTimeout(timerId: number): RunCmd;
export function clearInterval(timerId: number): RunCmd;
export function setTimeout<A extends Action = never>(
cmd: CmdType,
delayMs: number,
options?: {
scheduledActionCreator?: (timerId: number) => A;
},
): SetTimeoutCmd<A>;
export function setInterval<A extends Action = never>(
cmd: CmdType,
delayMs: number,
options?: {
scheduledActionCreator?: (timerId: number) => A;
},
): SetIntervalCmd<A>;
export function map<A extends Action, B extends Action>(
cmd: CmdType,
tagger: (subAction: B) => A,
args?: any[]
): MapCmd<A>;
// Allow the use of special dispatch | getState symbols
type ArgOrSymbol<T> = {
[K in keyof T]: T[K] extends GetState
? typeof getState
: T[K] extends Dispatch
? typeof dispatch
: T[K];
};
type RunFunc = (...args: any[]) => Promise<any> | any;
export type PromiseResult<T> = T extends Promise<infer U> ? U : T;
export type RunOptions<
Func extends RunFunc,
SuccessAction extends Action = never,
FailAction extends Action = never,
FailReason = unknown
> = {
args?: ArgOrSymbol<Parameters<Func>>;
forceSync?: boolean;
testInvariants?: boolean;
successActionCreator: (
value: PromiseResult<ReturnType<Func>>
) => SuccessAction;
failActionCreator: (error: FailReason) => FailAction;
};
export function run<Func extends RunFunc>(
f: Func,
options?: Omit<
RunOptions<Func>,
'successActionCreator' | 'failActionCreator'
>
): RunCmd<never, never>;
export function run<Func extends RunFunc, SuccessAction extends Action>(
f: Func,
options: Omit<RunOptions<Func, SuccessAction>, 'failActionCreator'>
): RunCmd<SuccessAction, never>;
export function run<
Func extends RunFunc,
FailAction extends Action,
FailReason = unknown
>(
f: Func,
options: Omit<
RunOptions<Func, never, FailAction, FailReason>,
'successActionCreator'
>
): RunCmd<never, FailAction>;
export function run<
Func extends RunFunc,
SuccessAction extends Action,
FailAction extends Action,
FailReason = unknown
>(
f: Func,
options: RunOptions<Func, SuccessAction, FailAction, FailReason>
): RunCmd<SuccessAction, FailAction>;
}
export type ReducersMapObject<S, A extends Action = AnyAction> = {
[K in keyof S]: LoopReducer<S[K], A>;
};
export function combineReducers<S, A extends Action = AnyAction>(
reducers: ReducersMapObject<S, A>
): LiftedLoopReducer<S, A>;
export function mergeChildReducers<S>(
parentResult: S | Loop<S>,
action: AnyAction,
childMap: ReducersMapObject<S>
): Loop<S>;
// eslint-disable-next-line camelcase
export function DEPRECATED_mergeChildReducers<S>(
parentResult: S | Loop<S>,
action: AnyAction,
childMap: ReducersMapObject<S>
): Loop<S>;
export function reduceReducers<S>(
initialReducer: LoopReducer<S, any>,
...reducers: Array<LoopReducerWithDefinedState<S, any>>
): LiftedLoopReducer<S, any>;
export function reduceReducers<S, A extends Action>(
initialReducer: LoopReducer<S, A>,
...reducers: Array<LoopReducerWithDefinedState<S, A>>
): LiftedLoopReducer<S, A>;
export function liftState<S>(state: S | Loop<S>): Loop<S>;
export function isLoop(test: any): boolean;
export function getModel<S>(loop: S | Loop<S>): S;
export function getCmd(a: any): CmdType | null;
export type HandlerReturn<S> = S | Loop<S>;