redux-sigma
Version:
A state machine library for redux and redux-saga.
463 lines (462 loc) • 17.5 kB
TypeScript
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 };