source: node_modules/@reduxjs/toolkit/src/query/core/buildInitiate.ts

Last change on this file was a762898, checked in by istevanoska <ilinastevanoska@…>, 5 months ago

Added visualizations

  • Property mode set to 100644
File size: 17.3 KB
Line 
1import type {
2 AsyncThunkAction,
3 SafePromise,
4 SerializedError,
5 ThunkAction,
6 UnknownAction,
7} from '@reduxjs/toolkit'
8import type { Dispatch } from 'redux'
9import { asSafePromise } from '../../tsHelpers'
10import { getEndpointDefinition, type Api, type ApiContext } from '../apiTypes'
11import type { BaseQueryError, QueryReturnValue } from '../baseQueryTypes'
12import type { InternalSerializeQueryArgs } from '../defaultSerializeQueryArgs'
13import {
14 ENDPOINT_QUERY,
15 isQueryDefinition,
16 type EndpointDefinition,
17 type EndpointDefinitions,
18 type InfiniteQueryArgFrom,
19 type InfiniteQueryDefinition,
20 type MutationDefinition,
21 type PageParamFrom,
22 type QueryArgFrom,
23 type QueryDefinition,
24 type ResultTypeFrom,
25} from '../endpointDefinitions'
26import { filterNullishValues } from '../utils'
27import type {
28 InfiniteData,
29 InfiniteQueryConfigOptions,
30 InfiniteQueryDirection,
31 SubscriptionOptions,
32} from './apiState'
33import type {
34 InfiniteQueryResultSelectorResult,
35 QueryResultSelectorResult,
36} from './buildSelectors'
37import type {
38 InfiniteQueryThunk,
39 InfiniteQueryThunkArg,
40 MutationThunk,
41 QueryThunk,
42 QueryThunkArg,
43 ThunkApiMetaConfig,
44} from './buildThunks'
45import type { ApiEndpointQuery } from './module'
46import type { InternalMiddlewareState } from './buildMiddleware/types'
47
48export type BuildInitiateApiEndpointQuery<
49 Definition extends QueryDefinition<any, any, any, any, any>,
50> = {
51 initiate: StartQueryActionCreator<Definition>
52}
53
54export type BuildInitiateApiEndpointInfiniteQuery<
55 Definition extends InfiniteQueryDefinition<any, any, any, any, any>,
56> = {
57 initiate: StartInfiniteQueryActionCreator<Definition>
58}
59
60export type BuildInitiateApiEndpointMutation<
61 Definition extends MutationDefinition<any, any, any, any, any>,
62> = {
63 initiate: StartMutationActionCreator<Definition>
64}
65
66export const forceQueryFnSymbol = Symbol('forceQueryFn')
67export const isUpsertQuery = (arg: QueryThunkArg) =>
68 typeof arg[forceQueryFnSymbol] === 'function'
69
70export type StartQueryActionCreatorOptions = {
71 subscribe?: boolean
72 forceRefetch?: boolean | number
73 subscriptionOptions?: SubscriptionOptions
74 [forceQueryFnSymbol]?: () => QueryReturnValue
75}
76
77type RefetchOptions = {
78 refetchCachedPages?: boolean
79}
80
81export type StartInfiniteQueryActionCreatorOptions<
82 D extends InfiniteQueryDefinition<any, any, any, any, any>,
83> = StartQueryActionCreatorOptions & {
84 direction?: InfiniteQueryDirection
85 param?: unknown
86} & Partial<
87 Pick<
88 Partial<
89 InfiniteQueryConfigOptions<
90 ResultTypeFrom<D>,
91 PageParamFrom<D>,
92 InfiniteQueryArgFrom<D>
93 >
94 >,
95 'initialPageParam' | 'refetchCachedPages'
96 >
97 >
98
99type AnyQueryActionCreator<D extends EndpointDefinition<any, any, any, any>> = (
100 arg: any,
101 options?: StartQueryActionCreatorOptions,
102) => ThunkAction<AnyActionCreatorResult, any, any, UnknownAction>
103
104type StartQueryActionCreator<
105 D extends QueryDefinition<any, any, any, any, any>,
106> = (
107 arg: QueryArgFrom<D>,
108 options?: StartQueryActionCreatorOptions,
109) => ThunkAction<QueryActionCreatorResult<D>, any, any, UnknownAction>
110
111export type StartInfiniteQueryActionCreator<
112 D extends InfiniteQueryDefinition<any, any, any, any, any>,
113> = (
114 arg: InfiniteQueryArgFrom<D>,
115 options?: StartInfiniteQueryActionCreatorOptions<D>,
116) => ThunkAction<InfiniteQueryActionCreatorResult<D>, any, any, UnknownAction>
117
118type QueryActionCreatorFields = {
119 requestId: string
120 subscriptionOptions: SubscriptionOptions | undefined
121 abort(): void
122 unsubscribe(): void
123 updateSubscriptionOptions(options: SubscriptionOptions): void
124 queryCacheKey: string
125}
126
127type AnyActionCreatorResult = SafePromise<any> &
128 QueryActionCreatorFields & {
129 arg: any
130 unwrap(): Promise<any>
131 refetch(options?: RefetchOptions): AnyActionCreatorResult
132 }
133
134export type QueryActionCreatorResult<
135 D extends QueryDefinition<any, any, any, any>,
136> = SafePromise<QueryResultSelectorResult<D>> &
137 QueryActionCreatorFields & {
138 arg: QueryArgFrom<D>
139 unwrap(): Promise<ResultTypeFrom<D>>
140 refetch(): QueryActionCreatorResult<D>
141 }
142
143export type InfiniteQueryActionCreatorResult<
144 D extends InfiniteQueryDefinition<any, any, any, any, any>,
145> = SafePromise<InfiniteQueryResultSelectorResult<D>> &
146 QueryActionCreatorFields & {
147 arg: InfiniteQueryArgFrom<D>
148 unwrap(): Promise<InfiniteData<ResultTypeFrom<D>, PageParamFrom<D>>>
149 refetch(
150 options?: Pick<
151 StartInfiniteQueryActionCreatorOptions<D>,
152 'refetchCachedPages'
153 >,
154 ): InfiniteQueryActionCreatorResult<D>
155 }
156
157type StartMutationActionCreator<
158 D extends MutationDefinition<any, any, any, any>,
159> = (
160 arg: QueryArgFrom<D>,
161 options?: {
162 /**
163 * If this mutation should be tracked in the store.
164 * If you just want to manually trigger this mutation using `dispatch` and don't care about the
165 * result, state & potential errors being held in store, you can set this to false.
166 * (defaults to `true`)
167 */
168 track?: boolean
169 fixedCacheKey?: string
170 },
171) => ThunkAction<MutationActionCreatorResult<D>, any, any, UnknownAction>
172
173export type MutationActionCreatorResult<
174 D extends MutationDefinition<any, any, any, any>,
175> = SafePromise<
176 | {
177 data: ResultTypeFrom<D>
178 error?: undefined
179 }
180 | {
181 data?: undefined
182 error:
183 | Exclude<
184 BaseQueryError<
185 D extends MutationDefinition<any, infer BaseQuery, any, any>
186 ? BaseQuery
187 : never
188 >,
189 undefined
190 >
191 | SerializedError
192 }
193> & {
194 /** @internal */
195 arg: {
196 /**
197 * The name of the given endpoint for the mutation
198 */
199 endpointName: string
200 /**
201 * The original arguments supplied to the mutation call
202 */
203 originalArgs: QueryArgFrom<D>
204 /**
205 * Whether the mutation is being tracked in the store.
206 */
207 track?: boolean
208 fixedCacheKey?: string
209 }
210 /**
211 * A unique string generated for the request sequence
212 */
213 requestId: string
214
215 /**
216 * A method to cancel the mutation promise. Note that this is not intended to prevent the mutation
217 * that was fired off from reaching the server, but only to assist in handling the response.
218 *
219 * Calling `abort()` prior to the promise resolving will force it to reach the error state with
220 * the serialized error:
221 * `{ name: 'AbortError', message: 'Aborted' }`
222 *
223 * @example
224 * ```ts
225 * const [updateUser] = useUpdateUserMutation();
226 *
227 * useEffect(() => {
228 * const promise = updateUser(id);
229 * promise
230 * .unwrap()
231 * .catch((err) => {
232 * if (err.name === 'AbortError') return;
233 * // else handle the unexpected error
234 * })
235 *
236 * return () => {
237 * promise.abort();
238 * }
239 * }, [id, updateUser])
240 * ```
241 */
242 abort(): void
243 /**
244 * Unwraps a mutation call to provide the raw response/error.
245 *
246 * @remarks
247 * If you need to access the error or success payload immediately after a mutation, you can chain .unwrap().
248 *
249 * @example
250 * ```ts
251 * // codeblock-meta title="Using .unwrap"
252 * addPost({ id: 1, name: 'Example' })
253 * .unwrap()
254 * .then((payload) => console.log('fulfilled', payload))
255 * .catch((error) => console.error('rejected', error));
256 * ```
257 *
258 * @example
259 * ```ts
260 * // codeblock-meta title="Using .unwrap with async await"
261 * try {
262 * const payload = await addPost({ id: 1, name: 'Example' }).unwrap();
263 * console.log('fulfilled', payload)
264 * } catch (error) {
265 * console.error('rejected', error);
266 * }
267 * ```
268 */
269 unwrap(): Promise<ResultTypeFrom<D>>
270 /**
271 * A method to manually unsubscribe from the mutation call, meaning it will be removed from cache after the usual caching grace period.
272 The value returned by the hook will reset to `isUninitialized` afterwards.
273 */
274 reset(): void
275}
276
277export function buildInitiate({
278 serializeQueryArgs,
279 queryThunk,
280 infiniteQueryThunk,
281 mutationThunk,
282 api,
283 context,
284 getInternalState,
285}: {
286 serializeQueryArgs: InternalSerializeQueryArgs
287 queryThunk: QueryThunk
288 infiniteQueryThunk: InfiniteQueryThunk<any>
289 mutationThunk: MutationThunk
290 api: Api<any, EndpointDefinitions, any, any>
291 context: ApiContext<EndpointDefinitions>
292 getInternalState: (dispatch: Dispatch) => InternalMiddlewareState
293}) {
294 const getRunningQueries = (dispatch: Dispatch) =>
295 getInternalState(dispatch)?.runningQueries
296 const getRunningMutations = (dispatch: Dispatch) =>
297 getInternalState(dispatch)?.runningMutations
298
299 const {
300 unsubscribeQueryResult,
301 removeMutationResult,
302 updateSubscriptionOptions,
303 } = api.internalActions
304 return {
305 buildInitiateQuery,
306 buildInitiateInfiniteQuery,
307 buildInitiateMutation,
308 getRunningQueryThunk,
309 getRunningMutationThunk,
310 getRunningQueriesThunk,
311 getRunningMutationsThunk,
312 }
313
314 function getRunningQueryThunk(endpointName: string, queryArgs: any) {
315 return (dispatch: Dispatch) => {
316 const endpointDefinition = getEndpointDefinition(context, endpointName)
317 const queryCacheKey = serializeQueryArgs({
318 queryArgs,
319 endpointDefinition,
320 endpointName,
321 })
322 return getRunningQueries(dispatch)?.get(queryCacheKey) as
323 | QueryActionCreatorResult<never>
324 | InfiniteQueryActionCreatorResult<never>
325 | undefined
326 }
327 }
328
329 function getRunningMutationThunk(
330 /**
331 * this is only here to allow TS to infer the result type by input value
332 * we could use it to validate the result, but it's probably not necessary
333 */
334 _endpointName: string,
335 fixedCacheKeyOrRequestId: string,
336 ) {
337 return (dispatch: Dispatch) => {
338 return getRunningMutations(dispatch)?.get(fixedCacheKeyOrRequestId) as
339 | MutationActionCreatorResult<never>
340 | undefined
341 }
342 }
343
344 function getRunningQueriesThunk() {
345 return (dispatch: Dispatch) =>
346 filterNullishValues(getRunningQueries(dispatch))
347 }
348
349 function getRunningMutationsThunk() {
350 return (dispatch: Dispatch) =>
351 filterNullishValues(getRunningMutations(dispatch))
352 }
353
354 function middlewareWarning(dispatch: Dispatch) {
355 if (process.env.NODE_ENV !== 'production') {
356 if ((middlewareWarning as any).triggered) return
357 const returnedValue = dispatch(
358 api.internalActions.internal_getRTKQSubscriptions(),
359 )
360
361 ;(middlewareWarning as any).triggered = true
362
363 // The RTKQ middleware should return the internal state object,
364 // but it should _not_ be the action object.
365 if (
366 typeof returnedValue !== 'object' ||
367 typeof returnedValue?.type === 'string'
368 ) {
369 // Otherwise, must not have been added
370 throw new Error(
371 `Warning: Middleware for RTK-Query API at reducerPath "${api.reducerPath}" has not been added to the store.
372You must add the middleware for RTK-Query to function correctly!`,
373 )
374 }
375 }
376 }
377
378 function buildInitiateAnyQuery<T extends 'query' | 'infiniteQuery'>(
379 endpointName: string,
380 endpointDefinition:
381 | QueryDefinition<any, any, any, any>
382 | InfiniteQueryDefinition<any, any, any, any, any>,
383 ) {
384 const queryAction: AnyQueryActionCreator<any> =
385 (
386 arg,
387 {
388 subscribe = true,
389 forceRefetch,
390 subscriptionOptions,
391 [forceQueryFnSymbol]: forceQueryFn,
392 ...rest
393 } = {},
394 ) =>
395 (dispatch, getState) => {
396 const queryCacheKey = serializeQueryArgs({
397 queryArgs: arg,
398 endpointDefinition,
399 endpointName,
400 })
401
402 let thunk: AsyncThunkAction<unknown, QueryThunkArg, ThunkApiMetaConfig>
403
404 const commonThunkArgs = {
405 ...rest,
406 type: ENDPOINT_QUERY as 'query',
407 subscribe,
408 forceRefetch: forceRefetch,
409 subscriptionOptions,
410 endpointName,
411 originalArgs: arg,
412 queryCacheKey,
413 [forceQueryFnSymbol]: forceQueryFn,
414 }
415
416 if (isQueryDefinition(endpointDefinition)) {
417 thunk = queryThunk(commonThunkArgs)
418 } else {
419 const { direction, initialPageParam, refetchCachedPages } =
420 rest as Pick<
421 InfiniteQueryThunkArg<any>,
422 'direction' | 'initialPageParam' | 'refetchCachedPages'
423 >
424 thunk = infiniteQueryThunk({
425 ...(commonThunkArgs as InfiniteQueryThunkArg<any>),
426 // Supply these even if undefined. This helps with a field existence
427 // check over in `buildSlice.ts`
428 direction,
429 initialPageParam,
430 refetchCachedPages,
431 })
432 }
433
434 const selector = (
435 api.endpoints[endpointName] as ApiEndpointQuery<any, any>
436 ).select(arg)
437
438 const thunkResult = dispatch(thunk)
439 const stateAfter = selector(getState())
440
441 middlewareWarning(dispatch)
442
443 const { requestId, abort } = thunkResult
444
445 const skippedSynchronously = stateAfter.requestId !== requestId
446
447 const runningQuery = getRunningQueries(dispatch)?.get(queryCacheKey)
448 const selectFromState = () => selector(getState())
449
450 const statePromise: AnyActionCreatorResult = Object.assign(
451 (forceQueryFn
452 ? // a query has been forced (upsertQueryData)
453 // -> we want to resolve it once data has been written with the data that will be written
454 thunkResult.then(selectFromState)
455 : skippedSynchronously && !runningQuery
456 ? // a query has been skipped due to a condition and we do not have any currently running query
457 // -> we want to resolve it immediately with the current data
458 Promise.resolve(stateAfter)
459 : // query just started or one is already in flight
460 // -> wait for the running query, then resolve with data from after that
461 Promise.all([runningQuery, thunkResult]).then(
462 selectFromState,
463 )) as SafePromise<any>,
464 {
465 arg,
466 requestId,
467 subscriptionOptions,
468 queryCacheKey,
469 abort,
470 async unwrap() {
471 const result = await statePromise
472
473 if (result.isError) {
474 throw result.error
475 }
476
477 return result.data
478 },
479 refetch: (options?: RefetchOptions) =>
480 dispatch(
481 queryAction(arg, {
482 subscribe: false,
483 forceRefetch: true,
484 ...options,
485 }),
486 ),
487 unsubscribe() {
488 if (subscribe)
489 dispatch(
490 unsubscribeQueryResult({
491 queryCacheKey,
492 requestId,
493 }),
494 )
495 },
496 updateSubscriptionOptions(options: SubscriptionOptions) {
497 statePromise.subscriptionOptions = options
498 dispatch(
499 updateSubscriptionOptions({
500 endpointName,
501 requestId,
502 queryCacheKey,
503 options,
504 }),
505 )
506 },
507 },
508 )
509
510 if (!runningQuery && !skippedSynchronously && !forceQueryFn) {
511 const runningQueries = getRunningQueries(dispatch)!
512 runningQueries.set(queryCacheKey, statePromise)
513
514 statePromise.then(() => {
515 runningQueries.delete(queryCacheKey)
516 })
517 }
518
519 return statePromise
520 }
521 return queryAction
522 }
523
524 function buildInitiateQuery(
525 endpointName: string,
526 endpointDefinition: QueryDefinition<any, any, any, any>,
527 ) {
528 const queryAction: StartQueryActionCreator<any> = buildInitiateAnyQuery(
529 endpointName,
530 endpointDefinition,
531 )
532
533 return queryAction
534 }
535
536 function buildInitiateInfiniteQuery(
537 endpointName: string,
538 endpointDefinition: InfiniteQueryDefinition<any, any, any, any, any>,
539 ) {
540 const infiniteQueryAction: StartInfiniteQueryActionCreator<any> =
541 buildInitiateAnyQuery(endpointName, endpointDefinition)
542
543 return infiniteQueryAction
544 }
545
546 function buildInitiateMutation(
547 endpointName: string,
548 ): StartMutationActionCreator<any> {
549 return (arg, { track = true, fixedCacheKey } = {}) =>
550 (dispatch, getState) => {
551 const thunk = mutationThunk({
552 type: 'mutation',
553 endpointName,
554 originalArgs: arg,
555 track,
556 fixedCacheKey,
557 })
558 const thunkResult = dispatch(thunk)
559 middlewareWarning(dispatch)
560 const { requestId, abort, unwrap } = thunkResult
561 const returnValuePromise = asSafePromise(
562 thunkResult.unwrap().then((data) => ({ data })),
563 (error) => ({ error }),
564 )
565
566 const reset = () => {
567 dispatch(removeMutationResult({ requestId, fixedCacheKey }))
568 }
569
570 const ret = Object.assign(returnValuePromise, {
571 arg: thunkResult.arg,
572 requestId,
573 abort,
574 unwrap,
575 reset,
576 })
577
578 const runningMutations = getRunningMutations(dispatch)!
579
580 runningMutations.set(requestId, ret)
581 ret.then(() => {
582 runningMutations.delete(requestId)
583 })
584 if (fixedCacheKey) {
585 runningMutations.set(fixedCacheKey, ret)
586 ret.then(() => {
587 if (runningMutations.get(fixedCacheKey) === ret) {
588 runningMutations.delete(fixedCacheKey)
589 }
590 })
591 }
592
593 return ret
594 }
595 }
596}
Note: See TracBrowser for help on using the repository browser.