1 | import type {
|
2 | ActionFromMatcher,
|
3 | Matcher,
|
4 | UnionToIntersection,
|
5 | } from './tsHelpers'
|
6 | import { hasMatchFunction } from './tsHelpers'
|
7 | import type {
|
8 | AsyncThunk,
|
9 | AsyncThunkFulfilledActionCreator,
|
10 | AsyncThunkPendingActionCreator,
|
11 | AsyncThunkRejectedActionCreator,
|
12 | } from './createAsyncThunk'
|
13 |
|
14 |
|
15 | export type ActionMatchingAnyOf<Matchers extends Matcher<any>[]> =
|
16 | ActionFromMatcher<Matchers[number]>
|
17 |
|
18 |
|
19 | export type ActionMatchingAllOf<Matchers extends Matcher<any>[]> =
|
20 | UnionToIntersection<ActionMatchingAnyOf<Matchers>>
|
21 |
|
22 | const matches = (matcher: Matcher<any>, action: any) => {
|
23 | if (hasMatchFunction(matcher)) {
|
24 | return matcher.match(action)
|
25 | } else {
|
26 | return matcher(action)
|
27 | }
|
28 | }
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 | export function isAnyOf<Matchers extends Matcher<any>[]>(
|
40 | ...matchers: Matchers
|
41 | ) {
|
42 | return (action: any): action is ActionMatchingAnyOf<Matchers> => {
|
43 | return matchers.some((matcher) => matches(matcher, action))
|
44 | }
|
45 | }
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 | export function isAllOf<Matchers extends Matcher<any>[]>(
|
57 | ...matchers: Matchers
|
58 | ) {
|
59 | return (action: any): action is ActionMatchingAllOf<Matchers> => {
|
60 | return matchers.every((matcher) => matches(matcher, action))
|
61 | }
|
62 | }
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 | export function hasExpectedRequestMetadata(
|
71 | action: any,
|
72 | validStatus: readonly string[],
|
73 | ) {
|
74 | if (!action || !action.meta) return false
|
75 |
|
76 | const hasValidRequestId = typeof action.meta.requestId === 'string'
|
77 | const hasValidRequestStatus =
|
78 | validStatus.indexOf(action.meta.requestStatus) > -1
|
79 |
|
80 | return hasValidRequestId && hasValidRequestStatus
|
81 | }
|
82 |
|
83 | function isAsyncThunkArray(a: [any] | AnyAsyncThunk[]): a is AnyAsyncThunk[] {
|
84 | return (
|
85 | typeof a[0] === 'function' &&
|
86 | 'pending' in a[0] &&
|
87 | 'fulfilled' in a[0] &&
|
88 | 'rejected' in a[0]
|
89 | )
|
90 | }
|
91 |
|
92 | export type UnknownAsyncThunkPendingAction = ReturnType<
|
93 | AsyncThunkPendingActionCreator<unknown>
|
94 | >
|
95 |
|
96 | export type PendingActionFromAsyncThunk<T extends AnyAsyncThunk> =
|
97 | ActionFromMatcher<T['pending']>
|
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 | export function isPending(): (
|
107 | action: any,
|
108 | ) => action is UnknownAsyncThunkPendingAction
|
109 | /**
|
110 | * A higher-order function that returns a function that may be used to check
|
111 | * whether an action belongs to one of the provided async thunk action creators,
|
112 | * and that the action is pending.
|
113 | *
|
114 | * @param asyncThunks (optional) The async thunk action creators to match against.
|
115 | *
|
116 | * @public
|
117 | */
|
118 | export function isPending<
|
119 | AsyncThunks extends [AnyAsyncThunk, ...AnyAsyncThunk[]],
|
120 | >(
|
121 | ...asyncThunks: AsyncThunks
|
122 | ): (action: any) => action is PendingActionFromAsyncThunk<AsyncThunks[number]>
|
123 | /**
|
124 | * Tests if `action` is a pending thunk action
|
125 | * @public
|
126 | */
|
127 | export function isPending(action: any): action is UnknownAsyncThunkPendingAction
|
128 | export function isPending<
|
129 | AsyncThunks extends [AnyAsyncThunk, ...AnyAsyncThunk[]],
|
130 | >(...asyncThunks: AsyncThunks | [any]) {
|
131 | if (asyncThunks.length === 0) {
|
132 | return (action: any) => hasExpectedRequestMetadata(action, ['pending'])
|
133 | }
|
134 |
|
135 | if (!isAsyncThunkArray(asyncThunks)) {
|
136 | return isPending()(asyncThunks[0])
|
137 | }
|
138 |
|
139 | return isAnyOf(...asyncThunks.map((asyncThunk) => asyncThunk.pending))
|
140 | }
|
141 |
|
142 | export type UnknownAsyncThunkRejectedAction = ReturnType<
|
143 | AsyncThunkRejectedActionCreator<unknown, unknown>
|
144 | >
|
145 |
|
146 | export type RejectedActionFromAsyncThunk<T extends AnyAsyncThunk> =
|
147 | ActionFromMatcher<T['rejected']>
|
148 |
|
149 | /**
|
150 | * A higher-order function that returns a function that may be used to check
|
151 | * whether an action was created by an async thunk action creator, and that
|
152 | * the action is rejected.
|
153 | *
|
154 | * @public
|
155 | */
|
156 | export function isRejected(): (
|
157 | action: any,
|
158 | ) => action is UnknownAsyncThunkRejectedAction
|
159 | /**
|
160 | * A higher-order function that returns a function that may be used to check
|
161 | * whether an action belongs to one of the provided async thunk action creators,
|
162 | * and that the action is rejected.
|
163 | *
|
164 | * @param asyncThunks (optional) The async thunk action creators to match against.
|
165 | *
|
166 | * @public
|
167 | */
|
168 | export function isRejected<
|
169 | AsyncThunks extends [AnyAsyncThunk, ...AnyAsyncThunk[]],
|
170 | >(
|
171 | ...asyncThunks: AsyncThunks
|
172 | ): (action: any) => action is RejectedActionFromAsyncThunk<AsyncThunks[number]>
|
173 | /**
|
174 | * Tests if `action` is a rejected thunk action
|
175 | * @public
|
176 | */
|
177 | export function isRejected(
|
178 | action: any,
|
179 | ): action is UnknownAsyncThunkRejectedAction
|
180 | export function isRejected<
|
181 | AsyncThunks extends [AnyAsyncThunk, ...AnyAsyncThunk[]],
|
182 | >(...asyncThunks: AsyncThunks | [any]) {
|
183 | if (asyncThunks.length === 0) {
|
184 | return (action: any) => hasExpectedRequestMetadata(action, ['rejected'])
|
185 | }
|
186 |
|
187 | if (!isAsyncThunkArray(asyncThunks)) {
|
188 | return isRejected()(asyncThunks[0])
|
189 | }
|
190 |
|
191 | return isAnyOf(...asyncThunks.map((asyncThunk) => asyncThunk.rejected))
|
192 | }
|
193 |
|
194 | export type UnknownAsyncThunkRejectedWithValueAction = ReturnType<
|
195 | AsyncThunkRejectedActionCreator<unknown, unknown>
|
196 | >
|
197 |
|
198 | export type RejectedWithValueActionFromAsyncThunk<T extends AnyAsyncThunk> =
|
199 | ActionFromMatcher<T['rejected']> &
|
200 | (T extends AsyncThunk<any, any, { rejectValue: infer RejectedValue }>
|
201 | ? { payload: RejectedValue }
|
202 | : unknown)
|
203 |
|
204 | /**
|
205 | * A higher-order function that returns a function that may be used to check
|
206 | * whether an action was created by an async thunk action creator, and that
|
207 | * the action is rejected with value.
|
208 | *
|
209 | * @public
|
210 | */
|
211 | export function isRejectedWithValue(): (
|
212 | action: any,
|
213 | ) => action is UnknownAsyncThunkRejectedAction
|
214 | /**
|
215 | * A higher-order function that returns a function that may be used to check
|
216 | * whether an action belongs to one of the provided async thunk action creators,
|
217 | * and that the action is rejected with value.
|
218 | *
|
219 | * @param asyncThunks (optional) The async thunk action creators to match against.
|
220 | *
|
221 | * @public
|
222 | */
|
223 | export function isRejectedWithValue<
|
224 | AsyncThunks extends [AnyAsyncThunk, ...AnyAsyncThunk[]],
|
225 | >(
|
226 | ...asyncThunks: AsyncThunks
|
227 | ): (
|
228 | action: any,
|
229 | ) => action is RejectedWithValueActionFromAsyncThunk<AsyncThunks[number]>
|
230 | /**
|
231 | * Tests if `action` is a rejected thunk action with value
|
232 | * @public
|
233 | */
|
234 | export function isRejectedWithValue(
|
235 | action: any,
|
236 | ): action is UnknownAsyncThunkRejectedAction
|
237 | export function isRejectedWithValue<
|
238 | AsyncThunks extends [AnyAsyncThunk, ...AnyAsyncThunk[]],
|
239 | >(...asyncThunks: AsyncThunks | [any]) {
|
240 | const hasFlag = (action: any): action is any => {
|
241 | return action && action.meta && action.meta.rejectedWithValue
|
242 | }
|
243 |
|
244 | if (asyncThunks.length === 0) {
|
245 | return isAllOf(isRejected(...asyncThunks), hasFlag)
|
246 | }
|
247 |
|
248 | if (!isAsyncThunkArray(asyncThunks)) {
|
249 | return isRejectedWithValue()(asyncThunks[0])
|
250 | }
|
251 |
|
252 | return isAllOf(isRejected(...asyncThunks), hasFlag)
|
253 | }
|
254 |
|
255 | export type UnknownAsyncThunkFulfilledAction = ReturnType<
|
256 | AsyncThunkFulfilledActionCreator<unknown, unknown>
|
257 | >
|
258 |
|
259 | export type FulfilledActionFromAsyncThunk<T extends AnyAsyncThunk> =
|
260 | ActionFromMatcher<T['fulfilled']>
|
261 |
|
262 | /**
|
263 | * A higher-order function that returns a function that may be used to check
|
264 | * whether an action was created by an async thunk action creator, and that
|
265 | * the action is fulfilled.
|
266 | *
|
267 | * @public
|
268 | */
|
269 | export function isFulfilled(): (
|
270 | action: any,
|
271 | ) => action is UnknownAsyncThunkFulfilledAction
|
272 | /**
|
273 | * A higher-order function that returns a function that may be used to check
|
274 | * whether an action belongs to one of the provided async thunk action creators,
|
275 | * and that the action is fulfilled.
|
276 | *
|
277 | * @param asyncThunks (optional) The async thunk action creators to match against.
|
278 | *
|
279 | * @public
|
280 | */
|
281 | export function isFulfilled<
|
282 | AsyncThunks extends [AnyAsyncThunk, ...AnyAsyncThunk[]],
|
283 | >(
|
284 | ...asyncThunks: AsyncThunks
|
285 | ): (action: any) => action is FulfilledActionFromAsyncThunk<AsyncThunks[number]>
|
286 | /**
|
287 | * Tests if `action` is a fulfilled thunk action
|
288 | * @public
|
289 | */
|
290 | export function isFulfilled(
|
291 | action: any,
|
292 | ): action is UnknownAsyncThunkFulfilledAction
|
293 | export function isFulfilled<
|
294 | AsyncThunks extends [AnyAsyncThunk, ...AnyAsyncThunk[]],
|
295 | >(...asyncThunks: AsyncThunks | [any]) {
|
296 | if (asyncThunks.length === 0) {
|
297 | return (action: any) => hasExpectedRequestMetadata(action, ['fulfilled'])
|
298 | }
|
299 |
|
300 | if (!isAsyncThunkArray(asyncThunks)) {
|
301 | return isFulfilled()(asyncThunks[0])
|
302 | }
|
303 |
|
304 | return isAnyOf(...asyncThunks.map((asyncThunk) => asyncThunk.fulfilled))
|
305 | }
|
306 |
|
307 | export type UnknownAsyncThunkAction =
|
308 | | UnknownAsyncThunkPendingAction
|
309 | | UnknownAsyncThunkRejectedAction
|
310 | | UnknownAsyncThunkFulfilledAction
|
311 |
|
312 | export type AnyAsyncThunk = {
|
313 | pending: { match: (action: any) => action is any }
|
314 | fulfilled: { match: (action: any) => action is any }
|
315 | rejected: { match: (action: any) => action is any }
|
316 | }
|
317 |
|
318 | export type ActionsFromAsyncThunk<T extends AnyAsyncThunk> =
|
319 | | ActionFromMatcher<T['pending']>
|
320 | | ActionFromMatcher<T['fulfilled']>
|
321 | | ActionFromMatcher<T['rejected']>
|
322 |
|
323 | /**
|
324 | * A higher-order function that returns a function that may be used to check
|
325 | * whether an action was created by an async thunk action creator.
|
326 | *
|
327 | * @public
|
328 | */
|
329 | export function isAsyncThunkAction(): (
|
330 | action: any,
|
331 | ) => action is UnknownAsyncThunkAction
|
332 | /**
|
333 | * A higher-order function that returns a function that may be used to check
|
334 | * whether an action belongs to one of the provided async thunk action creators.
|
335 | *
|
336 | * @param asyncThunks (optional) The async thunk action creators to match against.
|
337 | *
|
338 | * @public
|
339 | */
|
340 | export function isAsyncThunkAction<
|
341 | AsyncThunks extends [AnyAsyncThunk, ...AnyAsyncThunk[]],
|
342 | >(
|
343 | ...asyncThunks: AsyncThunks
|
344 | ): (action: any) => action is ActionsFromAsyncThunk<AsyncThunks[number]>
|
345 | /**
|
346 | * Tests if `action` is a thunk action
|
347 | * @public
|
348 | */
|
349 | export function isAsyncThunkAction(
|
350 | action: any,
|
351 | ): action is UnknownAsyncThunkAction
|
352 | export function isAsyncThunkAction<
|
353 | AsyncThunks extends [AnyAsyncThunk, ...AnyAsyncThunk[]],
|
354 | >(...asyncThunks: AsyncThunks | [any]) {
|
355 | if (asyncThunks.length === 0) {
|
356 | return (action: any) =>
|
357 | hasExpectedRequestMetadata(action, ['pending', 'fulfilled', 'rejected'])
|
358 | }
|
359 |
|
360 | if (!isAsyncThunkArray(asyncThunks)) {
|
361 | return isAsyncThunkAction()(asyncThunks[0])
|
362 | }
|
363 |
|
364 | return isAnyOf(...asyncThunks.flatMap(asyncThunk => [asyncThunk.pending, asyncThunk.rejected, asyncThunk.fulfilled]))
|
365 | }
|
366 |
|
\ | No newline at end of file |