UNPKG

redux-sigma

Version:

A state machine library for redux and redux-saga.

463 lines (462 loc) 17.5 kB
import { StrictEffect } from "@redux-saga/types"; import { Saga } from "redux-saga"; /* eslint-disable @typescript-eslint/no-explicit-any */ import { Action } from "redux"; /* eslint-disable @typescript-eslint/no-explicit-any */ import { StrictEffect as StrictEffect$0 } from "redux-saga/effects"; /** * An event without payload */ interface Event<T extends string> extends Action<T> { type: T; } /** * An event with generic payload */ interface PayloadEvent<T extends string> extends Event<T> { payload: any; } /** * An error event (FSA compliant) */ interface ErrorEvent<T extends string> extends Event<T> { payload: Error; error: true; } /** * An activity that takes no input */ type VoidActivity = Saga<[]> | ((...args: []) => void); /** * An activity that takes an event as input */ type Activity<K extends string> = Saga<[Event<K>]> | Saga<[PayloadEvent<K>]> | Saga<[ErrorEvent<K>]> | Saga<[]> | ((...args: [Event<K>]) => void) | ((...args: [PayloadEvent<K>]) => void) | ((...args: [ErrorEvent<K>]) => void); declare const startStmActionType = "@@redux-sigma/start-stm"; declare const stmStartedActionType = "@@redux-sigma/stm-started"; declare const stopStmActionType = "@@redux-sigma/stop-stm"; declare const stmStoppedActionType = "@@redux-sigma/stm-stopped"; declare const storeStmStateActionType = "@@redux-sigma/store-state"; declare const storeStmContextActionType = "@@redux-sigma/store-context"; declare const REACTION_POLICY_FIRST = "REACTION_POLICY_FIRST"; declare const REACTION_POLICY_LAST = "REACTION_POLICY_LAST"; declare const REACTION_POLICY_ALL = "REACTION_POLICY_ALL"; /** * This type defines the action that can start the state machine. * It contains the state machine identifier (its name), * and the initial context for that state machine. * * This action has no actual effect on the store. * The state machine will propagate the context through `StateMachineStartedAction` * according to its actual state. */ interface StartStateMachineAction<N extends string, C> extends Action<typeof startStmActionType> { payload: { name: N; context: C; }; } /** * This type defines the action that signals that a state machine * was successfully started. It initializes the store with the initial context * and the initial state of the state machine. */ interface StateMachineStartedAction<N extends string, C> extends Action<typeof stmStartedActionType> { payload: { name: N; context: C; }; } /** * This type defines the action that can stop the state machine. * It contains the state machine identifier (its name). */ interface StopStateMachineAction<N extends string> extends Action<typeof stopStmActionType> { payload: { name: N; }; } /** * This type defines the action that signals that a state machine * was successfully stopped. It has no practical use at the moment: * it is only triggered to flush the redux-saga event queue. * It contains the state machine identifier (its name). */ interface StateMachineStoppedAction<N extends string> extends Action<typeof stmStoppedActionType> { payload: { name: N; }; } /** * This type defines the action that will update the state of the state machine * stored inside its `stateReducer`. * It contains the state machine identifier (its name), * and the new state that will be stored. */ interface StoreStateMachineState<N extends string, S extends string> extends Action<typeof storeStmStateActionType> { payload: { name: N; state: S; }; } /** * This type defines the action that will update the context of the state machine * stored inside its `stateReducer`. * It contains the state machine identifier (its name), * and the new context that will be stored. */ interface StoreStateMachineContext<N extends string, C> extends Action<typeof storeStmContextActionType> { payload: { name: N; context: C; }; } /** * This is the state and context of a state machine that is not running. */ interface StoppedStmStorage { state: null; context: undefined; } /** * This is the state and context of a state machine that IS running. */ interface StartedStmStorage<S extends string, C> { state: S; context: C; } /** * The actual state and context of a state machine. */ type StmStorage<S extends string, C> = StartedStmStorage<S, C> | StoppedStmStorage; /** * This is the public interface for a state machine. * It removes private and protected fields, and can be used by other libraries. */ interface StateMachineInterface<S extends string, SM extends string, C> { name: SM; starterSaga: () => Generator<StrictEffect$0, void>; stateReducer: (state: StmStorage<S, C> | undefined, action: any) => StmStorage<S, C>; start: (ctx: C) => StartStateMachineAction<SM, C>; stop: () => StopStateMachineAction<SM>; } /** * This is a state machine that can be used as a sub state machine * without providing an initial context, since an empty object * can be assigned to its context. */ interface SubStateMachineWithoutContext<SM extends string> extends StateMachineInterface<any, SM, {}> { } /** * This is a state machine that can only be used as a sub state machine * by providing an initial context, since an empty object may not be * assignable to its context. * * The `contextBuilder` field is a function or generator that will return * the initial context for this state machine. */ interface SubStateMachineWithContext<SM extends string, SC = any> { stm: StateMachineInterface<any, SM, SC>; contextBuilder: (() => SC) | (() => Generator<StrictEffect$0, SC>); } /** * This is any state machine that can be used as a sub state machine. */ type SubStateMachine<SM extends string> = SubStateMachineWithContext<SM> | SubStateMachineWithoutContext<SM>; /** * A guard is a boolean function that takes in input an event and the * current context of the STM. */ type Guard<K extends string, C> = ((...args: [Event<K>, C]) => boolean) | ((...args: [PayloadEvent<K>, C]) => boolean) | ((...args: [ErrorEvent<K>, C]) => boolean); /** * A transition can be defined as a target state and a command (or commands) * to execute before reaching the target state. */ interface Transition<S extends string, E extends string> { target: S; command: Activity<E> | Activity<E>[]; } /** * A guarded transition is defined by a target state, and a guard that * returns true if the transition should happen. It can optionally have a * command (or commands). */ interface GuardedTransition<S extends string, E extends string, C> { target: S; guard: Guard<E, C>; command?: Activity<E> | Activity<E>[]; } /** * A transition can be one of the following: * - just a state * - a target state and a command to execute * - a state and a guard, and an optional command * - more than one state and guard, with optional commands */ type TransitionSpec<S extends string, K extends string, C = unknown> = S | Transition<S, K> | GuardedTransition<S, K, C> | GuardedTransition<S, K, C>[]; /** * The transition map is a partial mapping between events and target states. * Transitions may have a command, and a guard. */ type TransitionMap<E extends string, S extends string, C> = Partial<{ [key in E]: TransitionSpec<S, key, C>; }>; /** * A reaction policy determines what the state machine will do when a reaction * is triggered several times during a short period of time. */ type ReactionPolicy = typeof REACTION_POLICY_FIRST | typeof REACTION_POLICY_LAST | typeof REACTION_POLICY_ALL; /** * This type defines what a reaction looks like when a reaction policy * is specified explicitly. */ interface ReactionSpec<E extends string> { activity: Activity<E>; policy: ReactionPolicy; } /** * The reaction map is a partial mapping between possible events and * the commands to run when the event happens. */ type ReactionMap<E extends string> = Partial<{ [key in E]: Activity<key> | ReactionSpec<key>; }>; /** * Each state definition can contain the following fields: * * - what to do onEntry and onExit * - which subMachines to run when inside the state * - the possible transitions for the state * - the reactions for the state */ interface StateAttributes<E extends string, S extends string, SM extends string, C> { onEntry?: VoidActivity | VoidActivity[]; onExit?: VoidActivity | VoidActivity[]; subMachines?: SubStateMachine<SM> | SubStateMachine<SM>[]; transitions?: TransitionMap<E, S, C>; reactions?: ReactionMap<E>; } /** * This is the root type of the `spec` field of each state machine. * It's a mapping of each possible state to the specification of that * state. */ type StateMachineSpec<E extends string, S extends string, SM extends string, C> = { [key in S]: StateAttributes<E, S, SM, C>; }; declare abstract class StateMachine<E extends string = string, S extends string = string, SM extends string = string, C = {}, IS extends S = S, N extends SM = SM> implements StateMachineInterface<S, SM, C> { abstract readonly name: N; private _context; protected abstract readonly spec: StateMachineSpec<E, S, SM, C>; protected abstract readonly initialState: IS; private runningTasks; private currentState; private transitionChannel; /** * Returns a redux action that will start this state machine when dispatched, * with the initial context provided in input. * * @param context The initial context of the state machine. */ start: (context: C) => StartStateMachineAction<N, C>; /** * Returns a redux action that will stop this state machine when dispatched. */ stop: () => StopStateMachineAction<N>; /** * Returns a redux action that signals that the state machine * was successfully started. */ private started; /** * Returns a redux action that signals that the state machine * was successfully stopped. */ private stopped; /** * Returns an action that will update the state of this state machine stored * by the `stateReducer`. * * @param state The new state. */ private storeState; /** * Returns an action that will update the context of this state machine stored * by the `stateReducer`. * * @param context The new context. */ private storeContext; /** * Computes the new context and stores it using `storeContext`. * * @param newContext The new context, or an immer-style function that mutates * the current context. */ setContext(newContext: C | ((ctx: C) => void)): Generator<import("redux-saga/effects").PutEffect<StoreStateMachineContext<N, C>>, void, unknown>; /** * Returns the current context. */ get context(): C; /** * This saga is responsible for starting and stopping this state machine. * It listens to the `start` actions returned by the methods of * this state machine, and relies on the `run` method to catch `stop` actions. * * This saga shouldn't be used directly: rely on `stateMachineStarterSaga` * instead. */ starterSaga(): Generator<StrictEffect, void>; /** * Runs the state machine described by this state machines' spec. */ private run; /** * A generator that runs the "loop" for the current state. * It listens to transition events while running `onEntry` activities and * `reactions`, and starts sub state machines. As soon as a transition event * is returned, the state loop is stopped, and the transition trigger is * returned to the calling function. * * The loop listens for both transition events and the stop event. * The first event that is received exits the loop: a transition event * returns a TransitionTrigger, while a stop event returns nothing. */ private stateLoop; /** * Waits for the first event matching a regular transition or a guarded * transition, and returns it together with the next state and the * optional command (or commands) that must be executed before transitioning. */ private getNextState; /** * This reducer stores the current state of the State Machine. It can be * added to your application reducers if you need to access the state of a * State Machine somewhere in your application. * * @param state The current state and context. * @param action The action taken by the reducer. */ stateReducer: (state: StoppedStmStorage | StartedStmStorage<S, C> | undefined, action: StoreStateMachineState<N, S> | StartStateMachineAction<N, C> | StopStateMachineAction<N> | StateMachineStoppedAction<N> | StateMachineStartedAction<N, C> | StoreStateMachineContext<N, C>) => StmStorage<S, C>; /** * Starts all onEntry activities. Does not wait for them to complete. */ private startOnEntryActivities; /** * Starts and adds the background tasks listening to reactions * in the background task list. */ private registerToReactions; /** * Implements the `first` reaction policy: once an event triggering a reaction * is received, no other event are processed until the first event has * complete its processing. * * @param eventType The event triggering the reaction * @param activity The reaction activity */ private takeFirst; /** * Implements the `last` reaction policy: events are processed as they come. * If a new event is received while a reaction is running, the old reaction * is stopped, and a new reaction starts running. * * @param eventType The event triggering the reaction * @param activity The reaction activity */ private takeLast; /** * Implements the `all` reaction policy: events that can trigger a reaction * are stored in a queue, and processed sequentially. * * @param eventType The event triggering the reaction * @param activity The reaction activity */ private takeAll; /** * Stops all running tasks. Used when exiting from a state * or when the state machine is stopped. */ private cancelRunningTasks; /** * Starts all sub state machines for the current state.. */ private startSubMachines; /** * Stops all state machines for the current state. */ private stopSubMachines; /** * Runs all onExit activities, and waits for them to return before continuing. */ private runOnExitActivities; } /** * Returns the negation of the input function. * * @param f A function. */ declare function not<A extends unknown[]>(f: (...args: A) => boolean): (...args: A) => boolean; /** * Returns a boolean function returning true if all input functions * return true. * * @param fs An array of functions. */ declare function and<A extends unknown[]>(...fs: Array<(...args: A) => boolean>): (...args: A) => boolean; /** * Returns a boolean function returning true if at least one input functions * returns true. * * @param fs An array of functions. */ declare function or<A extends unknown[]>(...fs: Array<(...args: A) => boolean>): (...args: A) => boolean; /** * Instructs redux-sigma to use the `all` reaction policy for the input activity. * * @param activity An activity. */ declare function all<K extends string>(activity: Activity<K>): ReactionSpec<K>; /** * Instructs redux-sigma to use the `last` reaction policy for the input activity. * * @param activity An activity. */ declare function last<K extends string>(activity: Activity<K>): ReactionSpec<K>; /** * Instructs redux-sigma to use the `first` reaction policy for the input activity. * * @param activity An activity. */ declare function first<K extends string>(activity: Activity<K>): ReactionSpec<K>; /** * Helper function to bind a sub STM to a context builder function. * The context builder function is responsible for creating the initial context * for the sub STM. * * This is just some TypeScript magic (aka inference) to make sure that * the sub state machine context contract is respected. * * @param stm the sub state machine to start * @param contextBuilder a function or saga that returns * the initial context for `stm` * * @returns a sub state machine with context descriptor, * used inside another STM spec */ declare function bindStm<SM extends string = string, SC = unknown>(stm: StateMachineInterface<any, SM, SC>, contextBuilder: (() => SC) | (() => Generator<StrictEffect$0, SC>)): SubStateMachineWithContext<SM, SC>; /** * Creates a saga that runs the `starterSaga` for the state machines * given in input. * * It does some additional checks for you: * * - if two or more state machines have the same identifier (their name), * this saga throws an error, since running more than one instance of * the same state machine _will_ result in undefined behaviour * - if you try to start or stop a state machine that was not passed * to this saga, it will log an error in console (in development only) * * @param stms An array of StateMachine instances. */ declare function stateMachineStarterSaga(...stms: StateMachineInterface<any, any, any>[]): Generator<import("redux-saga/effects").ForkEffect<void>, void, unknown>; export { StateMachine, StateMachineInterface, StateMachineSpec, StmStorage, StartedStmStorage, StoppedStmStorage, stateMachineStarterSaga, and, not, or, all, first, last, bindStm };