@redux-saga/core
Version:
Saga middleware for Redux to handle Side Effects
392 lines (361 loc) • 13.4 kB
TypeScript
// TypeScript Version: 4.2
import { Saga, Buffer, Channel, END as EndType, Predicate, SagaIterator, Task, NotUndefined } from '@redux-saga/types'
import { ForkEffect } from './effects'
export { Saga, SagaIterator, Buffer, Channel, Task }
export type Action<T extends string = string> = {
type: T
}
export interface AnyAction extends Action {
[extraProps: string]: any
}
export interface UnknownAction extends Action {
[extraProps: string]: unknown
}
interface Dispatch<A extends Action = UnknownAction> {
<T extends A>(action: T, ...extraArgs: any[]): T
}
interface MiddlewareAPI<D extends Dispatch = Dispatch, S = any> {
dispatch: D
getState(): S
}
export interface Middleware<_DispatchExt = {}, S = any, D extends Dispatch = Dispatch> {
(api: MiddlewareAPI<D, S>): (next: (action: never) => unknown) => (action: unknown) => unknown
}
/**
* Used by the middleware to dispatch monitoring events. Actually the middleware
* dispatches 6 events:
*
* - When a root saga is started (via `runSaga` or `sagaMiddleware.run`) the
* middleware invokes `sagaMonitor.rootSagaStarted`
*
* - When an effect is triggered (via `yield someEffect`) the middleware invokes
* `sagaMonitor.effectTriggered`
*
* - If the effect is resolved with success the middleware invokes
* `sagaMonitor.effectResolved`
*
* - If the effect is rejected with an error the middleware invokes
* `sagaMonitor.effectRejected`
*
* - If the effect is cancelled the middleware invokes
* `sagaMonitor.effectCancelled`
*
* - Finally, the middleware invokes `sagaMonitor.actionDispatched` when a Redux
* action is dispatched.
*/
export interface SagaMonitor {
/**
* @param effectId Unique ID assigned to this root saga execution
* @param saga The generator function that starts to run
* @param args The arguments passed to the generator function
*/
rootSagaStarted?(options: { effectId: number; saga: Saga; args: any[] }): void
/**
* @param effectId Unique ID assigned to the yielded effect
* @param parentEffectId ID of the parent Effect. In the case of a `race` or
* `parallel` effect, all effects yielded inside will have the direct
* race/parallel effect as a parent. In case of a top-level effect, the
* parent will be the containing Saga
* @param label In case of a `race`/`all` effect, all child effects will be
* assigned as label the corresponding keys of the object passed to
* `race`/`all`
* @param effect The yielded effect itself
*/
effectTriggered?(options: { effectId: number; parentEffectId: number; label?: string; effect: any }): void
/**
* @param effectId The ID of the yielded effect
* @param result The result of the successful resolution of the effect. In
* case of `fork` or `spawn` effects, the result will be a `Task` object.
*/
effectResolved?(effectId: number, result: any): void
/**
* @param effectId The ID of the yielded effect
* @param error Error raised with the rejection of the effect
*/
effectRejected?(effectId: number, error: any): void
/**
* @param effectId The ID of the yielded effect
*/
effectCancelled?(effectId: number): void
/**
* @param action The dispatched Redux action. If the action was dispatched by
* a Saga then the action will have a property `SAGA_ACTION` set to true
* (`SAGA_ACTION` can be imported from `@redux-saga/symbols`).
*/
actionDispatched?(action: Action): void
}
/**
* Creates a Redux middleware and connects the Sagas to the Redux Store
*
* #### Example
*
* Below we will create a function `configureStore` which will enhance the Store
* with a new method `runSaga`. Then in our main module, we will use the method
* to start the root Saga of the application.
*
* **configureStore.js**
*
* import createSagaMiddleware from 'redux-saga'
* import reducer from './path/to/reducer'
*
* export default function configureStore(initialState) {
* // Note: passing middleware as the last argument to createStore requires redux@>=3.1.0
* const sagaMiddleware = createSagaMiddleware()
* return {
* ...createStore(reducer, initialState, applyMiddleware(... other middleware ..., sagaMiddleware)),
* runSaga: sagaMiddleware.run
* }
* }
*
* **main.js**
*
* import configureStore from './configureStore'
* import rootSaga from './sagas'
* // ... other imports
*
* const store = configureStore()
* store.runSaga(rootSaga)
*
* @param options A list of options to pass to the middleware
*/
export default function createSagaMiddleware<C extends object>(options?: SagaMiddlewareOptions<C>): SagaMiddleware<C>
export interface SagaMiddlewareOptions<C extends object = {}> {
/**
* Initial value of the saga's context.
*/
context?: C
/**
* If a Saga Monitor is provided, the middleware will deliver monitoring
* events to the monitor.
*/
sagaMonitor?: SagaMonitor
/**
* If provided, the middleware will call it with uncaught errors from Sagas.
* useful for sending uncaught exceptions to error tracking services.
*/
onError?(error: Error, errorInfo: ErrorInfo): void
/**
* Allows you to intercept any effect, resolve it on your own and pass to the
* next middleware.
*/
effectMiddlewares?: EffectMiddleware[]
/**
* If provided, the middleware will use this channel instead of the default `stdChannel` for
* take and put effects.
*/
channel?: MulticastChannel<Action>
}
export interface SagaMiddleware<C extends object = {}> extends Middleware {
/**
* Dynamically run `saga`. Can be used to run Sagas **only after** the
* `applyMiddleware` phase.
*
* The method returns a `Task` descriptor.
*
* #### Notes
*
* `saga` must be a function which returns a [Generator
* Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator).
* The middleware will then iterate over the Generator and execute all yielded
* Effects.
*
* `saga` may also start other sagas using the various Effects provided by the
* library. The iteration process described below is also applied to all child
* sagas.
*
* In the first iteration, the middleware invokes the `next()` method to
* retrieve the next Effect. The middleware then executes the yielded Effect
* as specified by the Effects API below. Meanwhile, the Generator will be
* suspended until the effect execution terminates. Upon receiving the result
* of the execution, the middleware calls `next(result)` on the Generator
* passing it the retrieved result as an argument. This process is repeated
* until the Generator terminates normally or by throwing some error.
*
* If the execution results in an error (as specified by each Effect creator)
* then the `throw(error)` method of the Generator is called instead. If the
* Generator function defines a `try/catch` surrounding the current yield
* instruction, then the `catch` block will be invoked by the underlying
* Generator runtime. The runtime will also invoke any corresponding finally
* block.
*
* In the case a Saga is cancelled (either manually or using the provided
* Effects), the middleware will invoke `return()` method of the Generator.
* This will cause the Generator to skip directly to the finally block.
*
* @param saga a Generator function
* @param args arguments to be provided to `saga`
*/
run<S extends Saga>(saga: S, ...args: Parameters<S>): Task
setContext(props: Partial<C>): void
}
export interface EffectMiddleware {
(next: (effect: any) => void): (effect: any) => void
}
/**
* Allows starting sagas outside the Redux middleware environment. Useful if you
* want to connect a Saga to external input/output, other than store actions.
*
* `runSaga` returns a Task object. Just like the one returned from a `fork`
* effect.
*/
export function runSaga<Action, State, S extends Saga>(
options: RunSagaOptions<Action, State>,
saga: S,
...args: Parameters<S>
): Task
interface ErrorInfo {
sagaStack: string
}
/**
* The `{subscribe, dispatch}` is used to fulfill `take` and `put` Effects. This
* defines the Input/Output interface of the Saga.
*
* `subscribe` is used to fulfill `take(PATTERN)` effects. It must call
* `callback` every time it has an input to dispatch (e.g. on every mouse click
* if the Saga is connected to DOM click events). Each time `subscribe` emits an
* input to its callbacks, if the Saga is blocked on a `take` effect, and if the
* take pattern matches the currently incoming input, the Saga is resumed with
* that input.
*
* `dispatch` is used to fulfill `put` effects. Each time the Saga emits a
* `yield put(output)`, `dispatch` is invoked with output.
*/
export interface RunSagaOptions<A, S> {
/**
* See docs for `channel`
*/
channel?: PredicateTakeableChannel<A>
/**
* Used to fulfill `put` effects.
*
* @param output argument provided by the Saga to the `put` Effect
*/
dispatch?(output: A): any
/**
* Used to fulfill `select` and `getState` effects
*/
getState?(): S
/**
* See docs for `createSagaMiddleware(options)`
*/
sagaMonitor?: SagaMonitor
/**
* See docs for `createSagaMiddleware(options)`
*/
onError?(error: Error, errorInfo: ErrorInfo): void
/**
* See docs for `createSagaMiddleware(options)`
*/
context?: object
/**
* See docs for `createSagaMiddleware(options)`
*/
effectMiddlewares?: EffectMiddleware[]
}
export const CANCEL: string
export const END: EndType
export type END = EndType
export interface TakeableChannel<T> {
take(cb: (message: T | END) => void): void
}
export interface PuttableChannel<T> {
put(message: T | END): void
}
export interface FlushableChannel<T> {
flush(cb: (items: T[] | END) => void): void
}
/**
* A factory method that can be used to create Channels. You can optionally pass
* it a buffer to control how the channel buffers the messages.
*
* By default, if no buffer is provided, the channel will queue incoming
* messages up to 10 until interested takers are registered. The default
* buffering will deliver message using a FIFO strategy: a new taker will be
* delivered the oldest message in the buffer.
*/
export function channel<T extends NotUndefined>(buffer?: Buffer<T>): Channel<T>
/**
* Creates channel that will subscribe to an event source using the `subscribe`
* method. Incoming events from the event source will be queued in the channel
* until interested takers are registered.
*
* To notify the channel that the event source has terminated, you can notify
* the provided subscriber with an `END`
*
* #### Example
*
* In the following example we create an event channel that will subscribe to a
* `setInterval`
*
* const countdown = (secs) => {
* return eventChannel(emitter => {
* const iv = setInterval(() => {
* console.log('countdown', secs)
* secs -= 1
* if (secs > 0) {
* emitter(secs)
* } else {
* emitter(END)
* clearInterval(iv)
* console.log('countdown terminated')
* }
* }, 1000);
* return () => {
* clearInterval(iv)
* console.log('countdown cancelled')
* }
* }
* )
* }
*
* @param subscribe used to subscribe to the underlying event source. The
* function must return an unsubscribe function to terminate the subscription.
* @param buffer optional Buffer object to buffer messages on this channel. If
* not provided, messages will not be buffered on this channel.
*/
export function eventChannel<T extends NotUndefined>(subscribe: Subscribe<T>, buffer?: Buffer<T>): EventChannel<T>
export type Subscribe<T> = (cb: (input: T | END) => void) => Unsubscribe
export type Unsubscribe = () => void
export interface EventChannel<T extends NotUndefined> {
take(cb: (message: T | END) => void): void
flush(cb: (items: T[] | END) => void): void
close(): void
}
export interface PredicateTakeableChannel<T> {
take(cb: (message: T | END) => void, matcher?: Predicate<T>): void
}
export interface MulticastChannel<T extends NotUndefined> {
take(cb: (message: T | END) => void, matcher?: Predicate<T>): void
put(message: T | END): void
close(): void
}
export function multicastChannel<T extends NotUndefined>(): MulticastChannel<T>
export function stdChannel<T extends NotUndefined>(): MulticastChannel<T>
export function detach(forkEffect: ForkEffect): ForkEffect
/**
* Provides some common buffers
*/
export const buffers: {
/**
* No buffering, new messages will be lost if there are no pending takers
*/
none<T>(): Buffer<T>
/**
* New messages will be buffered up to `limit`. Overflow will raise an Error.
* Omitting a `limit` value will result in a limit of 10.
*/
fixed<T>(limit?: number): Buffer<T>
/**
* Like `fixed` but Overflow will cause the buffer to expand dynamically.
*/
expanding<T>(limit?: number): Buffer<T>
/**
* Same as `fixed` but Overflow will silently drop the messages.
*/
dropping<T>(limit?: number): Buffer<T>
/**
* Same as `fixed` but Overflow will insert the new message at the end and
* drop the oldest message in the buffer.
*/
sliding<T>(limit?: number): Buffer<T>
}