@depup/reduxjs__toolkit 2.12.0-depup.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +33 -0
  3. package/changes.json +18 -0
  4. package/dist/cjs/index.js +6 -0
  5. package/dist/cjs/redux-toolkit.development.cjs +2406 -0
  6. package/dist/cjs/redux-toolkit.development.cjs.map +1 -0
  7. package/dist/cjs/redux-toolkit.production.min.cjs +3 -0
  8. package/dist/cjs/redux-toolkit.production.min.cjs.map +1 -0
  9. package/dist/index.d.mts +2665 -0
  10. package/dist/index.d.ts +2665 -0
  11. package/dist/query/cjs/index.js +6 -0
  12. package/dist/query/cjs/rtk-query.development.cjs +3092 -0
  13. package/dist/query/cjs/rtk-query.development.cjs.map +1 -0
  14. package/dist/query/cjs/rtk-query.production.min.cjs +2 -0
  15. package/dist/query/cjs/rtk-query.production.min.cjs.map +1 -0
  16. package/dist/query/index.d.mts +3066 -0
  17. package/dist/query/index.d.ts +3066 -0
  18. package/dist/query/react/cjs/index.js +6 -0
  19. package/dist/query/react/cjs/rtk-query-react.development.cjs +748 -0
  20. package/dist/query/react/cjs/rtk-query-react.development.cjs.map +1 -0
  21. package/dist/query/react/cjs/rtk-query-react.production.min.cjs +2 -0
  22. package/dist/query/react/cjs/rtk-query-react.production.min.cjs.map +1 -0
  23. package/dist/query/react/index.d.mts +1009 -0
  24. package/dist/query/react/index.d.ts +1009 -0
  25. package/dist/query/react/rtk-query-react.browser.mjs +2 -0
  26. package/dist/query/react/rtk-query-react.browser.mjs.map +1 -0
  27. package/dist/query/react/rtk-query-react.legacy-esm.js +740 -0
  28. package/dist/query/react/rtk-query-react.legacy-esm.js.map +1 -0
  29. package/dist/query/react/rtk-query-react.modern.mjs +705 -0
  30. package/dist/query/react/rtk-query-react.modern.mjs.map +1 -0
  31. package/dist/query/rtk-query.browser.mjs +2 -0
  32. package/dist/query/rtk-query.browser.mjs.map +1 -0
  33. package/dist/query/rtk-query.legacy-esm.js +3117 -0
  34. package/dist/query/rtk-query.legacy-esm.js.map +1 -0
  35. package/dist/query/rtk-query.modern.mjs +3052 -0
  36. package/dist/query/rtk-query.modern.mjs.map +1 -0
  37. package/dist/react/cjs/index.js +6 -0
  38. package/dist/react/cjs/redux-toolkit-react.development.cjs +55 -0
  39. package/dist/react/cjs/redux-toolkit-react.development.cjs.map +1 -0
  40. package/dist/react/cjs/redux-toolkit-react.production.min.cjs +2 -0
  41. package/dist/react/cjs/redux-toolkit-react.production.min.cjs.map +1 -0
  42. package/dist/react/index.d.mts +22 -0
  43. package/dist/react/index.d.ts +22 -0
  44. package/dist/react/redux-toolkit-react.browser.mjs +2 -0
  45. package/dist/react/redux-toolkit-react.browser.mjs.map +1 -0
  46. package/dist/react/redux-toolkit-react.legacy-esm.js +47 -0
  47. package/dist/react/redux-toolkit-react.legacy-esm.js.map +1 -0
  48. package/dist/react/redux-toolkit-react.modern.mjs +28 -0
  49. package/dist/react/redux-toolkit-react.modern.mjs.map +1 -0
  50. package/dist/redux-toolkit.browser.mjs +3 -0
  51. package/dist/redux-toolkit.browser.mjs.map +1 -0
  52. package/dist/redux-toolkit.legacy-esm.js +2351 -0
  53. package/dist/redux-toolkit.legacy-esm.js.map +1 -0
  54. package/dist/redux-toolkit.modern.mjs +2330 -0
  55. package/dist/redux-toolkit.modern.mjs.map +1 -0
  56. package/dist/uncheckedindexed.ts +16 -0
  57. package/package.json +310 -0
  58. package/query/package.json +13 -0
  59. package/query/react/package.json +13 -0
  60. package/react/package.json +13 -0
  61. package/skills/build-modern-redux-apps/modern-redux/SKILL.md +304 -0
  62. package/skills/build-modern-redux-apps/modern-redux/references/store-lifetime.md +53 -0
  63. package/skills/build-modern-redux-apps/redux-dataflow/SKILL.md +264 -0
  64. package/skills/evolve-and-diagnose-redux-apps/debug-redux-toolkit-apps/SKILL.md +269 -0
  65. package/skills/evolve-and-diagnose-redux-apps/migrate-to-modern-redux/SKILL.md +226 -0
  66. package/skills/manage-server-data/adopt-rtk-query/SKILL.md +385 -0
  67. package/skills/manage-server-data/adopt-rtk-query/references/endpoint-lifecycle.md +36 -0
  68. package/skills/model-redux-state/build-slices-and-selectors/SKILL.md +364 -0
  69. package/skills/model-redux-state/build-slices-and-selectors/references/slice-patterns.md +59 -0
  70. package/skills/model-redux-state/design-state-ownership/SKILL.md +322 -0
  71. package/skills/model-redux-state/design-state-ownership/references/state-ownership.md +28 -0
  72. package/skills/orchestrate-side-effects/handle-side-effects/SKILL.md +271 -0
  73. package/skills/orchestrate-side-effects/handle-side-effects/references/listener-workflows.md +34 -0
  74. package/src/actionCreatorInvariantMiddleware.ts +34 -0
  75. package/src/autoBatchEnhancer.ts +146 -0
  76. package/src/combineSlices.ts +487 -0
  77. package/src/configureStore.ts +248 -0
  78. package/src/createAction.ts +324 -0
  79. package/src/createAsyncThunk.ts +791 -0
  80. package/src/createDraftSafeSelector.ts +30 -0
  81. package/src/createReducer.ts +217 -0
  82. package/src/createSlice.ts +1079 -0
  83. package/src/devtoolsExtension.ts +241 -0
  84. package/src/dynamicMiddleware/index.ts +93 -0
  85. package/src/dynamicMiddleware/react/index.ts +101 -0
  86. package/src/dynamicMiddleware/types.ts +82 -0
  87. package/src/entities/create_adapter.ts +47 -0
  88. package/src/entities/entity_state.ts +38 -0
  89. package/src/entities/index.ts +8 -0
  90. package/src/entities/models.ts +198 -0
  91. package/src/entities/sorted_state_adapter.ts +266 -0
  92. package/src/entities/state_adapter.ts +58 -0
  93. package/src/entities/state_selectors.ts +73 -0
  94. package/src/entities/unsorted_state_adapter.ts +204 -0
  95. package/src/entities/utils.ts +68 -0
  96. package/src/formatProdErrorMessage.ts +13 -0
  97. package/src/getDefaultEnhancers.ts +31 -0
  98. package/src/getDefaultMiddleware.ts +113 -0
  99. package/src/immerImports.ts +7 -0
  100. package/src/immutableStateInvariantMiddleware.ts +274 -0
  101. package/src/index.ts +213 -0
  102. package/src/listenerMiddleware/exceptions.ts +20 -0
  103. package/src/listenerMiddleware/index.ts +562 -0
  104. package/src/listenerMiddleware/task.ts +100 -0
  105. package/src/listenerMiddleware/types.ts +886 -0
  106. package/src/listenerMiddleware/utils.ts +30 -0
  107. package/src/mapBuilders.ts +298 -0
  108. package/src/matchers.ts +365 -0
  109. package/src/nanoid.ts +20 -0
  110. package/src/query/HandledError.ts +6 -0
  111. package/src/query/apiTypes.ts +120 -0
  112. package/src/query/baseQueryTypes.ts +101 -0
  113. package/src/query/core/apiState.ts +377 -0
  114. package/src/query/core/buildInitiate.ts +596 -0
  115. package/src/query/core/buildMiddleware/batchActions.ts +207 -0
  116. package/src/query/core/buildMiddleware/cacheCollection.ts +204 -0
  117. package/src/query/core/buildMiddleware/cacheLifecycle.ts +379 -0
  118. package/src/query/core/buildMiddleware/devMiddleware.ts +34 -0
  119. package/src/query/core/buildMiddleware/index.ts +162 -0
  120. package/src/query/core/buildMiddleware/invalidationByTags.ts +139 -0
  121. package/src/query/core/buildMiddleware/polling.ts +187 -0
  122. package/src/query/core/buildMiddleware/queryLifecycle.ts +508 -0
  123. package/src/query/core/buildMiddleware/types.ts +173 -0
  124. package/src/query/core/buildMiddleware/windowEventHandling.ts +64 -0
  125. package/src/query/core/buildSelectors.ts +413 -0
  126. package/src/query/core/buildSlice.ts +735 -0
  127. package/src/query/core/buildThunks.ts +1124 -0
  128. package/src/query/core/index.ts +58 -0
  129. package/src/query/core/module.ts +727 -0
  130. package/src/query/core/rtkImports.ts +24 -0
  131. package/src/query/core/setupListeners.ts +118 -0
  132. package/src/query/createApi.ts +506 -0
  133. package/src/query/defaultSerializeQueryArgs.ts +52 -0
  134. package/src/query/endpointDefinitions.ts +1717 -0
  135. package/src/query/fakeBaseQuery.ts +21 -0
  136. package/src/query/fetchBaseQuery.ts +385 -0
  137. package/src/query/index.ts +105 -0
  138. package/src/query/react/ApiProvider.tsx +69 -0
  139. package/src/query/react/buildHooks.ts +2297 -0
  140. package/src/query/react/constants.ts +2 -0
  141. package/src/query/react/index.ts +45 -0
  142. package/src/query/react/module.ts +275 -0
  143. package/src/query/react/namedHooks.ts +59 -0
  144. package/src/query/react/reactImports.ts +10 -0
  145. package/src/query/react/reactReduxImports.ts +1 -0
  146. package/src/query/react/rtkqImports.ts +8 -0
  147. package/src/query/react/useSerializedStableValue.ts +17 -0
  148. package/src/query/react/useShallowStableValue.ts +13 -0
  149. package/src/query/retry.ts +233 -0
  150. package/src/query/standardSchema.ts +35 -0
  151. package/src/query/tsHelpers.ts +48 -0
  152. package/src/query/utils/capitalize.ts +3 -0
  153. package/src/query/utils/copyWithStructuralSharing.ts +27 -0
  154. package/src/query/utils/countObjectKeys.ts +14 -0
  155. package/src/query/utils/filterMap.ts +27 -0
  156. package/src/query/utils/getCurrent.ts +6 -0
  157. package/src/query/utils/getOrInsert.ts +41 -0
  158. package/src/query/utils/immerImports.ts +9 -0
  159. package/src/query/utils/index.ts +11 -0
  160. package/src/query/utils/isAbsoluteUrl.ts +9 -0
  161. package/src/query/utils/isDocumentVisible.ts +12 -0
  162. package/src/query/utils/isNotNullish.ts +7 -0
  163. package/src/query/utils/isOnline.ts +12 -0
  164. package/src/query/utils/isValidUrl.ts +9 -0
  165. package/src/query/utils/joinUrls.ts +26 -0
  166. package/src/query/utils/signals.ts +33 -0
  167. package/src/react/index.ts +7 -0
  168. package/src/reduxImports.ts +8 -0
  169. package/src/reselectImports.ts +1 -0
  170. package/src/serializableStateInvariantMiddleware.ts +285 -0
  171. package/src/tsHelpers.ts +214 -0
  172. package/src/uncheckedindexed.ts +16 -0
  173. package/src/utils.ts +125 -0
@@ -0,0 +1,187 @@
1
+ import type {
2
+ QueryCacheKey,
3
+ QuerySubstateIdentifier,
4
+ Subscribers,
5
+ SubscribersInternal,
6
+ } from '../apiState'
7
+ import { QueryStatus, STATUS_UNINITIALIZED } from '../apiState'
8
+ import type {
9
+ QueryStateMeta,
10
+ SubMiddlewareApi,
11
+ TimeoutId,
12
+ InternalHandlerBuilder,
13
+ ApiMiddlewareInternalHandler,
14
+ InternalMiddlewareState,
15
+ } from './types'
16
+
17
+ export const buildPollingHandler: InternalHandlerBuilder = ({
18
+ reducerPath,
19
+ queryThunk,
20
+ api,
21
+ refetchQuery,
22
+ internalState,
23
+ }) => {
24
+ const { currentPolls, currentSubscriptions } = internalState
25
+
26
+ // Batching state for polling updates
27
+ const pendingPollingUpdates = new Set<string>()
28
+ let pollingUpdateTimer: ReturnType<typeof setTimeout> | null = null
29
+
30
+ const handler: ApiMiddlewareInternalHandler = (action, mwApi) => {
31
+ if (
32
+ api.internalActions.updateSubscriptionOptions.match(action) ||
33
+ api.internalActions.unsubscribeQueryResult.match(action)
34
+ ) {
35
+ schedulePollingUpdate(action.payload.queryCacheKey, mwApi)
36
+ }
37
+
38
+ if (
39
+ queryThunk.pending.match(action) ||
40
+ (queryThunk.rejected.match(action) && action.meta.condition)
41
+ ) {
42
+ schedulePollingUpdate(action.meta.arg.queryCacheKey, mwApi)
43
+ }
44
+
45
+ if (
46
+ queryThunk.fulfilled.match(action) ||
47
+ (queryThunk.rejected.match(action) && !action.meta.condition)
48
+ ) {
49
+ startNextPoll(action.meta.arg, mwApi)
50
+ }
51
+
52
+ if (api.util.resetApiState.match(action)) {
53
+ clearPolls()
54
+ // Clear any pending updates
55
+ if (pollingUpdateTimer) {
56
+ clearTimeout(pollingUpdateTimer)
57
+ pollingUpdateTimer = null
58
+ }
59
+ pendingPollingUpdates.clear()
60
+ }
61
+ }
62
+
63
+ function schedulePollingUpdate(queryCacheKey: string, api: SubMiddlewareApi) {
64
+ pendingPollingUpdates.add(queryCacheKey)
65
+
66
+ if (!pollingUpdateTimer) {
67
+ pollingUpdateTimer = setTimeout(() => {
68
+ // Process all pending updates in a single batch
69
+ for (const key of pendingPollingUpdates) {
70
+ updatePollingInterval({ queryCacheKey: key as any }, api)
71
+ }
72
+ pendingPollingUpdates.clear()
73
+ pollingUpdateTimer = null
74
+ }, 0)
75
+ }
76
+ }
77
+
78
+ function startNextPoll(
79
+ { queryCacheKey }: QuerySubstateIdentifier,
80
+ api: SubMiddlewareApi,
81
+ ) {
82
+ const state = api.getState()[reducerPath]
83
+ const querySubState = state.queries[queryCacheKey]
84
+ const subscriptions = currentSubscriptions.get(queryCacheKey)
85
+
86
+ if (!querySubState || querySubState.status === STATUS_UNINITIALIZED) return
87
+
88
+ const { lowestPollingInterval, skipPollingIfUnfocused } =
89
+ findLowestPollingInterval(subscriptions)
90
+ if (!Number.isFinite(lowestPollingInterval)) return
91
+
92
+ const currentPoll = currentPolls.get(queryCacheKey)
93
+
94
+ if (currentPoll?.timeout) {
95
+ clearTimeout(currentPoll.timeout)
96
+ currentPoll.timeout = undefined
97
+ }
98
+
99
+ const nextPollTimestamp = Date.now() + lowestPollingInterval
100
+
101
+ currentPolls.set(queryCacheKey, {
102
+ nextPollTimestamp,
103
+ pollingInterval: lowestPollingInterval,
104
+ timeout: setTimeout(() => {
105
+ if (state.config.focused || !skipPollingIfUnfocused) {
106
+ api.dispatch(refetchQuery(querySubState))
107
+ }
108
+ startNextPoll({ queryCacheKey }, api)
109
+ }, lowestPollingInterval),
110
+ })
111
+ }
112
+
113
+ function updatePollingInterval(
114
+ { queryCacheKey }: QuerySubstateIdentifier,
115
+ api: SubMiddlewareApi,
116
+ ) {
117
+ const state = api.getState()[reducerPath]
118
+ const querySubState = state.queries[queryCacheKey]
119
+ const subscriptions = currentSubscriptions.get(queryCacheKey)
120
+
121
+ if (!querySubState || querySubState.status === STATUS_UNINITIALIZED) {
122
+ return
123
+ }
124
+
125
+ const { lowestPollingInterval } = findLowestPollingInterval(subscriptions)
126
+
127
+ // HACK add extra data to track how many times this has been called in tests
128
+ // yes we're mutating a nonexistent field on a Map here
129
+ if (process.env.NODE_ENV === 'test') {
130
+ const updateCounters = ((currentPolls as any).pollUpdateCounters ??= {})
131
+ updateCounters[queryCacheKey] ??= 0
132
+ updateCounters[queryCacheKey]++
133
+ }
134
+
135
+ if (!Number.isFinite(lowestPollingInterval)) {
136
+ cleanupPollForKey(queryCacheKey)
137
+ return
138
+ }
139
+
140
+ const currentPoll = currentPolls.get(queryCacheKey)
141
+
142
+ const nextPollTimestamp = Date.now() + lowestPollingInterval
143
+
144
+ if (!currentPoll || nextPollTimestamp < currentPoll.nextPollTimestamp) {
145
+ startNextPoll({ queryCacheKey }, api)
146
+ }
147
+ }
148
+
149
+ function cleanupPollForKey(key: string) {
150
+ const existingPoll = currentPolls.get(key)
151
+ if (existingPoll?.timeout) {
152
+ clearTimeout(existingPoll.timeout)
153
+ }
154
+ currentPolls.delete(key)
155
+ }
156
+
157
+ function clearPolls() {
158
+ for (const key of currentPolls.keys()) {
159
+ cleanupPollForKey(key)
160
+ }
161
+ }
162
+
163
+ function findLowestPollingInterval(
164
+ subscribers: SubscribersInternal = new Map(),
165
+ ) {
166
+ let skipPollingIfUnfocused: boolean | undefined = false
167
+ let lowestPollingInterval = Number.POSITIVE_INFINITY
168
+
169
+ for (const entry of subscribers.values()) {
170
+ if (!!entry.pollingInterval) {
171
+ lowestPollingInterval = Math.min(
172
+ entry.pollingInterval!,
173
+ lowestPollingInterval,
174
+ )
175
+ skipPollingIfUnfocused =
176
+ entry.skipPollingIfUnfocused || skipPollingIfUnfocused
177
+ }
178
+ }
179
+
180
+ return {
181
+ lowestPollingInterval,
182
+ skipPollingIfUnfocused,
183
+ }
184
+ }
185
+
186
+ return handler
187
+ }
@@ -0,0 +1,508 @@
1
+ import { getEndpointDefinition } from '@internal/query/apiTypes'
2
+ import type {
3
+ BaseQueryError,
4
+ BaseQueryFn,
5
+ BaseQueryMeta,
6
+ } from '../../baseQueryTypes'
7
+ import { isAnyQueryDefinition } from '../../endpointDefinitions'
8
+ import type { Recipe } from '../buildThunks'
9
+ import { isFulfilled, isPending, isRejected } from '../rtkImports'
10
+ import type {
11
+ MutationBaseLifecycleApi,
12
+ QueryBaseLifecycleApi,
13
+ } from './cacheLifecycle'
14
+ import type {
15
+ ApiMiddlewareInternalHandler,
16
+ InternalHandlerBuilder,
17
+ PromiseConstructorWithKnownReason,
18
+ PromiseWithKnownReason,
19
+ } from './types'
20
+
21
+ export type ReferenceQueryLifecycle = never
22
+
23
+ type QueryLifecyclePromises<ResultType, BaseQuery extends BaseQueryFn> = {
24
+ /**
25
+ * Promise that will resolve with the (transformed) query result.
26
+ *
27
+ * If the query fails, this promise will reject with the error.
28
+ *
29
+ * This allows you to `await` for the query to finish.
30
+ *
31
+ * If you don't interact with this promise, it will not throw.
32
+ */
33
+ queryFulfilled: PromiseWithKnownReason<
34
+ {
35
+ /**
36
+ * The (transformed) query result.
37
+ */
38
+ data: ResultType
39
+ /**
40
+ * The `meta` returned by the `baseQuery`
41
+ */
42
+ meta: BaseQueryMeta<BaseQuery>
43
+ },
44
+ QueryFulfilledRejectionReason<BaseQuery>
45
+ >
46
+ }
47
+
48
+ type QueryFulfilledRejectionReason<BaseQuery extends BaseQueryFn> =
49
+ | {
50
+ error: BaseQueryError<BaseQuery>
51
+ /**
52
+ * If this is `false`, that means this error was returned from the `baseQuery` or `queryFn` in a controlled manner.
53
+ */
54
+ isUnhandledError: false
55
+ /**
56
+ * The `meta` returned by the `baseQuery`
57
+ */
58
+ meta: BaseQueryMeta<BaseQuery>
59
+ }
60
+ | {
61
+ error: unknown
62
+ meta?: undefined
63
+ /**
64
+ * If this is `true`, that means that this error is the result of `baseQueryFn`, `queryFn`, `transformResponse` or `transformErrorResponse` throwing an error instead of handling it properly.
65
+ * There can not be made any assumption about the shape of `error`.
66
+ */
67
+ isUnhandledError: true
68
+ }
69
+
70
+ export type QueryLifecycleQueryExtraOptions<
71
+ ResultType,
72
+ QueryArg,
73
+ BaseQuery extends BaseQueryFn,
74
+ ReducerPath extends string = string,
75
+ > = {
76
+ /**
77
+ * A function that is called when the individual query is started. The function is called with a lifecycle api object containing properties such as `queryFulfilled`, allowing code to be run when a query is started, when it succeeds, and when it fails (i.e. throughout the lifecycle of an individual query/mutation call).
78
+ *
79
+ * Can be used to perform side-effects throughout the lifecycle of the query.
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query';
84
+ * import { messageCreated } from './notificationsSlice';
85
+ *
86
+ * export interface Post {
87
+ * id: number;
88
+ * name: string;
89
+ * }
90
+ *
91
+ * const api = createApi({
92
+ * baseQuery: fetchBaseQuery({
93
+ * baseUrl: '/',
94
+ * }),
95
+ * endpoints: (build) => ({
96
+ * getPost: build.query<Post, number>({
97
+ * query: (id) => `post/${id}`,
98
+ * async onQueryStarted(id, { dispatch, queryFulfilled }) {
99
+ * // `onStart` side-effect
100
+ * dispatch(messageCreated('Fetching posts...'));
101
+ * try {
102
+ * const { data } = await queryFulfilled;
103
+ * // `onSuccess` side-effect
104
+ * dispatch(messageCreated('Posts received!'));
105
+ * } catch (err) {
106
+ * // `onError` side-effect
107
+ * dispatch(messageCreated('Error fetching posts!'));
108
+ * }
109
+ * },
110
+ * }),
111
+ * }),
112
+ * });
113
+ * ```
114
+ */
115
+ onQueryStarted?(
116
+ queryArgument: QueryArg,
117
+ queryLifeCycleApi: QueryLifecycleApi<
118
+ QueryArg,
119
+ BaseQuery,
120
+ ResultType,
121
+ ReducerPath
122
+ >,
123
+ ): Promise<void> | void
124
+ }
125
+
126
+ export type QueryLifecycleInfiniteQueryExtraOptions<
127
+ ResultType,
128
+ QueryArg,
129
+ BaseQuery extends BaseQueryFn,
130
+ ReducerPath extends string = string,
131
+ > = QueryLifecycleQueryExtraOptions<
132
+ ResultType,
133
+ QueryArg,
134
+ BaseQuery,
135
+ ReducerPath
136
+ >
137
+
138
+ export type QueryLifecycleMutationExtraOptions<
139
+ ResultType,
140
+ QueryArg,
141
+ BaseQuery extends BaseQueryFn,
142
+ ReducerPath extends string = string,
143
+ > = {
144
+ /**
145
+ * A function that is called when the individual mutation is started. The function is called with a lifecycle api object containing properties such as `queryFulfilled`, allowing code to be run when a query is started, when it succeeds, and when it fails (i.e. throughout the lifecycle of an individual query/mutation call).
146
+ *
147
+ * Can be used for `optimistic updates`.
148
+ *
149
+ * @example
150
+ *
151
+ * ```ts
152
+ * import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
153
+ * export interface Post {
154
+ * id: number
155
+ * name: string
156
+ * }
157
+ *
158
+ * const api = createApi({
159
+ * baseQuery: fetchBaseQuery({
160
+ * baseUrl: '/',
161
+ * }),
162
+ * tagTypes: ['Post'],
163
+ * endpoints: (build) => ({
164
+ * getPost: build.query<Post, number>({
165
+ * query: (id) => `post/${id}`,
166
+ * providesTags: ['Post'],
167
+ * }),
168
+ * updatePost: build.mutation<void, Pick<Post, 'id'> & Partial<Post>>({
169
+ * query: ({ id, ...patch }) => ({
170
+ * url: `post/${id}`,
171
+ * method: 'PATCH',
172
+ * body: patch,
173
+ * }),
174
+ * invalidatesTags: ['Post'],
175
+ * async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
176
+ * const patchResult = dispatch(
177
+ * api.util.updateQueryData('getPost', id, (draft) => {
178
+ * Object.assign(draft, patch)
179
+ * })
180
+ * )
181
+ * try {
182
+ * await queryFulfilled
183
+ * } catch {
184
+ * patchResult.undo()
185
+ * }
186
+ * },
187
+ * }),
188
+ * }),
189
+ * })
190
+ * ```
191
+ */
192
+ onQueryStarted?(
193
+ queryArgument: QueryArg,
194
+ mutationLifeCycleApi: MutationLifecycleApi<
195
+ QueryArg,
196
+ BaseQuery,
197
+ ResultType,
198
+ ReducerPath
199
+ >,
200
+ ): Promise<void> | void
201
+ }
202
+
203
+ export interface QueryLifecycleApi<
204
+ QueryArg,
205
+ BaseQuery extends BaseQueryFn,
206
+ ResultType,
207
+ ReducerPath extends string = string,
208
+ >
209
+ extends
210
+ QueryBaseLifecycleApi<QueryArg, BaseQuery, ResultType, ReducerPath>,
211
+ QueryLifecyclePromises<ResultType, BaseQuery> {}
212
+
213
+ export type MutationLifecycleApi<
214
+ QueryArg,
215
+ BaseQuery extends BaseQueryFn,
216
+ ResultType,
217
+ ReducerPath extends string = string,
218
+ > = MutationBaseLifecycleApi<QueryArg, BaseQuery, ResultType, ReducerPath> &
219
+ QueryLifecyclePromises<ResultType, BaseQuery>
220
+
221
+ /**
222
+ * Provides a way to define a strongly-typed version of
223
+ * {@linkcode QueryLifecycleQueryExtraOptions.onQueryStarted | onQueryStarted}
224
+ * for a specific query.
225
+ *
226
+ * @example
227
+ * <caption>#### __Create and reuse a strongly-typed `onQueryStarted` function__</caption>
228
+ *
229
+ * ```ts
230
+ * import type { TypedQueryOnQueryStarted } from '@reduxjs/toolkit/query'
231
+ * import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
232
+ *
233
+ * type Post = {
234
+ * id: number
235
+ * title: string
236
+ * userId: number
237
+ * }
238
+ *
239
+ * type PostsApiResponse = {
240
+ * posts: Post[]
241
+ * total: number
242
+ * skip: number
243
+ * limit: number
244
+ * }
245
+ *
246
+ * type QueryArgument = number | undefined
247
+ *
248
+ * type BaseQueryFunction = ReturnType<typeof fetchBaseQuery>
249
+ *
250
+ * const baseApiSlice = createApi({
251
+ * baseQuery: fetchBaseQuery({ baseUrl: 'https://dummyjson.com' }),
252
+ * reducerPath: 'postsApi',
253
+ * tagTypes: ['Posts'],
254
+ * endpoints: (build) => ({
255
+ * getPosts: build.query<PostsApiResponse, void>({
256
+ * query: () => `/posts`,
257
+ * }),
258
+ *
259
+ * getPostById: build.query<Post, QueryArgument>({
260
+ * query: (postId) => `/posts/${postId}`,
261
+ * }),
262
+ * }),
263
+ * })
264
+ *
265
+ * const updatePostOnFulfilled: TypedQueryOnQueryStarted<
266
+ * PostsApiResponse,
267
+ * QueryArgument,
268
+ * BaseQueryFunction,
269
+ * 'postsApi'
270
+ * > = async (queryArgument, { dispatch, queryFulfilled }) => {
271
+ * const result = await queryFulfilled
272
+ *
273
+ * const { posts } = result.data
274
+ *
275
+ * // Pre-fill the individual post entries with the results
276
+ * // from the list endpoint query
277
+ * dispatch(
278
+ * baseApiSlice.util.upsertQueryEntries(
279
+ * posts.map((post) => ({
280
+ * endpointName: 'getPostById',
281
+ * arg: post.id,
282
+ * value: post,
283
+ * })),
284
+ * ),
285
+ * )
286
+ * }
287
+ *
288
+ * export const extendedApiSlice = baseApiSlice.injectEndpoints({
289
+ * endpoints: (build) => ({
290
+ * getPostsByUserId: build.query<PostsApiResponse, QueryArgument>({
291
+ * query: (userId) => `/posts/user/${userId}`,
292
+ *
293
+ * onQueryStarted: updatePostOnFulfilled,
294
+ * }),
295
+ * }),
296
+ * })
297
+ * ```
298
+ *
299
+ * @template ResultType - The type of the result `data` returned by the query.
300
+ * @template QueryArgumentType - The type of the argument passed into the query.
301
+ * @template BaseQueryFunctionType - The type of the base query function being used.
302
+ * @template ReducerPath - The type representing the `reducerPath` for the API slice.
303
+ *
304
+ * @since 2.4.0
305
+ * @public
306
+ */
307
+ export type TypedQueryOnQueryStarted<
308
+ ResultType,
309
+ QueryArgumentType,
310
+ BaseQueryFunctionType extends BaseQueryFn,
311
+ ReducerPath extends string = string,
312
+ > = QueryLifecycleQueryExtraOptions<
313
+ ResultType,
314
+ QueryArgumentType,
315
+ BaseQueryFunctionType,
316
+ ReducerPath
317
+ >['onQueryStarted']
318
+
319
+ /**
320
+ * Provides a way to define a strongly-typed version of
321
+ * {@linkcode QueryLifecycleMutationExtraOptions.onQueryStarted | onQueryStarted}
322
+ * for a specific mutation.
323
+ *
324
+ * @example
325
+ * <caption>#### __Create and reuse a strongly-typed `onQueryStarted` function__</caption>
326
+ *
327
+ * ```ts
328
+ * import type { TypedMutationOnQueryStarted } from '@reduxjs/toolkit/query'
329
+ * import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
330
+ *
331
+ * type Post = {
332
+ * id: number
333
+ * title: string
334
+ * userId: number
335
+ * }
336
+ *
337
+ * type PostsApiResponse = {
338
+ * posts: Post[]
339
+ * total: number
340
+ * skip: number
341
+ * limit: number
342
+ * }
343
+ *
344
+ * type QueryArgument = Pick<Post, 'id'> & Partial<Post>
345
+ *
346
+ * type BaseQueryFunction = ReturnType<typeof fetchBaseQuery>
347
+ *
348
+ * const baseApiSlice = createApi({
349
+ * baseQuery: fetchBaseQuery({ baseUrl: 'https://dummyjson.com' }),
350
+ * reducerPath: 'postsApi',
351
+ * tagTypes: ['Posts'],
352
+ * endpoints: (build) => ({
353
+ * getPosts: build.query<PostsApiResponse, void>({
354
+ * query: () => `/posts`,
355
+ * }),
356
+ *
357
+ * getPostById: build.query<Post, number>({
358
+ * query: (postId) => `/posts/${postId}`,
359
+ * }),
360
+ * }),
361
+ * })
362
+ *
363
+ * const updatePostOnFulfilled: TypedMutationOnQueryStarted<
364
+ * Post,
365
+ * QueryArgument,
366
+ * BaseQueryFunction,
367
+ * 'postsApi'
368
+ * > = async ({ id, ...patch }, { dispatch, queryFulfilled }) => {
369
+ * const patchCollection = dispatch(
370
+ * baseApiSlice.util.updateQueryData('getPostById', id, (draftPost) => {
371
+ * Object.assign(draftPost, patch)
372
+ * }),
373
+ * )
374
+ *
375
+ * try {
376
+ * await queryFulfilled
377
+ * } catch {
378
+ * patchCollection.undo()
379
+ * }
380
+ * }
381
+ *
382
+ * export const extendedApiSlice = baseApiSlice.injectEndpoints({
383
+ * endpoints: (build) => ({
384
+ * addPost: build.mutation<Post, Omit<QueryArgument, 'id'>>({
385
+ * query: (body) => ({
386
+ * url: `posts/add`,
387
+ * method: 'POST',
388
+ * body,
389
+ * }),
390
+ *
391
+ * onQueryStarted: updatePostOnFulfilled,
392
+ * }),
393
+ *
394
+ * updatePost: build.mutation<Post, QueryArgument>({
395
+ * query: ({ id, ...patch }) => ({
396
+ * url: `post/${id}`,
397
+ * method: 'PATCH',
398
+ * body: patch,
399
+ * }),
400
+ *
401
+ * onQueryStarted: updatePostOnFulfilled,
402
+ * }),
403
+ * }),
404
+ * })
405
+ * ```
406
+ *
407
+ * @template ResultType - The type of the result `data` returned by the query.
408
+ * @template QueryArgumentType - The type of the argument passed into the query.
409
+ * @template BaseQueryFunctionType - The type of the base query function being used.
410
+ * @template ReducerPath - The type representing the `reducerPath` for the API slice.
411
+ *
412
+ * @since 2.4.0
413
+ * @public
414
+ */
415
+ export type TypedMutationOnQueryStarted<
416
+ ResultType,
417
+ QueryArgumentType,
418
+ BaseQueryFunctionType extends BaseQueryFn,
419
+ ReducerPath extends string = string,
420
+ > = QueryLifecycleMutationExtraOptions<
421
+ ResultType,
422
+ QueryArgumentType,
423
+ BaseQueryFunctionType,
424
+ ReducerPath
425
+ >['onQueryStarted']
426
+
427
+ export const buildQueryLifecycleHandler: InternalHandlerBuilder = ({
428
+ api,
429
+ context,
430
+ queryThunk,
431
+ mutationThunk,
432
+ }) => {
433
+ const isPendingThunk = isPending(queryThunk, mutationThunk)
434
+ const isRejectedThunk = isRejected(queryThunk, mutationThunk)
435
+ const isFulfilledThunk = isFulfilled(queryThunk, mutationThunk)
436
+
437
+ type CacheLifecycle = {
438
+ resolve(value: { data: unknown; meta: unknown }): unknown
439
+ reject(value: QueryFulfilledRejectionReason<any>): unknown
440
+ }
441
+ const lifecycleMap: Record<string, CacheLifecycle> = {}
442
+
443
+ const handler: ApiMiddlewareInternalHandler = (action, mwApi) => {
444
+ if (isPendingThunk(action)) {
445
+ const {
446
+ requestId,
447
+ arg: { endpointName, originalArgs },
448
+ } = action.meta
449
+ const endpointDefinition = getEndpointDefinition(context, endpointName)
450
+ const onQueryStarted = endpointDefinition?.onQueryStarted
451
+ if (onQueryStarted) {
452
+ const lifecycle = {} as CacheLifecycle
453
+ const queryFulfilled =
454
+ new (Promise as PromiseConstructorWithKnownReason)<
455
+ { data: unknown; meta: unknown },
456
+ QueryFulfilledRejectionReason<any>
457
+ >((resolve, reject) => {
458
+ lifecycle.resolve = resolve
459
+ lifecycle.reject = reject
460
+ })
461
+ // prevent uncaught promise rejections from happening.
462
+ // if the original promise is used in any way, that will create a new promise that will throw again
463
+ queryFulfilled.catch(() => {})
464
+ lifecycleMap[requestId] = lifecycle
465
+ const selector = (api.endpoints[endpointName] as any).select(
466
+ isAnyQueryDefinition(endpointDefinition) ? originalArgs : requestId,
467
+ )
468
+
469
+ const extra = mwApi.dispatch((_, __, extra) => extra)
470
+ const lifecycleApi = {
471
+ ...mwApi,
472
+ getCacheEntry: () => selector(mwApi.getState()),
473
+ requestId,
474
+ extra,
475
+ updateCachedData: (isAnyQueryDefinition(endpointDefinition)
476
+ ? (updateRecipe: Recipe<any>) =>
477
+ mwApi.dispatch(
478
+ api.util.updateQueryData(
479
+ endpointName as never,
480
+ originalArgs as never,
481
+ updateRecipe,
482
+ ),
483
+ )
484
+ : undefined) as any,
485
+ queryFulfilled,
486
+ }
487
+ onQueryStarted(originalArgs, lifecycleApi as any)
488
+ }
489
+ } else if (isFulfilledThunk(action)) {
490
+ const { requestId, baseQueryMeta } = action.meta
491
+ lifecycleMap[requestId]?.resolve({
492
+ data: action.payload,
493
+ meta: baseQueryMeta,
494
+ })
495
+ delete lifecycleMap[requestId]
496
+ } else if (isRejectedThunk(action)) {
497
+ const { requestId, rejectedWithValue, baseQueryMeta } = action.meta
498
+ lifecycleMap[requestId]?.reject({
499
+ error: action.payload ?? action.error,
500
+ isUnhandledError: !rejectedWithValue,
501
+ meta: baseQueryMeta as any,
502
+ })
503
+ delete lifecycleMap[requestId]
504
+ }
505
+ }
506
+
507
+ return handler
508
+ }