From 5a084a62fda9ebf39ffa969458d9950b4f051688 Mon Sep 17 00:00:00 2001
From: Nathan Bierema <nbierema@gmail.com>
Date: Sun, 12 Feb 2023 14:47:15 -0500
Subject: [PATCH] Fix type of next parameter in StoreEnhancer type

---
 src/applyMiddleware.ts       |  9 +---
 src/createStore.ts           | 33 +++++++++----
 src/index.ts                 |  3 +-
 src/types/store.ts           | 43 ++++++-----------
 test/typescript/enhancers.ts | 92 +++++++++++++++++++++++++++++-------
 test/typescript/store.ts     | 43 +----------------
 6 files changed, 118 insertions(+), 105 deletions(-)

diff --git a/src/applyMiddleware.ts b/src/applyMiddleware.ts
index af68589ab0..744ed27895 100644
--- a/src/applyMiddleware.ts
+++ b/src/applyMiddleware.ts
@@ -1,12 +1,7 @@
 import compose from './compose'
 import { Middleware, MiddlewareAPI } from './types/middleware'
 import { AnyAction } from './types/actions'
-import {
-  StoreEnhancer,
-  Dispatch,
-  PreloadedState,
-  StoreEnhancerStoreCreator
-} from './types/store'
+import { StoreEnhancer, Dispatch, PreloadedState } from './types/store'
 import { Reducer } from './types/reducers'
 
 /**
@@ -60,7 +55,7 @@ export default function applyMiddleware<Ext, S = any>(
 export default function applyMiddleware(
   ...middlewares: Middleware[]
 ): StoreEnhancer<any> {
-  return (createStore: StoreEnhancerStoreCreator) =>
+  return createStore =>
     <S, A extends AnyAction>(
       reducer: Reducer<S, A>,
       preloadedState?: PreloadedState<S>
diff --git a/src/createStore.ts b/src/createStore.ts
index 9e13aa2291..921b723c5f 100644
--- a/src/createStore.ts
+++ b/src/createStore.ts
@@ -39,7 +39,12 @@ import { kindOf } from './utils/kindOf'
  * `import { legacy_createStore as createStore} from 'redux'`
  *
  */
-export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
+export function createStore<
+  S,
+  A extends Action,
+  Ext extends {} = {},
+  StateExt extends {} = {}
+>(
   reducer: Reducer<S, A>,
   enhancer?: StoreEnhancer<Ext, StateExt>
 ): Store<S, A, StateExt> & Ext
@@ -68,12 +73,22 @@ export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
  * `import { legacy_createStore as createStore} from 'redux'`
  *
  */
-export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
+export function createStore<
+  S,
+  A extends Action,
+  Ext extends {} = {},
+  StateExt extends {} = {}
+>(
   reducer: Reducer<S, A>,
   preloadedState?: PreloadedState<S>,
   enhancer?: StoreEnhancer<Ext, StateExt>
 ): Store<S, A, StateExt> & Ext
-export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
+export function createStore<
+  S,
+  A extends Action,
+  Ext extends {} = {},
+  StateExt extends {} = {}
+>(
   reducer: Reducer<S, A>,
   preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
   enhancer?: StoreEnhancer<Ext, StateExt>
@@ -401,8 +416,8 @@ export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
 export function legacy_createStore<
   S,
   A extends Action,
-  Ext = {},
-  StateExt = never
+  Ext extends {} = {},
+  StateExt extends {} = {}
 >(
   reducer: Reducer<S, A>,
   enhancer?: StoreEnhancer<Ext, StateExt>
@@ -440,8 +455,8 @@ export function legacy_createStore<
 export function legacy_createStore<
   S,
   A extends Action,
-  Ext = {},
-  StateExt = never
+  Ext extends {} = {},
+  StateExt extends {} = {}
 >(
   reducer: Reducer<S, A>,
   preloadedState?: PreloadedState<S>,
@@ -450,8 +465,8 @@ export function legacy_createStore<
 export function legacy_createStore<
   S,
   A extends Action,
-  Ext = {},
-  StateExt = never
+  Ext extends {} = {},
+  StateExt extends {} = {}
 >(
   reducer: Reducer<S, A>,
   preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
diff --git a/src/index.ts b/src/index.ts
index 926be4d143..0fa083343b 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -18,8 +18,7 @@ export {
   Store,
   StoreCreator,
   StoreEnhancer,
-  StoreEnhancerStoreCreator,
-  ExtendState
+  StoreEnhancerStoreCreator
 } from './types/store'
 // reducers
 export {
diff --git a/src/types/store.ts b/src/types/store.ts
index fb5b1a1d5f..c477cb6471 100644
--- a/src/types/store.ts
+++ b/src/types/store.ts
@@ -2,20 +2,6 @@ import { Action, AnyAction } from './actions'
 import { Reducer } from './reducers'
 import '../utils/symbol-observable'
 
-/**
- * Extend the state
- *
- * This is used by store enhancers and store creators to extend state.
- * If there is no state extension, it just returns the state, as is, otherwise
- * it returns the state joined with its extension.
- *
- * Reference for future devs:
- * https://github.com/microsoft/TypeScript/issues/31751#issuecomment-498526919
- */
-export type ExtendState<State, Extension> = [Extension] extends [never]
-  ? State
-  : State & Extension
-
 /**
  * Internal "virtual" symbol used to make the `CombinedState` type unique.
  */
@@ -134,11 +120,7 @@ export type Observer<T> = {
  * @template A the type of actions which may be dispatched by this store.
  * @template StateExt any extension to state from store enhancers
  */
-export interface Store<
-  S = any,
-  A extends Action = AnyAction,
-  StateExt = never
-> {
+export interface Store<S = any, A extends Action = AnyAction, StateExt = {}> {
   /**
    * Dispatches an action. It is the only way to trigger a state change.
    *
@@ -172,7 +154,7 @@ export interface Store<
    *
    * @returns The current state tree of your application.
    */
-  getState(): ExtendState<S, StateExt>
+  getState(): S & StateExt
 
   /**
    * Adds a change listener. It will be called any time an action is
@@ -217,7 +199,7 @@ export interface Store<
    * For more information, see the observable proposal:
    * https://github.com/tc39/proposal-observable
    */
-  [Symbol.observable](): Observable<ExtendState<S, StateExt>>
+  [Symbol.observable](): Observable<S & StateExt>
 }
 
 /**
@@ -232,11 +214,11 @@ export interface Store<
  * @template StateExt State extension that is mixed into the state type.
  */
 export interface StoreCreator {
-  <S, A extends Action, Ext = {}, StateExt = never>(
+  <S, A extends Action, Ext extends {} = {}, StateExt extends {} = {}>(
     reducer: Reducer<S, A>,
     enhancer?: StoreEnhancer<Ext, StateExt>
   ): Store<S, A, StateExt> & Ext
-  <S, A extends Action, Ext = {}, StateExt = never>(
+  <S, A extends Action, Ext extends {} = {}, StateExt extends {} = {}>(
     reducer: Reducer<S, A>,
     preloadedState?: PreloadedState<S>,
     enhancer?: StoreEnhancer<Ext>
@@ -264,13 +246,16 @@ export interface StoreCreator {
  * @template Ext Store extension that is mixed into the Store type.
  * @template StateExt State extension that is mixed into the state type.
  */
-export type StoreEnhancer<Ext = {}, StateExt = never> = (
-  next: StoreEnhancerStoreCreator<Ext, StateExt>
-) => StoreEnhancerStoreCreator<Ext, StateExt>
-export type StoreEnhancerStoreCreator<Ext = {}, StateExt = never> = <
-  S = any,
-  A extends Action = AnyAction
+export type StoreEnhancer<Ext extends {} = {}, StateExt extends {} = {}> = <
+  NextExt extends {},
+  NextStateExt extends {}
 >(
+  next: StoreEnhancerStoreCreator<NextExt, NextStateExt>
+) => StoreEnhancerStoreCreator<NextExt & Ext, NextStateExt & StateExt>
+export type StoreEnhancerStoreCreator<
+  Ext extends {} = {},
+  StateExt extends {} = {}
+> = <S = any, A extends Action = AnyAction>(
   reducer: Reducer<S, A>,
   preloadedState?: PreloadedState<S>
 ) => Store<S, A, StateExt> & Ext
diff --git a/test/typescript/enhancers.ts b/test/typescript/enhancers.ts
index ece858f652..5acdbb8d0a 100644
--- a/test/typescript/enhancers.ts
+++ b/test/typescript/enhancers.ts
@@ -64,11 +64,13 @@ function stateExtension() {
       reducer: Reducer<S, A>,
       preloadedState?: any
     ) => {
-      const wrappedReducer: Reducer<S & ExtraState, A> = (state, action) => {
-        const newState = reducer(state, action)
-        return {
-          ...newState,
-          extraField: 'extra'
+      function wrapReducer(reducer: Reducer<S, A>): Reducer<S & ExtraState, A> {
+        return (state, action) => {
+          const newState = reducer(state, action)
+          return {
+            ...newState,
+            extraField: 'extra'
+          }
         }
       }
       const wrappedPreloadedState = preloadedState
@@ -77,7 +79,13 @@ function stateExtension() {
             extraField: 'extra'
           }
         : undefined
-      return createStore(wrappedReducer, wrappedPreloadedState)
+      const store = createStore(wrapReducer(reducer), wrappedPreloadedState)
+      return {
+        ...store,
+        replaceReducer(nextReducer: Reducer<S, A>) {
+          store.replaceReducer(wrapReducer(nextReducer))
+        }
+      }
     }
 
   const store = createStore(reducer, enhancer)
@@ -96,8 +104,10 @@ function extraMethods() {
     createStore =>
     (...args) => {
       const store = createStore(...args)
-      store.method = () => 'foo'
-      return store
+      return {
+        ...store,
+        method: () => 'foo'
+      }
     }
 
   const store = createStore(reducer, enhancer)
@@ -122,11 +132,13 @@ function replaceReducerExtender() {
       reducer: Reducer<S, A>,
       preloadedState?: any
     ) => {
-      const wrappedReducer: Reducer<S & ExtraState, A> = (state, action) => {
-        const newState = reducer(state, action)
-        return {
-          ...newState,
-          extraField: 'extra'
+      function wrapReducer(reducer: Reducer<S, A>): Reducer<S & ExtraState, A> {
+        return (state, action) => {
+          const newState = reducer(state, action)
+          return {
+            ...newState,
+            extraField: 'extra'
+          }
         }
       }
       const wrappedPreloadedState = preloadedState
@@ -135,7 +147,14 @@ function replaceReducerExtender() {
             extraField: 'extra'
           }
         : undefined
-      return createStore(wrappedReducer, wrappedPreloadedState)
+      const store = createStore(wrapReducer(reducer), wrappedPreloadedState)
+      return {
+        ...store,
+        replaceReducer(nextReducer: Reducer<S, A>) {
+          store.replaceReducer(wrapReducer(nextReducer))
+        },
+        method: () => 'foo'
+      }
     }
 
   const store = createStore<
@@ -270,14 +289,14 @@ function finalHelmersonExample() {
       <S, A extends Action<unknown>>(
         reducer: Reducer<S, A>,
         preloadedState?: any
-      ): Store<S, A, ExtraState> & { persistor: Store<S, A, ExtraState> } => {
+      ) => {
         const persistedReducer = persistReducer<S, A>(persistConfig, reducer)
         const store = createStore(persistedReducer, preloadedState)
         const persistor = persistStore(store)
 
         return {
           ...store,
-          replaceReducer: nextReducer => {
+          replaceReducer: (nextReducer: Reducer<S, A>) => {
             store.replaceReducer(persistReducer(persistConfig, nextReducer))
           },
           persistor
@@ -308,3 +327,44 @@ function finalHelmersonExample() {
   // @ts-expect-error
   store.getState().wrongField
 }
+
+function composedEnhancers() {
+  interface State {
+    someState: string
+  }
+  const reducer: Reducer<State> = null as any
+
+  interface Ext1 {
+    enhancer1: string
+  }
+  interface Ext2 {
+    enhancer2: number
+  }
+
+  const enhancer1: StoreEnhancer<Ext1> =
+    createStore => (reducer, preloadedState) => {
+      const store = createStore(reducer, preloadedState)
+      return {
+        ...store,
+        enhancer1: 'foo'
+      }
+    }
+
+  const enhancer2: StoreEnhancer<Ext2> =
+    createStore => (reducer, preloadedState) => {
+      const store = createStore(reducer, preloadedState)
+      return {
+        ...store,
+        enhancer2: 5
+      }
+    }
+
+  const composedEnhancer: StoreEnhancer<Ext1 & Ext2> = createStore =>
+    enhancer2(enhancer1(createStore))
+
+  const enhancedStore = createStore(reducer, composedEnhancer)
+  enhancedStore.enhancer1
+  enhancedStore.enhancer2
+  // @ts-expect-error
+  enhancedStore.enhancer3
+}
diff --git a/test/typescript/store.ts b/test/typescript/store.ts
index 3cdb6b769c..5d4158e526 100644
--- a/test/typescript/store.ts
+++ b/test/typescript/store.ts
@@ -5,8 +5,7 @@ import {
   Action,
   StoreEnhancer,
   Unsubscribe,
-  Observer,
-  ExtendState
+  Observer
 } from '../../src'
 import 'symbol-observable'
 
@@ -22,46 +21,6 @@ type State = {
   e: BrandedString
 }
 
-/* extended state */
-const noExtend: ExtendState<State, never> = {
-  a: 'a',
-  b: {
-    c: 'c',
-    d: 'd'
-  },
-  e: brandedString
-}
-
-const noExtendError: ExtendState<State, never> = {
-  a: 'a',
-  b: {
-    c: 'c',
-    d: 'd'
-  },
-  e: brandedString,
-  // @ts-expect-error
-  f: 'oops'
-}
-
-const yesExtend: ExtendState<State, { yes: 'we can' }> = {
-  a: 'a',
-  b: {
-    c: 'c',
-    d: 'd'
-  },
-  e: brandedString,
-  yes: 'we can'
-}
-// @ts-expect-error
-const yesExtendError: ExtendState<State, { yes: 'we can' }> = {
-  a: 'a',
-  b: {
-    c: 'c',
-    d: 'd'
-  },
-  e: brandedString
-}
-
 interface DerivedAction extends Action {
   type: 'a'
   b: 'b'