@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,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
|
+
}
|