@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,562 @@
1
+ import type { Action, Dispatch, MiddlewareAPI, UnknownAction } from 'redux'
2
+ import { isAction } from '../reduxImports'
3
+ import type { ThunkDispatch } from 'redux-thunk'
4
+ import { createAction } from '../createAction'
5
+ import { nanoid } from '../nanoid'
6
+
7
+ import {
8
+ TaskAbortError,
9
+ listenerCancelled,
10
+ listenerCompleted,
11
+ taskCancelled,
12
+ taskCompleted,
13
+ } from './exceptions'
14
+ import {
15
+ createDelay,
16
+ createPause,
17
+ raceWithSignal,
18
+ runTask,
19
+ validateActive,
20
+ } from './task'
21
+ import type {
22
+ AddListenerOverloads,
23
+ AnyListenerPredicate,
24
+ CreateListenerMiddlewareOptions,
25
+ FallbackAddListenerOptions,
26
+ ForkOptions,
27
+ ForkedTask,
28
+ ForkedTaskExecutor,
29
+ ListenerEntry,
30
+ ListenerErrorHandler,
31
+ ListenerErrorInfo,
32
+ ListenerMiddleware,
33
+ ListenerMiddlewareInstance,
34
+ TakePattern,
35
+ TaskResult,
36
+ TypedAddListener,
37
+ TypedCreateListenerEntry,
38
+ TypedRemoveListener,
39
+ UnsubscribeListener,
40
+ UnsubscribeListenerOptions,
41
+ } from './types'
42
+ import {
43
+ addAbortSignalListener,
44
+ assertFunction,
45
+ catchRejection,
46
+ noop,
47
+ } from './utils'
48
+ export { TaskAbortError } from './exceptions'
49
+ export type {
50
+ AsyncTaskExecutor,
51
+ CreateListenerMiddlewareOptions,
52
+ ForkedTask,
53
+ ForkedTaskAPI,
54
+ ForkedTaskExecutor,
55
+ ListenerEffect,
56
+ ListenerEffectAPI,
57
+ ListenerErrorHandler,
58
+ ListenerMiddleware,
59
+ ListenerMiddlewareInstance,
60
+ SyncTaskExecutor,
61
+ TaskCancelled,
62
+ TaskRejected,
63
+ TaskResolved,
64
+ TaskResult,
65
+ TypedAddListener,
66
+ TypedRemoveListener,
67
+ TypedStartListening,
68
+ TypedStopListening,
69
+ UnsubscribeListener,
70
+ UnsubscribeListenerOptions,
71
+ } from './types'
72
+
73
+ //Overly-aggressive byte-shaving
74
+ const { assign } = Object
75
+ /**
76
+ * @internal
77
+ */
78
+ const INTERNAL_NIL_TOKEN = {} as const
79
+
80
+ const alm = 'listenerMiddleware' as const
81
+
82
+ const createFork = (
83
+ parentAbortSignal: AbortSignal,
84
+ parentBlockingPromises: Promise<any>[],
85
+ ) => {
86
+ const linkControllers = (controller: AbortController) =>
87
+ addAbortSignalListener(parentAbortSignal, () =>
88
+ controller.abort(parentAbortSignal.reason),
89
+ )
90
+
91
+ return <T>(
92
+ taskExecutor: ForkedTaskExecutor<T>,
93
+ opts?: ForkOptions,
94
+ ): ForkedTask<T> => {
95
+ assertFunction(taskExecutor, 'taskExecutor')
96
+ const childAbortController = new AbortController()
97
+
98
+ linkControllers(childAbortController)
99
+
100
+ const result = runTask<T>(
101
+ async (): Promise<T> => {
102
+ validateActive(parentAbortSignal)
103
+ validateActive(childAbortController.signal)
104
+ const result = (await taskExecutor({
105
+ pause: createPause(childAbortController.signal),
106
+ delay: createDelay(childAbortController.signal),
107
+ signal: childAbortController.signal,
108
+ })) as T
109
+ validateActive(childAbortController.signal)
110
+ return result
111
+ },
112
+ () => childAbortController.abort(taskCompleted),
113
+ )
114
+
115
+ if (opts?.autoJoin) {
116
+ parentBlockingPromises.push(result.catch(noop))
117
+ }
118
+
119
+ return {
120
+ result: createPause<TaskResult<T>>(parentAbortSignal)(result),
121
+ cancel() {
122
+ childAbortController.abort(taskCancelled)
123
+ },
124
+ }
125
+ }
126
+ }
127
+
128
+ const createTakePattern = <S>(
129
+ startListening: AddListenerOverloads<UnsubscribeListener, S, Dispatch>,
130
+ signal: AbortSignal,
131
+ ): TakePattern<S> => {
132
+ /**
133
+ * A function that takes a ListenerPredicate and an optional timeout,
134
+ * and resolves when either the predicate returns `true` based on an action
135
+ * state combination or when the timeout expires.
136
+ * If the parent listener is canceled while waiting, this will throw a
137
+ * TaskAbortError.
138
+ */
139
+ const take = async <P extends AnyListenerPredicate<S>>(
140
+ predicate: P,
141
+ timeout: number | undefined,
142
+ ) => {
143
+ validateActive(signal)
144
+
145
+ // Placeholder unsubscribe function until the listener is added
146
+ let unsubscribe: UnsubscribeListener = () => {}
147
+
148
+ const tuplePromise = new Promise<[Action, S, S]>((resolve, reject) => {
149
+ // Inside the Promise, we synchronously add the listener.
150
+ let stopListening = startListening({
151
+ predicate: predicate as any,
152
+ effect: (action, listenerApi): void => {
153
+ // One-shot listener that cleans up as soon as the predicate passes
154
+ listenerApi.unsubscribe()
155
+ // Resolve the promise with the same arguments the predicate saw
156
+ resolve([
157
+ action,
158
+ listenerApi.getState(),
159
+ listenerApi.getOriginalState(),
160
+ ])
161
+ },
162
+ })
163
+ unsubscribe = () => {
164
+ stopListening()
165
+ reject()
166
+ }
167
+ })
168
+
169
+ const promises: (Promise<null> | Promise<[Action, S, S]>)[] = [tuplePromise]
170
+
171
+ if (timeout != null) {
172
+ promises.push(
173
+ new Promise<null>((resolve) => setTimeout(resolve, timeout, null)),
174
+ )
175
+ }
176
+
177
+ try {
178
+ const output = await raceWithSignal(signal, Promise.race(promises))
179
+
180
+ validateActive(signal)
181
+ return output
182
+ } finally {
183
+ // Always clean up the listener
184
+ unsubscribe()
185
+ }
186
+ }
187
+
188
+ return ((predicate: AnyListenerPredicate<S>, timeout: number | undefined) =>
189
+ catchRejection(take(predicate, timeout))) as TakePattern<S>
190
+ }
191
+
192
+ const getListenerEntryPropsFrom = (options: FallbackAddListenerOptions) => {
193
+ let { type, actionCreator, matcher, predicate, effect } = options
194
+
195
+ if (type) {
196
+ predicate = createAction(type).match
197
+ } else if (actionCreator) {
198
+ type = actionCreator!.type
199
+ predicate = actionCreator.match
200
+ } else if (matcher) {
201
+ predicate = matcher
202
+ } else if (predicate) {
203
+ // pass
204
+ } else {
205
+ throw new Error(
206
+ 'Creating or removing a listener requires one of the known fields for matching an action',
207
+ )
208
+ }
209
+
210
+ assertFunction(effect, 'options.listener')
211
+
212
+ return { predicate, type, effect }
213
+ }
214
+
215
+ /** Accepts the possible options for creating a listener, and returns a formatted listener entry */
216
+ export const createListenerEntry: TypedCreateListenerEntry<unknown> =
217
+ /* @__PURE__ */ assign(
218
+ (options: FallbackAddListenerOptions) => {
219
+ const { type, predicate, effect } = getListenerEntryPropsFrom(options)
220
+
221
+ const entry: ListenerEntry<unknown> = {
222
+ id: nanoid(),
223
+ effect,
224
+ type,
225
+ predicate,
226
+ pending: new Set<AbortController>(),
227
+ unsubscribe: () => {
228
+ throw new Error('Unsubscribe not initialized')
229
+ },
230
+ }
231
+
232
+ return entry
233
+ },
234
+ { withTypes: () => createListenerEntry },
235
+ ) as unknown as TypedCreateListenerEntry<unknown>
236
+
237
+ const findListenerEntry = (
238
+ listenerMap: Map<string, ListenerEntry>,
239
+ options: FallbackAddListenerOptions,
240
+ ) => {
241
+ const { type, effect, predicate } = getListenerEntryPropsFrom(options)
242
+
243
+ return Array.from(listenerMap.values()).find((entry) => {
244
+ const matchPredicateOrType =
245
+ typeof type === 'string'
246
+ ? entry.type === type
247
+ : entry.predicate === predicate
248
+
249
+ return matchPredicateOrType && entry.effect === effect
250
+ })
251
+ }
252
+
253
+ const cancelActiveListeners = (
254
+ entry: ListenerEntry<unknown, Dispatch<UnknownAction>>,
255
+ ) => {
256
+ entry.pending.forEach((controller) => {
257
+ controller.abort(listenerCancelled)
258
+ })
259
+ }
260
+
261
+ const createClearListenerMiddleware = (
262
+ listenerMap: Map<string, ListenerEntry>,
263
+ executingListeners: Map<ListenerEntry, number>,
264
+ ) => {
265
+ return () => {
266
+ for (const listener of executingListeners.keys()) {
267
+ cancelActiveListeners(listener)
268
+ }
269
+ listenerMap.clear()
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Safely reports errors to the `errorHandler` provided.
275
+ * Errors that occur inside `errorHandler` are notified in a new task.
276
+ * Inspired by [rxjs reportUnhandledError](https://github.com/ReactiveX/rxjs/blob/6fafcf53dc9e557439b25debaeadfd224b245a66/src/internal/util/reportUnhandledError.ts)
277
+ * @param errorHandler
278
+ * @param errorToNotify
279
+ */
280
+ const safelyNotifyError = (
281
+ errorHandler: ListenerErrorHandler,
282
+ errorToNotify: unknown,
283
+ errorInfo: ListenerErrorInfo,
284
+ ): void => {
285
+ try {
286
+ errorHandler(errorToNotify, errorInfo)
287
+ } catch (errorHandlerError) {
288
+ // We cannot let an error raised here block the listener queue.
289
+ // The error raised here will be picked up by `window.onerror`, `process.on('error')` etc...
290
+ setTimeout(() => {
291
+ throw errorHandlerError
292
+ }, 0)
293
+ }
294
+ }
295
+
296
+ /**
297
+ * @public
298
+ */
299
+ export const addListener = /* @__PURE__ */ assign(
300
+ /* @__PURE__ */ createAction(`${alm}/add`),
301
+ {
302
+ withTypes: () => addListener,
303
+ },
304
+ ) as unknown as TypedAddListener<unknown>
305
+
306
+ /**
307
+ * @public
308
+ */
309
+ export const clearAllListeners = /* @__PURE__ */ createAction(
310
+ `${alm}/removeAll`,
311
+ )
312
+
313
+ /**
314
+ * @public
315
+ */
316
+ export const removeListener = /* @__PURE__ */ assign(
317
+ /* @__PURE__ */ createAction(`${alm}/remove`),
318
+ {
319
+ withTypes: () => removeListener,
320
+ },
321
+ ) as unknown as TypedRemoveListener<unknown>
322
+
323
+ const defaultErrorHandler: ListenerErrorHandler = (...args: unknown[]) => {
324
+ console.error(`${alm}/error`, ...args)
325
+ }
326
+
327
+ /**
328
+ * @public
329
+ */
330
+ export const createListenerMiddleware = <
331
+ StateType = unknown,
332
+ DispatchType extends Dispatch<Action> = ThunkDispatch<
333
+ StateType,
334
+ unknown,
335
+ UnknownAction
336
+ >,
337
+ ExtraArgument = unknown,
338
+ >(
339
+ middlewareOptions: CreateListenerMiddlewareOptions<ExtraArgument> = {},
340
+ ) => {
341
+ const listenerMap = new Map<string, ListenerEntry>()
342
+
343
+ // Track listeners whose effect is currently executing so clearListeners can
344
+ // abort even listeners that have become unsubscribed while executing.
345
+ const executingListeners = new Map<ListenerEntry, number>()
346
+ const trackExecutingListener = (entry: ListenerEntry) => {
347
+ const count = executingListeners.get(entry) ?? 0
348
+ executingListeners.set(entry, count + 1)
349
+ }
350
+ const untrackExecutingListener = (entry: ListenerEntry) => {
351
+ const count = executingListeners.get(entry) ?? 1
352
+ if (count === 1) {
353
+ executingListeners.delete(entry)
354
+ } else {
355
+ executingListeners.set(entry, count - 1)
356
+ }
357
+ }
358
+
359
+ const { extra, onError = defaultErrorHandler } = middlewareOptions
360
+
361
+ assertFunction(onError, 'onError')
362
+
363
+ const insertEntry = (entry: ListenerEntry) => {
364
+ entry.unsubscribe = () => listenerMap.delete(entry.id)
365
+
366
+ listenerMap.set(entry.id, entry)
367
+ return (cancelOptions?: UnsubscribeListenerOptions) => {
368
+ entry.unsubscribe()
369
+ if (cancelOptions?.cancelActive) {
370
+ cancelActiveListeners(entry)
371
+ }
372
+ }
373
+ }
374
+
375
+ const startListening = ((options: FallbackAddListenerOptions) => {
376
+ const entry =
377
+ findListenerEntry(listenerMap, options) ??
378
+ createListenerEntry(options as any)
379
+
380
+ return insertEntry(entry)
381
+ }) as AddListenerOverloads<any>
382
+
383
+ assign(startListening, {
384
+ withTypes: () => startListening,
385
+ })
386
+
387
+ const stopListening = (
388
+ options: FallbackAddListenerOptions & UnsubscribeListenerOptions,
389
+ ): boolean => {
390
+ const entry = findListenerEntry(listenerMap, options)
391
+
392
+ if (entry) {
393
+ entry.unsubscribe()
394
+ if (options.cancelActive) {
395
+ cancelActiveListeners(entry)
396
+ }
397
+ }
398
+
399
+ return !!entry
400
+ }
401
+
402
+ assign(stopListening, {
403
+ withTypes: () => stopListening,
404
+ })
405
+
406
+ const notifyListener = async (
407
+ entry: ListenerEntry<unknown, Dispatch<UnknownAction>>,
408
+ action: unknown,
409
+ api: MiddlewareAPI,
410
+ getOriginalState: () => StateType,
411
+ ) => {
412
+ const internalTaskController = new AbortController()
413
+ const take = createTakePattern(
414
+ startListening as AddListenerOverloads<any>,
415
+ internalTaskController.signal,
416
+ )
417
+ const autoJoinPromises: Promise<any>[] = []
418
+
419
+ try {
420
+ entry.pending.add(internalTaskController)
421
+ trackExecutingListener(entry)
422
+ await Promise.resolve(
423
+ entry.effect(
424
+ action,
425
+ // Use assign() rather than ... to avoid extra helper functions added to bundle
426
+ assign({}, api, {
427
+ getOriginalState,
428
+ condition: (
429
+ predicate: AnyListenerPredicate<any>,
430
+ timeout?: number,
431
+ ) => take(predicate, timeout).then(Boolean),
432
+ take,
433
+ delay: createDelay(internalTaskController.signal),
434
+ pause: createPause<any>(internalTaskController.signal),
435
+ extra,
436
+ signal: internalTaskController.signal,
437
+ fork: createFork(internalTaskController.signal, autoJoinPromises),
438
+ unsubscribe: entry.unsubscribe,
439
+ subscribe: () => {
440
+ listenerMap.set(entry.id, entry)
441
+ },
442
+ cancelActiveListeners: () => {
443
+ entry.pending.forEach((controller, _, set) => {
444
+ if (controller !== internalTaskController) {
445
+ controller.abort(listenerCancelled)
446
+ set.delete(controller)
447
+ }
448
+ })
449
+ },
450
+ cancel: () => {
451
+ internalTaskController.abort(listenerCancelled)
452
+ entry.pending.delete(internalTaskController)
453
+ },
454
+ throwIfCancelled: () => {
455
+ validateActive(internalTaskController.signal)
456
+ },
457
+ }),
458
+ ),
459
+ )
460
+ } catch (listenerError) {
461
+ if (!(listenerError instanceof TaskAbortError)) {
462
+ safelyNotifyError(onError, listenerError, {
463
+ raisedBy: 'effect',
464
+ })
465
+ }
466
+ } finally {
467
+ await Promise.all(autoJoinPromises)
468
+
469
+ internalTaskController.abort(listenerCompleted) // Notify that the task has completed
470
+ untrackExecutingListener(entry)
471
+ entry.pending.delete(internalTaskController)
472
+ }
473
+ }
474
+
475
+ const clearListenerMiddleware = createClearListenerMiddleware(
476
+ listenerMap,
477
+ executingListeners,
478
+ )
479
+
480
+ const middleware: ListenerMiddleware<
481
+ StateType,
482
+ DispatchType,
483
+ ExtraArgument
484
+ > = (api) => (next) => (action) => {
485
+ if (!isAction(action)) {
486
+ // we only want to notify listeners for action objects
487
+ return next(action)
488
+ }
489
+
490
+ if (addListener.match(action)) {
491
+ return startListening(action.payload as any)
492
+ }
493
+
494
+ if (clearAllListeners.match(action)) {
495
+ clearListenerMiddleware()
496
+ return
497
+ }
498
+
499
+ if (removeListener.match(action)) {
500
+ return stopListening(action.payload)
501
+ }
502
+
503
+ // Need to get this state _before_ the reducer processes the action
504
+ let originalState: StateType | typeof INTERNAL_NIL_TOKEN = api.getState()
505
+
506
+ // `getOriginalState` can only be called synchronously.
507
+ // @see https://github.com/reduxjs/redux-toolkit/discussions/1648#discussioncomment-1932820
508
+ const getOriginalState = (): StateType => {
509
+ if (originalState === INTERNAL_NIL_TOKEN) {
510
+ throw new Error(
511
+ `${alm}: getOriginalState can only be called synchronously`,
512
+ )
513
+ }
514
+
515
+ return originalState as StateType
516
+ }
517
+
518
+ let result: unknown
519
+
520
+ try {
521
+ // Actually forward the action to the reducer before we handle listeners
522
+ result = next(action)
523
+
524
+ if (listenerMap.size > 0) {
525
+ const currentState = api.getState()
526
+ // Work around ESBuild+TS transpilation issue
527
+ const listenerEntries = Array.from(listenerMap.values())
528
+ for (const entry of listenerEntries) {
529
+ let runListener = false
530
+
531
+ try {
532
+ runListener = entry.predicate(action, currentState, originalState)
533
+ } catch (predicateError) {
534
+ runListener = false
535
+
536
+ safelyNotifyError(onError, predicateError, {
537
+ raisedBy: 'predicate',
538
+ })
539
+ }
540
+
541
+ if (!runListener) {
542
+ continue
543
+ }
544
+
545
+ notifyListener(entry, action, api, getOriginalState)
546
+ }
547
+ }
548
+ } finally {
549
+ // Remove `originalState` store from this scope.
550
+ originalState = INTERNAL_NIL_TOKEN
551
+ }
552
+
553
+ return result
554
+ }
555
+
556
+ return {
557
+ middleware,
558
+ startListening,
559
+ stopListening,
560
+ clearListeners: clearListenerMiddleware,
561
+ } as ListenerMiddlewareInstance<StateType, DispatchType, ExtraArgument>
562
+ }
@@ -0,0 +1,100 @@
1
+ import { TaskAbortError } from './exceptions'
2
+ import type { TaskResult } from './types'
3
+ import { addAbortSignalListener, catchRejection, noop } from './utils'
4
+
5
+ /**
6
+ * Synchronously raises {@link TaskAbortError} if the task tied to the input `signal` has been cancelled.
7
+ * @param signal
8
+ * @see {TaskAbortError}
9
+ * @throws {TaskAbortError} if the task tied to the input `signal` has been cancelled.
10
+ */
11
+ export const validateActive = (signal: AbortSignal): void => {
12
+ if (signal.aborted) {
13
+ throw new TaskAbortError(signal.reason)
14
+ }
15
+ }
16
+
17
+ /**
18
+ * Generates a race between the promise(s) and the AbortSignal
19
+ * This avoids `Promise.race()`-related memory leaks:
20
+ * https://github.com/nodejs/node/issues/17469#issuecomment-349794909
21
+ */
22
+ export function raceWithSignal<T>(
23
+ signal: AbortSignal,
24
+ promise: Promise<T>,
25
+ ): Promise<T> {
26
+ let cleanup = noop
27
+ return new Promise<T>((resolve, reject) => {
28
+ const notifyRejection = () => reject(new TaskAbortError(signal.reason))
29
+
30
+ if (signal.aborted) {
31
+ notifyRejection()
32
+ return
33
+ }
34
+
35
+ cleanup = addAbortSignalListener(signal, notifyRejection)
36
+ promise.finally(() => cleanup()).then(resolve, reject)
37
+ }).finally(() => {
38
+ // after this point, replace `cleanup` with a noop, so there is no reference to `signal` any more
39
+ cleanup = noop
40
+ })
41
+ }
42
+
43
+ /**
44
+ * Runs a task and returns promise that resolves to {@link TaskResult}.
45
+ * Second argument is an optional `cleanUp` function that always runs after task.
46
+ *
47
+ * **Note:** `runTask` runs the executor in the next microtask.
48
+ * @returns
49
+ */
50
+ export const runTask = async <T>(
51
+ task: () => Promise<T>,
52
+ cleanUp?: () => void,
53
+ ): Promise<TaskResult<T>> => {
54
+ try {
55
+ await Promise.resolve()
56
+ const value = await task()
57
+ return {
58
+ status: 'ok',
59
+ value,
60
+ }
61
+ } catch (error: any) {
62
+ return {
63
+ status: error instanceof TaskAbortError ? 'cancelled' : 'rejected',
64
+ error,
65
+ }
66
+ } finally {
67
+ cleanUp?.()
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Given an input `AbortSignal` and a promise returns another promise that resolves
73
+ * as soon the input promise is provided or rejects as soon as
74
+ * `AbortSignal.abort` is `true`.
75
+ * @param signal
76
+ * @returns
77
+ */
78
+ export const createPause = <T>(signal: AbortSignal) => {
79
+ return (promise: Promise<T>): Promise<T> => {
80
+ return catchRejection(
81
+ raceWithSignal(signal, promise).then((output) => {
82
+ validateActive(signal)
83
+ return output
84
+ }),
85
+ )
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Given an input `AbortSignal` and `timeoutMs` returns a promise that resolves
91
+ * after `timeoutMs` or rejects as soon as `AbortSignal.abort` is `true`.
92
+ * @param signal
93
+ * @returns
94
+ */
95
+ export const createDelay = (signal: AbortSignal) => {
96
+ const pause = createPause<void>(signal)
97
+ return (timeoutMs: number): Promise<void> => {
98
+ return pause(new Promise<void>((resolve) => setTimeout(resolve, timeoutMs)))
99
+ }
100
+ }