@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.
- package/LICENSE +21 -0
- package/README.md +33 -0
- package/changes.json +18 -0
- package/dist/cjs/index.js +6 -0
- package/dist/cjs/redux-toolkit.development.cjs +2406 -0
- package/dist/cjs/redux-toolkit.development.cjs.map +1 -0
- package/dist/cjs/redux-toolkit.production.min.cjs +3 -0
- package/dist/cjs/redux-toolkit.production.min.cjs.map +1 -0
- package/dist/index.d.mts +2665 -0
- package/dist/index.d.ts +2665 -0
- package/dist/query/cjs/index.js +6 -0
- package/dist/query/cjs/rtk-query.development.cjs +3092 -0
- package/dist/query/cjs/rtk-query.development.cjs.map +1 -0
- package/dist/query/cjs/rtk-query.production.min.cjs +2 -0
- package/dist/query/cjs/rtk-query.production.min.cjs.map +1 -0
- package/dist/query/index.d.mts +3066 -0
- package/dist/query/index.d.ts +3066 -0
- package/dist/query/react/cjs/index.js +6 -0
- package/dist/query/react/cjs/rtk-query-react.development.cjs +748 -0
- package/dist/query/react/cjs/rtk-query-react.development.cjs.map +1 -0
- package/dist/query/react/cjs/rtk-query-react.production.min.cjs +2 -0
- package/dist/query/react/cjs/rtk-query-react.production.min.cjs.map +1 -0
- package/dist/query/react/index.d.mts +1009 -0
- package/dist/query/react/index.d.ts +1009 -0
- package/dist/query/react/rtk-query-react.browser.mjs +2 -0
- package/dist/query/react/rtk-query-react.browser.mjs.map +1 -0
- package/dist/query/react/rtk-query-react.legacy-esm.js +740 -0
- package/dist/query/react/rtk-query-react.legacy-esm.js.map +1 -0
- package/dist/query/react/rtk-query-react.modern.mjs +705 -0
- package/dist/query/react/rtk-query-react.modern.mjs.map +1 -0
- package/dist/query/rtk-query.browser.mjs +2 -0
- package/dist/query/rtk-query.browser.mjs.map +1 -0
- package/dist/query/rtk-query.legacy-esm.js +3117 -0
- package/dist/query/rtk-query.legacy-esm.js.map +1 -0
- package/dist/query/rtk-query.modern.mjs +3052 -0
- package/dist/query/rtk-query.modern.mjs.map +1 -0
- package/dist/react/cjs/index.js +6 -0
- package/dist/react/cjs/redux-toolkit-react.development.cjs +55 -0
- package/dist/react/cjs/redux-toolkit-react.development.cjs.map +1 -0
- package/dist/react/cjs/redux-toolkit-react.production.min.cjs +2 -0
- package/dist/react/cjs/redux-toolkit-react.production.min.cjs.map +1 -0
- package/dist/react/index.d.mts +22 -0
- package/dist/react/index.d.ts +22 -0
- package/dist/react/redux-toolkit-react.browser.mjs +2 -0
- package/dist/react/redux-toolkit-react.browser.mjs.map +1 -0
- package/dist/react/redux-toolkit-react.legacy-esm.js +47 -0
- package/dist/react/redux-toolkit-react.legacy-esm.js.map +1 -0
- package/dist/react/redux-toolkit-react.modern.mjs +28 -0
- package/dist/react/redux-toolkit-react.modern.mjs.map +1 -0
- package/dist/redux-toolkit.browser.mjs +3 -0
- package/dist/redux-toolkit.browser.mjs.map +1 -0
- package/dist/redux-toolkit.legacy-esm.js +2351 -0
- package/dist/redux-toolkit.legacy-esm.js.map +1 -0
- package/dist/redux-toolkit.modern.mjs +2330 -0
- package/dist/redux-toolkit.modern.mjs.map +1 -0
- package/dist/uncheckedindexed.ts +16 -0
- package/package.json +310 -0
- package/query/package.json +13 -0
- package/query/react/package.json +13 -0
- package/react/package.json +13 -0
- package/skills/build-modern-redux-apps/modern-redux/SKILL.md +304 -0
- package/skills/build-modern-redux-apps/modern-redux/references/store-lifetime.md +53 -0
- package/skills/build-modern-redux-apps/redux-dataflow/SKILL.md +264 -0
- package/skills/evolve-and-diagnose-redux-apps/debug-redux-toolkit-apps/SKILL.md +269 -0
- package/skills/evolve-and-diagnose-redux-apps/migrate-to-modern-redux/SKILL.md +226 -0
- package/skills/manage-server-data/adopt-rtk-query/SKILL.md +385 -0
- package/skills/manage-server-data/adopt-rtk-query/references/endpoint-lifecycle.md +36 -0
- package/skills/model-redux-state/build-slices-and-selectors/SKILL.md +364 -0
- package/skills/model-redux-state/build-slices-and-selectors/references/slice-patterns.md +59 -0
- package/skills/model-redux-state/design-state-ownership/SKILL.md +322 -0
- package/skills/model-redux-state/design-state-ownership/references/state-ownership.md +28 -0
- package/skills/orchestrate-side-effects/handle-side-effects/SKILL.md +271 -0
- package/skills/orchestrate-side-effects/handle-side-effects/references/listener-workflows.md +34 -0
- package/src/actionCreatorInvariantMiddleware.ts +34 -0
- package/src/autoBatchEnhancer.ts +146 -0
- package/src/combineSlices.ts +487 -0
- package/src/configureStore.ts +248 -0
- package/src/createAction.ts +324 -0
- package/src/createAsyncThunk.ts +791 -0
- package/src/createDraftSafeSelector.ts +30 -0
- package/src/createReducer.ts +217 -0
- package/src/createSlice.ts +1079 -0
- package/src/devtoolsExtension.ts +241 -0
- package/src/dynamicMiddleware/index.ts +93 -0
- package/src/dynamicMiddleware/react/index.ts +101 -0
- package/src/dynamicMiddleware/types.ts +82 -0
- package/src/entities/create_adapter.ts +47 -0
- package/src/entities/entity_state.ts +38 -0
- package/src/entities/index.ts +8 -0
- package/src/entities/models.ts +198 -0
- package/src/entities/sorted_state_adapter.ts +266 -0
- package/src/entities/state_adapter.ts +58 -0
- package/src/entities/state_selectors.ts +73 -0
- package/src/entities/unsorted_state_adapter.ts +204 -0
- package/src/entities/utils.ts +68 -0
- package/src/formatProdErrorMessage.ts +13 -0
- package/src/getDefaultEnhancers.ts +31 -0
- package/src/getDefaultMiddleware.ts +113 -0
- package/src/immerImports.ts +7 -0
- package/src/immutableStateInvariantMiddleware.ts +274 -0
- package/src/index.ts +213 -0
- package/src/listenerMiddleware/exceptions.ts +20 -0
- package/src/listenerMiddleware/index.ts +562 -0
- package/src/listenerMiddleware/task.ts +100 -0
- package/src/listenerMiddleware/types.ts +886 -0
- package/src/listenerMiddleware/utils.ts +30 -0
- package/src/mapBuilders.ts +298 -0
- package/src/matchers.ts +365 -0
- package/src/nanoid.ts +20 -0
- package/src/query/HandledError.ts +6 -0
- package/src/query/apiTypes.ts +120 -0
- package/src/query/baseQueryTypes.ts +101 -0
- package/src/query/core/apiState.ts +377 -0
- package/src/query/core/buildInitiate.ts +596 -0
- package/src/query/core/buildMiddleware/batchActions.ts +207 -0
- package/src/query/core/buildMiddleware/cacheCollection.ts +204 -0
- package/src/query/core/buildMiddleware/cacheLifecycle.ts +379 -0
- package/src/query/core/buildMiddleware/devMiddleware.ts +34 -0
- package/src/query/core/buildMiddleware/index.ts +162 -0
- package/src/query/core/buildMiddleware/invalidationByTags.ts +139 -0
- package/src/query/core/buildMiddleware/polling.ts +187 -0
- package/src/query/core/buildMiddleware/queryLifecycle.ts +508 -0
- package/src/query/core/buildMiddleware/types.ts +173 -0
- package/src/query/core/buildMiddleware/windowEventHandling.ts +64 -0
- package/src/query/core/buildSelectors.ts +413 -0
- package/src/query/core/buildSlice.ts +735 -0
- package/src/query/core/buildThunks.ts +1124 -0
- package/src/query/core/index.ts +58 -0
- package/src/query/core/module.ts +727 -0
- package/src/query/core/rtkImports.ts +24 -0
- package/src/query/core/setupListeners.ts +118 -0
- package/src/query/createApi.ts +506 -0
- package/src/query/defaultSerializeQueryArgs.ts +52 -0
- package/src/query/endpointDefinitions.ts +1717 -0
- package/src/query/fakeBaseQuery.ts +21 -0
- package/src/query/fetchBaseQuery.ts +385 -0
- package/src/query/index.ts +105 -0
- package/src/query/react/ApiProvider.tsx +69 -0
- package/src/query/react/buildHooks.ts +2297 -0
- package/src/query/react/constants.ts +2 -0
- package/src/query/react/index.ts +45 -0
- package/src/query/react/module.ts +275 -0
- package/src/query/react/namedHooks.ts +59 -0
- package/src/query/react/reactImports.ts +10 -0
- package/src/query/react/reactReduxImports.ts +1 -0
- package/src/query/react/rtkqImports.ts +8 -0
- package/src/query/react/useSerializedStableValue.ts +17 -0
- package/src/query/react/useShallowStableValue.ts +13 -0
- package/src/query/retry.ts +233 -0
- package/src/query/standardSchema.ts +35 -0
- package/src/query/tsHelpers.ts +48 -0
- package/src/query/utils/capitalize.ts +3 -0
- package/src/query/utils/copyWithStructuralSharing.ts +27 -0
- package/src/query/utils/countObjectKeys.ts +14 -0
- package/src/query/utils/filterMap.ts +27 -0
- package/src/query/utils/getCurrent.ts +6 -0
- package/src/query/utils/getOrInsert.ts +41 -0
- package/src/query/utils/immerImports.ts +9 -0
- package/src/query/utils/index.ts +11 -0
- package/src/query/utils/isAbsoluteUrl.ts +9 -0
- package/src/query/utils/isDocumentVisible.ts +12 -0
- package/src/query/utils/isNotNullish.ts +7 -0
- package/src/query/utils/isOnline.ts +12 -0
- package/src/query/utils/isValidUrl.ts +9 -0
- package/src/query/utils/joinUrls.ts +26 -0
- package/src/query/utils/signals.ts +33 -0
- package/src/react/index.ts +7 -0
- package/src/reduxImports.ts +8 -0
- package/src/reselectImports.ts +1 -0
- package/src/serializableStateInvariantMiddleware.ts +285 -0
- package/src/tsHelpers.ts +214 -0
- package/src/uncheckedindexed.ts +16 -0
- 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
|
+
}
|