UNPKG

16.4 kBTypeScriptView Raw
1import type { PayloadAction, BaseActionCreator } from '../createAction';
2import type { Dispatch as ReduxDispatch, AnyAction, MiddlewareAPI, Middleware, Action as ReduxAction } from 'redux';
3import type { ThunkDispatch } from 'redux-thunk';
4import type { TaskAbortError } from './exceptions';
5/**
6 * @internal
7 * At the time of writing `lib.dom.ts` does not provide `abortSignal.reason`.
8 */
9export declare type AbortSignalWithReason<T> = AbortSignal & {
10 reason?: T;
11};
12/**
13 * Types copied from RTK
14 */
15/** @internal */
16export interface TypedActionCreator<Type extends string> {
17 (...args: any[]): ReduxAction<Type>;
18 type: Type;
19 match: MatchFunction<any>;
20}
21/** @internal */
22export declare type AnyListenerPredicate<State> = (action: AnyAction, currentState: State, originalState: State) => boolean;
23/** @public */
24export declare type ListenerPredicate<Action extends AnyAction, State> = (action: AnyAction, currentState: State, originalState: State) => action is Action;
25/** @public */
26export interface ConditionFunction<State> {
27 (predicate: AnyListenerPredicate<State>, timeout?: number): Promise<boolean>;
28 (predicate: AnyListenerPredicate<State>, timeout?: number): Promise<boolean>;
29 (predicate: () => boolean, timeout?: number): Promise<boolean>;
30}
31/** @internal */
32export declare type MatchFunction<T> = (v: any) => v is T;
33/** @public */
34export interface ForkedTaskAPI {
35 /**
36 * Returns a promise that resolves when `waitFor` resolves or
37 * rejects if the task or the parent listener has been cancelled or is completed.
38 */
39 pause<W>(waitFor: Promise<W>): Promise<W>;
40 /**
41 * Returns a promise that resolves after `timeoutMs` or
42 * rejects if the task or the parent listener has been cancelled or is completed.
43 * @param timeoutMs
44 */
45 delay(timeoutMs: number): Promise<void>;
46 /**
47 * An abort signal whose `aborted` property is set to `true`
48 * if the task execution is either aborted or completed.
49 * @see https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal
50 */
51 signal: AbortSignal;
52}
53/** @public */
54export interface AsyncTaskExecutor<T> {
55 (forkApi: ForkedTaskAPI): Promise<T>;
56}
57/** @public */
58export interface SyncTaskExecutor<T> {
59 (forkApi: ForkedTaskAPI): T;
60}
61/** @public */
62export declare type ForkedTaskExecutor<T> = AsyncTaskExecutor<T> | SyncTaskExecutor<T>;
63/** @public */
64export declare type TaskResolved<T> = {
65 readonly status: 'ok';
66 readonly value: T;
67};
68/** @public */
69export declare type TaskRejected = {
70 readonly status: 'rejected';
71 readonly error: unknown;
72};
73/** @public */
74export declare type TaskCancelled = {
75 readonly status: 'cancelled';
76 readonly error: TaskAbortError;
77};
78/** @public */
79export declare type TaskResult<Value> = TaskResolved<Value> | TaskRejected | TaskCancelled;
80/** @public */
81export interface ForkedTask<T> {
82 /**
83 * A promise that resolves when the task is either completed or cancelled or rejects
84 * if parent listener execution is cancelled or completed.
85 *
86 * ### Example
87 * ```ts
88 * const result = await fork(async (forkApi) => Promise.resolve(4)).result
89 *
90 * if(result.status === 'ok') {
91 * console.log(result.value) // logs 4
92 * }}
93 * ```
94 */
95 result: Promise<TaskResult<T>>;
96 /**
97 * Cancel task if it is in progress or not yet started,
98 * it is noop otherwise.
99 */
100 cancel(): void;
101}
102/** @public */
103export interface ListenerEffectAPI<State, Dispatch extends ReduxDispatch<AnyAction>, ExtraArgument = unknown> extends MiddlewareAPI<Dispatch, State> {
104 /**
105 * Returns the store state as it existed when the action was originally dispatched, _before_ the reducers ran.
106 *
107 * ### Synchronous invocation
108 *
109 * This function can **only** be invoked **synchronously**, it throws error otherwise.
110 *
111 * @example
112 *
113 * ```ts
114 * middleware.startListening({
115 * predicate: () => true,
116 * async effect(_, { getOriginalState }) {
117 * getOriginalState(); // sync: OK!
118 *
119 * setTimeout(getOriginalState, 0); // async: throws Error
120 *
121 * await Promise().resolve();
122 *
123 * getOriginalState() // async: throws Error
124 * }
125 * })
126 * ```
127 */
128 getOriginalState: () => State;
129 /**
130 * Removes the listener entry from the middleware and prevent future instances of the listener from running.
131 *
132 * It does **not** cancel any active instances.
133 */
134 unsubscribe(): void;
135 /**
136 * It will subscribe a listener if it was previously removed, noop otherwise.
137 */
138 subscribe(): void;
139 /**
140 * Returns a promise that resolves when the input predicate returns `true` or
141 * rejects if the listener has been cancelled or is completed.
142 *
143 * The return value is `true` if the predicate succeeds or `false` if a timeout is provided and expires first.
144 *
145 * ### Example
146 *
147 * ```ts
148 * const updateBy = createAction<number>('counter/updateBy');
149 *
150 * middleware.startListening({
151 * actionCreator: updateBy,
152 * async effect(_, { condition }) {
153 * // wait at most 3s for `updateBy` actions.
154 * if(await condition(updateBy.match, 3_000)) {
155 * // `updateBy` has been dispatched twice in less than 3s.
156 * }
157 * }
158 * })
159 * ```
160 */
161 condition: ConditionFunction<State>;
162 /**
163 * Returns a promise that resolves when the input predicate returns `true` or
164 * rejects if the listener has been cancelled or is completed.
165 *
166 * The return value is the `[action, currentState, previousState]` combination that the predicate saw as arguments.
167 *
168 * The promise resolves to null if a timeout is provided and expires first,
169 *
170 * ### Example
171 *
172 * ```ts
173 * const updateBy = createAction<number>('counter/updateBy');
174 *
175 * middleware.startListening({
176 * actionCreator: updateBy,
177 * async effect(_, { take }) {
178 * const [{ payload }] = await take(updateBy.match);
179 * console.log(payload); // logs 5;
180 * }
181 * })
182 *
183 * store.dispatch(updateBy(5));
184 * ```
185 */
186 take: TakePattern<State>;
187 /**
188 * Cancels all other running instances of this same listener except for the one that made this call.
189 */
190 cancelActiveListeners: () => void;
191 /**
192 * An abort signal whose `aborted` property is set to `true`
193 * if the listener execution is either aborted or completed.
194 * @see https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal
195 */
196 signal: AbortSignal;
197 /**
198 * Returns a promise that resolves after `timeoutMs` or
199 * rejects if the listener has been cancelled or is completed.
200 */
201 delay(timeoutMs: number): Promise<void>;
202 /**
203 * Queues in the next microtask the execution of a task.
204 * @param executor
205 */
206 fork<T>(executor: ForkedTaskExecutor<T>): ForkedTask<T>;
207 /**
208 * Returns a promise that resolves when `waitFor` resolves or
209 * rejects if the listener has been cancelled or is completed.
210 * @param promise
211 */
212 pause<M>(promise: Promise<M>): Promise<M>;
213 extra: ExtraArgument;
214}
215/** @public */
216export declare type ListenerEffect<Action extends AnyAction, State, Dispatch extends ReduxDispatch<AnyAction>, ExtraArgument = unknown> = (action: Action, api: ListenerEffectAPI<State, Dispatch, ExtraArgument>) => void | Promise<void>;
217/**
218 * @public
219 * Additional infos regarding the error raised.
220 */
221export interface ListenerErrorInfo {
222 /**
223 * Which function has generated the exception.
224 */
225 raisedBy: 'effect' | 'predicate';
226}
227/**
228 * @public
229 * Gets notified with synchronous and asynchronous errors raised by `listeners` or `predicates`.
230 * @param error The thrown error.
231 * @param errorInfo Additional information regarding the thrown error.
232 */
233export interface ListenerErrorHandler {
234 (error: unknown, errorInfo: ListenerErrorInfo): void;
235}
236/** @public */
237export interface CreateListenerMiddlewareOptions<ExtraArgument = unknown> {
238 extra?: ExtraArgument;
239 /**
240 * Receives synchronous errors that are raised by `listener` and `listenerOption.predicate`.
241 */
242 onError?: ListenerErrorHandler;
243}
244/** @public */
245export declare type ListenerMiddleware<State = unknown, Dispatch extends ThunkDispatch<State, unknown, AnyAction> = ThunkDispatch<State, unknown, AnyAction>, ExtraArgument = unknown> = Middleware<{
246 (action: ReduxAction<'listenerMiddleware/add'>): UnsubscribeListener;
247}, State, Dispatch>;
248/** @public */
249export interface ListenerMiddlewareInstance<State = unknown, Dispatch extends ThunkDispatch<State, unknown, AnyAction> = ThunkDispatch<State, unknown, AnyAction>, ExtraArgument = unknown> {
250 middleware: ListenerMiddleware<State, Dispatch, ExtraArgument>;
251 startListening: AddListenerOverloads<UnsubscribeListener, State, Dispatch, ExtraArgument>;
252 stopListening: RemoveListenerOverloads<State, Dispatch>;
253 /**
254 * Unsubscribes all listeners, cancels running listeners and tasks.
255 */
256 clearListeners: () => void;
257}
258/**
259 * API Function Overloads
260 */
261/** @public */
262export declare type TakePatternOutputWithoutTimeout<State, Predicate extends AnyListenerPredicate<State>> = Predicate extends MatchFunction<infer Action> ? Promise<[Action, State, State]> : Promise<[AnyAction, State, State]>;
263/** @public */
264export declare type TakePatternOutputWithTimeout<State, Predicate extends AnyListenerPredicate<State>> = Predicate extends MatchFunction<infer Action> ? Promise<[Action, State, State] | null> : Promise<[AnyAction, State, State] | null>;
265/** @public */
266export interface TakePattern<State> {
267 <Predicate extends AnyListenerPredicate<State>>(predicate: Predicate): TakePatternOutputWithoutTimeout<State, Predicate>;
268 <Predicate extends AnyListenerPredicate<State>>(predicate: Predicate, timeout: number): TakePatternOutputWithTimeout<State, Predicate>;
269 <Predicate extends AnyListenerPredicate<State>>(predicate: Predicate, timeout?: number | undefined): TakePatternOutputWithTimeout<State, Predicate>;
270}
271/** @public */
272export interface UnsubscribeListenerOptions {
273 cancelActive?: true;
274}
275/** @public */
276export declare type UnsubscribeListener = (unsubscribeOptions?: UnsubscribeListenerOptions) => void;
277/**
278 * @public
279 * The possible overloads and options for defining a listener. The return type of each function is specified as a generic arg, so the overloads can be reused for multiple different functions
280 */
281export interface AddListenerOverloads<Return, State = unknown, Dispatch extends ReduxDispatch = ThunkDispatch<State, unknown, AnyAction>, ExtraArgument = unknown, AdditionalOptions = unknown> {
282 /** Accepts a "listener predicate" that is also a TS type predicate for the action*/
283 <MA extends AnyAction, LP extends ListenerPredicate<MA, State>>(options: {
284 actionCreator?: never;
285 type?: never;
286 matcher?: never;
287 predicate: LP;
288 effect: ListenerEffect<ListenerPredicateGuardedActionType<LP>, State, Dispatch, ExtraArgument>;
289 } & AdditionalOptions): Return;
290 /** Accepts an RTK action creator, like `incrementByAmount` */
291 <C extends TypedActionCreator<any>>(options: {
292 actionCreator: C;
293 type?: never;
294 matcher?: never;
295 predicate?: never;
296 effect: ListenerEffect<ReturnType<C>, State, Dispatch, ExtraArgument>;
297 } & AdditionalOptions): Return;
298 /** Accepts a specific action type string */
299 <T extends string>(options: {
300 actionCreator?: never;
301 type: T;
302 matcher?: never;
303 predicate?: never;
304 effect: ListenerEffect<ReduxAction<T>, State, Dispatch, ExtraArgument>;
305 } & AdditionalOptions): Return;
306 /** Accepts an RTK matcher function, such as `incrementByAmount.match` */
307 <MA extends AnyAction, M extends MatchFunction<MA>>(options: {
308 actionCreator?: never;
309 type?: never;
310 matcher: M;
311 predicate?: never;
312 effect: ListenerEffect<GuardedType<M>, State, Dispatch, ExtraArgument>;
313 } & AdditionalOptions): Return;
314 /** Accepts a "listener predicate" that just returns a boolean, no type assertion */
315 <LP extends AnyListenerPredicate<State>>(options: {
316 actionCreator?: never;
317 type?: never;
318 matcher?: never;
319 predicate: LP;
320 effect: ListenerEffect<AnyAction, State, Dispatch, ExtraArgument>;
321 } & AdditionalOptions): Return;
322}
323/** @public */
324export declare type RemoveListenerOverloads<State = unknown, Dispatch extends ReduxDispatch = ThunkDispatch<State, unknown, AnyAction>> = AddListenerOverloads<boolean, State, Dispatch, any, UnsubscribeListenerOptions>;
325/** @public */
326export interface RemoveListenerAction<Action extends AnyAction, State, Dispatch extends ReduxDispatch<AnyAction>> {
327 type: 'listenerMiddleware/remove';
328 payload: {
329 type: string;
330 listener: ListenerEffect<Action, State, Dispatch>;
331 };
332}
333/**
334 * @public
335 * A "pre-typed" version of `addListenerAction`, so the listener args are well-typed */
336export declare type TypedAddListener<State, Dispatch extends ReduxDispatch<AnyAction> = ThunkDispatch<State, unknown, AnyAction>, ExtraArgument = unknown, Payload = ListenerEntry<State, Dispatch>, T extends string = 'listenerMiddleware/add'> = BaseActionCreator<Payload, T> & AddListenerOverloads<PayloadAction<Payload, T>, State, Dispatch, ExtraArgument>;
337/**
338 * @public
339 * A "pre-typed" version of `removeListenerAction`, so the listener args are well-typed */
340export declare type TypedRemoveListener<State, Dispatch extends ReduxDispatch<AnyAction> = ThunkDispatch<State, unknown, AnyAction>, Payload = ListenerEntry<State, Dispatch>, T extends string = 'listenerMiddleware/remove'> = BaseActionCreator<Payload, T> & AddListenerOverloads<PayloadAction<Payload, T>, State, Dispatch, any, UnsubscribeListenerOptions>;
341/**
342 * @public
343 * A "pre-typed" version of `middleware.startListening`, so the listener args are well-typed */
344export declare type TypedStartListening<State, Dispatch extends ReduxDispatch<AnyAction> = ThunkDispatch<State, unknown, AnyAction>, ExtraArgument = unknown> = AddListenerOverloads<UnsubscribeListener, State, Dispatch, ExtraArgument>;
345/** @public
346 * A "pre-typed" version of `middleware.stopListening`, so the listener args are well-typed */
347export declare type TypedStopListening<State, Dispatch extends ReduxDispatch<AnyAction> = ThunkDispatch<State, unknown, AnyAction>> = RemoveListenerOverloads<State, Dispatch>;
348/** @public
349 * A "pre-typed" version of `createListenerEntry`, so the listener args are well-typed */
350export declare type TypedCreateListenerEntry<State, Dispatch extends ReduxDispatch<AnyAction> = ThunkDispatch<State, unknown, AnyAction>> = AddListenerOverloads<ListenerEntry<State, Dispatch>, State, Dispatch>;
351/**
352 * Internal Types
353 */
354/** @internal An single listener entry */
355export declare type ListenerEntry<State = unknown, Dispatch extends ReduxDispatch<AnyAction> = ReduxDispatch<AnyAction>> = {
356 id: string;
357 effect: ListenerEffect<any, State, Dispatch>;
358 unsubscribe: () => void;
359 pending: Set<AbortController>;
360 type?: string;
361 predicate: ListenerPredicate<AnyAction, State>;
362};
363/**
364 * @internal
365 * A shorthand form of the accepted args, solely so that `createListenerEntry` has validly-typed conditional logic when checking the options contents
366 */
367export declare type FallbackAddListenerOptions = {
368 actionCreator?: TypedActionCreator<string>;
369 type?: string;
370 matcher?: MatchFunction<any>;
371 predicate?: ListenerPredicate<any, any>;
372} & {
373 effect: ListenerEffect<any, any, any>;
374};
375/**
376 * Utility Types
377 */
378/** @public */
379export declare type GuardedType<T> = T extends (x: any, ...args: unknown[]) => x is infer T ? T : never;
380/** @public */
381export declare type ListenerPredicateGuardedActionType<T> = T extends ListenerPredicate<infer Action, any> ? Action : never;