@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,735 @@
1
+ import type { PayloadAction } from '@reduxjs/toolkit'
2
+ import {
3
+ combineReducers,
4
+ createAction,
5
+ createSlice,
6
+ isAnyOf,
7
+ isFulfilled,
8
+ isRejectedWithValue,
9
+ createNextState,
10
+ prepareAutoBatched,
11
+ SHOULD_AUTOBATCH,
12
+ nanoid,
13
+ } from './rtkImports'
14
+ import type {
15
+ QuerySubstateIdentifier,
16
+ QuerySubState,
17
+ MutationSubstateIdentifier,
18
+ MutationSubState,
19
+ MutationState,
20
+ QueryState,
21
+ InvalidationState,
22
+ Subscribers,
23
+ QueryCacheKey,
24
+ SubscriptionState,
25
+ ConfigState,
26
+ InfiniteQuerySubState,
27
+ InfiniteQueryDirection,
28
+ } from './apiState'
29
+ import {
30
+ STATUS_FULFILLED,
31
+ STATUS_PENDING,
32
+ QueryStatus,
33
+ STATUS_REJECTED,
34
+ STATUS_UNINITIALIZED,
35
+ } from './apiState'
36
+ import type {
37
+ AllQueryKeys,
38
+ QueryArgFromAnyQueryDefinition,
39
+ DataFromAnyQueryDefinition,
40
+ InfiniteQueryThunk,
41
+ MutationThunk,
42
+ QueryThunk,
43
+ QueryThunkArg,
44
+ } from './buildThunks'
45
+ import { calculateProvidedByThunk } from './buildThunks'
46
+ import {
47
+ ENDPOINT_QUERY,
48
+ isInfiniteQueryDefinition,
49
+ type AssertTagTypes,
50
+ type EndpointDefinitions,
51
+ type FullTagDescription,
52
+ type QueryDefinition,
53
+ } from '../endpointDefinitions'
54
+ import type { Patch } from 'immer'
55
+ import { applyPatches, original, isDraft } from '../utils/immerImports'
56
+ import { onFocus, onFocusLost, onOffline, onOnline } from './setupListeners'
57
+ import {
58
+ isDocumentVisible,
59
+ isOnline,
60
+ copyWithStructuralSharing,
61
+ } from '../utils'
62
+ import type { ApiContext } from '../apiTypes'
63
+ import { isUpsertQuery } from './buildInitiate'
64
+ import type { InternalSerializeQueryArgs } from '../defaultSerializeQueryArgs'
65
+ import type { UnwrapPromise } from '../tsHelpers'
66
+ import { getCurrent } from '../utils/getCurrent'
67
+
68
+ /**
69
+ * A typesafe single entry to be upserted into the cache
70
+ */
71
+ export type NormalizedQueryUpsertEntry<
72
+ Definitions extends EndpointDefinitions,
73
+ EndpointName extends AllQueryKeys<Definitions>,
74
+ > = {
75
+ endpointName: EndpointName
76
+ arg: QueryArgFromAnyQueryDefinition<Definitions, EndpointName>
77
+ value: DataFromAnyQueryDefinition<Definitions, EndpointName>
78
+ }
79
+
80
+ /**
81
+ * The internal version that is not typesafe since we can't carry the generics through `createSlice`
82
+ */
83
+ type NormalizedQueryUpsertEntryPayload = {
84
+ endpointName: string
85
+ arg: unknown
86
+ value: unknown
87
+ }
88
+
89
+ export type ProcessedQueryUpsertEntry = {
90
+ queryDescription: QueryThunkArg
91
+ value: unknown
92
+ }
93
+
94
+ /**
95
+ * A typesafe representation of a util action creator that accepts cache entry descriptions to upsert
96
+ */
97
+ export type UpsertEntries<Definitions extends EndpointDefinitions> = (<
98
+ EndpointNames extends Array<AllQueryKeys<Definitions>>,
99
+ >(
100
+ entries: [
101
+ ...{
102
+ [I in keyof EndpointNames]: NormalizedQueryUpsertEntry<
103
+ Definitions,
104
+ EndpointNames[I]
105
+ >
106
+ },
107
+ ],
108
+ ) => PayloadAction<NormalizedQueryUpsertEntryPayload[]>) & {
109
+ match: (
110
+ action: unknown,
111
+ ) => action is PayloadAction<NormalizedQueryUpsertEntryPayload[]>
112
+ }
113
+
114
+ function updateQuerySubstateIfExists(
115
+ state: QueryState<any>,
116
+ queryCacheKey: QueryCacheKey,
117
+ update: (substate: QuerySubState<any> | InfiniteQuerySubState<any>) => void,
118
+ ) {
119
+ const substate = state[queryCacheKey]
120
+ if (substate) {
121
+ update(substate)
122
+ }
123
+ }
124
+
125
+ export function getMutationCacheKey(
126
+ id:
127
+ | MutationSubstateIdentifier
128
+ | { requestId: string; arg: { fixedCacheKey?: string | undefined } },
129
+ ): string
130
+ export function getMutationCacheKey(id: {
131
+ fixedCacheKey?: string
132
+ requestId?: string
133
+ }): string | undefined
134
+
135
+ export function getMutationCacheKey(
136
+ id:
137
+ | { fixedCacheKey?: string; requestId?: string }
138
+ | MutationSubstateIdentifier
139
+ | { requestId: string; arg: { fixedCacheKey?: string | undefined } },
140
+ ): string | undefined {
141
+ return ('arg' in id ? id.arg.fixedCacheKey : id.fixedCacheKey) ?? id.requestId
142
+ }
143
+
144
+ function updateMutationSubstateIfExists(
145
+ state: MutationState<any>,
146
+ id:
147
+ | MutationSubstateIdentifier
148
+ | { requestId: string; arg: { fixedCacheKey?: string | undefined } },
149
+ update: (substate: MutationSubState<any>) => void,
150
+ ) {
151
+ const substate = state[getMutationCacheKey(id)]
152
+ if (substate) {
153
+ update(substate)
154
+ }
155
+ }
156
+
157
+ const initialState = {} as any
158
+
159
+ export function buildSlice({
160
+ reducerPath,
161
+ queryThunk,
162
+ mutationThunk,
163
+ serializeQueryArgs,
164
+ context: {
165
+ endpointDefinitions: definitions,
166
+ apiUid,
167
+ extractRehydrationInfo,
168
+ hasRehydrationInfo,
169
+ },
170
+ assertTagType,
171
+ config,
172
+ }: {
173
+ reducerPath: string
174
+ queryThunk: QueryThunk
175
+ infiniteQueryThunk: InfiniteQueryThunk<any>
176
+ mutationThunk: MutationThunk
177
+ serializeQueryArgs: InternalSerializeQueryArgs
178
+ context: ApiContext<EndpointDefinitions>
179
+ assertTagType: AssertTagTypes
180
+ config: Omit<
181
+ ConfigState<string>,
182
+ 'online' | 'focused' | 'middlewareRegistered'
183
+ >
184
+ }) {
185
+ const resetApiState = createAction(`${reducerPath}/resetApiState`)
186
+
187
+ function writePendingCacheEntry(
188
+ draft: QueryState<any>,
189
+ arg: QueryThunkArg,
190
+ upserting: boolean,
191
+ meta: {
192
+ arg: QueryThunkArg
193
+ requestId: string
194
+ // requestStatus: 'pending'
195
+ } & { startedTimeStamp: number },
196
+ ) {
197
+ draft[arg.queryCacheKey] ??= {
198
+ status: STATUS_UNINITIALIZED,
199
+ endpointName: arg.endpointName,
200
+ }
201
+
202
+ updateQuerySubstateIfExists(draft, arg.queryCacheKey, (substate) => {
203
+ substate.status = STATUS_PENDING
204
+
205
+ substate.requestId =
206
+ upserting && substate.requestId
207
+ ? // for `upsertQuery` **updates**, keep the current `requestId`
208
+ substate.requestId
209
+ : // for normal queries or `upsertQuery` **inserts** always update the `requestId`
210
+ meta.requestId
211
+ if (arg.originalArgs !== undefined) {
212
+ substate.originalArgs = arg.originalArgs
213
+ }
214
+ substate.startedTimeStamp = meta.startedTimeStamp
215
+
216
+ const endpointDefinition = definitions[meta.arg.endpointName]
217
+
218
+ if (isInfiniteQueryDefinition(endpointDefinition) && 'direction' in arg) {
219
+ ;(substate as InfiniteQuerySubState<any>).direction =
220
+ arg.direction as InfiniteQueryDirection
221
+ }
222
+ })
223
+ }
224
+
225
+ function writeFulfilledCacheEntry(
226
+ draft: QueryState<any>,
227
+ meta: { arg: QueryThunkArg; requestId: string } & {
228
+ fulfilledTimeStamp: number
229
+ baseQueryMeta: unknown
230
+ },
231
+ payload: unknown,
232
+ upserting: boolean,
233
+ ) {
234
+ updateQuerySubstateIfExists(draft, meta.arg.queryCacheKey, (substate) => {
235
+ if (substate.requestId !== meta.requestId && !upserting) return
236
+ const { merge } = definitions[meta.arg.endpointName] as QueryDefinition<
237
+ any,
238
+ any,
239
+ any,
240
+ any
241
+ >
242
+ substate.status = STATUS_FULFILLED
243
+
244
+ if (merge) {
245
+ if (substate.data !== undefined) {
246
+ const { fulfilledTimeStamp, arg, baseQueryMeta, requestId } = meta
247
+ // There's existing cache data. Let the user merge it in themselves.
248
+ // We're already inside an Immer-powered reducer, and the user could just mutate `substate.data`
249
+ // themselves inside of `merge()`. But, they might also want to return a new value.
250
+ // Try to let Immer figure that part out, save the result, and assign it to `substate.data`.
251
+ let newData = createNextState(substate.data, (draftSubstateData) => {
252
+ // As usual with Immer, you can mutate _or_ return inside here, but not both
253
+ return merge(draftSubstateData, payload, {
254
+ arg: arg.originalArgs,
255
+ baseQueryMeta,
256
+ fulfilledTimeStamp,
257
+ requestId,
258
+ })
259
+ })
260
+ substate.data = newData
261
+ } else {
262
+ // Presumably a fresh request. Just cache the response data.
263
+ substate.data = payload
264
+ }
265
+ } else {
266
+ // Assign or safely update the cache data.
267
+ substate.data =
268
+ (definitions[meta.arg.endpointName].structuralSharing ?? true)
269
+ ? copyWithStructuralSharing(
270
+ isDraft(substate.data)
271
+ ? original(substate.data)
272
+ : substate.data,
273
+ payload,
274
+ )
275
+ : payload
276
+ }
277
+
278
+ delete substate.error
279
+ substate.fulfilledTimeStamp = meta.fulfilledTimeStamp
280
+ })
281
+ }
282
+
283
+ const querySlice = createSlice({
284
+ name: `${reducerPath}/queries`,
285
+ initialState: initialState as QueryState<any>,
286
+ reducers: {
287
+ removeQueryResult: {
288
+ reducer(
289
+ draft,
290
+ {
291
+ payload: { queryCacheKey },
292
+ }: PayloadAction<QuerySubstateIdentifier>,
293
+ ) {
294
+ delete draft[queryCacheKey]
295
+ },
296
+ prepare: prepareAutoBatched<QuerySubstateIdentifier>(),
297
+ },
298
+ cacheEntriesUpserted: {
299
+ reducer(
300
+ draft,
301
+ action: PayloadAction<
302
+ ProcessedQueryUpsertEntry[],
303
+ string,
304
+ { RTK_autoBatch: boolean; requestId: string; timestamp: number }
305
+ >,
306
+ ) {
307
+ for (const entry of action.payload) {
308
+ const { queryDescription: arg, value } = entry
309
+ writePendingCacheEntry(draft, arg, true, {
310
+ arg,
311
+ requestId: action.meta.requestId,
312
+ startedTimeStamp: action.meta.timestamp,
313
+ })
314
+
315
+ writeFulfilledCacheEntry(
316
+ draft,
317
+ {
318
+ arg,
319
+ requestId: action.meta.requestId,
320
+ fulfilledTimeStamp: action.meta.timestamp,
321
+ baseQueryMeta: {},
322
+ },
323
+ value,
324
+ // We know we're upserting here
325
+ true,
326
+ )
327
+ }
328
+ },
329
+ prepare: (payload: NormalizedQueryUpsertEntryPayload[]) => {
330
+ const queryDescriptions: ProcessedQueryUpsertEntry[] = payload.map(
331
+ (entry) => {
332
+ const { endpointName, arg, value } = entry
333
+ const endpointDefinition = definitions[endpointName]
334
+ const queryDescription: QueryThunkArg = {
335
+ type: ENDPOINT_QUERY as 'query',
336
+ endpointName,
337
+ originalArgs: entry.arg,
338
+ queryCacheKey: serializeQueryArgs({
339
+ queryArgs: arg,
340
+ endpointDefinition,
341
+ endpointName,
342
+ }),
343
+ }
344
+ return { queryDescription, value }
345
+ },
346
+ )
347
+
348
+ const result = {
349
+ payload: queryDescriptions,
350
+ meta: {
351
+ [SHOULD_AUTOBATCH]: true,
352
+ requestId: nanoid(),
353
+ timestamp: Date.now(),
354
+ },
355
+ }
356
+ return result
357
+ },
358
+ },
359
+ queryResultPatched: {
360
+ reducer(
361
+ draft,
362
+ {
363
+ payload: { queryCacheKey, patches },
364
+ }: PayloadAction<
365
+ QuerySubstateIdentifier & { patches: readonly Patch[] }
366
+ >,
367
+ ) {
368
+ updateQuerySubstateIfExists(draft, queryCacheKey, (substate) => {
369
+ substate.data = applyPatches(substate.data as any, patches.concat())
370
+ })
371
+ },
372
+ prepare: prepareAutoBatched<
373
+ QuerySubstateIdentifier & { patches: readonly Patch[] }
374
+ >(),
375
+ },
376
+ },
377
+ extraReducers(builder) {
378
+ builder
379
+ .addCase(queryThunk.pending, (draft, { meta, meta: { arg } }) => {
380
+ const upserting = isUpsertQuery(arg)
381
+ writePendingCacheEntry(draft, arg, upserting, meta)
382
+ })
383
+ .addCase(queryThunk.fulfilled, (draft, { meta, payload }) => {
384
+ const upserting = isUpsertQuery(meta.arg)
385
+ writeFulfilledCacheEntry(draft, meta, payload, upserting)
386
+ })
387
+ .addCase(
388
+ queryThunk.rejected,
389
+ (draft, { meta: { condition, arg, requestId }, error, payload }) => {
390
+ updateQuerySubstateIfExists(
391
+ draft,
392
+ arg.queryCacheKey,
393
+ (substate) => {
394
+ if (condition) {
395
+ // request was aborted due to condition (another query already running)
396
+ } else {
397
+ // request failed
398
+ if (substate.requestId !== requestId) return
399
+ substate.status = STATUS_REJECTED
400
+ substate.error = (payload ?? error) as any
401
+ }
402
+ },
403
+ )
404
+ },
405
+ )
406
+ .addMatcher(hasRehydrationInfo, (draft, action) => {
407
+ const { queries } = extractRehydrationInfo(action)!
408
+ for (const [key, entry] of Object.entries(queries)) {
409
+ if (
410
+ // do not rehydrate entries that were currently in flight.
411
+ entry?.status === STATUS_FULFILLED ||
412
+ entry?.status === STATUS_REJECTED
413
+ ) {
414
+ draft[key] = entry
415
+ }
416
+ }
417
+ })
418
+ },
419
+ })
420
+ const mutationSlice = createSlice({
421
+ name: `${reducerPath}/mutations`,
422
+ initialState: initialState as MutationState<any>,
423
+ reducers: {
424
+ removeMutationResult: {
425
+ reducer(draft, { payload }: PayloadAction<MutationSubstateIdentifier>) {
426
+ const cacheKey = getMutationCacheKey(payload)
427
+ if (cacheKey in draft) {
428
+ delete draft[cacheKey]
429
+ }
430
+ },
431
+ prepare: prepareAutoBatched<MutationSubstateIdentifier>(),
432
+ },
433
+ },
434
+ extraReducers(builder) {
435
+ builder
436
+ .addCase(
437
+ mutationThunk.pending,
438
+ (draft, { meta, meta: { requestId, arg, startedTimeStamp } }) => {
439
+ if (!arg.track) return
440
+
441
+ draft[getMutationCacheKey(meta)] = {
442
+ requestId,
443
+ status: STATUS_PENDING,
444
+ endpointName: arg.endpointName,
445
+ startedTimeStamp,
446
+ }
447
+ },
448
+ )
449
+ .addCase(mutationThunk.fulfilled, (draft, { payload, meta }) => {
450
+ if (!meta.arg.track) return
451
+
452
+ updateMutationSubstateIfExists(draft, meta, (substate) => {
453
+ if (substate.requestId !== meta.requestId) return
454
+ substate.status = STATUS_FULFILLED
455
+ substate.data = payload
456
+ substate.fulfilledTimeStamp = meta.fulfilledTimeStamp
457
+ })
458
+ })
459
+ .addCase(mutationThunk.rejected, (draft, { payload, error, meta }) => {
460
+ if (!meta.arg.track) return
461
+
462
+ updateMutationSubstateIfExists(draft, meta, (substate) => {
463
+ if (substate.requestId !== meta.requestId) return
464
+
465
+ substate.status = STATUS_REJECTED
466
+ substate.error = (payload ?? error) as any
467
+ })
468
+ })
469
+ .addMatcher(hasRehydrationInfo, (draft, action) => {
470
+ const { mutations } = extractRehydrationInfo(action)!
471
+ for (const [key, entry] of Object.entries(mutations)) {
472
+ if (
473
+ // do not rehydrate entries that were currently in flight.
474
+ (entry?.status === STATUS_FULFILLED ||
475
+ entry?.status === STATUS_REJECTED) &&
476
+ // only rehydrate endpoints that were persisted using a `fixedCacheKey`
477
+ key !== entry?.requestId
478
+ ) {
479
+ draft[key] = entry
480
+ }
481
+ }
482
+ })
483
+ },
484
+ })
485
+
486
+ type CalculateProvidedByAction = UnwrapPromise<
487
+ | ReturnType<ReturnType<QueryThunk>>
488
+ | ReturnType<ReturnType<InfiniteQueryThunk<any>>>
489
+ >
490
+
491
+ const initialInvalidationState: InvalidationState<string> = {
492
+ tags: {},
493
+ keys: {},
494
+ }
495
+
496
+ const invalidationSlice = createSlice({
497
+ name: `${reducerPath}/invalidation`,
498
+ initialState: initialInvalidationState,
499
+ reducers: {
500
+ updateProvidedBy: {
501
+ reducer(
502
+ draft,
503
+ action: PayloadAction<
504
+ Array<{
505
+ queryCacheKey: QueryCacheKey
506
+ providedTags: readonly FullTagDescription<string>[]
507
+ }>
508
+ >,
509
+ ) {
510
+ for (const { queryCacheKey, providedTags } of action.payload) {
511
+ removeCacheKeyFromTags(draft, queryCacheKey)
512
+
513
+ for (const { type, id } of providedTags) {
514
+ const subscribedQueries = ((draft.tags[type] ??= {})[
515
+ id || '__internal_without_id'
516
+ ] ??= [])
517
+ const alreadySubscribed =
518
+ subscribedQueries.includes(queryCacheKey)
519
+ if (!alreadySubscribed) {
520
+ subscribedQueries.push(queryCacheKey)
521
+ }
522
+ }
523
+
524
+ // Remove readonly from the providedTags array
525
+ draft.keys[queryCacheKey] =
526
+ providedTags as FullTagDescription<string>[]
527
+ }
528
+ },
529
+ prepare: prepareAutoBatched<
530
+ Array<{
531
+ queryCacheKey: QueryCacheKey
532
+ providedTags: readonly FullTagDescription<string>[]
533
+ }>
534
+ >(),
535
+ },
536
+ },
537
+ extraReducers(builder) {
538
+ builder
539
+ .addCase(
540
+ querySlice.actions.removeQueryResult,
541
+ (draft, { payload: { queryCacheKey } }) => {
542
+ removeCacheKeyFromTags(draft, queryCacheKey)
543
+ },
544
+ )
545
+ .addMatcher(hasRehydrationInfo, (draft, action) => {
546
+ const { provided } = extractRehydrationInfo(action)!
547
+ for (const [type, incomingTags] of Object.entries(
548
+ provided.tags ?? {},
549
+ )) {
550
+ for (const [id, cacheKeys] of Object.entries(incomingTags)) {
551
+ const subscribedQueries = ((draft.tags[type] ??= {})[
552
+ id || '__internal_without_id'
553
+ ] ??= [])
554
+ for (const queryCacheKey of cacheKeys) {
555
+ const alreadySubscribed =
556
+ subscribedQueries.includes(queryCacheKey)
557
+ if (!alreadySubscribed) {
558
+ subscribedQueries.push(queryCacheKey)
559
+ }
560
+ draft.keys[queryCacheKey] = provided.keys[queryCacheKey]
561
+ }
562
+ }
563
+ }
564
+ })
565
+ .addMatcher(
566
+ isAnyOf(isFulfilled(queryThunk), isRejectedWithValue(queryThunk)),
567
+ (draft, action) => {
568
+ writeProvidedTagsForQueries(draft, [action])
569
+ },
570
+ )
571
+ .addMatcher(
572
+ querySlice.actions.cacheEntriesUpserted.match,
573
+ (draft, action) => {
574
+ const mockActions: CalculateProvidedByAction[] = action.payload.map(
575
+ ({ queryDescription, value }) => {
576
+ return {
577
+ type: 'UNKNOWN',
578
+ payload: value,
579
+ meta: {
580
+ requestStatus: 'fulfilled',
581
+ requestId: 'UNKNOWN',
582
+ arg: queryDescription,
583
+ },
584
+ }
585
+ },
586
+ )
587
+ writeProvidedTagsForQueries(draft, mockActions)
588
+ },
589
+ )
590
+ },
591
+ })
592
+
593
+ function removeCacheKeyFromTags(
594
+ draft: InvalidationState<any>,
595
+ queryCacheKey: QueryCacheKey,
596
+ ) {
597
+ const existingTags = getCurrent(draft.keys[queryCacheKey] ?? [])
598
+
599
+ // Delete this cache key from any existing tags that may have provided it
600
+ for (const tag of existingTags) {
601
+ const tagType = tag.type
602
+ const tagId = tag.id ?? '__internal_without_id'
603
+ const tagSubscriptions = draft.tags[tagType]?.[tagId]
604
+
605
+ if (tagSubscriptions) {
606
+ draft.tags[tagType][tagId] = getCurrent(tagSubscriptions).filter(
607
+ (qc) => qc !== queryCacheKey,
608
+ )
609
+ }
610
+ }
611
+
612
+ delete draft.keys[queryCacheKey]
613
+ }
614
+
615
+ function writeProvidedTagsForQueries(
616
+ draft: InvalidationState<string>,
617
+ actions: CalculateProvidedByAction[],
618
+ ) {
619
+ const providedByEntries = actions.map((action) => {
620
+ const providedTags = calculateProvidedByThunk(
621
+ action,
622
+ 'providesTags',
623
+ definitions,
624
+ assertTagType,
625
+ )
626
+ const { queryCacheKey } = action.meta.arg
627
+ return { queryCacheKey, providedTags }
628
+ })
629
+
630
+ invalidationSlice.caseReducers.updateProvidedBy(
631
+ draft,
632
+ invalidationSlice.actions.updateProvidedBy(providedByEntries),
633
+ )
634
+ }
635
+
636
+ // Dummy slice to generate actions
637
+ const subscriptionSlice = createSlice({
638
+ name: `${reducerPath}/subscriptions`,
639
+ initialState: initialState as SubscriptionState,
640
+ reducers: {
641
+ updateSubscriptionOptions(
642
+ d,
643
+ a: PayloadAction<
644
+ {
645
+ endpointName: string
646
+ requestId: string
647
+ options: Subscribers[number]
648
+ } & QuerySubstateIdentifier
649
+ >,
650
+ ) {
651
+ // Dummy
652
+ },
653
+ unsubscribeQueryResult(
654
+ d,
655
+ a: PayloadAction<{ requestId: string } & QuerySubstateIdentifier>,
656
+ ) {
657
+ // Dummy
658
+ },
659
+ internal_getRTKQSubscriptions() {},
660
+ },
661
+ })
662
+
663
+ const internalSubscriptionsSlice = createSlice({
664
+ name: `${reducerPath}/internalSubscriptions`,
665
+ initialState: initialState as SubscriptionState,
666
+ reducers: {
667
+ subscriptionsUpdated: {
668
+ reducer(state, action: PayloadAction<Patch[]>) {
669
+ return applyPatches(state, action.payload)
670
+ },
671
+ prepare: prepareAutoBatched<Patch[]>(),
672
+ },
673
+ },
674
+ })
675
+
676
+ const configSlice = createSlice({
677
+ name: `${reducerPath}/config`,
678
+ initialState: {
679
+ online: isOnline(),
680
+ focused: isDocumentVisible(),
681
+ middlewareRegistered: false,
682
+ ...config,
683
+ } as ConfigState<string>,
684
+ reducers: {
685
+ middlewareRegistered(state, { payload }: PayloadAction<string>) {
686
+ state.middlewareRegistered =
687
+ state.middlewareRegistered === 'conflict' || apiUid !== payload
688
+ ? 'conflict'
689
+ : true
690
+ },
691
+ },
692
+ extraReducers: (builder) => {
693
+ builder
694
+ .addCase(onOnline, (state) => {
695
+ state.online = true
696
+ })
697
+ .addCase(onOffline, (state) => {
698
+ state.online = false
699
+ })
700
+ .addCase(onFocus, (state) => {
701
+ state.focused = true
702
+ })
703
+ .addCase(onFocusLost, (state) => {
704
+ state.focused = false
705
+ })
706
+ // update the state to be a new object to be picked up as a "state change"
707
+ // by redux-persist's `autoMergeLevel2`
708
+ .addMatcher(hasRehydrationInfo, (draft) => ({ ...draft }))
709
+ },
710
+ })
711
+
712
+ const combinedReducer = combineReducers({
713
+ queries: querySlice.reducer,
714
+ mutations: mutationSlice.reducer,
715
+ provided: invalidationSlice.reducer,
716
+ subscriptions: internalSubscriptionsSlice.reducer,
717
+ config: configSlice.reducer,
718
+ })
719
+
720
+ const reducer: typeof combinedReducer = (state, action) =>
721
+ combinedReducer(resetApiState.match(action) ? undefined : state, action)
722
+
723
+ const actions = {
724
+ ...configSlice.actions,
725
+ ...querySlice.actions,
726
+ ...subscriptionSlice.actions,
727
+ ...internalSubscriptionsSlice.actions,
728
+ ...mutationSlice.actions,
729
+ ...invalidationSlice.actions,
730
+ resetApiState,
731
+ }
732
+
733
+ return { reducer, actions }
734
+ }
735
+ export type SliceActions = ReturnType<typeof buildSlice>['actions']