Skip to content

Commit 09939c1

Browse files
authored
Merge pull request #1787 from reduxjs/feature/reorganize-middleware
2 parents 1ab9c2b + 2c60a3e commit 09939c1

File tree

3 files changed

+365
-292
lines changed

3 files changed

+365
-292
lines changed

packages/action-listener-middleware/src/index.ts

Lines changed: 36 additions & 289 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,47 @@
11
import type {
2-
PayloadAction,
32
Middleware,
43
Dispatch,
54
AnyAction,
6-
MiddlewareAPI,
75
Action,
86
ThunkDispatch,
97
} from '@reduxjs/toolkit'
108
import { createAction, nanoid } from '@reduxjs/toolkit'
119

12-
interface BaseActionCreator<P, T extends string, M = never, E = never> {
13-
type: T
14-
match(action: Action<unknown>): action is PayloadAction<P, T, M, E>
15-
}
16-
17-
interface TypedActionCreator<Type extends string> {
18-
(...args: any[]): Action<Type>
19-
type: Type
20-
match: MatchFunction<any>
21-
}
22-
23-
type AnyActionListenerPredicate<State> = (
24-
action: AnyAction,
25-
currentState: State,
26-
originalState: State
27-
) => boolean
28-
29-
type ListenerPredicate<Action extends AnyAction, State> = (
30-
action: AnyAction,
31-
currentState: State,
32-
originalState: State
33-
) => action is Action
34-
35-
interface ConditionFunction<State> {
36-
(
37-
predicate: AnyActionListenerPredicate<State>,
38-
timeout?: number
39-
): Promise<boolean>
40-
(
41-
predicate: AnyActionListenerPredicate<State>,
42-
timeout?: number
43-
): Promise<boolean>
44-
(predicate: () => boolean, timeout?: number): Promise<boolean>
45-
}
46-
47-
type MatchFunction<T> = (v: any) => v is T
48-
49-
type TakePatternOutputWithoutTimeout<
50-
State,
51-
Predicate extends AnyActionListenerPredicate<State>
52-
> = Predicate extends MatchFunction<infer Action>
53-
? Promise<[Action, State, State]>
54-
: Promise<[AnyAction, State, State]>
55-
56-
type TakePatternOutputWithTimeout<
57-
State,
58-
Predicate extends AnyActionListenerPredicate<State>
59-
> = Predicate extends MatchFunction<infer Action>
60-
? Promise<[Action, State, State] | null>
61-
: Promise<[AnyAction, State, State] | null>
62-
63-
interface TakePattern<State> {
64-
<Predicate extends AnyActionListenerPredicate<State>>(
65-
predicate: Predicate
66-
): TakePatternOutputWithoutTimeout<State, Predicate>
67-
<Predicate extends AnyActionListenerPredicate<State>>(
68-
predicate: Predicate,
69-
timeout: number
70-
): TakePatternOutputWithTimeout<State, Predicate>;
71-
<Predicate extends AnyActionListenerPredicate<State>>(
72-
predicate: Predicate,
73-
timeout?: number | undefined
74-
): Promise<[AnyAction, State, State] | null>;
75-
}
76-
77-
export interface HasMatchFunction<T> {
78-
match: MatchFunction<T>
79-
}
10+
import type {
11+
ActionListener,
12+
AddListenerOverloads,
13+
BaseActionCreator,
14+
AnyActionListenerPredicate,
15+
CreateListenerMiddlewareOptions,
16+
ConditionFunction,
17+
ListenerPredicate,
18+
TypedActionCreator,
19+
TypedAddListener,
20+
TypedAddListenerAction,
21+
TypedCreateListenerEntry,
22+
RemoveListenerAction,
23+
FallbackAddListenerOptions,
24+
ListenerEntry,
25+
ListenerErrorHandler,
26+
Unsubscribe,
27+
MiddlewarePhase,
28+
WithMiddlewareType,
29+
TakePattern,
30+
} from './types'
31+
32+
export type {
33+
ActionListener,
34+
ActionListenerMiddleware,
35+
ActionListenerMiddlewareAPI,
36+
ActionListenerOptions,
37+
CreateListenerMiddlewareOptions,
38+
MiddlewarePhase,
39+
When,
40+
ListenerErrorHandler,
41+
TypedAddListener,
42+
TypedAddListenerAction,
43+
Unsubscribe,
44+
} from './types'
8045

8146
function assertFunction(
8247
func: unknown,
@@ -87,196 +52,11 @@ function assertFunction(
8752
}
8853
}
8954

90-
type Unsubscribe = () => void
91-
92-
type GuardedType<T> = T extends (x: any, ...args: unknown[]) => x is infer T
93-
? T
94-
: never
95-
96-
type ListenerPredicateGuardedActionType<T> = T extends ListenerPredicate<
97-
infer Action,
98-
any
99-
>
100-
? Action
101-
: never
102-
103-
const declaredMiddlewareType: unique symbol = undefined as any
104-
export type WithMiddlewareType<T extends Middleware<any, any, any>> = {
105-
[declaredMiddlewareType]: T
106-
}
107-
108-
export type MiddlewarePhase = 'beforeReducer' | 'afterReducer'
109-
11055
const defaultWhen: MiddlewarePhase = 'afterReducer'
11156
const actualMiddlewarePhases = ['beforeReducer', 'afterReducer'] as const
11257

113-
export type When = MiddlewarePhase | 'both' | undefined
114-
115-
/**
116-
* @alpha
117-
*/
118-
export interface ActionListenerMiddlewareAPI<S, D extends Dispatch<AnyAction>>
119-
extends MiddlewareAPI<D, S> {
120-
getOriginalState: () => S
121-
unsubscribe(): void
122-
subscribe(): void
123-
condition: ConditionFunction<S>
124-
take: TakePattern<S>
125-
currentPhase: MiddlewarePhase
126-
// TODO Figure out how to pass this through the other types correctly
127-
extra: unknown
128-
}
129-
130-
/**
131-
* @alpha
132-
*/
133-
export type ActionListener<
134-
A extends AnyAction,
135-
S,
136-
D extends Dispatch<AnyAction>
137-
> = (action: A, api: ActionListenerMiddlewareAPI<S, D>) => void
138-
139-
export interface ListenerErrorHandler {
140-
(error: unknown): void
141-
}
142-
143-
export interface ActionListenerOptions {
144-
/**
145-
* Determines if the listener runs 'before' or 'after' the reducers have been called.
146-
* If set to 'before', calling `api.stopPropagation()` from the listener becomes possible.
147-
* Defaults to 'before'.
148-
*/
149-
when?: When
150-
}
151-
152-
export interface CreateListenerMiddlewareOptions<ExtraArgument = unknown> {
153-
extra?: ExtraArgument
154-
/**
155-
* Receives synchronous errors that are raised by `listener` and `listenerOption.predicate`.
156-
*/
157-
onError?: ListenerErrorHandler
158-
}
159-
160-
/**
161-
* 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
162-
*/
163-
interface AddListenerOverloads<
164-
Return,
165-
S = unknown,
166-
D extends Dispatch = ThunkDispatch<S, unknown, AnyAction>
167-
> {
168-
/** Accepts a "listener predicate" that is also a TS type predicate for the action*/
169-
<MA extends AnyAction, LP extends ListenerPredicate<MA, S>>(
170-
options: {
171-
actionCreator?: never
172-
type?: never
173-
matcher?: never
174-
predicate: LP
175-
listener: ActionListener<ListenerPredicateGuardedActionType<LP>, S, D>
176-
} & ActionListenerOptions
177-
): Return
178-
179-
/** Accepts an RTK action creator, like `incrementByAmount` */
180-
<C extends TypedActionCreator<any>>(
181-
options: {
182-
actionCreator: C
183-
type?: never
184-
matcher?: never
185-
predicate?: never
186-
listener: ActionListener<ReturnType<C>, S, D>
187-
} & ActionListenerOptions
188-
): Return
189-
190-
/** Accepts a specific action type string */
191-
<T extends string>(
192-
options: {
193-
actionCreator?: never
194-
type: T
195-
matcher?: never
196-
predicate?: never
197-
listener: ActionListener<Action<T>, S, D>
198-
} & ActionListenerOptions
199-
): Return
200-
201-
/** Accepts an RTK matcher function, such as `incrementByAmount.match` */
202-
<MA extends AnyAction, M extends MatchFunction<MA>>(
203-
options: {
204-
actionCreator?: never
205-
type?: never
206-
matcher: M
207-
predicate?: never
208-
listener: ActionListener<GuardedType<M>, S, D>
209-
} & ActionListenerOptions
210-
): Return
211-
212-
/** Accepts a "listener predicate" that just returns a boolean, no type assertion */
213-
<LP extends AnyActionListenerPredicate<S>>(
214-
options: {
215-
actionCreator?: never
216-
type?: never
217-
matcher?: never
218-
predicate: LP
219-
listener: ActionListener<AnyAction, S, D>
220-
} & ActionListenerOptions
221-
): Return
222-
}
223-
224-
interface RemoveListenerOverloads<
225-
S = unknown,
226-
D extends Dispatch = ThunkDispatch<S, unknown, AnyAction>
227-
> {
228-
<C extends TypedActionCreator<any>>(
229-
actionCreator: C,
230-
listener: ActionListener<ReturnType<C>, S, D>
231-
): boolean
232-
(type: string, listener: ActionListener<AnyAction, S, D>): boolean
233-
}
234-
235-
/** A "pre-typed" version of `addListenerAction`, so the listener args are well-typed */
236-
export type TypedAddListenerAction<
237-
S,
238-
D extends Dispatch<AnyAction> = ThunkDispatch<S, unknown, AnyAction>,
239-
Payload = ListenerEntry<S, D>,
240-
T extends string = 'actionListenerMiddleware/add'
241-
> = BaseActionCreator<Payload, T> &
242-
AddListenerOverloads<PayloadAction<Payload, T>, S, D>
243-
244-
/** A "pre-typed" version of `middleware.addListener`, so the listener args are well-typed */
245-
export type TypedAddListener<
246-
S,
247-
D extends Dispatch<AnyAction> = ThunkDispatch<S, unknown, AnyAction>
248-
> = AddListenerOverloads<Unsubscribe, S, D>
249-
250-
/** @internal An single listener entry */
251-
type ListenerEntry<
252-
S = unknown,
253-
D extends Dispatch<AnyAction> = Dispatch<AnyAction>
254-
> = {
255-
id: string
256-
when: When
257-
listener: ActionListener<any, S, D>
258-
unsubscribe: () => void
259-
type?: string
260-
predicate: ListenerPredicate<AnyAction, S>
261-
}
262-
263-
/** A "pre-typed" version of `createListenerEntry`, so the listener args are well-typed */
264-
export type TypedCreateListenerEntry<
265-
S,
266-
D extends Dispatch<AnyAction> = ThunkDispatch<S, unknown, AnyAction>
267-
> = AddListenerOverloads<ListenerEntry<S, D>, S, D>
268-
269-
// A shorthand form of the accepted args, solely so that `createListenerEntry` has validly-typed conditional logic when checking the options contents
270-
type FallbackAddListenerOptions = (
271-
| { actionCreator: TypedActionCreator<string> }
272-
| { type: string }
273-
| { matcher: MatchFunction<any> }
274-
| { predicate: ListenerPredicate<any, any> }
275-
) &
276-
ActionListenerOptions & { listener: ActionListener<any, any, any> }
277-
27858
function createTakePattern<S>(
279-
addListener: AddListenerOverloads<Unsubscribe, S,Dispatch<AnyAction>>
59+
addListener: AddListenerOverloads<Unsubscribe, S, Dispatch<AnyAction>>
28060
): TakePattern<S> {
28161
async function take<P extends AnyActionListenerPredicate<S>>(
28262
predicate: P,
@@ -352,27 +132,6 @@ export const createListenerEntry: TypedCreateListenerEntry<unknown> = (
352132
return entry
353133
}
354134

355-
export type ActionListenerMiddleware<
356-
S = unknown,
357-
// TODO Carry through the thunk extra arg somehow?
358-
D extends ThunkDispatch<S, unknown, AnyAction> = ThunkDispatch<
359-
S,
360-
unknown,
361-
AnyAction
362-
>,
363-
ExtraArgument = unknown
364-
> = Middleware<
365-
{
366-
(action: Action<'actionListenerMiddleware/add'>): Unsubscribe
367-
},
368-
S,
369-
D
370-
> & {
371-
addListener: AddListenerOverloads<Unsubscribe, S, D>
372-
removeListener: RemoveListenerOverloads<S, D>
373-
addListenerAction: TypedAddListenerAction<S, D>
374-
}
375-
376135
/**
377136
* Safely reports errors to the `errorHandler` provided.
378137
* Errors that occur inside `errorHandler` are notified in a new task.
@@ -412,18 +171,6 @@ export const addListenerAction = createAction(
412171
}
413172
) as TypedAddListenerAction<unknown>
414173

415-
interface RemoveListenerAction<
416-
A extends AnyAction,
417-
S,
418-
D extends Dispatch<AnyAction>
419-
> {
420-
type: 'actionListenerMiddleware/remove'
421-
payload: {
422-
type: string
423-
listener: ActionListener<A, S, D>
424-
}
425-
}
426-
427174
/**
428175
* @alpha
429176
*/

0 commit comments

Comments
 (0)