UNPKG

16.6 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 ForkOptions {
104 /**
105 * If true, causes the parent task to not be marked as complete until
106 * all autoJoined forks have completed or failed.
107 */
108 autoJoin: boolean;
109}
110/** @public */
111export interface ListenerEffectAPI<State, Dispatch extends ReduxDispatch<AnyAction>, ExtraArgument = unknown> extends MiddlewareAPI<Dispatch, State> {
112 /**
113 * Returns the store state as it existed when the action was originally dispatched, _before_ the reducers ran.
114 *
115 * ### Synchronous invocation
116 *
117 * This function can **only** be invoked **synchronously**, it throws error otherwise.
118 *
119 * @example
120 *
121 * ```ts
122 * middleware.startListening({
123 * predicate: () => true,
124 * async effect(_, { getOriginalState }) {
125 * getOriginalState(); // sync: OK!
126 *
127 * setTimeout(getOriginalState, 0); // async: throws Error
128 *
129 * await Promise().resolve();
130 *
131 * getOriginalState() // async: throws Error
132 * }
133 * })
134 * ```
135 */
136 getOriginalState: () => State;
137 /**
138 * Removes the listener entry from the middleware and prevent future instances of the listener from running.
139 *
140 * It does **not** cancel any active instances.
141 */
142 unsubscribe(): void;
143 /**
144 * It will subscribe a listener if it was previously removed, noop otherwise.
145 */
146 subscribe(): void;
147 /**
148 * Returns a promise that resolves when the input predicate returns `true` or
149 * rejects if the listener has been cancelled or is completed.
150 *
151 * The return value is `true` if the predicate succeeds or `false` if a timeout is provided and expires first.
152 *
153 * ### Example
154 *
155 * ```ts
156 * const updateBy = createAction<number>('counter/updateBy');
157 *
158 * middleware.startListening({
159 * actionCreator: updateBy,
160 * async effect(_, { condition }) {
161 * // wait at most 3s for `updateBy` actions.
162 * if(await condition(updateBy.match, 3_000)) {
163 * // `updateBy` has been dispatched twice in less than 3s.
164 * }
165 * }
166 * })
167 * ```
168 */
169 condition: ConditionFunction<State>;
170 /**
171 * Returns a promise that resolves when the input predicate returns `true` or
172 * rejects if the listener has been cancelled or is completed.
173 *
174 * The return value is the `[action, currentState, previousState]` combination that the predicate saw as arguments.
175 *
176 * The promise resolves to null if a timeout is provided and expires first,
177 *
178 * ### Example
179 *
180 * ```ts
181 * const updateBy = createAction<number>('counter/updateBy');
182 *
183 * middleware.startListening({
184 * actionCreator: updateBy,
185 * async effect(_, { take }) {
186 * const [{ payload }] = await take(updateBy.match);
187 * console.log(payload); // logs 5;
188 * }
189 * })
190 *
191 * store.dispatch(updateBy(5));
192 * ```
193 */
194 take: TakePattern<State>;
195 /**
196 * Cancels all other running instances of this same listener except for the one that made this call.
197 */
198 cancelActiveListeners: () => void;
199 /**
200 * An abort signal whose `aborted` property is set to `true`
201 * if the listener execution is either aborted or completed.
202 * @see https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal
203 */
204 signal: AbortSignal;
205 /**
206 * Returns a promise that resolves after `timeoutMs` or
207 * rejects if the listener has been cancelled or is completed.
208 */
209 delay(timeoutMs: number): Promise<void>;
210 /**
211 * Queues in the next microtask the execution of a task.
212 * @param executor
213 * @param options
214 */
215 fork<T>(executor: ForkedTaskExecutor<T>, options?: ForkOptions): ForkedTask<T>;
216 /**
217 * Returns a promise that resolves when `waitFor` resolves or
218 * rejects if the listener has been cancelled or is completed.
219 * @param promise
220 */
221 pause<M>(promise: Promise<M>): Promise<M>;
222 extra: ExtraArgument;
223}
224/** @public */
225export declare type ListenerEffect<Action extends AnyAction, State, Dispatch extends ReduxDispatch<AnyAction>, ExtraArgument = unknown> = (action: Action, api: ListenerEffectAPI<State, Dispatch, ExtraArgument>) => void | Promise<void>;
226/**
227 * @public
228 * Additional infos regarding the error raised.
229 */
230export interface ListenerErrorInfo {
231 /**
232 * Which function has generated the exception.
233 */
234 raisedBy: 'effect' | 'predicate';
235}
236/**
237 * @public
238 * Gets notified with synchronous and asynchronous errors raised by `listeners` or `predicates`.
239 * @param error The thrown error.
240 * @param errorInfo Additional information regarding the thrown error.
241 */
242export interface ListenerErrorHandler {
243 (error: unknown, errorInfo: ListenerErrorInfo): void;
244}
245/** @public */
246export interface CreateListenerMiddlewareOptions<ExtraArgument = unknown> {
247 extra?: ExtraArgument;
248 /**
249 * Receives synchronous errors that are raised by `listener` and `listenerOption.predicate`.
250 */
251 onError?: ListenerErrorHandler;
252}
253/** @public */
254export declare type ListenerMiddleware<State = unknown, Dispatch extends ThunkDispatch<State, unknown, AnyAction> = ThunkDispatch<State, unknown, AnyAction>, ExtraArgument = unknown> = Middleware<{
255 (action: ReduxAction<'listenerMiddleware/add'>): UnsubscribeListener;
256}, State, Dispatch>;
257/** @public */
258export interface ListenerMiddlewareInstance<State = unknown, Dispatch extends ThunkDispatch<State, unknown, AnyAction> = ThunkDispatch<State, unknown, AnyAction>, ExtraArgument = unknown> {
259 middleware: ListenerMiddleware<State, Dispatch, ExtraArgument>;
260 startListening: AddListenerOverloads<UnsubscribeListener, State, Dispatch, ExtraArgument>;
261 stopListening: RemoveListenerOverloads<State, Dispatch>;
262 /**
263 * Unsubscribes all listeners, cancels running listeners and tasks.
264 */
265 clearListeners: () => void;
266}
267/**
268 * API Function Overloads
269 */
270/** @public */
271export declare type TakePatternOutputWithoutTimeout<State, Predicate extends AnyListenerPredicate<State>> = Predicate extends MatchFunction<infer Action> ? Promise<[Action, State, State]> : Promise<[AnyAction, State, State]>;
272/** @public */
273export declare type TakePatternOutputWithTimeout<State, Predicate extends AnyListenerPredicate<State>> = Predicate extends MatchFunction<infer Action> ? Promise<[Action, State, State] | null> : Promise<[AnyAction, State, State] | null>;
274/** @public */
275export interface TakePattern<State> {
276 <Predicate extends AnyListenerPredicate<State>>(predicate: Predicate): TakePatternOutputWithoutTimeout<State, Predicate>;
277 <Predicate extends AnyListenerPredicate<State>>(predicate: Predicate, timeout: number): TakePatternOutputWithTimeout<State, Predicate>;
278 <Predicate extends AnyListenerPredicate<State>>(predicate: Predicate, timeout?: number | undefined): TakePatternOutputWithTimeout<State, Predicate>;
279}
280/** @public */
281export interface UnsubscribeListenerOptions {
282 cancelActive?: true;
283}
284/** @public */
285export declare type UnsubscribeListener = (unsubscribeOptions?: UnsubscribeListenerOptions) => void;
286/**
287 * @public
288 * 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
289 */
290export interface AddListenerOverloads<Return, State = unknown, Dispatch extends ReduxDispatch = ThunkDispatch<State, unknown, AnyAction>, ExtraArgument = unknown, AdditionalOptions = unknown> {
291 /** Accepts a "listener predicate" that is also a TS type predicate for the action*/
292 <MA extends AnyAction, LP extends ListenerPredicate<MA, State>>(options: {
293 actionCreator?: never;
294 type?: never;
295 matcher?: never;
296 predicate: LP;
297 effect: ListenerEffect<ListenerPredicateGuardedActionType<LP>, State, Dispatch, ExtraArgument>;
298 } & AdditionalOptions): Return;
299 /** Accepts an RTK action creator, like `incrementByAmount` */
300 <C extends TypedActionCreator<any>>(options: {
301 actionCreator: C;
302 type?: never;
303 matcher?: never;
304 predicate?: never;
305 effect: ListenerEffect<ReturnType<C>, State, Dispatch, ExtraArgument>;
306 } & AdditionalOptions): Return;
307 /** Accepts a specific action type string */
308 <T extends string>(options: {
309 actionCreator?: never;
310 type: T;
311 matcher?: never;
312 predicate?: never;
313 effect: ListenerEffect<ReduxAction<T>, State, Dispatch, ExtraArgument>;
314 } & AdditionalOptions): Return;
315 /** Accepts an RTK matcher function, such as `incrementByAmount.match` */
316 <MA extends AnyAction, M extends MatchFunction<MA>>(options: {
317 actionCreator?: never;
318 type?: never;
319 matcher: M;
320 predicate?: never;
321 effect: ListenerEffect<GuardedType<M>, State, Dispatch, ExtraArgument>;
322 } & AdditionalOptions): Return;
323 /** Accepts a "listener predicate" that just returns a boolean, no type assertion */
324 <LP extends AnyListenerPredicate<State>>(options: {
325 actionCreator?: never;
326 type?: never;
327 matcher?: never;
328 predicate: LP;
329 effect: ListenerEffect<AnyAction, State, Dispatch, ExtraArgument>;
330 } & AdditionalOptions): Return;
331}
332/** @public */
333export declare type RemoveListenerOverloads<State = unknown, Dispatch extends ReduxDispatch = ThunkDispatch<State, unknown, AnyAction>> = AddListenerOverloads<boolean, State, Dispatch, any, UnsubscribeListenerOptions>;
334/** @public */
335export interface RemoveListenerAction<Action extends AnyAction, State, Dispatch extends ReduxDispatch<AnyAction>> {
336 type: 'listenerMiddleware/remove';
337 payload: {
338 type: string;
339 listener: ListenerEffect<Action, State, Dispatch>;
340 };
341}
342/**
343 * @public
344 * A "pre-typed" version of `addListenerAction`, so the listener args are well-typed */
345export 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>;
346/**
347 * @public
348 * A "pre-typed" version of `removeListenerAction`, so the listener args are well-typed */
349export 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>;
350/**
351 * @public
352 * A "pre-typed" version of `middleware.startListening`, so the listener args are well-typed */
353export declare type TypedStartListening<State, Dispatch extends ReduxDispatch<AnyAction> = ThunkDispatch<State, unknown, AnyAction>, ExtraArgument = unknown> = AddListenerOverloads<UnsubscribeListener, State, Dispatch, ExtraArgument>;
354/** @public
355 * A "pre-typed" version of `middleware.stopListening`, so the listener args are well-typed */
356export declare type TypedStopListening<State, Dispatch extends ReduxDispatch<AnyAction> = ThunkDispatch<State, unknown, AnyAction>> = RemoveListenerOverloads<State, Dispatch>;
357/** @public
358 * A "pre-typed" version of `createListenerEntry`, so the listener args are well-typed */
359export declare type TypedCreateListenerEntry<State, Dispatch extends ReduxDispatch<AnyAction> = ThunkDispatch<State, unknown, AnyAction>> = AddListenerOverloads<ListenerEntry<State, Dispatch>, State, Dispatch>;
360/**
361 * Internal Types
362 */
363/** @internal An single listener entry */
364export declare type ListenerEntry<State = unknown, Dispatch extends ReduxDispatch<AnyAction> = ReduxDispatch<AnyAction>> = {
365 id: string;
366 effect: ListenerEffect<any, State, Dispatch>;
367 unsubscribe: () => void;
368 pending: Set<AbortController>;
369 type?: string;
370 predicate: ListenerPredicate<AnyAction, State>;
371};
372/**
373 * @internal
374 * A shorthand form of the accepted args, solely so that `createListenerEntry` has validly-typed conditional logic when checking the options contents
375 */
376export declare type FallbackAddListenerOptions = {
377 actionCreator?: TypedActionCreator<string>;
378 type?: string;
379 matcher?: MatchFunction<any>;
380 predicate?: ListenerPredicate<any, any>;
381} & {
382 effect: ListenerEffect<any, any, any>;
383};
384/**
385 * Utility Types
386 */
387/** @public */
388export declare type GuardedType<T> = T extends (x: any, ...args: unknown[]) => x is infer T ? T : never;
389/** @public */
390export declare type ListenerPredicateGuardedActionType<T> = T extends ListenerPredicate<infer Action, any> ? Action : never;