File size: 7,740 Bytes
c2b7eb3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
import type {
  Reducer,
  ReducersMapObject,
  Middleware,
  Action,
  StoreEnhancer,
  Store,
  UnknownAction,
} from 'redux'
import {
  applyMiddleware,
  createStore,
  compose,
  combineReducers,
  isPlainObject,
} from './reduxImports'
import type { DevToolsEnhancerOptions as DevToolsOptions } from './devtoolsExtension'
import { composeWithDevTools } from './devtoolsExtension'

import type {
  ThunkMiddlewareFor,
  GetDefaultMiddleware,
} from './getDefaultMiddleware'
import { buildGetDefaultMiddleware } from './getDefaultMiddleware'
import type {
  ExtractDispatchExtensions,
  ExtractStoreExtensions,
  ExtractStateExtensions,
  UnknownIfNonSpecific,
} from './tsHelpers'
import type { Tuple } from './utils'
import type { GetDefaultEnhancers } from './getDefaultEnhancers'
import { buildGetDefaultEnhancers } from './getDefaultEnhancers'

/**
 * Options for `configureStore()`.
 *
 * @public
 */
export interface ConfigureStoreOptions<
  S = any,
  A extends Action = UnknownAction,
  M extends Tuple<Middlewares<S>> = Tuple<Middlewares<S>>,
  E extends Tuple<Enhancers> = Tuple<Enhancers>,
  P = S,
> {
  /**
   * A single reducer function that will be used as the root reducer, or an
   * object of slice reducers that will be passed to `combineReducers()`.
   */
  reducer: Reducer<S, A, P> | ReducersMapObject<S, A, P>

  /**
   * An array of Redux middleware to install, or a callback receiving `getDefaultMiddleware` and returning a Tuple of middleware.
   * If not supplied, defaults to the set of middleware returned by `getDefaultMiddleware()`.
   *
   * @example `middleware: (gDM) => gDM().concat(logger, apiMiddleware, yourCustomMiddleware)`
   * @see https://redux-toolkit.js.org/api/getDefaultMiddleware#intended-usage
   */
  middleware?: (getDefaultMiddleware: GetDefaultMiddleware<S>) => M

  /**
   * Whether to enable Redux DevTools integration. Defaults to `true`.
   *
   * Additional configuration can be done by passing Redux DevTools options
   */
  devTools?: boolean | DevToolsOptions

  /**
   * Whether to check for duplicate middleware instances. Defaults to `true`.
   */
  duplicateMiddlewareCheck?: boolean

  /**
   * The initial state, same as Redux's createStore.
   * You may optionally specify it to hydrate the state
   * from the server in universal apps, or to restore a previously serialized
   * user session. If you use `combineReducers()` to produce the root reducer
   * function (either directly or indirectly by passing an object as `reducer`),
   * this must be an object with the same shape as the reducer map keys.
   */
  // we infer here, and instead complain if the reducer doesn't match
  preloadedState?: P

  /**
   * The store enhancers to apply. See Redux's `createStore()`.
   * All enhancers will be included before the DevTools Extension enhancer.
   * If you need to customize the order of enhancers, supply a callback
   * function that will receive a `getDefaultEnhancers` function that returns a Tuple,
   * and should return a Tuple of enhancers (such as `getDefaultEnhancers().concat(offline)`).
   * If you only need to add middleware, you can use the `middleware` parameter instead.
   */
  enhancers?: (getDefaultEnhancers: GetDefaultEnhancers<M>) => E
}

export type Middlewares<S> = ReadonlyArray<Middleware<{}, S>>

type Enhancers = ReadonlyArray<StoreEnhancer>

/**
 * A Redux store returned by `configureStore()`. Supports dispatching
 * side-effectful _thunks_ in addition to plain actions.
 *
 * @public
 */
export type EnhancedStore<
  S = any,
  A extends Action = UnknownAction,
  E extends Enhancers = Enhancers,
> = ExtractStoreExtensions<E> &
  Store<S, A, UnknownIfNonSpecific<ExtractStateExtensions<E>>>

/**
 * A friendly abstraction over the standard Redux `createStore()` function.
 *
 * @param options The store configuration.
 * @returns A configured Redux store.
 *
 * @public
 */
export function configureStore<
  S = any,
  A extends Action = UnknownAction,
  M extends Tuple<Middlewares<S>> = Tuple<[ThunkMiddlewareFor<S>]>,
  E extends Tuple<Enhancers> = Tuple<
    [StoreEnhancer<{ dispatch: ExtractDispatchExtensions<M> }>, StoreEnhancer]
  >,
  P = S,
>(options: ConfigureStoreOptions<S, A, M, E, P>): EnhancedStore<S, A, E> {
  const getDefaultMiddleware = buildGetDefaultMiddleware<S>()

  const {
    reducer = undefined,
    middleware,
    devTools = true,
    duplicateMiddlewareCheck = true,
    preloadedState = undefined,
    enhancers = undefined,
  } = options || {}

  let rootReducer: Reducer<S, A, P>

  if (typeof reducer === 'function') {
    rootReducer = reducer
  } else if (isPlainObject(reducer)) {
    rootReducer = combineReducers(reducer) as unknown as Reducer<S, A, P>
  } else {
    throw new Error(
      '`reducer` is a required argument, and must be a function or an object of functions that can be passed to combineReducers',
    )
  }

  if (
    process.env.NODE_ENV !== 'production' &&
    middleware &&
    typeof middleware !== 'function'
  ) {
    throw new Error('`middleware` field must be a callback')
  }

  let finalMiddleware: Tuple<Middlewares<S>>
  if (typeof middleware === 'function') {
    finalMiddleware = middleware(getDefaultMiddleware)

    if (
      process.env.NODE_ENV !== 'production' &&
      !Array.isArray(finalMiddleware)
    ) {
      throw new Error(
        'when using a middleware builder function, an array of middleware must be returned',
      )
    }
  } else {
    finalMiddleware = getDefaultMiddleware()
  }
  if (
    process.env.NODE_ENV !== 'production' &&
    finalMiddleware.some((item: any) => typeof item !== 'function')
  ) {
    throw new Error(
      'each middleware provided to configureStore must be a function',
    )
  }

  if (process.env.NODE_ENV !== 'production' && duplicateMiddlewareCheck) {
    let middlewareReferences = new Set<Middleware<any, S>>()
    finalMiddleware.forEach((middleware) => {
      if (middlewareReferences.has(middleware)) {
        throw new Error(
          'Duplicate middleware references found when creating the store. Ensure that each middleware is only included once.',
        )
      }
      middlewareReferences.add(middleware)
    })
  }

  let finalCompose = compose

  if (devTools) {
    finalCompose = composeWithDevTools({
      // Enable capture of stack traces for dispatched Redux actions
      trace: process.env.NODE_ENV !== 'production',
      ...(typeof devTools === 'object' && devTools),
    })
  }

  const middlewareEnhancer = applyMiddleware(...finalMiddleware)

  const getDefaultEnhancers = buildGetDefaultEnhancers<M>(middlewareEnhancer)

  if (
    process.env.NODE_ENV !== 'production' &&
    enhancers &&
    typeof enhancers !== 'function'
  ) {
    throw new Error('`enhancers` field must be a callback')
  }

  let storeEnhancers =
    typeof enhancers === 'function'
      ? enhancers(getDefaultEnhancers)
      : getDefaultEnhancers()

  if (process.env.NODE_ENV !== 'production' && !Array.isArray(storeEnhancers)) {
    throw new Error('`enhancers` callback must return an array')
  }
  if (
    process.env.NODE_ENV !== 'production' &&
    storeEnhancers.some((item: any) => typeof item !== 'function')
  ) {
    throw new Error(
      'each enhancer provided to configureStore must be a function',
    )
  }
  if (
    process.env.NODE_ENV !== 'production' &&
    finalMiddleware.length &&
    !storeEnhancers.includes(middlewareEnhancer)
  ) {
    console.error(
      'middlewares were provided, but middleware enhancer was not included in final enhancers - make sure to call `getDefaultEnhancers`',
    )
  }

  const composedEnhancer: StoreEnhancer<any> = finalCompose(...storeEnhancers)

  return createStore(rootReducer, preloadedState as P, composedEnhancer)
}