import type { Reducer, ReducersMapObject, Middleware, Action, StoreEnhancer, Store, UnknownAction, } from 'redux' import { applyMiddleware, createStore, compose, combineReducers, isPlainObject, } from 'redux' 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' const IS_PRODUCTION = process.env.NODE_ENV === 'production' /** * Options for `configureStore()`. * * @public */ export interface ConfigureStoreOptions< S = any, A extends Action = UnknownAction, M extends Tuple> = Tuple>, E extends Tuple = Tuple, 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 | ReducersMapObject /** * 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) => M /** * Whether to enable Redux DevTools integration. Defaults to `true`. * * Additional configuration can be done by passing Redux DevTools options */ devTools?: boolean | DevToolsOptions /** * 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) => E } export type Middlewares = ReadonlyArray> type Enhancers = ReadonlyArray /** * 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 & Store>> /** * 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> = Tuple<[ThunkMiddlewareFor]>, E extends Tuple = Tuple< [StoreEnhancer<{ dispatch: ExtractDispatchExtensions }>, StoreEnhancer] >, P = S, >(options: ConfigureStoreOptions): EnhancedStore { const getDefaultMiddleware = buildGetDefaultMiddleware() const { reducer = undefined, middleware, devTools = true, preloadedState = undefined, enhancers = undefined, } = options || {} let rootReducer: Reducer if (typeof reducer === 'function') { rootReducer = reducer } else if (isPlainObject(reducer)) { rootReducer = combineReducers(reducer) as unknown as Reducer } 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 (!IS_PRODUCTION && middleware && typeof middleware !== 'function') { throw new Error('`middleware` field must be a callback') } let finalMiddleware: Tuple> if (typeof middleware === 'function') { finalMiddleware = middleware(getDefaultMiddleware) if (!IS_PRODUCTION && !Array.isArray(finalMiddleware)) { throw new Error( 'when using a middleware builder function, an array of middleware must be returned', ) } } else { finalMiddleware = getDefaultMiddleware() } if ( !IS_PRODUCTION && finalMiddleware.some((item: any) => typeof item !== 'function') ) { throw new Error( 'each middleware provided to configureStore must be a function', ) } let finalCompose = compose if (devTools) { finalCompose = composeWithDevTools({ // Enable capture of stack traces for dispatched Redux actions trace: !IS_PRODUCTION, ...(typeof devTools === 'object' && devTools), }) } const middlewareEnhancer = applyMiddleware(...finalMiddleware) const getDefaultEnhancers = buildGetDefaultEnhancers(middlewareEnhancer) if (!IS_PRODUCTION && enhancers && typeof enhancers !== 'function') { throw new Error('`enhancers` field must be a callback') } let storeEnhancers = typeof enhancers === 'function' ? enhancers(getDefaultEnhancers) : getDefaultEnhancers() if (!IS_PRODUCTION && !Array.isArray(storeEnhancers)) { throw new Error('`enhancers` callback must return an array') } if ( !IS_PRODUCTION && storeEnhancers.some((item: any) => typeof item !== 'function') ) { throw new Error( 'each enhancer provided to configureStore must be a function', ) } if ( !IS_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 = finalCompose(...storeEnhancers) return createStore(rootReducer, preloadedState as P, composedEnhancer) }