@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,146 @@
|
|
|
1
|
+
import type { StoreEnhancer } from 'redux'
|
|
2
|
+
|
|
3
|
+
export const SHOULD_AUTOBATCH = 'RTK_autoBatch'
|
|
4
|
+
|
|
5
|
+
export const prepareAutoBatched =
|
|
6
|
+
<T>() =>
|
|
7
|
+
(payload: T): { payload: T; meta: unknown } => ({
|
|
8
|
+
payload,
|
|
9
|
+
meta: { [SHOULD_AUTOBATCH]: true },
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const createQueueWithTimer = (timeout: number) => {
|
|
13
|
+
return (notify: () => void) => {
|
|
14
|
+
setTimeout(notify, timeout)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const createRafWithFallbackTimer = (
|
|
19
|
+
raf: typeof requestAnimationFrame,
|
|
20
|
+
timeout: number,
|
|
21
|
+
) => {
|
|
22
|
+
return (notify: () => void) => {
|
|
23
|
+
let called = false
|
|
24
|
+
const callback = () => {
|
|
25
|
+
if (called) return
|
|
26
|
+
called = true
|
|
27
|
+
cancelAnimationFrame(rafId)
|
|
28
|
+
clearTimeout(timerId)
|
|
29
|
+
notify()
|
|
30
|
+
}
|
|
31
|
+
const rafId = raf(callback)
|
|
32
|
+
const timerId = setTimeout(callback, timeout)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type AutoBatchOptions =
|
|
37
|
+
| { type: 'tick' }
|
|
38
|
+
| { type: 'timer'; timeout: number }
|
|
39
|
+
| { type: 'raf' }
|
|
40
|
+
| { type: 'callback'; queueNotification: (notify: () => void) => void }
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A Redux store enhancer that watches for "low-priority" actions, and delays
|
|
44
|
+
* notifying subscribers until either the queued callback executes or the
|
|
45
|
+
* next "standard-priority" action is dispatched.
|
|
46
|
+
*
|
|
47
|
+
* This allows dispatching multiple "low-priority" actions in a row with only
|
|
48
|
+
* a single subscriber notification to the UI after the sequence of actions
|
|
49
|
+
* is finished, thus improving UI re-render performance.
|
|
50
|
+
*
|
|
51
|
+
* Watches for actions with the `action.meta[SHOULD_AUTOBATCH]` attribute.
|
|
52
|
+
* This can be added to `action.meta` manually, or by using the
|
|
53
|
+
* `prepareAutoBatched` helper.
|
|
54
|
+
*
|
|
55
|
+
* By default, it will queue a notification for the end of the event loop tick.
|
|
56
|
+
* However, you can pass several other options to configure the behavior:
|
|
57
|
+
* - `{type: 'tick'}`: queues using `queueMicrotask`
|
|
58
|
+
* - `{type: 'timer', timeout: number}`: queues using `setTimeout`
|
|
59
|
+
* - `{type: 'raf'}`: queues using `requestAnimationFrame` (default)
|
|
60
|
+
* - `{type: 'callback', queueNotification: (notify: () => void) => void}`: lets you provide your own callback
|
|
61
|
+
*
|
|
62
|
+
*
|
|
63
|
+
*/
|
|
64
|
+
export const autoBatchEnhancer =
|
|
65
|
+
(options: AutoBatchOptions = { type: 'raf' }): StoreEnhancer =>
|
|
66
|
+
(next) =>
|
|
67
|
+
(...args) => {
|
|
68
|
+
const store = next(...args)
|
|
69
|
+
|
|
70
|
+
let notifying = true
|
|
71
|
+
let shouldNotifyAtEndOfTick = false
|
|
72
|
+
let notificationQueued = false
|
|
73
|
+
|
|
74
|
+
const listeners = new Set<() => void>()
|
|
75
|
+
|
|
76
|
+
const queueCallback =
|
|
77
|
+
options.type === 'tick'
|
|
78
|
+
? queueMicrotask
|
|
79
|
+
: options.type === 'raf'
|
|
80
|
+
? // requestAnimationFrame won't exist in SSR environments. Fall back to a vague approximation just to keep from erroring.
|
|
81
|
+
typeof window !== 'undefined' && window.requestAnimationFrame
|
|
82
|
+
? createRafWithFallbackTimer(window.requestAnimationFrame, 100)
|
|
83
|
+
: createQueueWithTimer(10)
|
|
84
|
+
: options.type === 'callback'
|
|
85
|
+
? options.queueNotification
|
|
86
|
+
: createQueueWithTimer(options.timeout)
|
|
87
|
+
|
|
88
|
+
const notifyListeners = () => {
|
|
89
|
+
// We're running at the end of the event loop tick.
|
|
90
|
+
// Run the real listener callbacks to actually update the UI.
|
|
91
|
+
notificationQueued = false
|
|
92
|
+
if (shouldNotifyAtEndOfTick) {
|
|
93
|
+
shouldNotifyAtEndOfTick = false
|
|
94
|
+
listeners.forEach((l) => l())
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return Object.assign({}, store, {
|
|
99
|
+
// Override the base `store.subscribe` method to keep original listeners
|
|
100
|
+
// from running if we're delaying notifications
|
|
101
|
+
subscribe(listener: () => void) {
|
|
102
|
+
// Each wrapped listener will only call the real listener if
|
|
103
|
+
// the `notifying` flag is currently active when it's called.
|
|
104
|
+
// This lets the base store work as normal, while the actual UI
|
|
105
|
+
// update becomes controlled by this enhancer.
|
|
106
|
+
const wrappedListener: typeof listener = () => notifying && listener()
|
|
107
|
+
const unsubscribe = store.subscribe(wrappedListener)
|
|
108
|
+
listeners.add(listener)
|
|
109
|
+
return () => {
|
|
110
|
+
unsubscribe()
|
|
111
|
+
listeners.delete(listener)
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
// Override the base `store.dispatch` method so that we can check actions
|
|
115
|
+
// for the `shouldAutoBatch` flag and determine if batching is active
|
|
116
|
+
dispatch(action: any) {
|
|
117
|
+
try {
|
|
118
|
+
// If the action does _not_ have the `shouldAutoBatch` flag,
|
|
119
|
+
// we resume/continue normal notify-after-each-dispatch behavior
|
|
120
|
+
notifying = !action?.meta?.[SHOULD_AUTOBATCH]
|
|
121
|
+
// If a `notifyListeners` microtask was queued, you can't cancel it.
|
|
122
|
+
// Instead, we set a flag so that it's a no-op when it does run
|
|
123
|
+
shouldNotifyAtEndOfTick = !notifying
|
|
124
|
+
if (shouldNotifyAtEndOfTick) {
|
|
125
|
+
// We've seen at least 1 action with `SHOULD_AUTOBATCH`. Try to queue
|
|
126
|
+
// a microtask to notify listeners at the end of the event loop tick.
|
|
127
|
+
// Make sure we only enqueue this _once_ per tick.
|
|
128
|
+
if (!notificationQueued) {
|
|
129
|
+
notificationQueued = true
|
|
130
|
+
queueCallback(notifyListeners)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Go ahead and process the action as usual, including reducers.
|
|
134
|
+
// If normal notification behavior is enabled, the store will notify
|
|
135
|
+
// all of its own listeners, and the wrapper callbacks above will
|
|
136
|
+
// see `notifying` is true and pass on to the real listener callbacks.
|
|
137
|
+
// If we're "batching" behavior, then the wrapped callbacks will
|
|
138
|
+
// bail out, causing the base store notification behavior to be no-ops.
|
|
139
|
+
return store.dispatch(action)
|
|
140
|
+
} finally {
|
|
141
|
+
// Assume we're back to normal behavior after each action
|
|
142
|
+
notifying = true
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
})
|
|
146
|
+
}
|
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PreloadedStateShapeFromReducersMapObject,
|
|
3
|
+
Reducer,
|
|
4
|
+
StateFromReducersMapObject,
|
|
5
|
+
UnknownAction,
|
|
6
|
+
} from 'redux'
|
|
7
|
+
import { combineReducers } from 'redux'
|
|
8
|
+
import { nanoid } from './nanoid'
|
|
9
|
+
import type {
|
|
10
|
+
Id,
|
|
11
|
+
NonUndefined,
|
|
12
|
+
Tail,
|
|
13
|
+
UnionToIntersection,
|
|
14
|
+
WithOptionalProp,
|
|
15
|
+
} from './tsHelpers'
|
|
16
|
+
import { getOrInsertComputed } from './utils'
|
|
17
|
+
|
|
18
|
+
type SliceLike<ReducerPath extends string, State, PreloadedState = State> = {
|
|
19
|
+
reducerPath: ReducerPath
|
|
20
|
+
reducer: Reducer<State, any, PreloadedState>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type AnySliceLike = SliceLike<string, any>
|
|
24
|
+
|
|
25
|
+
type SliceLikeReducerPath<A extends AnySliceLike> =
|
|
26
|
+
A extends SliceLike<infer ReducerPath, any> ? ReducerPath : never
|
|
27
|
+
|
|
28
|
+
type SliceLikeState<A extends AnySliceLike> =
|
|
29
|
+
A extends SliceLike<any, infer State, any> ? State : never
|
|
30
|
+
|
|
31
|
+
type SliceLikePreloadedState<A extends AnySliceLike> =
|
|
32
|
+
A extends SliceLike<any, any, infer PreloadedState> ? PreloadedState : never
|
|
33
|
+
|
|
34
|
+
export type WithSlice<A extends AnySliceLike> = {
|
|
35
|
+
[Path in SliceLikeReducerPath<A>]: SliceLikeState<A>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type WithSlicePreloadedState<A extends AnySliceLike> = {
|
|
39
|
+
[Path in SliceLikeReducerPath<A>]: SliceLikePreloadedState<A>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type ReducerMap = Record<string, Reducer>
|
|
43
|
+
|
|
44
|
+
type ExistingSliceLike<DeclaredState, PreloadedState> = {
|
|
45
|
+
[ReducerPath in keyof DeclaredState]: SliceLike<
|
|
46
|
+
ReducerPath & string,
|
|
47
|
+
NonUndefined<DeclaredState[ReducerPath]>,
|
|
48
|
+
NonUndefined<PreloadedState[ReducerPath & keyof PreloadedState]>
|
|
49
|
+
>
|
|
50
|
+
}[keyof DeclaredState]
|
|
51
|
+
|
|
52
|
+
export type InjectConfig = {
|
|
53
|
+
/**
|
|
54
|
+
* Allow replacing reducer with a different reference. Normally, an error will be thrown if a different reducer instance to the one already injected is used.
|
|
55
|
+
*/
|
|
56
|
+
overrideExisting?: boolean
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A reducer that allows for slices/reducers to be injected after initialisation.
|
|
61
|
+
*/
|
|
62
|
+
export interface CombinedSliceReducer<
|
|
63
|
+
InitialState,
|
|
64
|
+
DeclaredState extends InitialState = InitialState,
|
|
65
|
+
PreloadedState extends Partial<
|
|
66
|
+
Record<keyof PreloadedState, any>
|
|
67
|
+
> = Partial<DeclaredState>,
|
|
68
|
+
> extends Reducer<DeclaredState, UnknownAction, PreloadedState> {
|
|
69
|
+
/**
|
|
70
|
+
* Provide a type for slices that will be injected lazily.
|
|
71
|
+
*
|
|
72
|
+
* One way to do this would be with interface merging:
|
|
73
|
+
* ```ts
|
|
74
|
+
*
|
|
75
|
+
* export interface LazyLoadedSlices {}
|
|
76
|
+
*
|
|
77
|
+
* export const rootReducer = combineSlices(stringSlice).withLazyLoadedSlices<LazyLoadedSlices>();
|
|
78
|
+
*
|
|
79
|
+
* // elsewhere
|
|
80
|
+
*
|
|
81
|
+
* declare module './reducer' {
|
|
82
|
+
* export interface LazyLoadedSlices extends WithSlice<typeof booleanSlice> {}
|
|
83
|
+
* }
|
|
84
|
+
*
|
|
85
|
+
* const withBoolean = rootReducer.inject(booleanSlice);
|
|
86
|
+
*
|
|
87
|
+
* // elsewhere again
|
|
88
|
+
*
|
|
89
|
+
* declare module './reducer' {
|
|
90
|
+
* export interface LazyLoadedSlices {
|
|
91
|
+
* customName: CustomState
|
|
92
|
+
* }
|
|
93
|
+
* }
|
|
94
|
+
*
|
|
95
|
+
* const withCustom = rootReducer.inject({ reducerPath: "customName", reducer: customSlice.reducer })
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
withLazyLoadedSlices<Lazy = {}, LazyPreloaded = Lazy>(): CombinedSliceReducer<
|
|
99
|
+
InitialState,
|
|
100
|
+
Id<DeclaredState & Partial<Lazy>>,
|
|
101
|
+
Id<PreloadedState & Partial<LazyPreloaded>>
|
|
102
|
+
>
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Inject a slice.
|
|
106
|
+
*
|
|
107
|
+
* Accepts an individual slice, RTKQ API instance, or a "slice-like" { reducerPath, reducer } object.
|
|
108
|
+
*
|
|
109
|
+
* ```ts
|
|
110
|
+
* rootReducer.inject(booleanSlice)
|
|
111
|
+
* rootReducer.inject(baseApi)
|
|
112
|
+
* rootReducer.inject({ reducerPath: 'boolean' as const, reducer: newReducer }, { overrideExisting: true })
|
|
113
|
+
* ```
|
|
114
|
+
*
|
|
115
|
+
*/
|
|
116
|
+
inject<Sl extends Id<ExistingSliceLike<DeclaredState, PreloadedState>>>(
|
|
117
|
+
slice: Sl,
|
|
118
|
+
config?: InjectConfig,
|
|
119
|
+
): CombinedSliceReducer<
|
|
120
|
+
InitialState,
|
|
121
|
+
Id<DeclaredState & WithSlice<Sl>>,
|
|
122
|
+
Id<PreloadedState & Partial<WithSlicePreloadedState<Sl>>>
|
|
123
|
+
>
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Inject a slice.
|
|
127
|
+
*
|
|
128
|
+
* Accepts an individual slice, RTKQ API instance, or a "slice-like" { reducerPath, reducer } object.
|
|
129
|
+
*
|
|
130
|
+
* ```ts
|
|
131
|
+
* rootReducer.inject(booleanSlice)
|
|
132
|
+
* rootReducer.inject(baseApi)
|
|
133
|
+
* rootReducer.inject({ reducerPath: 'boolean' as const, reducer: newReducer }, { overrideExisting: true })
|
|
134
|
+
* ```
|
|
135
|
+
*
|
|
136
|
+
*/
|
|
137
|
+
inject<ReducerPath extends string, State, PreloadedState = State>(
|
|
138
|
+
slice: SliceLike<
|
|
139
|
+
ReducerPath,
|
|
140
|
+
State & (ReducerPath extends keyof DeclaredState ? never : State),
|
|
141
|
+
PreloadedState &
|
|
142
|
+
(ReducerPath extends keyof PreloadedState ? never : PreloadedState)
|
|
143
|
+
>,
|
|
144
|
+
config?: InjectConfig,
|
|
145
|
+
): CombinedSliceReducer<
|
|
146
|
+
InitialState,
|
|
147
|
+
Id<DeclaredState & WithSlice<SliceLike<ReducerPath, State>>>,
|
|
148
|
+
Id<
|
|
149
|
+
PreloadedState &
|
|
150
|
+
WithSlicePreloadedState<SliceLike<ReducerPath, State, PreloadedState>>
|
|
151
|
+
>
|
|
152
|
+
>
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Create a selector that guarantees that the slices injected will have a defined value when selector is run.
|
|
156
|
+
*
|
|
157
|
+
* ```ts
|
|
158
|
+
* const selectBooleanWithoutInjection = (state: RootState) => state.boolean;
|
|
159
|
+
* // ^? boolean | undefined
|
|
160
|
+
*
|
|
161
|
+
* const selectBoolean = rootReducer.inject(booleanSlice).selector((state) => {
|
|
162
|
+
* // if action hasn't been dispatched since slice was injected, this would usually be undefined
|
|
163
|
+
* // however selector() uses a Proxy around the first parameter to ensure that it evaluates to the initial state instead, if undefined
|
|
164
|
+
* return state.boolean;
|
|
165
|
+
* // ^? boolean
|
|
166
|
+
* })
|
|
167
|
+
* ```
|
|
168
|
+
*
|
|
169
|
+
* If the reducer is nested inside the root state, a selectState callback can be passed to retrieve the reducer's state.
|
|
170
|
+
*
|
|
171
|
+
* ```ts
|
|
172
|
+
*
|
|
173
|
+
* export interface LazyLoadedSlices {};
|
|
174
|
+
*
|
|
175
|
+
* export const innerReducer = combineSlices(stringSlice).withLazyLoadedSlices<LazyLoadedSlices>();
|
|
176
|
+
*
|
|
177
|
+
* export const rootReducer = combineSlices({ inner: innerReducer });
|
|
178
|
+
*
|
|
179
|
+
* export type RootState = ReturnType<typeof rootReducer>;
|
|
180
|
+
*
|
|
181
|
+
* // elsewhere
|
|
182
|
+
*
|
|
183
|
+
* declare module "./reducer.ts" {
|
|
184
|
+
* export interface LazyLoadedSlices extends WithSlice<typeof booleanSlice> {}
|
|
185
|
+
* }
|
|
186
|
+
*
|
|
187
|
+
* const withBool = innerReducer.inject(booleanSlice);
|
|
188
|
+
*
|
|
189
|
+
* const selectBoolean = withBool.selector(
|
|
190
|
+
* (state) => state.boolean,
|
|
191
|
+
* (rootState: RootState) => state.inner
|
|
192
|
+
* );
|
|
193
|
+
* // now expects to be passed RootState instead of innerReducer state
|
|
194
|
+
*
|
|
195
|
+
* ```
|
|
196
|
+
*
|
|
197
|
+
* Value passed to selectorFn will be a Proxy - use selector.original(proxy) to get original state value (useful for debugging)
|
|
198
|
+
*
|
|
199
|
+
* ```ts
|
|
200
|
+
* const injectedReducer = rootReducer.inject(booleanSlice);
|
|
201
|
+
* const selectBoolean = injectedReducer.selector((state) => {
|
|
202
|
+
* console.log(injectedReducer.selector.original(state).boolean) // possibly undefined
|
|
203
|
+
* return state.boolean
|
|
204
|
+
* })
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
selector: {
|
|
208
|
+
/**
|
|
209
|
+
* Create a selector that guarantees that the slices injected will have a defined value when selector is run.
|
|
210
|
+
*
|
|
211
|
+
* ```ts
|
|
212
|
+
* const selectBooleanWithoutInjection = (state: RootState) => state.boolean;
|
|
213
|
+
* // ^? boolean | undefined
|
|
214
|
+
*
|
|
215
|
+
* const selectBoolean = rootReducer.inject(booleanSlice).selector((state) => {
|
|
216
|
+
* // if action hasn't been dispatched since slice was injected, this would usually be undefined
|
|
217
|
+
* // however selector() uses a Proxy around the first parameter to ensure that it evaluates to the initial state instead, if undefined
|
|
218
|
+
* return state.boolean;
|
|
219
|
+
* // ^? boolean
|
|
220
|
+
* })
|
|
221
|
+
* ```
|
|
222
|
+
*
|
|
223
|
+
* Value passed to selectorFn will be a Proxy - use selector.original(proxy) to get original state value (useful for debugging)
|
|
224
|
+
*
|
|
225
|
+
* ```ts
|
|
226
|
+
* const injectedReducer = rootReducer.inject(booleanSlice);
|
|
227
|
+
* const selectBoolean = injectedReducer.selector((state) => {
|
|
228
|
+
* console.log(injectedReducer.selector.original(state).boolean) // undefined
|
|
229
|
+
* return state.boolean
|
|
230
|
+
* })
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
233
|
+
<Selector extends (state: DeclaredState, ...args: any[]) => unknown>(
|
|
234
|
+
selectorFn: Selector,
|
|
235
|
+
): (
|
|
236
|
+
state: WithOptionalProp<
|
|
237
|
+
Parameters<Selector>[0],
|
|
238
|
+
Exclude<keyof DeclaredState, keyof InitialState>
|
|
239
|
+
>,
|
|
240
|
+
...args: Tail<Parameters<Selector>>
|
|
241
|
+
) => ReturnType<Selector>
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Create a selector that guarantees that the slices injected will have a defined value when selector is run.
|
|
245
|
+
*
|
|
246
|
+
* ```ts
|
|
247
|
+
* const selectBooleanWithoutInjection = (state: RootState) => state.boolean;
|
|
248
|
+
* // ^? boolean | undefined
|
|
249
|
+
*
|
|
250
|
+
* const selectBoolean = rootReducer.inject(booleanSlice).selector((state) => {
|
|
251
|
+
* // if action hasn't been dispatched since slice was injected, this would usually be undefined
|
|
252
|
+
* // however selector() uses a Proxy around the first parameter to ensure that it evaluates to the initial state instead, if undefined
|
|
253
|
+
* return state.boolean;
|
|
254
|
+
* // ^? boolean
|
|
255
|
+
* })
|
|
256
|
+
* ```
|
|
257
|
+
*
|
|
258
|
+
* If the reducer is nested inside the root state, a selectState callback can be passed to retrieve the reducer's state.
|
|
259
|
+
*
|
|
260
|
+
* ```ts
|
|
261
|
+
*
|
|
262
|
+
* interface LazyLoadedSlices {};
|
|
263
|
+
*
|
|
264
|
+
* const innerReducer = combineSlices(stringSlice).withLazyLoadedSlices<LazyLoadedSlices>();
|
|
265
|
+
*
|
|
266
|
+
* const rootReducer = combineSlices({ inner: innerReducer });
|
|
267
|
+
*
|
|
268
|
+
* type RootState = ReturnType<typeof rootReducer>;
|
|
269
|
+
*
|
|
270
|
+
* // elsewhere
|
|
271
|
+
*
|
|
272
|
+
* declare module "./reducer.ts" {
|
|
273
|
+
* interface LazyLoadedSlices extends WithSlice<typeof booleanSlice> {}
|
|
274
|
+
* }
|
|
275
|
+
*
|
|
276
|
+
* const withBool = innerReducer.inject(booleanSlice);
|
|
277
|
+
*
|
|
278
|
+
* const selectBoolean = withBool.selector(
|
|
279
|
+
* (state) => state.boolean,
|
|
280
|
+
* (rootState: RootState) => state.inner
|
|
281
|
+
* );
|
|
282
|
+
* // now expects to be passed RootState instead of innerReducer state
|
|
283
|
+
*
|
|
284
|
+
* ```
|
|
285
|
+
*
|
|
286
|
+
* Value passed to selectorFn will be a Proxy - use selector.original(proxy) to get original state value (useful for debugging)
|
|
287
|
+
*
|
|
288
|
+
* ```ts
|
|
289
|
+
* const injectedReducer = rootReducer.inject(booleanSlice);
|
|
290
|
+
* const selectBoolean = injectedReducer.selector((state) => {
|
|
291
|
+
* console.log(injectedReducer.selector.original(state).boolean) // possibly undefined
|
|
292
|
+
* return state.boolean
|
|
293
|
+
* })
|
|
294
|
+
* ```
|
|
295
|
+
*/
|
|
296
|
+
<
|
|
297
|
+
Selector extends (state: DeclaredState, ...args: any[]) => unknown,
|
|
298
|
+
RootState,
|
|
299
|
+
>(
|
|
300
|
+
selectorFn: Selector,
|
|
301
|
+
selectState: (
|
|
302
|
+
rootState: RootState,
|
|
303
|
+
...args: Tail<Parameters<Selector>>
|
|
304
|
+
) => WithOptionalProp<
|
|
305
|
+
Parameters<Selector>[0],
|
|
306
|
+
Exclude<keyof DeclaredState, keyof InitialState>
|
|
307
|
+
>,
|
|
308
|
+
): (
|
|
309
|
+
state: RootState,
|
|
310
|
+
...args: Tail<Parameters<Selector>>
|
|
311
|
+
) => ReturnType<Selector>
|
|
312
|
+
/**
|
|
313
|
+
* Returns the unproxied state. Useful for debugging.
|
|
314
|
+
* @param state state Proxy, that ensures injected reducers have value
|
|
315
|
+
* @returns original, unproxied state
|
|
316
|
+
* @throws if value passed is not a state Proxy
|
|
317
|
+
*/
|
|
318
|
+
original: (state: DeclaredState) => InitialState & Partial<DeclaredState>
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
type InitialState<Slices extends Array<AnySliceLike | ReducerMap>> =
|
|
323
|
+
UnionToIntersection<
|
|
324
|
+
Slices[number] extends infer Slice
|
|
325
|
+
? Slice extends AnySliceLike
|
|
326
|
+
? WithSlice<Slice>
|
|
327
|
+
: StateFromReducersMapObject<Slice>
|
|
328
|
+
: never
|
|
329
|
+
>
|
|
330
|
+
|
|
331
|
+
type InitialPreloadedState<Slices extends Array<AnySliceLike | ReducerMap>> =
|
|
332
|
+
UnionToIntersection<
|
|
333
|
+
Slices[number] extends infer Slice
|
|
334
|
+
? Slice extends AnySliceLike
|
|
335
|
+
? WithSlicePreloadedState<Slice>
|
|
336
|
+
: PreloadedStateShapeFromReducersMapObject<Slice>
|
|
337
|
+
: never
|
|
338
|
+
>
|
|
339
|
+
|
|
340
|
+
const isSliceLike = (
|
|
341
|
+
maybeSliceLike: AnySliceLike | ReducerMap,
|
|
342
|
+
): maybeSliceLike is AnySliceLike =>
|
|
343
|
+
'reducerPath' in maybeSliceLike &&
|
|
344
|
+
typeof maybeSliceLike.reducerPath === 'string'
|
|
345
|
+
|
|
346
|
+
const getReducers = (slices: Array<AnySliceLike | ReducerMap>) =>
|
|
347
|
+
slices.flatMap<[string, Reducer]>((sliceOrMap) =>
|
|
348
|
+
isSliceLike(sliceOrMap)
|
|
349
|
+
? [[sliceOrMap.reducerPath, sliceOrMap.reducer]]
|
|
350
|
+
: Object.entries(sliceOrMap),
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
const ORIGINAL_STATE = Symbol.for('rtk-state-proxy-original')
|
|
354
|
+
|
|
355
|
+
const isStateProxy = (value: any) => !!value && !!value[ORIGINAL_STATE]
|
|
356
|
+
|
|
357
|
+
const stateProxyMap = new WeakMap<object, object>()
|
|
358
|
+
|
|
359
|
+
const createStateProxy = <State extends object>(
|
|
360
|
+
state: State,
|
|
361
|
+
reducerMap: Partial<Record<PropertyKey, Reducer>>,
|
|
362
|
+
initialStateCache: Record<PropertyKey, unknown>,
|
|
363
|
+
) =>
|
|
364
|
+
getOrInsertComputed(
|
|
365
|
+
stateProxyMap,
|
|
366
|
+
state,
|
|
367
|
+
() =>
|
|
368
|
+
new Proxy(state, {
|
|
369
|
+
get: (target, prop, receiver) => {
|
|
370
|
+
if (prop === ORIGINAL_STATE) return target
|
|
371
|
+
const result = Reflect.get(target, prop, receiver)
|
|
372
|
+
if (typeof result === 'undefined') {
|
|
373
|
+
const cached = initialStateCache[prop]
|
|
374
|
+
if (typeof cached !== 'undefined') return cached
|
|
375
|
+
const reducer = reducerMap[prop]
|
|
376
|
+
if (reducer) {
|
|
377
|
+
// ensure action type is random, to prevent reducer treating it differently
|
|
378
|
+
const reducerResult = reducer(undefined, { type: nanoid() })
|
|
379
|
+
if (typeof reducerResult === 'undefined') {
|
|
380
|
+
throw new Error(
|
|
381
|
+
`The slice reducer for key "${prop.toString()}" returned undefined when called for selector(). ` +
|
|
382
|
+
`If the state passed to the reducer is undefined, you must ` +
|
|
383
|
+
`explicitly return the initial state. The initial state may ` +
|
|
384
|
+
`not be undefined. If you don't want to set a value for this reducer, ` +
|
|
385
|
+
`you can use null instead of undefined.`,
|
|
386
|
+
)
|
|
387
|
+
}
|
|
388
|
+
initialStateCache[prop] = reducerResult
|
|
389
|
+
return reducerResult
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return result
|
|
393
|
+
},
|
|
394
|
+
}),
|
|
395
|
+
) as State
|
|
396
|
+
|
|
397
|
+
const original = (state: any) => {
|
|
398
|
+
if (!isStateProxy(state)) {
|
|
399
|
+
throw new Error('original must be used on state Proxy')
|
|
400
|
+
}
|
|
401
|
+
return state[ORIGINAL_STATE]
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const emptyObject = {}
|
|
405
|
+
const noopReducer: Reducer<Record<string, any>> = (state = emptyObject) => state
|
|
406
|
+
|
|
407
|
+
export function combineSlices<Slices extends Array<AnySliceLike | ReducerMap>>(
|
|
408
|
+
...slices: Slices
|
|
409
|
+
): CombinedSliceReducer<
|
|
410
|
+
Id<InitialState<Slices>>,
|
|
411
|
+
Id<InitialState<Slices>>,
|
|
412
|
+
Partial<Id<InitialPreloadedState<Slices>>>
|
|
413
|
+
> {
|
|
414
|
+
const reducerMap = Object.fromEntries(getReducers(slices))
|
|
415
|
+
|
|
416
|
+
const getReducer = () =>
|
|
417
|
+
Object.keys(reducerMap).length ? combineReducers(reducerMap) : noopReducer
|
|
418
|
+
|
|
419
|
+
let reducer = getReducer()
|
|
420
|
+
|
|
421
|
+
function combinedReducer(
|
|
422
|
+
state: Record<string, unknown>,
|
|
423
|
+
action: UnknownAction,
|
|
424
|
+
) {
|
|
425
|
+
return reducer(state, action)
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
combinedReducer.withLazyLoadedSlices = () => combinedReducer
|
|
429
|
+
|
|
430
|
+
const initialStateCache: Record<PropertyKey, unknown> = {}
|
|
431
|
+
|
|
432
|
+
const inject = (
|
|
433
|
+
slice: AnySliceLike,
|
|
434
|
+
config: InjectConfig = {},
|
|
435
|
+
): typeof combinedReducer => {
|
|
436
|
+
const { reducerPath, reducer: reducerToInject } = slice
|
|
437
|
+
|
|
438
|
+
const currentReducer = reducerMap[reducerPath]
|
|
439
|
+
if (
|
|
440
|
+
!config.overrideExisting &&
|
|
441
|
+
currentReducer &&
|
|
442
|
+
currentReducer !== reducerToInject
|
|
443
|
+
) {
|
|
444
|
+
if (
|
|
445
|
+
typeof process !== 'undefined' &&
|
|
446
|
+
process.env.NODE_ENV === 'development'
|
|
447
|
+
) {
|
|
448
|
+
console.error(
|
|
449
|
+
`called \`inject\` to override already-existing reducer ${reducerPath} without specifying \`overrideExisting: true\``,
|
|
450
|
+
)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return combinedReducer
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (config.overrideExisting && currentReducer !== reducerToInject) {
|
|
457
|
+
delete initialStateCache[reducerPath]
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
reducerMap[reducerPath] = reducerToInject
|
|
461
|
+
|
|
462
|
+
reducer = getReducer()
|
|
463
|
+
|
|
464
|
+
return combinedReducer
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const selector = Object.assign(
|
|
468
|
+
function makeSelector<State extends object, RootState, Args extends any[]>(
|
|
469
|
+
selectorFn: (state: State, ...args: Args) => any,
|
|
470
|
+
selectState?: (rootState: RootState, ...args: Args) => State,
|
|
471
|
+
) {
|
|
472
|
+
return function selector(state: State, ...args: Args) {
|
|
473
|
+
return selectorFn(
|
|
474
|
+
createStateProxy(
|
|
475
|
+
selectState ? selectState(state as any, ...args) : state,
|
|
476
|
+
reducerMap,
|
|
477
|
+
initialStateCache,
|
|
478
|
+
),
|
|
479
|
+
...args,
|
|
480
|
+
)
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
{ original },
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
return Object.assign(combinedReducer, { inject, selector }) as any
|
|
487
|
+
}
|