Repository: reduxjs/redux-toolkit
Pull request #813 (2020-11-19 10:12:21)

upcoming: RTK 1.5.0
Just keeping this open as a PR so we have up-to-date CodeSandbox builds for the integration branch.

netlify[bot]: Deploy preview for *redux-starter-kit-docs* ready!

Built with commit 6093e0c5a9ec65e341a324646c018bc2f398712e

https://deploy-preview-813--redux-starter-kit-docs.netlify.app
codesandbox[bot]: This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/reduxjs/redux-toolkit/pr/813/builds/74888) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 6093e0c5a9ec65e341a324646c018bc2f398712e:

|Sandbox| Source |
|--|--|
|[Vanilla](https://codesandbox.io/s/vanilla-newdy)| Configuration |
|[Vanilla Typescript](https://codesandbox.io/s/vanilla-typescript-npqzw)| Configuration |
|[rsk-github-issues-example](https://codesandbox.io/s/rsk-github-issues-example-9vfwp)| Configuration |

github-actions[bot]: **Size Change:** +207 B (0%) 

**Total Size:** 64.4 kB

| Filename | Size | Change | |
|:--- |:---:|:---:|:---:|
| `dist/redux-toolkit.cjs.development.js` | 12.5 kB | +44 B (0%) |  |
| `dist/redux-toolkit.cjs.production.min.js` | 4.78 kB | +36 B (0%) |  |
| `dist/redux-toolkit.esm.js` | 12.4 kB | +44 B (0%) |  |
| `dist/redux-toolkit.umd.js` | 24.2 kB | +44 B (0%) |  |
| `dist/redux-toolkit.umd.min.js` | 10.4 kB | +39 B (0%) |  |

<details><summary>ℹ️ <strong>View Unchanged</strong></summary>

| Filename | Size | Change | |
|:--- |:---:|:---:|:---:|
| `dist/index.js` | 149 B | 0 B |  |


</details>



<a href="https://github.com/preactjs/compressed-size-action"><sub>compressed-size-action</sub></a>

Patch:
diff --git a/.travis.yml b/.travis.yml
index 59a1bbe0..5737d047 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,11 +4,46 @@ env:
   - TYPESCRIPT_VERSION=next
   - TYPESCRIPT_VERSION=4.0
   - TYPESCRIPT_VERSION=3.9
-  - TYPESCRIPT_VERSION=3.8
-  - TYPESCRIPT_VERSION=3.7
-  - TYPESCRIPT_VERSION=3.6
-  - TYPESCRIPT_VERSION=3.5
 install:
   - npm ci --ignore-scripts
   - npm install typescript@$TYPESCRIPT_VERSION
 script: npm run prepare
+
+jobs:
+  include:
+    - env:
+        - TYPESCRIPT_VERSION=3.8
+      install:
+        - npm ci --ignore-scripts
+        - npm install typescript@$TYPESCRIPT_VERSION
+        - sed -i -e 's/"cd type-tests.*"/"true"/' package.json
+        - sed -i -e 's/@ts-expect-error/typings:expect-error/' type-tests/files/*
+        - mv type-tests/types.test.disabled.ts type-tests/types.test.ts
+      script: npm run prepare
+    - env:
+        - TYPESCRIPT_VERSION=3.7
+      install:
+        - npm ci --ignore-scripts
+        - npm install typescript@$TYPESCRIPT_VERSION
+        - sed -i -e 's/"cd type-tests.*"/"true"/' package.json
+        - sed -i -e 's/@ts-expect-error/typings:expect-error/' type-tests/files/*
+        - mv type-tests/types.test.disabled.ts type-tests/types.test.ts
+      script: npm run prepare
+    - env:
+        - TYPESCRIPT_VERSION=3.6
+      install:
+        - npm ci --ignore-scripts
+        - npm install typescript@$TYPESCRIPT_VERSION
+        - sed -i -e 's/"cd type-tests.*"/"true"/' package.json
+        - sed -i -e 's/@ts-expect-error/typings:expect-error/' type-tests/files/*
+        - mv type-tests/types.test.disabled.ts type-tests/types.test.ts
+      script: npm run prepare
+    - env:
+        - TYPESCRIPT_VERSION=3.5
+      install:
+        - npm ci --ignore-scripts
+        - npm install typescript@$TYPESCRIPT_VERSION
+        - sed -i -e 's/"cd type-tests.*"/"true"/' package.json
+        - sed -i -e 's/@ts-expect-error/typings:expect-error/' type-tests/files/*
+        - mv type-tests/types.test.disabled.ts type-tests/types.test.ts
+      script: npm run prepare
diff --git a/docs/api/createAction.mdx b/docs/api/createAction.mdx
index 8608a2fb..e5de5886 100644
--- a/docs/api/createAction.mdx
+++ b/docs/api/createAction.mdx
@@ -145,7 +145,7 @@ Every generated actionCreator has a `.match(action)` method that can be used to
 
 This has different uses:
 
-### As a TypeScript TypeGuard
+### As a TypeScript Type Guard
 
 This `match` method is a [TypeScript type guard](https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards) and can be used to discriminate the `payload` type of an action.
 
diff --git a/docs/api/createAsyncThunk.mdx b/docs/api/createAsyncThunk.mdx
index 7ea033ed..21f3dce0 100644
--- a/docs/api/createAsyncThunk.mdx
+++ b/docs/api/createAsyncThunk.mdx
@@ -234,7 +234,7 @@ const onClick = () => {
 
 The thunks generated by `createAsyncThunk` **will always return a resolved promise** with either the `fulfilled` action object or `rejected` action object inside, as appropriate.
 
-The calling logic may wish to treat these actions as if they were the original promise contents. Redux Toolkit exports an `unwrapResult` function that can be used to extract the `payload` or `error` from the action, and return or throw the result appropriately:
+The calling logic may wish to treat these actions as if they were the original promise contents. Redux Toolkit exports an `unwrapResult` function that can be used to extract the `payload` of a `fulfilled` action or to throw either the `error` or, if available, `payload` created by `rejectWithValue` from a `rejected` action:
 
 ```js
 import { unwrapResult } from '@reduxjs/toolkit'
@@ -244,7 +244,7 @@ const onClick = () => {
   dispatch(fetchUserById(userId))
     .then(unwrapResult)
     .then(originalPromiseResult => {})
-    .catch(serializedError => {})
+    .catch(rejectedValueOrSerializedError => {})
 }
 ```
 
@@ -437,7 +437,7 @@ const fetchUserById = createAsyncThunk(
 - Requesting a user by ID, with loading state, and only one request at a time:
 
 ```js
-import { createAsyncThunk, createSlice, unwrapResult } from '@reduxjs/toolkit'
+import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
 import { userAPI } from './userAPI'
 
 const fetchUserById = createAsyncThunk(
@@ -494,7 +494,7 @@ const UsersComponent = () => {
   const fetchOneUser = async userId => {
     try {
       const resultAction = await dispatch(fetchUserById(userId))
-      const user = unwrapResult(resultAction)
+      const user = resultAction.payload
       showToast('success', `Fetched ${user.name}`)
     } catch (err) {
       showToast('error', `Fetch failed: ${err.message}`)
diff --git a/docs/api/createSelector.mdx b/docs/api/createSelector.mdx
index fa93dc9c..b06b1148 100644
--- a/docs/api/createSelector.mdx
+++ b/docs/api/createSelector.mdx
@@ -19,3 +19,44 @@ For more details on using `createSelector`, see:
 > **Note**: Prior to v0.7, RTK re-exported `createSelector` from [`selectorator`](https://github.com/planttheidea/selectorator), which
 > allowed using string keypaths as input selectors. This was removed, as it ultimately did not provide enough benefits, and
 > the string keypaths made static typing for selectors difficult.
+
+# `createDraftSafeSelector`
+
+In general, we recommend against using selectors inside of reducers:
+
+- Selectors typically expect the entire Redux state object as an argument, while slice reducers only have access to a specific subset of the entire Redux state
+- Reselect's `createSelector` relies on reference comparisons to determine if inputs have changed, and if an Immer Proxy-wrapped draft value is passed in to a selector, the selector may see the same reference and think nothing has changed.
+
+However, some users have requested the ability to create selectors that will work correctly inside of Immer-powered reducers. One use case for this might be collecting an ordered set of items when using `createEntityAdapter, such as`const orderedTodos = todosSelectors.selectAll(todosState)`, and then using`orderedTodos` in the rest of the reducer logic.
+
+Besides re-exporting `createSelector`, RTK also exports a wrapped version of `createSelector` named `createDraftSafeSelector` that allows you to create selectors that can safely be used inside of `createReducer` and `createSlice` reducers with Immer-powered mutable logic. When used with plain state values, the selector will still memoize normally based on the inputs. But, when used with Immer draft values, the selector will err on the side of recalculating the results, just to be safe.
+
+All selectors created by `entityAdapter.getSelectors` are "draft safe" selectors by default.
+
+Example:
+
+```js
+const selectSelf = (state: State) => state
+const unsafeSelector = createSelector(selectSelf, state => state.value)
+const draftSafeSelector = createDraftSafeSelector(
+  selectSelf,
+  state => state.value
+)
+
+// in your reducer:
+
+state.value = 1
+
+const unsafe1 = unsafeSelector(state)
+const safe1 = draftSafeSelector(state)
+
+state.value = 2
+
+const unsafe2 = unsafeSelector(state)
+const safe2 = draftSafeSelector(state)
+```
+
+After executing that, `unsafe1` and `unsafe2` will be of the same value, because the memoized selector was
+executed on the same object - but `safe2` will actually be different from `safe1` (with the updated value of `2`),
+because the safe selector detected that it was executed on a Immer draft object and recalculated using the current
+value instead of returning a cached value.
diff --git a/docs/api/matching-utilities.mdx b/docs/api/matching-utilities.mdx
new file mode 100644
index 00000000..e0341eaa
--- /dev/null
+++ b/docs/api/matching-utilities.mdx
@@ -0,0 +1,274 @@
+---
+id: matching-utilities
+title: Matching Utilities
+sidebar_label: Matching Utilities
+hide_title: true
+---
+
+# Matching Utilities
+
+Redux Toolkit exports several type-safe action matching utilities that you can leverage when checking for specific kinds of actions. These are primarily useful for the `builder.addMatcher()` cases in `createSlice` and `createReducer`, as well as when writing custom middleware.
+
+### General Purpose
+
+- [`isAllOf`](#isallof) - returns true when **all** conditions are met
+- [`isAnyOf`](#isanyof) - returns true when **at least one of** the conditions are met
+
+### `createAsyncThunk`-specific matchers
+
+All these matchers can either be called with one or more thunks as arguments, in which case they will return a matcher function for that condition and thunks, or with one actions, in which case they will match for any thunk action with said condition.
+
+- [`isAsyncThunkAction`](#isasyncthunkaction) - accepts one or more action creators and returns true when all match
+- [`isPending`](#ispending) - accepts one or more action creators and returns true when all match
+- [`isFulfilled`](#isfulfilled) - accepts one or more action creators and returns true when all match
+- [`isRejected`](#isrejected) - accepts one or more action creators and returns true when all match
+- [`isRejectedWithValue`](#isrejectedwithvalue) - accepts one or more action creators and returns true when all match
+
+## `isAllOf`
+
+A higher-order function that accepts one or more of:
+
+- `redux-toolkit` action creator functions such as the ones produced by:
+  - [`createAction`](./createAction)
+  - [`createSlice`](./createSlice#return-value)
+  - [`createAsyncThunk`](./createAsyncThunk#promise-lifecycle-actions)
+- type guard functions
+- custom action creator functions that have a `.match` property that is a type guard
+
+It will return a type guard function that returns `true` if _all_ of the provided functions match.
+
+## `isAnyOf`
+
+Accepts the same inputs as `isAllOf` and will return a type guard function that returns `true` if at least one of the provided functions match.
+
+## `isAsyncThunkAction`
+
+A higher-order function that returns a type guard function that may be used to check whether an action was created by [`createAsyncThunk`](./createAsyncThunk).
+
+```ts title="isAsyncThunkAction usage"
+import { isAsyncThunkAction, AnyAction } from '@reduxjs/toolkit'
+import { requestThunk1, requestThunk2 } from '@virtual/matchers'
+
+const isARequestAction = isAsyncThunkAction(requestThunk1, requestThunk2)
+
+function handleRequestAction(action: AnyAction) {
+  if (isARequestAction(action)) {
+    // action is an action dispatched by either `requestThunk1` or `requestThunk2`
+  }
+}
+```
+
+## `isPending`
+
+A higher-order function that returns a type guard function that may be used to check whether an action is a 'pending' action creator from the `createAsyncThunk` promise lifecycle.
+
+```ts title="isPending usage"
+import { isPending, AnyAction } from '@reduxjs/toolkit'
+import { requestThunk1, requestThunk2 } from '@virtual/matchers'
+
+const isAPendingAction = isPending(requestThunk1, requestThunk2)
+
+function handlePendingAction(action: AnyAction) {
+  if (isAPendingAction(action)) {
+    // action is a pending action dispatched by either `requestThunk1` or `requestThunk2`
+  }
+}
+```
+
+## `isFulfilled`
+
+A higher-order function that returns a type guard function that may be used to check whether an action is a 'fulfilled'' action creator from the `createAsyncThunk` promise lifecycle.
+
+```ts title="isFulfilled usage"
+import { isFulfilled, AnyAction } from '@reduxjs/toolkit'
+import { requestThunk1, requestThunk2 } from '@virtual/matchers'
+
+const isAFulfilledAction = isFulfilled(requestThunk1, requestThunk2)
+
+function handleFulfilledAction(action: AnyAction) {
+  if (isAFulfilledAction(action)) {
+    // action is a fulfilled action dispatched by either `requestThunk1` or `requestThunk2`
+  }
+}
+```
+
+## `isRejected`
+
+A higher-order function that returns a type guard function that may be used to check whether an action is a 'rejected' action creator from the `createAsyncThunk` promise lifecycle.
+
+```ts title="isRejected usage"
+import { isRejected, AnyAction } from '@reduxjs/toolkit'
+import { requestThunk1, requestThunk2 } from '@virtual/matchers'
+
+const isARejectedAction = isRejected(requestThunk1, requestThunk2)
+
+function handleRejectedAction(action: AnyAction) {
+  if (isARejectedAction(action)) {
+    // action is a rejected action dispatched by either `requestThunk1` or `requestThunk2`
+  }
+}
+```
+
+## `isRejectedWithValue`
+
+A higher-order function that returns a type guard function that may be used to check whether an action is a 'rejected' action creator from the `createAsyncThunk` promise lifecycle that was created by [`rejectWithValue`](./createAsyncThunk#handling-thunk-errors).
+
+```ts title="isRejectedWithValue usage"
+import { isRejectedWithValue, AnyAction } from '@reduxjs/toolkit'
+import { requestThunk1, requestThunk2 } from '@virtual/matchers'
+
+const isARejectedWithValueAction = isRejectedWithValue(
+  requestThunk1,
+  requestThunk2
+)
+
+function handleRejectedWithValueAction(action: AnyAction) {
+  if (isARejectedWithValueAction(action)) {
+    // action is a rejected action dispatched by either `requestThunk1` or `requestThunk2`
+    // where rejectWithValue was used
+  }
+}
+```
+
+## Using matchers to reduce code complexity, duplication and boilerplate
+
+When using the `builder` pattern to construct a reducer, we add cases or matchers one at a time. However, by using `isAnyOf` or `isAllOf`,
+we're able to easily use the same matcher for several cases in a type-safe manner.
+
+First, let's examine an unnecessarily complex example:
+
+```ts title="Example without using a matcher utility"
+import {
+  createAsyncThunk,
+  createReducer,
+  PayloadAction
+} from '@reduxjs/toolkit'
+
+interface Data {
+  isInteresting: boolean
+  isSpecial: boolean
+}
+
+interface Special extends Data {
+  isSpecial: true
+}
+
+interface Interesting extends Data {
+  isInteresting: true
+}
+
+function isSpecial(
+  action: PayloadAction<Data>
+): action is PayloadAction<Special> {
+  return action.payload.isSpecial
+}
+
+function isInteresting(
+  action: PayloadAction<Data>
+): action is PayloadAction<Interesting> {
+  return action.payload.isInteresting
+}
+
+interface ExampleState {
+  isSpecial: boolean
+  isInteresting: boolean
+}
+
+const initialState: ExampleState = {
+  isSpecial: false,
+  isInteresting: false
+}
+
+export const isSpecialAndInterestingThunk = createAsyncThunk(
+  'isSpecialAndInterestingThunk',
+  () => {
+    return {
+      isSpecial: true,
+      isInteresting: true
+    }
+  }
+)
+
+// This has unnecessary complexity
+const loadingReducer = createReducer(initialState, builder => {
+  builder.addCase(isSpecialAndInterestingThunk.fulfilled, (state, action) => {
+    if (isSpecial(action)) {
+      state.isSpecial = true
+    }
+    if (isInteresting(action)) {
+      state.isInteresting = true
+    }
+  })
+})
+```
+
+In this scenario, we can use `isAllOf` to simplify our code and reduce some of the boilerplate.
+
+```ts title="Refactoring with isAllOf"
+import { createReducer, isAllOf } from '@reduxjs/toolkit'
+import {
+  isSpecialAndInterestingThunk,
+  initialState,
+  isSpecial,
+  isInteresting,
+  Data
+} from '@virtual/matchers' // This is a fake pkg that provides the types shown above
+
+const loadingReducer = createReducer(initialState, builder => {
+  builder
+    .addMatcher(
+      isAllOf(isSpecialAndInterestingThunk.fulfilled, isSpecial),
+      (state, action) => {
+        state.isSpecial = true
+      }
+    )
+    .addMatcher(
+      isAllOf(isSpecialAndInterestingThunk.fulfilled, isInteresting),
+      (state, action) => {
+        state.isInteresting = true
+      }
+    )
+})
+```
+
+## Using matchers as a TypeScript Type Guard
+
+The function returned by `isAllOf` and `isAnyOf` can also be used as a TypeScript type guard in other contexts.
+
+```ts title="Using isAllOf as a type guard"
+import { isAllOf, PayloadAction } from '@reduxjs/toolkit'
+import { Data, isSpecial, isInteresting } from '@virtual/matchers' // This is a fake pkg that provides the types shown above
+
+const isSpecialAndInteresting = isAllOf(isSpecial, isInteresting)
+
+function someFunction(action: PayloadAction<Data>) {
+  if (isSpecialAndInteresting(action)) {
+    // "action" will be correctly typed as:
+    // `PayloadAction<Special> & PayloadAction<Interesting>`
+  }
+}
+```
+
+```ts title="Using isAnyOf as a type guard"
+import { isAnyOf, PayloadAction } from '@reduxjs/toolkit'
+import { Data, isSpecial, isInteresting } from '@virtual/matchers' // this is a fake pkg that provides the types shown above
+
+const isSpecialOrInteresting = isAnyOf(isSpecial, isInteresting)
+
+function someFunction(action: PayloadAction<Data>) {
+  if (isSpecialOrInteresting(action)) {
+    // "action" will be correctly typed as:
+    // `PayloadAction<Special> | PayloadAction<Interesting>`
+  }
+}
+```
+
+## Example
+
+<iframe
+  src="https://codesandbox.io/embed/redux-toolkit-matchers-example-e765q?fontsize=14&hidenavigation=1&module=%2Fsrc%2Ffeatures%2Fcounter%2FcounterSlice.ts&theme=dark"
+  style={{width:'100%', height:'500px', border:0, borderRadius: 4, overflow:'hidden'}}
+  title="redux-toolkit-matchers-example"
+  allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
+  sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
+></iframe>
diff --git a/docs/tutorials/advanced-tutorial.md b/docs/tutorials/advanced-tutorial.md
index 9784d85b..67a5be79 100644
--- a/docs/tutorials/advanced-tutorial.md
+++ b/docs/tutorials/advanced-tutorial.md
@@ -851,7 +851,7 @@ export const fetchIssue = (
 
 This slice is a bit longer, but it's the same basic approach as before: write the slice with reducers that handle API call results, then write thunks that do the fetching and dispatch actions with those results. The only new and interesting bits in this slice are:
 
-- Our "start fetching" and "fetch failed" reducer logic is the same for both the single issue and multiple issue fetch cases. So, we write those functions outside the slice once, then reuse them multiple times with different names inside the `reducers` object.
+- Our "start loading" and "loading failed" reducer logic is the same for both the single issue and multiple issue fetch cases. So, we write those functions outside the slice once, then reuse them multiple times inside the `reducers` object.
 - The Github API returns an array of issue entries, but we [want to store the data in a "normalized" structure to make it easy to look up an issue by its number](https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape). In this case, we use a plain object as a lookup table, by declaring that it is a `Record<number, Issue>`.
 
 ### Fetching Issues in the Issues List
diff --git a/docs/tutorials/basic-tutorial.md b/docs/tutorials/basic-tutorial.md
index be5333e3..9efb17e0 100644
--- a/docs/tutorials/basic-tutorial.md
+++ b/docs/tutorials/basic-tutorial.md
@@ -9,7 +9,7 @@ hide_title: true
 
 Welcome to Redux Toolkit ! This tutorial will show you the basic functions that are included with Redux Toolkit (also known as "RTK" for short).
 
-This tutorial assumes that you are already familiar with the concepts of the core Redux library, as well as how to use it with React. If you aren't, please take some time to read through the [Redux docs](https://redux.js.org) and [React-Redux docs](https://react-redux.js.org) first, as the explanations here focus on how RTK usage differs from "typical" Redux code.
+This tutorial assumes that you are already familiar with the concepts of the core [Redux](https://redux.js.org) library, as well as how to use it with [React](https://reactjs.org). If you aren't, please take some time to read through the [Redux docs](https://redux.js.org) and [React-Redux docs](https://react-redux.js.org) first, as the explanations here focus on how RTK usage differs from "typical" Redux code.
 
 ## Introduction: Writing a Counter Application
 
diff --git a/docs/usage/usage-with-typescript.md b/docs/usage/usage-with-typescript.md
index 2318953b..523d9d69 100644
--- a/docs/usage/usage-with-typescript.md
+++ b/docs/usage/usage-with-typescript.md
@@ -582,7 +582,7 @@ const usersSlice = createSlice({
 const handleUpdateUser = async userData => {
   const resultAction = await dispatch(updateUser(userData))
   if (updateUser.fulfilled.match(resultAction)) {
-    const user = unwrapResult(resultAction)
+    const user = resultAction.payload
     showToast('success', `Updated ${user.name}`)
   } else {
     if (resultAction.payload) {
diff --git a/docs/virtual/matchers/index.ts b/docs/virtual/matchers/index.ts
new file mode 100644
index 00000000..54f8db6b
--- /dev/null
+++ b/docs/virtual/matchers/index.ts
@@ -0,0 +1,64 @@
+import {
+  createAsyncThunk,
+  createReducer,
+  PayloadAction
+} from '@reduxjs/toolkit'
+
+export interface Data {
+  isInteresting: boolean
+  isSpecial: boolean
+}
+
+export interface Special extends Data {
+  isSpecial: true
+}
+
+export interface Interesting extends Data {
+  isInteresting: true
+}
+
+export function isSpecial(
+  action: PayloadAction<Data>
+): action is PayloadAction<Special> {
+  return action.payload.isSpecial
+}
+
+export function isInteresting(
+  action: PayloadAction<Data>
+): action is PayloadAction<Interesting> {
+  return action.payload.isInteresting
+}
+
+export interface ExampleState {
+  isSpecial: boolean
+  isInteresting: boolean
+}
+
+export const initialState: ExampleState = {
+  isSpecial: false,
+  isInteresting: false
+}
+
+export const isSpecialAndInterestingThunk = createAsyncThunk(
+  'isSpecialAndInterestingThunk',
+  () => {
+    return {
+      isSpecial: true,
+      isInteresting: true
+    }
+  }
+)
+
+export const requestThunk1 = createAsyncThunk('requestThunk1', () => ({}))
+
+export const requestThunk2 = createAsyncThunk('requestThunk2', () => ({}))
+
+export const loadingReducer = createReducer(initialState, builder => {
+  builder.addCase(isSpecialAndInterestingThunk.fulfilled, (state, action) => {
+    if (isSpecial(action)) {
+      state.isSpecial = true
+    } else if (isInteresting(action)) {
+      state.isInteresting = true
+    }
+  })
+})
diff --git a/etc/redux-toolkit.api.md b/etc/redux-toolkit.api.md
index 63385d02..108f0381 100644
--- a/etc/redux-toolkit.api.md
+++ b/etc/redux-toolkit.api.md
@@ -13,6 +13,7 @@ import { current } from 'immer';
 import { DeepPartial } from 'redux';
 import { Dispatch } from 'redux';
 import { Draft } from 'immer';
+import { freeze } from 'immer';
 import { Middleware } from 'redux';
 import { OutputParametricSelector } from 'reselect';
 import { OutputSelector } from 'reselect';
@@ -51,6 +52,12 @@ export interface ActionCreatorWithPreparedPayload<Args extends unknown[], P, T e
     (...args: Args): PayloadAction<P, T, M, E>;
 }
 
+// @public (undocumented)
+export type ActionMatchingAllOf<Matchers extends [Matcher<any>, ...Matcher<any>[]]> = UnionToIntersection<ActionMatchingAnyOf<Matchers>>;
+
+// @public (undocumented)
+export type ActionMatchingAnyOf<Matchers extends [Matcher<any>, ...Matcher<any>[]]> = ActionFromMatcher<Matchers[number]>;
+
 // @public
 export interface ActionReducerMapBuilder<State> {
     addCase<ActionCreator extends TypedActionCreator<string>>(actionCreator: ActionCreator, reducer: CaseReducer<State, ReturnType<ActionCreator>>): ActionReducerMapBuilder<State>;
@@ -71,15 +78,7 @@ export type AsyncThunk<Returned, ThunkArg, ThunkApiConfig extends AsyncThunkConf
 };
 
 // @public
-export type AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig extends AsyncThunkConfig> = (dispatch: GetDispatch<ThunkApiConfig>, getState: () => GetState<ThunkApiConfig>, extra: GetExtra<ThunkApiConfig>) => Promise<PayloadAction<Returned, string, {
-    arg: ThunkArg;
-    requestId: string;
-}> | PayloadAction<undefined | GetRejectValue<ThunkApiConfig>, string, {
-    arg: ThunkArg;
-    requestId: string;
-    aborted: boolean;
-    condition: boolean;
-}, SerializedError>> & {
+export type AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig extends AsyncThunkConfig> = (dispatch: GetDispatch<ThunkApiConfig>, getState: () => GetState<ThunkApiConfig>, extra: GetExtra<ThunkApiConfig>) => Promise<ReturnType<AsyncThunkFulfilledActionCreator<Returned, ThunkArg>> | ReturnType<AsyncThunkRejectedActionCreator<ThunkArg, ThunkApiConfig>>> & {
     abort(reason?: string): void;
     requestId: string;
     arg: ThunkArg;
@@ -139,6 +138,9 @@ export function createAction<PA extends PrepareAction<any>, T extends string = s
 // @public (undocumented)
 export function createAsyncThunk<Returned, ThunkArg = void, ThunkApiConfig extends AsyncThunkConfig = {}>(typePrefix: string, payloadCreator: AsyncThunkPayloadCreator<Returned, ThunkArg, ThunkApiConfig>, options?: AsyncThunkOptions<ThunkArg, ThunkApiConfig>): AsyncThunk<Returned, ThunkArg, ThunkApiConfig>;
 
+// @public
+export const createDraftSafeSelector: typeof createSelector;
+
 // @public (undocumented)
 export function createEntityAdapter<T>(options?: {
     selectId?: IdSelector<T>;
@@ -273,6 +275,8 @@ export interface EntityStateAdapter<T> {
 // @public (undocumented)
 export function findNonSerializableValue(value: unknown, path?: ReadonlyArray<string>, isSerializable?: (value: unknown) => boolean, getEntries?: (value: unknown) => [string, any][], ignoredPaths?: string[]): NonSerializableValue | false;
 
+export { freeze }
+
 // @public
 export function getDefaultMiddleware<S = any, O extends Partial<GetDefaultMiddlewareOptions> = {
     thunk: true;
@@ -295,15 +299,66 @@ export interface ImmutableStateInvariantMiddlewareOptions {
     warnAfter?: number;
 }
 
+// @public
+export function isAllOf<Matchers extends [Matcher<any>, ...Matcher<any>[]]>(...matchers: Matchers): (action: any) => action is UnionToIntersection<ActionFromMatcher<Matchers[number]>>;
+
+// @public
+export function isAnyOf<Matchers extends [Matcher<any>, ...Matcher<any>[]]>(...matchers: Matchers): (action: any) => action is ActionFromMatcher<Matchers[number]>;
+
+// @public
+export function isAsyncThunkAction(): (action: any) => action is UnknownAsyncThunkAction;
+
+// @public
+export function isAsyncThunkAction<AsyncThunks extends [AnyAsyncThunk, ...AnyAsyncThunk[]]>(...asyncThunks: AsyncThunks): (action: any) => action is ActionsFromAsyncThunk<AsyncThunks[number]>;
+
+// @public
+export function isAsyncThunkAction(action: any): action is UnknownAsyncThunkAction;
+
+// @public
+export function isFulfilled(): (action: any) => action is UnknownAsyncThunkFulfilledAction;
+
+// @public
+export function isFulfilled<AsyncThunks extends [AnyAsyncThunk, ...AnyAsyncThunk[]]>(...asyncThunks: AsyncThunks): (action: any) => action is FulfilledActionFromAsyncThunk<AsyncThunks[number]>;
+
+// @public
+export function isFulfilled(action: any): action is UnknownAsyncThunkFulfilledAction;
+
 // @public
 export function isImmutableDefault(value: unknown): boolean;
 
+// @public
+export function isPending(): (action: any) => action is UnknownAsyncThunkPendingAction;
+
+// @public
+export function isPending<AsyncThunks extends [AnyAsyncThunk, ...AnyAsyncThunk[]]>(...asyncThunks: AsyncThunks): (action: any) => action is PendingActionFromAsyncThunk<AsyncThunks[number]>;
+
+// @public
+export function isPending(action: any): action is UnknownAsyncThunkPendingAction;
+
 // @public
 export function isPlain(val: any): boolean;
 
 // @public
 export function isPlainObject(value: unknown): value is object;
 
+// @public
+export function isRejected(): (action: any) => action is UnknownAsyncThunkRejectedAction;
+
+// @public
+export function isRejected<AsyncThunks extends [AnyAsyncThunk, ...AnyAsyncThunk[]]>(...asyncThunks: AsyncThunks): (action: any) => action is RejectedActionFromAsyncThunk<AsyncThunks[number]>;
+
+// @public
+export function isRejected(action: any): action is UnknownAsyncThunkRejectedAction;
+
+// @public
+export function isRejectedWithValue(): (action: any) => action is UnknownAsyncThunkRejectedAction;
+
+// @public
+export function isRejectedWithValue<AsyncThunks extends [AnyAsyncThunk, ...AnyAsyncThunk[]]>(...asyncThunks: AsyncThunks): (action: any) => action is RejectedWithValueActionFromAsyncThunk<AsyncThunks[number]>;
+
+// @public
+export function isRejectedWithValue(action: any): action is UnknownAsyncThunkRejectedAction;
+
 // @public (undocumented)
 export class MiddlewareArray<Middlewares extends Middleware<any, any>> extends Array<Middlewares> {
     // (undocumented)
@@ -398,7 +453,7 @@ export { ThunkAction }
 export { ThunkDispatch }
 
 // @public (undocumented)
-export function unwrapResult<R extends ActionTypesWithOptionalErrorAction>(returned: R): PayloadForActionTypesExcludingErrorActions<R>;
+export function unwrapResult<R extends UnwrappableAction>(action: R): UnwrappedActionPayload<R>;
 
 // @public (undocumented)
 export type Update<T> = {
diff --git a/package-lock.json b/package-lock.json
index 2fda1b7b..93704a4d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3763,7 +3763,8 @@
           "version": "2.1.1",
           "resolved": false,
           "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "aproba": {
           "version": "1.2.0",
@@ -3787,13 +3788,15 @@
           "version": "1.0.0",
           "resolved": false,
           "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "brace-expansion": {
           "version": "1.1.11",
           "resolved": false,
           "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
           "dev": true,
+          "optional": true,
           "requires": {
             "balanced-match": "^1.0.0",
             "concat-map": "0.0.1"
@@ -3810,19 +3813,22 @@
           "version": "1.1.0",
           "resolved": false,
           "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "concat-map": {
           "version": "0.0.1",
           "resolved": false,
           "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "console-control-strings": {
           "version": "1.1.0",
           "resolved": false,
           "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "core-util-is": {
           "version": "1.0.2",
@@ -3953,7 +3959,8 @@
           "version": "2.0.3",
           "resolved": false,
           "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "ini": {
           "version": "1.3.5",
@@ -3967,6 +3974,7 @@
           "resolved": false,
           "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
           "dev": true,
+          "optional": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
@@ -3983,6 +3991,7 @@
           "resolved": false,
           "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
           "dev": true,
+          "optional": true,
           "requires": {
             "brace-expansion": "^1.1.7"
           }
@@ -3991,13 +4000,15 @@
           "version": "0.0.8",
           "resolved": false,
           "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "minipass": {
           "version": "2.3.5",
           "resolved": false,
           "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==",
           "dev": true,
+          "optional": true,
           "requires": {
             "safe-buffer": "^5.1.2",
             "yallist": "^3.0.0"
@@ -4018,6 +4029,7 @@
           "resolved": false,
           "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
           "dev": true,
+          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
@@ -4106,7 +4118,8 @@
           "version": "1.0.1",
           "resolved": false,
           "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "object-assign": {
           "version": "4.1.1",
@@ -4120,6 +4133,7 @@
           "resolved": false,
           "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
           "dev": true,
+          "optional": true,
           "requires": {
             "wrappy": "1"
           }
@@ -4215,7 +4229,8 @@
           "version": "5.1.2",
           "resolved": false,
           "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "safer-buffer": {
           "version": "2.1.2",
@@ -4257,6 +4272,7 @@
           "resolved": false,
           "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
           "dev": true,
+          "optional": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",
@@ -4278,6 +4294,7 @@
           "resolved": false,
           "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
           "dev": true,
+          "optional": true,
           "requires": {
             "ansi-regex": "^2.0.0"
           }
@@ -4326,13 +4343,15 @@
           "version": "1.0.2",
           "resolved": false,
           "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "yallist": {
           "version": "3.0.3",
           "resolved": false,
           "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==",
-          "dev": true
+          "dev": true,
+          "optional": true
         }
       }
     },
@@ -4583,9 +4602,9 @@
       "dev": true
     },
     "immer": {
-      "version": "7.0.3",
-      "resolved": "https://registry.npmjs.org/immer/-/immer-7.0.3.tgz",
-      "integrity": "sha512-1f/jaF27WVb3X5Tk1AmOYZE+JZgMl50pIS5I8e/Je+HpYHjHwPfzm6dTEvOAUi6jq+/anFXev7agoOgJaL5RFw=="
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/immer/-/immer-8.0.0.tgz",
+      "integrity": "sha512-jm87NNBAIG4fHwouilCHIecFXp5rMGkiFrAuhVO685UnMAlOneEAnOyzPt8OnP47TC11q/E7vpzZe0WvwepFTg=="
     },
     "import-fresh": {
       "version": "3.2.1",
@@ -4722,9 +4741,9 @@
       }
     },
     "interpret": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz",
-      "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==",
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz",
+      "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==",
       "dev": true
     },
     "invariant": {
@@ -7622,9 +7641,9 @@
       "dev": true
     },
     "shelljs": {
-      "version": "0.8.3",
-      "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz",
-      "integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==",
+      "version": "0.8.4",
+      "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz",
+      "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==",
       "dev": true,
       "requires": {
         "glob": "^7.0.0",
@@ -8619,9 +8638,9 @@
       "dev": true
     },
     "typescript": {
-      "version": "3.8.2",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.2.tgz",
-      "integrity": "sha512-EgOVgL/4xfVrCMbhYKUQTdF37SQn4Iw73H5BgCrF1Abdun7Kwy/QZsE/ssAy0y4LxBbvua3PIbFsbRczWWnDdQ==",
+      "version": "3.9.7",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz",
+      "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==",
       "dev": true
     },
     "typings-tester": {
diff --git a/package.json b/package.json
index ab364fb7..a11e076f 100644
--- a/package.json
+++ b/package.json
@@ -40,18 +40,19 @@
     "rollup-plugin-strip-code": "^0.2.6",
     "tsdx": "^0.11.0",
     "tslib": "^1.10.0",
-    "typescript": "^3.8.2",
+    "typescript": "^3.9.7",
     "typings-tester": "^0.3.2"
   },
   "scripts": {
-    "build-ci": "tsdx build --format cjs,esm,umd --name redux-toolkit && api-extractor run",
-    "build": "tsdx build --format cjs,esm,umd --name redux-toolkit && api-extractor run --local",
-    "dev": "tsdx watch  --format cjs,esm,umd",
+    "build-ci": "tsdx build --format cjs,esm,system,umd --name redux-toolkit && api-extractor run",
+    "build": "tsdx build --format cjs,esm,system,umd --name redux-toolkit && api-extractor run --local",
+    "dev": "tsdx watch  --format cjs,esm,system,umd",
     "format": "prettier --write \"src/**/*.ts\" \"**/*.md\"",
     "format:check": "prettier --list-different \"src/**/*.ts\" \"docs/*/**.md\"",
     "lint": "tsdx lint src",
-    "prepare": "npm run lint && npm run format:check && npm test && npm run build-ci",
-    "test": "tsdx test"
+    "prepare": "npm run lint && npm run format:check && npm test && npm run type-tests && npm run build-ci",
+    "test": "tsdx test",
+    "type-tests": "cd type-tests/files && tsc"
   },
   "files": [
     "dist/*.js",
@@ -60,7 +61,7 @@
     "src"
   ],
   "dependencies": {
-    "immer": "^7.0.3",
+    "immer": "^8.0.0",
     "redux": "^4.0.0",
     "redux-thunk": "^2.3.0",
     "reselect": "^4.0.0"
diff --git a/src/createAsyncThunk.ts b/src/createAsyncThunk.ts
index 8650ba8f..f6af8098 100644
--- a/src/createAsyncThunk.ts
+++ b/src/createAsyncThunk.ts
@@ -43,7 +43,9 @@ const commonProperties: Array<keyof SerializedError> = [
 ]
 
 class RejectWithValue<RejectValue> {
-  constructor(public readonly value: RejectValue) {}
+  public name = 'RejectWithValue'
+  public message = 'Rejected'
+  constructor(public readonly payload: RejectValue) {}
 }
 
 // Reworked from https://github.com/sindresorhus/serialize-error
@@ -67,6 +69,7 @@ type AsyncThunkConfig = {
   dispatch?: Dispatch
   extra?: unknown
   rejectValue?: unknown
+  serializedErrorType?: unknown
 }
 
 type GetState<ThunkApiConfig> = ThunkApiConfig extends {
@@ -102,6 +105,13 @@ type GetRejectValue<ThunkApiConfig> = ThunkApiConfig extends {
 }
   ? RejectValue
   : unknown
+
+type GetSerializedErrorType<ThunkApiConfig> = ThunkApiConfig extends {
+  serializedErrorType: infer GetSerializedErrorType
+}
+  ? GetSerializedErrorType
+  : SerializedError
+
 /**
  * A type describing the return value of the `payloadCreator` argument to `createAsyncThunk`.
  * Might be useful for wrapping `createAsyncThunk` in custom abstractions.
@@ -148,18 +158,8 @@ export type AsyncThunkAction<
   getState: () => GetState<ThunkApiConfig>,
   extra: GetExtra<ThunkApiConfig>
 ) => Promise<
-  | PayloadAction<Returned, string, { arg: ThunkArg; requestId: string }>
-  | PayloadAction<
-      undefined | GetRejectValue<ThunkApiConfig>,
-      string,
-      {
-        arg: ThunkArg
-        requestId: string
-        aborted: boolean
-        condition: boolean
-      },
-      SerializedError
-    >
+  | ReturnType<AsyncThunkFulfilledActionCreator<Returned, ThunkArg>>
+  | ReturnType<AsyncThunkRejectedActionCreator<ThunkArg, ThunkApiConfig>>
 > & {
   abort(reason?: string): void
   requestId: string
@@ -215,9 +215,11 @@ interface AsyncThunkOptions<
    * @default `false`
    */
   dispatchConditionRejection?: boolean
+
+  serializeError?: (x: unknown) => GetSerializedErrorType<ThunkApiConfig>
 }
 
-type AsyncThunkPendingActionCreator<
+export type AsyncThunkPendingActionCreator<
   ThunkArg
 > = ActionCreatorWithPreparedPayload<
   [string, ThunkArg],
@@ -227,31 +229,29 @@ type AsyncThunkPendingActionCreator<
   {
     arg: ThunkArg
     requestId: string
+    requestStatus: 'pending'
   }
 >
 
-type AsyncThunkRejectedActionCreator<
+export type AsyncThunkRejectedActionCreator<
   ThunkArg,
   ThunkApiConfig
 > = ActionCreatorWithPreparedPayload<
-  [
-    Error | null,
-    string,
-    ThunkArg,
-    (GetRejectValue<ThunkApiConfig> | undefined)?
-  ],
+  [Error | null, string, ThunkArg],
   GetRejectValue<ThunkApiConfig> | undefined,
   string,
-  SerializedError,
+  GetSerializedErrorType<ThunkApiConfig>,
   {
     arg: ThunkArg
     requestId: string
+    rejectedWithValue: boolean
+    requestStatus: 'rejected'
     aborted: boolean
     condition: boolean
   }
 >
 
-type AsyncThunkFulfilledActionCreator<
+export type AsyncThunkFulfilledActionCreator<
   Returned,
   ThunkArg
 > = ActionCreatorWithPreparedPayload<
@@ -262,6 +262,7 @@ type AsyncThunkFulfilledActionCreator<
   {
     arg: ThunkArg
     requestId: string
+    requestStatus: 'fulfilled'
   }
 >
 
@@ -306,7 +307,11 @@ export function createAsyncThunk<
     (result: Returned, requestId: string, arg: ThunkArg) => {
       return {
         payload: result,
-        meta: { arg, requestId }
+        meta: {
+          arg,
+          requestId,
+          requestStatus: 'fulfilled' as const
+        }
       }
     }
   )
@@ -316,27 +321,32 @@ export function createAsyncThunk<
     (requestId: string, arg: ThunkArg) => {
       return {
         payload: undefined,
-        meta: { arg, requestId }
+        meta: {
+          arg,
+          requestId,
+          requestStatus: 'pending' as const
+        }
       }
     }
   )
 
   const rejected = createAction(
     typePrefix + '/rejected',
-    (
-      error: Error | null,
-      requestId: string,
-      arg: ThunkArg,
-      payload?: RejectedValue
-    ) => {
+    (error: Error | null, requestId: string, arg: ThunkArg) => {
+      const rejectedWithValue = error instanceof RejectWithValue
       const aborted = !!error && error.name === 'AbortError'
       const condition = !!error && error.name === 'ConditionError'
+
       return {
-        payload,
-        error: miniSerializeError(error || 'Rejected'),
+        payload: error instanceof RejectWithValue ? error.payload : undefined,
+        error: ((options && options.serializeError) || miniSerializeError)(
+          error || 'Rejected'
+        ) as GetSerializedErrorType<ThunkApiConfig>,
         meta: {
           arg,
           requestId,
+          rejectedWithValue,
+          requestStatus: 'rejected' as const,
           aborted,
           condition
         }
@@ -426,7 +436,7 @@ If you want to use the AbortController to react to \`abort\` events, please cons
               })
             ).then(result => {
               if (result instanceof RejectWithValue) {
-                return rejected(null, requestId, arg, result.value)
+                return rejected(result, requestId, arg)
               }
               return fulfilled(result, requestId, arg)
             })
@@ -469,25 +479,30 @@ If you want to use the AbortController to react to \`abort\` events, please cons
   )
 }
 
-type ActionTypesWithOptionalErrorAction =
-  | { error: any }
-  | { error?: never; payload: any }
-type PayloadForActionTypesExcludingErrorActions<T> = T extends { error: any }
-  ? never
-  : T extends { payload: infer P }
-  ? P
-  : never
+interface UnwrappableAction {
+  payload: any
+  meta?: any
+  error?: any
+}
+
+type UnwrappedActionPayload<T extends UnwrappableAction> = Exclude<
+  T,
+  { error: any }
+>['payload']
 
 /**
  * @public
  */
-export function unwrapResult<R extends ActionTypesWithOptionalErrorAction>(
-  returned: R
-): PayloadForActionTypesExcludingErrorActions<R> {
-  if ('error' in returned) {
-    throw returned.error
+export function unwrapResult<R extends UnwrappableAction>(
+  action: R
+): UnwrappedActionPayload<R> {
+  if (action.meta && action.meta.rejectedWithValue) {
+    throw action.payload
+  }
+  if (action.error) {
+    throw action.error
   }
-  return (returned as any).payload
+  return action.payload
 }
 
 type WithStrictNullChecks<True, False> = undefined extends boolean
diff --git a/src/createDraftSafeSelector.ts b/src/createDraftSafeSelector.ts
new file mode 100644
index 00000000..b1858af2
--- /dev/null
+++ b/src/createDraftSafeSelector.ts
@@ -0,0 +1,18 @@
+import { current, isDraft } from 'immer'
+import { createSelector } from 'reselect'
+
+/**
+ * "Draft-Safe" version of `reselect`'s `createSelector`:
+ * If an `immer`-drafted object is passed into the resulting selector's first argument,
+ * the selector will act on the current draft value, instead of returning a cached value
+ * that might be possibly outdated if the draft has been modified since.
+ * @public
+ */
+export const createDraftSafeSelector: typeof createSelector = (
+  ...args: unknown[]
+) => {
+  const selector = (createSelector as any)(...args)
+  const wrappedSelector = (value: unknown, ...rest: unknown[]) =>
+    selector(isDraft(value) ? current(value) : value, ...rest)
+  return wrappedSelector as any
+}
diff --git a/src/createSlice.ts b/src/createSlice.ts
index 5bf44523..38d583d5 100644
--- a/src/createSlice.ts
+++ b/src/createSlice.ts
@@ -12,7 +12,7 @@ import {
   ActionReducerMapBuilder,
   executeReducerBuilderCallback
 } from './mapBuilders'
-import { Omit, NoInfer } from './tsHelpers'
+import { NoInfer } from './tsHelpers'
 
 /**
  * An action creator attached to a slice.
diff --git a/src/entities/state_selectors.ts b/src/entities/state_selectors.ts
index d7370c87..5b10a371 100644
--- a/src/entities/state_selectors.ts
+++ b/src/entities/state_selectors.ts
@@ -1,4 +1,4 @@
-import { createSelector } from 'reselect'
+import { createDraftSafeSelector } from '../createDraftSafeSelector'
 import { EntityState, EntitySelectors, Dictionary, EntityId } from './models'
 
 export function createSelectorsFactory<T>() {
@@ -13,7 +13,7 @@ export function createSelectorsFactory<T>() {
 
     const selectEntities = (state: EntityState<T>) => state.entities
 
-    const selectAll = createSelector(
+    const selectAll = createDraftSafeSelector(
       selectIds,
       selectEntities,
       (ids: T[], entities: Dictionary<T>): any =>
@@ -24,7 +24,7 @@ export function createSelectorsFactory<T>() {
 
     const selectById = (entities: Dictionary<T>, id: EntityId) => entities[id]
 
-    const selectTotal = createSelector(selectIds, ids => ids.length)
+    const selectTotal = createDraftSafeSelector(selectIds, ids => ids.length)
 
     if (!selectState) {
       return {
@@ -32,18 +32,29 @@ export function createSelectorsFactory<T>() {
         selectEntities,
         selectAll,
         selectTotal,
-        selectById: createSelector(selectEntities, selectId, selectById)
+        selectById: createDraftSafeSelector(
+          selectEntities,
+          selectId,
+          selectById
+        )
       }
     }
 
-    const selectGlobalizedEntities = createSelector(selectState, selectEntities)
+    const selectGlobalizedEntities = createDraftSafeSelector(
+      selectState,
+      selectEntities
+    )
 
     return {
-      selectIds: createSelector(selectState, selectIds),
+      selectIds: createDraftSafeSelector(selectState, selectIds),
       selectEntities: selectGlobalizedEntities,
-      selectAll: createSelector(selectState, selectAll),
-      selectTotal: createSelector(selectState, selectTotal),
-      selectById: createSelector(selectGlobalizedEntities, selectId, selectById)
+      selectAll: createDraftSafeSelector(selectState, selectAll),
+      selectTotal: createDraftSafeSelector(selectState, selectTotal),
+      selectById: createDraftSafeSelector(
+        selectGlobalizedEntities,
+        selectId,
+        selectById
+      )
     }
   }
 
diff --git a/src/entities/utils.spec.ts b/src/entities/utils.spec.ts
index 4dedcfa1..c16a3c6d 100644
--- a/src/entities/utils.spec.ts
+++ b/src/entities/utils.spec.ts
@@ -17,7 +17,7 @@ describe('Entity utils', () => {
     it('should not warn when key does exist', () => {
       const spy = spyOn(console, 'warn')
 
-      const key = selectIdValue(AClockworkOrange, book => book.id)
+      selectIdValue(AClockworkOrange, book => book.id)
 
       expect(spy).not.toHaveBeenCalled()
     })
@@ -25,7 +25,7 @@ describe('Entity utils', () => {
     it('should warn when key does not exist in dev mode', () => {
       const spy = spyOn(console, 'warn')
 
-      const key = selectIdValue(AClockworkOrange, (book: any) => book.foo)
+      selectIdValue(AClockworkOrange, (book: any) => book.foo)
 
       expect(spy).toHaveBeenCalled()
     })
@@ -34,10 +34,7 @@ describe('Entity utils', () => {
       const spy = spyOn(console, 'warn')
 
       const undefinedAClockworkOrange = { ...AClockworkOrange, id: undefined }
-      const key = selectIdValue(
-        undefinedAClockworkOrange,
-        (book: any) => book.id
-      )
+      selectIdValue(undefinedAClockworkOrange, (book: any) => book.id)
 
       expect(spy).toHaveBeenCalled()
     })
@@ -46,7 +43,7 @@ describe('Entity utils', () => {
       process.env.NODE_ENV = 'production'
       const spy = spyOn(console, 'warn')
 
-      const key = selectIdValue(AClockworkOrange, (book: any) => book.foo)
+      selectIdValue(AClockworkOrange, (book: any) => book.foo)
 
       expect(spy).not.toHaveBeenCalled()
     })
@@ -56,10 +53,7 @@ describe('Entity utils', () => {
       const spy = spyOn(console, 'warn')
 
       const undefinedAClockworkOrange = { ...AClockworkOrange, id: undefined }
-      const key = selectIdValue(
-        undefinedAClockworkOrange,
-        (book: any) => book.id
-      )
+      selectIdValue(undefinedAClockworkOrange, (book: any) => book.id)
 
       expect(spy).not.toHaveBeenCalled()
     })
diff --git a/src/index.ts b/src/index.ts
index 69f6fc65..b178ea61 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,6 +1,7 @@
 import { enableES5 } from 'immer'
 export * from 'redux'
-export { default as createNextState, Draft, current } from 'immer'
+// @ts-ignore
+export { default as createNextState, Draft, current, freeze } from 'immer'
 export {
   createSelector,
   Selector,
@@ -8,6 +9,7 @@ export {
   OutputSelector,
   ParametricSelector
 } from 'reselect'
+export { createDraftSafeSelector } from './createDraftSafeSelector'
 export { ThunkAction, ThunkDispatch } from 'redux-thunk'
 
 // We deliberately enable Immer's ES5 support, on the grounds that
@@ -106,6 +108,20 @@ export {
   SerializedError
 } from './createAsyncThunk'
 
+export {
+  // js
+  isAllOf,
+  isAnyOf,
+  isPending,
+  isRejected,
+  isFulfilled,
+  isAsyncThunkAction,
+  isRejectedWithValue,
+  // types
+  ActionMatchingAllOf,
+  ActionMatchingAnyOf
+} from './matchers'
+
 export { nanoid } from './nanoid'
 
 export { default as isPlainObject } from './isPlainObject'
diff --git a/src/matchers.ts b/src/matchers.ts
new file mode 100644
index 00000000..18b7d3a8
--- /dev/null
+++ b/src/matchers.ts
@@ -0,0 +1,424 @@
+import {
+  ActionFromMatcher,
+  hasMatchFunction,
+  Matcher,
+  UnionToIntersection
+} from './tsHelpers'
+import {
+  AsyncThunk,
+  AsyncThunkFulfilledActionCreator,
+  AsyncThunkPendingActionCreator,
+  AsyncThunkRejectedActionCreator
+} from './createAsyncThunk'
+
+/** @public */
+export type ActionMatchingAnyOf<
+  Matchers extends [Matcher<any>, ...Matcher<any>[]]
+> = ActionFromMatcher<Matchers[number]>
+
+/** @public */
+export type ActionMatchingAllOf<
+  Matchers extends [Matcher<any>, ...Matcher<any>[]]
+> = UnionToIntersection<ActionMatchingAnyOf<Matchers>>
+
+const matches = (matcher: Matcher<any>, action: any) => {
+  if (hasMatchFunction(matcher)) {
+    return matcher.match(action)
+  } else {
+    return matcher(action)
+  }
+}
+
+/**
+ * A higher-order function that returns a function that may be used to check
+ * whether an action matches any one of the supplied type guards or action
+ * creators.
+ *
+ * @param matchers The type guards or action creators to match against.
+ *
+ * @public
+ */
+export function isAnyOf<Matchers extends [Matcher<any>, ...Matcher<any>[]]>(
+  ...matchers: Matchers
+) {
+  return (action: any): action is ActionMatchingAnyOf<Matchers> => {
+    return matchers.some(matcher => matches(matcher, action))
+  }
+}
+
+/**
+ * A higher-order function that returns a function that may be used to check
+ * whether an action matches all of the supplied type guards or action
+ * creators.
+ *
+ * @param matchers The type guards or action creators to match against.
+ *
+ * @public
+ */
+export function isAllOf<Matchers extends [Matcher<any>, ...Matcher<any>[]]>(
+  ...matchers: Matchers
+) {
+  return (action: any): action is ActionMatchingAllOf<Matchers> => {
+    return matchers.every(matcher => matches(matcher, action))
+  }
+}
+
+/**
+ * @param action A redux action
+ * @param validStatus An array of valid meta.requestStatus values
+ *
+ * @internal
+ */
+export function hasExpectedRequestMetadata(action: any, validStatus: string[]) {
+  if (!action || !action.meta) return false
+
+  const hasValidRequestId = typeof action.meta.requestId === 'string'
+  const hasValidRequestStatus =
+    validStatus.indexOf(action.meta.requestStatus) > -1
+
+  return hasValidRequestId && hasValidRequestStatus
+}
+
+function isAsyncThunkArray(a: [any] | AnyAsyncThunk[]): a is AnyAsyncThunk[] {
+  return (
+    typeof a[0] === 'function' &&
+    'pending' in a[0] &&
+    'fulfilled' in a[0] &&
+    'rejected' in a[0]
+  )
+}
+
+export type UnknownAsyncThunkPendingAction = ReturnType<
+  AsyncThunkPendingActionCreator<unknown>
+>
+
+export type PendingActionFromAsyncThunk<
+  T extends AnyAsyncThunk
+> = ActionFromMatcher<T['pending']>
+
+/**
+ * A higher-order function that returns a function that may be used to check
+ * whether an action was created by an async thunk action creator, and that
+ * the action is pending.
+ *
+ * @public
+ */
+export function isPending(): (
+  action: any
+) => action is UnknownAsyncThunkPendingAction
+/**
+ * A higher-order function that returns a function that may be used to check
+ * whether an action belongs to one of the provided async thunk action creators,
+ * and that the action is pending.
+ *
+ * @param asyncThunks (optional) The async thunk action creators to match against.
+ *
+ * @public
+ */
+export function isPending<
+  AsyncThunks extends [AnyAsyncThunk, ...AnyAsyncThunk[]]
+>(
+  ...asyncThunks: AsyncThunks
+): (action: any) => action is PendingActionFromAsyncThunk<AsyncThunks[number]>
+/**
+ * Tests if `action` is a pending thunk action
+ * @public
+ */
+export function isPending(action: any): action is UnknownAsyncThunkPendingAction
+export function isPending<
+  AsyncThunks extends [AnyAsyncThunk, ...AnyAsyncThunk[]]
+>(...asyncThunks: AsyncThunks | [any]) {
+  if (asyncThunks.length === 0) {
+    return (action: any) => hasExpectedRequestMetadata(action, ['pending'])
+  }
+
+  if (!isAsyncThunkArray(asyncThunks)) {
+    return isPending()(asyncThunks[0])
+  }
+
+  return (
+    action: any
+  ): action is PendingActionFromAsyncThunk<AsyncThunks[number]> => {
+    // note: this type will be correct because we have at least 1 asyncThunk
+    const matchers: [Matcher<any>, ...Matcher<any>[]] = asyncThunks.map(
+      asyncThunk => asyncThunk.pending
+    ) as any
+
+    const combinedMatcher = isAnyOf(...matchers)
+
+    return combinedMatcher(action)
+  }
+}
+
+export type UnknownAsyncThunkRejectedAction = ReturnType<
+  AsyncThunkRejectedActionCreator<unknown, unknown>
+>
+
+export type RejectedActionFromAsyncThunk<
+  T extends AnyAsyncThunk
+> = ActionFromMatcher<T['rejected']>
+
+/**
+ * A higher-order function that returns a function that may be used to check
+ * whether an action was created by an async thunk action creator, and that
+ * the action is rejected.
+ *
+ * @public
+ */
+export function isRejected(): (
+  action: any
+) => action is UnknownAsyncThunkRejectedAction
+/**
+ * A higher-order function that returns a function that may be used to check
+ * whether an action belongs to one of the provided async thunk action creators,
+ * and that the action is rejected.
+ *
+ * @param asyncThunks (optional) The async thunk action creators to match against.
+ *
+ * @public
+ */
+export function isRejected<
+  AsyncThunks extends [AnyAsyncThunk, ...AnyAsyncThunk[]]
+>(
+  ...asyncThunks: AsyncThunks
+): (action: any) => action is RejectedActionFromAsyncThunk<AsyncThunks[number]>
+/**
+ * Tests if `action` is a rejected thunk action
+ * @public
+ */
+export function isRejected(
+  action: any
+): action is UnknownAsyncThunkRejectedAction
+export function isRejected<
+  AsyncThunks extends [AnyAsyncThunk, ...AnyAsyncThunk[]]
+>(...asyncThunks: AsyncThunks | [any]) {
+  if (asyncThunks.length === 0) {
+    return (action: any) => hasExpectedRequestMetadata(action, ['rejected'])
+  }
+
+  if (!isAsyncThunkArray(asyncThunks)) {
+    return isRejected()(asyncThunks[0])
+  }
+
+  return (
+    action: any
+  ): action is RejectedActionFromAsyncThunk<AsyncThunks[number]> => {
+    // note: this type will be correct because we have at least 1 asyncThunk
+    const matchers: [Matcher<any>, ...Matcher<any>[]] = asyncThunks.map(
+      asyncThunk => asyncThunk.rejected
+    ) as any
+
+    const combinedMatcher = isAnyOf(...matchers)
+
+    return combinedMatcher(action)
+  }
+}
+
+export type UnknownAsyncThunkRejectedWithValueAction = ReturnType<
+  AsyncThunkRejectedActionCreator<unknown, unknown>
+>
+
+export type RejectedWithValueActionFromAsyncThunk<
+  T extends AnyAsyncThunk
+> = ActionFromMatcher<T['rejected']> &
+  (T extends AsyncThunk<any, any, { rejectValue: infer RejectedValue }>
+    ? { payload: RejectedValue }
+    : unknown)
+
+/**
+ * A higher-order function that returns a function that may be used to check
+ * whether an action was created by an async thunk action creator, and that
+ * the action is rejected with value.
+ *
+ * @public
+ */
+export function isRejectedWithValue(): (
+  action: any
+) => action is UnknownAsyncThunkRejectedAction
+/**
+ * A higher-order function that returns a function that may be used to check
+ * whether an action belongs to one of the provided async thunk action creators,
+ * and that the action is rejected with value.
+ *
+ * @param asyncThunks (optional) The async thunk action creators to match against.
+ *
+ * @public
+ */
+export function isRejectedWithValue<
+  AsyncThunks extends [AnyAsyncThunk, ...AnyAsyncThunk[]]
+>(
+  ...asyncThunks: AsyncThunks
+): (
+  action: any
+) => action is RejectedWithValueActionFromAsyncThunk<AsyncThunks[number]>
+/**
+ * Tests if `action` is a rejected thunk action with value
+ * @public
+ */
+export function isRejectedWithValue(
+  action: any
+): action is UnknownAsyncThunkRejectedAction
+export function isRejectedWithValue<
+  AsyncThunks extends [AnyAsyncThunk, ...AnyAsyncThunk[]]
+>(...asyncThunks: AsyncThunks | [any]) {
+  const hasFlag = (action: any): action is any => {
+    return action && action.meta && action.meta.rejectedWithValue
+  }
+
+  if (asyncThunks.length === 0) {
+    return (action: any) => {
+      const combinedMatcher = isAllOf(isRejected(...asyncThunks), hasFlag)
+
+      return combinedMatcher(action)
+    }
+  }
+
+  if (!isAsyncThunkArray(asyncThunks)) {
+    return isRejectedWithValue()(asyncThunks[0])
+  }
+
+  return (
+    action: any
+  ): action is RejectedActionFromAsyncThunk<AsyncThunks[number]> => {
+    const combinedMatcher = isAllOf(isRejected(...asyncThunks), hasFlag)
+
+    return combinedMatcher(action)
+  }
+}
+
+export type UnknownAsyncThunkFulfilledAction = ReturnType<
+  AsyncThunkFulfilledActionCreator<unknown, unknown>
+>
+
+export type FulfilledActionFromAsyncThunk<
+  T extends AnyAsyncThunk
+> = ActionFromMatcher<T['fulfilled']>
+
+/**
+ * A higher-order function that returns a function that may be used to check
+ * whether an action was created by an async thunk action creator, and that
+ * the action is fulfilled.
+ *
+ * @public
+ */
+export function isFulfilled(): (
+  action: any
+) => action is UnknownAsyncThunkFulfilledAction
+/**
+ * A higher-order function that returns a function that may be used to check
+ * whether an action belongs to one of the provided async thunk action creators,
+ * and that the action is fulfilled.
+ *
+ * @param asyncThunks (optional) The async thunk action creators to match against.
+ *
+ * @public
+ */
+export function isFulfilled<
+  AsyncThunks extends [AnyAsyncThunk, ...AnyAsyncThunk[]]
+>(
+  ...asyncThunks: AsyncThunks
+): (action: any) => action is FulfilledActionFromAsyncThunk<AsyncThunks[number]>
+/**
+ * Tests if `action` is a fulfilled thunk action
+ * @public
+ */
+export function isFulfilled(
+  action: any
+): action is UnknownAsyncThunkFulfilledAction
+export function isFulfilled<
+  AsyncThunks extends [AnyAsyncThunk, ...AnyAsyncThunk[]]
+>(...asyncThunks: AsyncThunks | [any]) {
+  if (asyncThunks.length === 0) {
+    return (action: any) => hasExpectedRequestMetadata(action, ['fulfilled'])
+  }
+
+  if (!isAsyncThunkArray(asyncThunks)) {
+    return isFulfilled()(asyncThunks[0])
+  }
+
+  return (
+    action: any
+  ): action is FulfilledActionFromAsyncThunk<AsyncThunks[number]> => {
+    // note: this type will be correct because we have at least 1 asyncThunk
+    const matchers: [Matcher<any>, ...Matcher<any>[]] = asyncThunks.map(
+      asyncThunk => asyncThunk.fulfilled
+    ) as any
+
+    const combinedMatcher = isAnyOf(...matchers)
+
+    return combinedMatcher(action)
+  }
+}
+
+export type UnknownAsyncThunkAction =
+  | UnknownAsyncThunkPendingAction
+  | UnknownAsyncThunkRejectedAction
+  | UnknownAsyncThunkFulfilledAction
+
+export type AnyAsyncThunk = AsyncThunk<any, any, any>
+
+export type ActionsFromAsyncThunk<T extends AnyAsyncThunk> =
+  | ActionFromMatcher<T['pending']>
+  | ActionFromMatcher<T['fulfilled']>
+  | ActionFromMatcher<T['rejected']>
+
+/**
+ * A higher-order function that returns a function that may be used to check
+ * whether an action was created by an async thunk action creator.
+ *
+ * @public
+ */
+export function isAsyncThunkAction(): (
+  action: any
+) => action is UnknownAsyncThunkAction
+/**
+ * A higher-order function that returns a function that may be used to check
+ * whether an action belongs to one of the provided async thunk action creators.
+ *
+ * @param asyncThunks (optional) The async thunk action creators to match against.
+ *
+ * @public
+ */
+export function isAsyncThunkAction<
+  AsyncThunks extends [AnyAsyncThunk, ...AnyAsyncThunk[]]
+>(
+  ...asyncThunks: AsyncThunks
+): (action: any) => action is ActionsFromAsyncThunk<AsyncThunks[number]>
+/**
+ * Tests if `action` is a thunk action
+ * @public
+ */
+export function isAsyncThunkAction(
+  action: any
+): action is UnknownAsyncThunkAction
+export function isAsyncThunkAction<
+  AsyncThunks extends [AnyAsyncThunk, ...AnyAsyncThunk[]]
+>(...asyncThunks: AsyncThunks | [any]) {
+  if (asyncThunks.length === 0) {
+    return (action: any) =>
+      hasExpectedRequestMetadata(action, ['pending', 'fulfilled', 'rejected'])
+  }
+
+  if (!isAsyncThunkArray(asyncThunks)) {
+    return isAsyncThunkAction()(asyncThunks[0])
+  }
+
+  return (
+    action: any
+  ): action is ActionsFromAsyncThunk<AsyncThunks[number]> => {
+    // note: this type will be correct because we have at least 1 asyncThunk
+    const matchers: [Matcher<any>, ...Matcher<any>[]] = [] as any
+
+    for (const asyncThunk of asyncThunks) {
+      matchers.push(
+        asyncThunk.pending,
+        asyncThunk.rejected,
+        asyncThunk.fulfilled
+      )
+    }
+
+    const combinedMatcher = isAnyOf(...matchers)
+
+    return combinedMatcher(action)
+  }
+}
diff --git a/src/tsHelpers.ts b/src/tsHelpers.ts
index 43ec40a3..68eabefb 100644
--- a/src/tsHelpers.ts
+++ b/src/tsHelpers.ts
@@ -84,7 +84,7 @@ export type DispatchForMiddlewares<M> = M extends ReadonlyArray<any>
 /**
  * Convert a Union type `(A|B)` to and intersecion type `(A&B)`
  */
-type UnionToIntersection<U> = (U extends any
+export type UnionToIntersection<U> = (U extends any
 ? (k: U) => void
 : never) extends (k: infer I) => void
   ? I
@@ -100,3 +100,23 @@ type UnionToIntersection<U> = (U extends any
 export type NoInfer<T> = [T][T extends any ? 0 : never]
 
 export type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>
+
+export interface HasMatchFunction<T> {
+  match(v: any): v is T
+}
+
+export const hasMatchFunction = <T>(
+  v: Matcher<T>
+): v is HasMatchFunction<T> => {
+  return v && typeof (v as HasMatchFunction<T>).match === 'function'
+}
+
+/** @public */
+export type Matcher<T> = HasMatchFunction<T> | ((v: any) => v is T)
+
+/** @public */
+export type ActionFromMatcher<M extends Matcher<any>> = M extends Matcher<
+  infer T
+>
+  ? T
+  : never
diff --git a/tsdx.config.js b/tsdx.config.js
index d012d1ee..616815c0 100644
--- a/tsdx.config.js
+++ b/tsdx.config.js
@@ -9,8 +9,32 @@ module.exports = {
     config.output.name = 'RTK'
 
     const { env, format } = options
+
     // eslint-disable-next-line default-case
     switch (format) {
+      case 'system':
+        delete config.external
+        config.input = 'src/index.ts'
+        config.output = {
+          file: 'dist/redux-toolkit.esm.production.mjs',
+          format: 'es',
+          indent: false
+        }
+        if (env === 'production') {
+          config.plugins.unshift([
+            stripCode({
+              start_comment: 'PROD_START_REMOVE_UMD',
+              end_comment: 'PROD_STOP_REMOVE_UMD'
+            })
+          ])
+        } else {
+          config.output.file = config.output.file.replace(
+            'production',
+            'development'
+          )
+        }
+        break
+
       case 'umd':
         delete config.external
         config.output.indent = false
diff --git a/type-tests/types.test.ts b/type-tests/types.test.disabled.ts
similarity index 100%
rename from type-tests/types.test.ts
rename to type-tests/types.test.disabled.ts
diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js
index cbd26fcd..3372f7ec 100644
--- a/website/docusaurus.config.js
+++ b/website/docusaurus.config.js
@@ -39,6 +39,17 @@ module.exports = {
                         subModuleName: 'index.ts',
                         version: '1.0'
                       }
+                    },
+                    '@virtual/matchers': {
+                      resolvedPath: resolve(
+                        __dirname,
+                        '../docs/virtual/matchers'
+                      ),
+                      packageId: {
+                        name: '@virtual/matchers',
+                        subModuleName: 'index.ts',
+                        version: '1.0'
+                      }
                     }
                   }
                 }
diff --git a/website/sidebars.json b/website/sidebars.json
index f0b6e5b8..8a3ca9bc 100644
--- a/website/sidebars.json
+++ b/website/sidebars.json
@@ -35,7 +35,11 @@
         "type": "category",
         "label": "Other",
         "collapsed": false,
-        "items": ["api/createSelector", "api/other-exports"]
+        "items": [
+          "api/createSelector",
+          "api/matching-utilities",
+          "api/other-exports"
+        ]
       }
     ]
   }


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 00000000..5b240294
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,89 @@
+name: CI
+on: [push, pull_request]
+jobs:
+  build:
+    name: Lint, Test, Build & Pack on Node ${{ matrix.node }}
+
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        node: ['12.x']
+
+    steps:
+      - name: Checkout repo
+        uses: actions/checkout@v2
+
+      - name: Use node ${{ matrix.node }}
+        uses: actions/setup-node@v1
+        with:
+          node-version: ${{ matrix.node }}
+
+      - uses: c-hive/gha-npm-cache@v1
+
+      - name: Install deps
+        run: npm ci --ignore-scripts
+
+      - name: Pack (including Prepare)
+        run: npm pack
+
+      - uses: actions/upload-artifact@v2
+        with:
+          name: package
+          path: reduxjs-toolkit*.tgz
+
+  test:
+    name: Test Types with TypeScript ${{ matrix.ts }}
+
+    needs: [build]
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        node: ['12.x']
+        ts: ['3.5', '3.6', '3.7', '3.8', '3.9', '4.0', '4.1', 'next']
+    steps:
+      - name: Checkout repo
+        uses: actions/checkout@v2
+
+      - name: Use node ${{ matrix.node }}
+        uses: actions/setup-node@v1
+        with:
+          node-version: ${{ matrix.node }}
+
+      - uses: c-hive/gha-npm-cache@v1
+
+      - name: Install deps
+        run: npm ci --ignore-scripts
+
+      - name: Install TypeScript ${{ matrix.ts }}
+        run: npm install typescript@${{ matrix.ts }} --ignore-scripts
+
+      - uses: actions/download-artifact@v2
+        with:
+          name: package
+
+      - name: Unpack build artifact to dist
+        run: tar -xzvf reduxjs-toolkit-1.4.0.tgz --strip-components=1 package/dist
+
+      - name: Remap @redux/toolkit from src to dist
+        run: |
+          sed -i -e 's|@reduxjs/toolkit": \["./src"\]|@reduxjs/toolkit": ["."]|' ./type-tests/files/tsconfig.json
+
+      - name: Prefix `freeze` re-export for pre-3.7 TS versions with @ts-ignore
+        if: ${{ matrix.ts < 3.7 }}
+        run: |
+          sed -i -e "/import .* freeze .* from 'immer'/s/^/\/\/ @ts-ignore\n/" dist/typings.d.ts
+          sed -i -e "/export .* freeze .* from 'immer'/s/^/\/\/ @ts-ignore\n/" src/index.ts
+
+      - name: Use typings-tester for old TS versions
+        if: ${{ matrix.ts < 3.9 }}
+        run: |
+          sed -i -e 's/"cd type-tests.*"/"npm run test type-tests"/' package.json
+          sed -i -e 's/@ts-expect-error/typings:expect-error/' type-tests/files/*.typetest.ts
+          sed -i -e 's/@ts-expect-error/@ts-ignore/' type-tests/files/*.ts
+          mv type-tests/types.test.disabled.ts type-tests/types.test.ts
+
+      - name: Test types
+        run: |
+          ./node_modules/.bin/tsc --version
+          npm run type-tests
diff --git a/src/createAsyncThunk.test.ts b/src/createAsyncThunk.test.ts
index 359127bd..ed514886 100644
--- a/src/createAsyncThunk.test.ts
+++ b/src/createAsyncThunk.test.ts
@@ -587,12 +587,84 @@ describe('conditional skipping of asyncThunks', () => {
         meta: {
           aborted: false,
           arg: arg,
+          rejectedWithValue: false,
           condition: true,
-          requestId: expect.stringContaining('')
+          requestId: expect.stringContaining(''),
+          requestStatus: 'rejected'
         },
         payload: undefined,
         type: 'test/rejected'
       })
     )
   })
+
+  test('serializeError implementation', async () => {
+    function serializeError() {
+      return 'serialized!'
+    }
+    const errorObject = 'something else!'
+
+    const store = configureStore({
+      reducer: (state = [], action) => [...state, action]
+    })
+
+    const asyncThunk = createAsyncThunk<
+      unknown,
+      void,
+      { serializedErrorType: string }
+    >('test', () => Promise.reject(errorObject), { serializeError })
+    const rejected = await store.dispatch(asyncThunk())
+    if (!asyncThunk.rejected.match(rejected)) {
+      throw new Error()
+    }
+
+    const expectation = {
+      type: 'test/rejected',
+      payload: undefined,
+      error: 'serialized!',
+      meta: expect.any(Object)
+    }
+    expect(rejected).toEqual(expectation)
+    expect(store.getState()[2]).toEqual(expectation)
+    expect(rejected.error).not.toEqual(miniSerializeError(errorObject))
+  })
+})
+describe('unwrapResult', () => {
+  const getState = jest.fn(() => ({}))
+  const dispatch = jest.fn((x: any) => x)
+  const extra = {}
+  test('fulfilled case', async () => {
+    const asyncThunk = createAsyncThunk('test', () => {
+      return 'fulfilled!'
+    })
+
+    const unwrapPromise = asyncThunk()(dispatch, getState, extra).then(
+      unwrapResult
+    )
+
+    await expect(unwrapPromise).resolves.toBe('fulfilled!')
+  })
+  test('error case', async () => {
+    const error = new Error('Panic!')
+    const asyncThunk = createAsyncThunk('test', () => {
+      throw error
+    })
+
+    const unwrapPromise = asyncThunk()(dispatch, getState, extra).then(
+      unwrapResult
+    )
+
+    await expect(unwrapPromise).rejects.toEqual(miniSerializeError(error))
+  })
+  test('rejectWithValue case', async () => {
+    const asyncThunk = createAsyncThunk('test', (_, { rejectWithValue }) => {
+      return rejectWithValue('rejectWithValue!')
+    })
+
+    const unwrapPromise = asyncThunk()(dispatch, getState, extra).then(
+      unwrapResult
+    )
+
+    await expect(unwrapPromise).rejects.toBe('rejectWithValue!')
+  })
 })
diff --git a/src/createDraftSafeSelector.test.ts b/src/createDraftSafeSelector.test.ts
new file mode 100644
index 00000000..04518064
--- /dev/null
+++ b/src/createDraftSafeSelector.test.ts
@@ -0,0 +1,37 @@
+import { createSelector } from 'reselect'
+import { createDraftSafeSelector } from './createDraftSafeSelector'
+import { produce } from 'immer'
+
+type State = { value: number }
+const selectSelf = (state: State) => state
+
+test('handles normal values correctly', () => {
+  const unsafeSelector = createSelector(selectSelf, x => x.value)
+  const draftSafeSelector = createDraftSafeSelector(selectSelf, x => x.value)
+
+  let state = { value: 1 }
+  expect(unsafeSelector(state)).toBe(1)
+  expect(draftSafeSelector(state)).toBe(1)
+
+  state = { value: 2 }
+  expect(unsafeSelector(state)).toBe(2)
+  expect(draftSafeSelector(state)).toBe(2)
+})
+
+test('handles drafts correctly', () => {
+  const unsafeSelector = createSelector(selectSelf, state => state.value)
+  const draftSafeSelector = createDraftSafeSelector(
+    selectSelf,
+    state => state.value
+  )
+
+  produce({ value: 1 }, state => {
+    expect(unsafeSelector(state)).toBe(1)
+    expect(draftSafeSelector(state)).toBe(1)
+
+    state.value = 2
+
+    expect(unsafeSelector(state)).toBe(1)
+    expect(draftSafeSelector(state)).toBe(2)
+  })
+})
diff --git a/src/createReducer.test.ts b/src/createReducer.test.ts
index 780a2004..b2e7c0ab 100644
--- a/src/createReducer.test.ts
+++ b/src/createReducer.test.ts
@@ -50,6 +50,48 @@ describe('createReducer', () => {
     behavesLikeReducer(todosReducer)
   })
 
+  describe('Immer in a production environment', () => {
+    let originalNodeEnv = process.env.NODE_ENV
+
+    beforeEach(() => {
+      jest.resetModules()
+      process.env.NODE_ENV = 'production'
+    })
+
+    afterEach(() => {
+      process.env.NODE_ENV = originalNodeEnv
+    })
+
+    test('Freezes data in production', () => {
+      const { createReducer } = require('./createReducer')
+      const addTodo: AddTodoReducer = (state, action) => {
+        const { newTodo } = action.payload
+        state.push({ ...newTodo, completed: false })
+      }
+
+      const toggleTodo: ToggleTodoReducer = (state, action) => {
+        const { index } = action.payload
+        const todo = state[index]
+        todo.completed = !todo.completed
+      }
+
+      const todosReducer = createReducer([] as TodoState, {
+        ADD_TODO: addTodo,
+        TOGGLE_TODO: toggleTodo
+      })
+
+      const result = todosReducer([], {
+        type: 'ADD_TODO',
+        payload: { text: 'Buy milk' }
+      })
+
+      const mutateStateOutsideReducer = () => (result[0].text = 'edited')
+      expect(mutateStateOutsideReducer).toThrowError(
+        'Cannot add property text, object is not extensible'
+      )
+    })
+  })
+
   describe('given pure reducers with immutable updates', () => {
     const addTodo: AddTodoReducer = (state, action) => {
       const { newTodo } = action.payload
diff --git a/src/entities/state_selectors.test.ts b/src/entities/state_selectors.test.ts
index 7b8c11b7..21db6b5e 100644
--- a/src/entities/state_selectors.test.ts
+++ b/src/entities/state_selectors.test.ts
@@ -100,10 +100,9 @@ describe('Entity State Selectors', () => {
     })
 
     it('should type single entity from Dictionary as entity type or undefined', () => {
-      const singleEntity: Selector<
-        EntityState<BookModel>,
-        BookModel | undefined
-      > = createSelector(selectors.selectEntities, entities => entities[0])
+      expectType<Selector<EntityState<BookModel>, BookModel | undefined>>(
+        createSelector(selectors.selectEntities, entities => entities[0])
+      )
     })
 
     it('should create a selector for selecting the list of models', () => {
@@ -126,3 +125,7 @@ describe('Entity State Selectors', () => {
     })
   })
 })
+
+function expectType<T>(t: T) {
+  return t
+}
diff --git a/src/entities/unsorted_state_adapter.test.ts b/src/entities/unsorted_state_adapter.test.ts
index 3e4521b5..3c5a7087 100644
--- a/src/entities/unsorted_state_adapter.test.ts
+++ b/src/entities/unsorted_state_adapter.test.ts
@@ -126,7 +126,7 @@ describe('Unsorted State Adapter', () => {
   it('should let you add remove an entity from the state', () => {
     const withOneEntity = adapter.addOne(state, TheGreatGatsby)
 
-    const withoutOne = adapter.removeOne(state, TheGreatGatsby.id)
+    const withoutOne = adapter.removeOne(withOneEntity, TheGreatGatsby.id)
 
     expect(withoutOne).toEqual({
       ids: [],
diff --git a/src/matchers.test.ts b/src/matchers.test.ts
new file mode 100644
index 00000000..fe2ab444
--- /dev/null
+++ b/src/matchers.test.ts
@@ -0,0 +1,461 @@
+import {
+  isAllOf,
+  isAnyOf,
+  isAsyncThunkAction,
+  isFulfilled,
+  isPending,
+  isRejected,
+  isRejectedWithValue
+} from './matchers'
+import { createAction } from './createAction'
+import { createAsyncThunk } from './createAsyncThunk'
+import { createReducer } from './createReducer'
+import { ThunkAction } from 'redux-thunk'
+import { AnyAction } from 'redux'
+
+const thunk: ThunkAction<any, any, any, AnyAction> = () => {}
+
+describe('isAnyOf', () => {
+  it('returns true only if any matchers match (match function)', () => {
+    const actionA = createAction<string>('a')
+    const actionB = createAction<number>('b')
+
+    const trueAction = {
+      type: 'a',
+      payload: 'payload'
+    }
+
+    expect(isAnyOf(actionA, actionB)(trueAction)).toEqual(true)
+
+    const falseAction = {
+      type: 'c',
+      payload: 'payload'
+    }
+
+    expect(isAnyOf(actionA, actionB)(falseAction)).toEqual(false)
+  })
+
+  it('returns true only if any type guards match', () => {
+    const actionA = createAction<string>('a')
+    const actionB = createAction<number>('b')
+
+    const isActionA = actionA.match
+    const isActionB = actionB.match
+
+    const trueAction = {
+      type: 'a',
+      payload: 'payload'
+    }
+
+    expect(isAnyOf(isActionA, isActionB)(trueAction)).toEqual(true)
+
+    const falseAction = {
+      type: 'c',
+      payload: 'payload'
+    }
+
+    expect(isAnyOf(isActionA, isActionB)(falseAction)).toEqual(false)
+  })
+
+  it('returns true only if any matchers match (thunk action creators)', () => {
+    const thunkA = createAsyncThunk<string>('a', () => {
+      return 'noop'
+    })
+    const thunkB = createAsyncThunk<number>('b', () => {
+      return 0
+    })
+
+    const action = thunkA.fulfilled('fakeRequestId', 'test')
+
+    expect(isAnyOf(thunkA.fulfilled, thunkB.fulfilled)(action)).toEqual(true)
+
+    expect(
+      isAnyOf(thunkA.pending, thunkA.rejected, thunkB.fulfilled)(action)
+    ).toEqual(false)
+  })
+
+  it('works with reducers', () => {
+    const actionA = createAction<string>('a')
+    const actionB = createAction<number>('b')
+
+    const trueAction = {
+      type: 'a',
+      payload: 'payload'
+    }
+
+    const initialState = { value: false }
+
+    const reducer = createReducer(initialState, builder => {
+      builder.addMatcher(isAnyOf(actionA, actionB), state => {
+        return { ...state, value: true }
+      })
+    })
+
+    expect(reducer(initialState, trueAction)).toEqual({ value: true })
+
+    const falseAction = {
+      type: 'c',
+      payload: 'payload'
+    }
+
+    expect(reducer(initialState, falseAction)).toEqual(initialState)
+  })
+})
+
+describe('isAllOf', () => {
+  it('returns true only if all matchers match', () => {
+    const actionA = createAction<string>('a')
+
+    interface SpecialAction {
+      payload: 'SPECIAL'
+    }
+
+    const isActionSpecial = (action: any): action is SpecialAction => {
+      return action.payload === 'SPECIAL'
+    }
+
+    const trueAction = {
+      type: 'a',
+      payload: 'SPECIAL'
+    }
+
+    expect(isAllOf(actionA, isActionSpecial)(trueAction)).toEqual(true)
+
+    const falseAction = {
+      type: 'a',
+      payload: 'ORDINARY'
+    }
+
+    expect(isAllOf(actionA, isActionSpecial)(falseAction)).toEqual(false)
+
+    const thunkA = createAsyncThunk<string>('a', () => 'result')
+
+    const specialThunkAction = thunkA.fulfilled('SPECIAL', 'fakeRequestId')
+
+    expect(isAllOf(thunkA.fulfilled, isActionSpecial)(specialThunkAction)).toBe(
+      true
+    )
+
+    const ordinaryThunkAction = thunkA.fulfilled('ORDINARY', 'fakeRequestId')
+
+    expect(
+      isAllOf(thunkA.fulfilled, isActionSpecial)(ordinaryThunkAction)
+    ).toBe(false)
+  })
+})
+
+describe('isPending', () => {
+  test('should return false for a regular action', () => {
+    const action = createAction<string>('action/type')('testPayload')
+
+    expect(isPending()(action)).toBe(false)
+    expect(isPending(action)).toBe(false)
+    expect(isPending(thunk)).toBe(false)
+  })
+
+  test('should return true only for pending async thunk actions', () => {
+    const thunk = createAsyncThunk<string>('a', () => 'result')
+
+    const pendingAction = thunk.pending('fakeRequestId')
+    expect(isPending()(pendingAction)).toBe(true)
+    expect(isPending(pendingAction)).toBe(true)
+
+    const rejectedAction = thunk.rejected(
+      new Error('rejected'),
+      'fakeRequestId'
+    )
+    expect(isPending()(rejectedAction)).toBe(false)
+
+    const fulfilledAction = thunk.fulfilled('result', 'fakeRequestId')
+    expect(isPending()(fulfilledAction)).toBe(false)
+  })
+
+  test('should return true only for thunks provided as arguments', () => {
+    const thunkA = createAsyncThunk<string>('a', () => 'result')
+    const thunkB = createAsyncThunk<string>('b', () => 'result')
+    const thunkC = createAsyncThunk<string>('c', () => 'result')
+
+    const matchAC = isPending(thunkA, thunkC)
+    const matchB = isPending(thunkB)
+
+    function testPendingAction(
+      thunk: typeof thunkA | typeof thunkB | typeof thunkC,
+      expected: boolean
+    ) {
+      const pendingAction = thunk.pending('fakeRequestId')
+      expect(matchAC(pendingAction)).toBe(expected)
+      expect(matchB(pendingAction)).toBe(!expected)
+
+      const rejectedAction = thunk.rejected(
+        new Error('rejected'),
+        'fakeRequestId'
+      )
+      expect(matchAC(rejectedAction)).toBe(false)
+
+      const fulfilledAction = thunk.fulfilled('result', 'fakeRequestId')
+      expect(matchAC(fulfilledAction)).toBe(false)
+    }
+
+    testPendingAction(thunkA, true)
+    testPendingAction(thunkC, true)
+    testPendingAction(thunkB, false)
+  })
+})
+
+describe('isRejected', () => {
+  test('should return false for a regular action', () => {
+    const action = createAction<string>('action/type')('testPayload')
+
+    expect(isRejected()(action)).toBe(false)
+    expect(isRejected(action)).toBe(false)
+    expect(isRejected(thunk)).toBe(false)
+  })
+
+  test('should return true only for rejected async thunk actions', () => {
+    const thunk = createAsyncThunk<string>('a', () => 'result')
+
+    const pendingAction = thunk.pending('fakeRequestId')
+    expect(isRejected()(pendingAction)).toBe(false)
+
+    const rejectedAction = thunk.rejected(
+      new Error('rejected'),
+      'fakeRequestId'
+    )
+    expect(isRejected()(rejectedAction)).toBe(true)
+    expect(isRejected(rejectedAction)).toBe(true)
+
+    const fulfilledAction = thunk.fulfilled('result', 'fakeRequestId')
+    expect(isRejected()(fulfilledAction)).toBe(false)
+  })
+
+  test('should return true only for thunks provided as arguments', () => {
+    const thunkA = createAsyncThunk<string>('a', () => 'result')
+    const thunkB = createAsyncThunk<string>('b', () => 'result')
+    const thunkC = createAsyncThunk<string>('c', () => 'result')
+
+    const matchAC = isRejected(thunkA, thunkC)
+    const matchB = isRejected(thunkB)
+
+    function testRejectedAction(
+      thunk: typeof thunkA | typeof thunkB | typeof thunkC,
+      expected: boolean
+    ) {
+      const pendingAction = thunk.pending('fakeRequestId')
+      expect(matchAC(pendingAction)).toBe(false)
+
+      const rejectedAction = thunk.rejected(
+        new Error('rejected'),
+        'fakeRequestId'
+      )
+      expect(matchAC(rejectedAction)).toBe(expected)
+      expect(matchB(rejectedAction)).toBe(!expected)
+
+      const fulfilledAction = thunk.fulfilled('result', 'fakeRequestId')
+      expect(matchAC(fulfilledAction)).toBe(false)
+    }
+
+    testRejectedAction(thunkA, true)
+    testRejectedAction(thunkC, true)
+    testRejectedAction(thunkB, false)
+  })
+})
+
+describe('isRejectedWithValue', () => {
+  test('should return false for a regular action', () => {
+    const action = createAction<string>('action/type')('testPayload')
+
+    expect(isRejectedWithValue()(action)).toBe(false)
+    expect(isRejectedWithValue(action)).toBe(false)
+    expect(isRejectedWithValue(thunk)).toBe(false)
+  })
+
+  test('should return true only for rejected-with-value async thunk actions', async () => {
+    const thunk = createAsyncThunk<string>('a', (_, { rejectWithValue }) => {
+      return rejectWithValue('rejectWithValue!')
+    })
+
+    const pendingAction = thunk.pending('fakeRequestId')
+    expect(isRejectedWithValue()(pendingAction)).toBe(false)
+
+    const rejectedAction = thunk.rejected(
+      new Error('rejected'),
+      'fakeRequestId'
+    )
+    expect(isRejectedWithValue()(rejectedAction)).toBe(false)
+
+    const getState = jest.fn(() => ({}))
+    const dispatch = jest.fn((x: any) => x)
+    const extra = {}
+
+    // note: doesn't throw because we don't unwrap it
+    const rejectedWithValueAction = await thunk()(dispatch, getState, extra)
+
+    expect(isRejectedWithValue()(rejectedWithValueAction)).toBe(true)
+
+    const fulfilledAction = thunk.fulfilled('result', 'fakeRequestId')
+    expect(isRejectedWithValue()(fulfilledAction)).toBe(false)
+  })
+
+  test('should return true only for thunks provided as arguments', async () => {
+    const payloadCreator = (_: any, { rejectWithValue }: any) => {
+      return rejectWithValue('rejectWithValue!')
+    }
+
+    const thunkA = createAsyncThunk<string>('a', payloadCreator)
+    const thunkB = createAsyncThunk<string>('b', payloadCreator)
+    const thunkC = createAsyncThunk<string>('c', payloadCreator)
+
+    const matchAC = isRejectedWithValue(thunkA, thunkC)
+    const matchB = isRejectedWithValue(thunkB)
+
+    async function testRejectedAction(
+      thunk: typeof thunkA | typeof thunkB | typeof thunkC,
+      expected: boolean
+    ) {
+      const pendingAction = thunk.pending('fakeRequestId')
+      expect(matchAC(pendingAction)).toBe(false)
+
+      const rejectedAction = thunk.rejected(
+        new Error('rejected'),
+        'fakeRequestId'
+      )
+      // rejected-with-value is a narrower requirement than rejected
+      expect(matchAC(rejectedAction)).toBe(false)
+
+      const getState = jest.fn(() => ({}))
+      const dispatch = jest.fn((x: any) => x)
+      const extra = {}
+
+      // note: doesn't throw because we don't unwrap it
+      const rejectedWithValueAction = await thunk()(dispatch, getState, extra)
+
+      expect(matchAC(rejectedWithValueAction)).toBe(expected)
+      expect(matchB(rejectedWithValueAction)).toBe(!expected)
+
+      const fulfilledAction = thunk.fulfilled('result', 'fakeRequestId')
+      expect(matchAC(fulfilledAction)).toBe(false)
+    }
+
+    await testRejectedAction(thunkA, true)
+    await testRejectedAction(thunkC, true)
+    await testRejectedAction(thunkB, false)
+  })
+})
+
+describe('isFulfilled', () => {
+  test('should return false for a regular action', () => {
+    const action = createAction<string>('action/type')('testPayload')
+
+    expect(isFulfilled()(action)).toBe(false)
+    expect(isFulfilled(action)).toBe(false)
+    expect(isFulfilled(thunk)).toBe(false)
+  })
+
+  test('should return true only for fulfilled async thunk actions', () => {
+    const thunk = createAsyncThunk<string>('a', () => 'result')
+
+    const pendingAction = thunk.pending('fakeRequestId')
+    expect(isFulfilled()(pendingAction)).toBe(false)
+
+    const rejectedAction = thunk.rejected(
+      new Error('rejected'),
+      'fakeRequestId'
+    )
+    expect(isFulfilled()(rejectedAction)).toBe(false)
+
+    const fulfilledAction = thunk.fulfilled('result', 'fakeRequestId')
+    expect(isFulfilled()(fulfilledAction)).toBe(true)
+    expect(isFulfilled(fulfilledAction)).toBe(true)
+  })
+
+  test('should return true only for thunks provided as arguments', () => {
+    const thunkA = createAsyncThunk<string>('a', () => 'result')
+    const thunkB = createAsyncThunk<string>('b', () => 'result')
+    const thunkC = createAsyncThunk<string>('c', () => 'result')
+
+    const matchAC = isFulfilled(thunkA, thunkC)
+    const matchB = isFulfilled(thunkB)
+
+    function testFulfilledAction(
+      thunk: typeof thunkA | typeof thunkB | typeof thunkC,
+      expected: boolean
+    ) {
+      const pendingAction = thunk.pending('fakeRequestId')
+      expect(matchAC(pendingAction)).toBe(false)
+
+      const rejectedAction = thunk.rejected(
+        new Error('rejected'),
+        'fakeRequestId'
+      )
+      expect(matchAC(rejectedAction)).toBe(false)
+
+      const fulfilledAction = thunk.fulfilled('result', 'fakeRequestId')
+      expect(matchAC(fulfilledAction)).toBe(expected)
+      expect(matchB(fulfilledAction)).toBe(!expected)
+    }
+
+    testFulfilledAction(thunkA, true)
+    testFulfilledAction(thunkC, true)
+    testFulfilledAction(thunkB, false)
+  })
+})
+
+describe('isAsyncThunkAction', () => {
+  test('should return false for a regular action', () => {
+    const action = createAction<string>('action/type')('testPayload')
+
+    expect(isAsyncThunkAction()(action)).toBe(false)
+    expect(isAsyncThunkAction(action)).toBe(false)
+    expect(isAsyncThunkAction(thunk)).toBe(false)
+  })
+
+  test('should return true for any async thunk action if no arguments were provided', () => {
+    const thunk = createAsyncThunk<string>('a', () => 'result')
+    const matcher = isAsyncThunkAction()
+
+    const pendingAction = thunk.pending('fakeRequestId')
+    expect(matcher(pendingAction)).toBe(true)
+
+    const rejectedAction = thunk.rejected(
+      new Error('rejected'),
+      'fakeRequestId'
+    )
+    expect(matcher(rejectedAction)).toBe(true)
+
+    const fulfilledAction = thunk.fulfilled('result', 'fakeRequestId')
+    expect(matcher(fulfilledAction)).toBe(true)
+  })
+
+  test('should return true only for thunks provided as arguments', () => {
+    const thunkA = createAsyncThunk<string>('a', () => 'result')
+    const thunkB = createAsyncThunk<string>('b', () => 'result')
+    const thunkC = createAsyncThunk<string>('c', () => 'result')
+
+    const matchAC = isAsyncThunkAction(thunkA, thunkC)
+    const matchB = isAsyncThunkAction(thunkB)
+
+    function testAllActions(
+      thunk: typeof thunkA | typeof thunkB | typeof thunkC,
+      expected: boolean
+    ) {
+      const pendingAction = thunk.pending('fakeRequestId')
+      expect(matchAC(pendingAction)).toBe(expected)
+      expect(matchB(pendingAction)).toBe(!expected)
+
+      const rejectedAction = thunk.rejected(
+        new Error('rejected'),
+        'fakeRequestId'
+      )
+      expect(matchAC(rejectedAction)).toBe(expected)
+      expect(matchB(rejectedAction)).toBe(!expected)
+
+      const fulfilledAction = thunk.fulfilled('result', 'fakeRequestId')
+      expect(matchAC(fulfilledAction)).toBe(expected)
+      expect(matchB(fulfilledAction)).toBe(!expected)
+    }
+
+    testAllActions(thunkA, true)
+    testAllActions(thunkC, true)
+    testAllActions(thunkB, false)
+  })
+})
diff --git a/type-tests/files/MiddlewareArray.typetest.ts b/type-tests/files/MiddlewareArray.typetest.ts
index eb77189e..468db08d 100644
--- a/type-tests/files/MiddlewareArray.typetest.ts
+++ b/type-tests/files/MiddlewareArray.typetest.ts
@@ -1,6 +1,6 @@
-import { getDefaultMiddleware } from 'src/getDefaultMiddleware'
+import { getDefaultMiddleware } from '@reduxjs/toolkit'
 import { Middleware } from 'redux'
-import { DispatchForMiddlewares } from 'src/tsHelpers'
+import { DispatchForMiddlewares } from '@internal/tsHelpers'
 
 declare const expectType: <T>(t: T) => T
 
@@ -29,7 +29,7 @@ declare const thunkCreator: () => () => ThunkReturn
     expectType<number>(dispatch('foo'))
     expectType<ThunkReturn>(dispatch(thunkCreator()))
 
-    // typings:expect-error
+    // @ts-expect-error
     expectType<string>(dispatch('foo'))
   }
 
@@ -41,7 +41,7 @@ declare const thunkCreator: () => () => ThunkReturn
     expectType<string>(dispatch(5))
     expectType<ThunkReturn>(dispatch(thunkCreator()))
 
-    // typings:expect-error
+    // @ts-expect-error
     expectType<string>(dispatch('foo'))
   }
 
@@ -56,7 +56,7 @@ declare const thunkCreator: () => () => ThunkReturn
     expectType<string>(dispatch(5))
     expectType<ThunkReturn>(dispatch(thunkCreator()))
 
-    // typings:expect-error
+    // @ts-expect-error
     expectType<string>(dispatch('foo'))
   }
 
@@ -67,7 +67,7 @@ declare const thunkCreator: () => () => ThunkReturn
     expectType<number>(dispatch('foo'))
     expectType<ThunkReturn>(dispatch(thunkCreator()))
 
-    // typings:expect-error
+    // @ts-expect-error
     expectType<string>(dispatch('foo'))
   }
 
@@ -79,7 +79,7 @@ declare const thunkCreator: () => () => ThunkReturn
     expectType<string>(dispatch(5))
     expectType<ThunkReturn>(dispatch(thunkCreator()))
 
-    // typings:expect-error
+    // @ts-expect-error
     expectType<string>(dispatch('foo'))
   }
 
@@ -94,7 +94,7 @@ declare const thunkCreator: () => () => ThunkReturn
     expectType<string>(dispatch(5))
     expectType<ThunkReturn>(dispatch(thunkCreator()))
 
-    // typings:expect-error
+    // @ts-expect-error
     expectType<string>(dispatch('foo'))
   }
 
@@ -108,7 +108,7 @@ declare const thunkCreator: () => () => ThunkReturn
     expectType<string>(dispatch(5))
     expectType<ThunkReturn>(dispatch(thunkCreator()))
 
-    // typings:expect-error
+    // @ts-expect-error
     expectType<string>(dispatch('foo'))
   }
 }
diff --git a/type-tests/files/configureStore.typetest.ts b/type-tests/files/configureStore.typetest.ts
index 5b43eed7..a36ecb35 100644
--- a/type-tests/files/configureStore.typetest.ts
+++ b/type-tests/files/configureStore.typetest.ts
@@ -7,13 +7,15 @@ import {
   Reducer,
   Store
 } from 'redux'
-import { configureStore, PayloadAction, getDefaultMiddleware } from 'src'
+import {
+  configureStore,
+  PayloadAction,
+  getDefaultMiddleware
+} from '@reduxjs/toolkit'
 import thunk, { ThunkMiddleware, ThunkAction, ThunkDispatch } from 'redux-thunk'
+import { expectType } from './helpers'
 
 const _anyMiddleware: any = () => () => () => {}
-function expectType<T>(t: T) {
-  return t
-}
 
 /*
  * Test: configureStore() requires a valid reducer or reducer map.
@@ -30,13 +32,13 @@ function expectType<T>(t: T) {
     }
   })
 
-  // typings:expect-error
+  // @ts-expect-error
   configureStore({ reducer: 'not a reducer' })
 
-  // typings:expect-error
+  // @ts-expect-error
   configureStore({ reducer: { a: 'not a reducer' } })
 
-  // typings:expect-error
+  // @ts-expect-error
   configureStore({})
 }
 
@@ -48,7 +50,7 @@ function expectType<T>(t: T) {
   const store = configureStore({ reducer })
   const numberStore: Store<number, AnyAction> = store
 
-  // typings:expect-error
+  // @ts-expect-error
   const stringStore: Store<string, AnyAction> = store
 }
 
@@ -60,7 +62,7 @@ function expectType<T>(t: T) {
   const store = configureStore({ reducer })
   const numberStore: Store<number, PayloadAction<number>> = store
 
-  // typings:expect-error
+  // @ts-expect-error
   const stringStore: Store<number, PayloadAction<string>> = store
 }
 
@@ -75,9 +77,9 @@ function expectType<T>(t: T) {
     middleware: [middleware]
   })
 
-  // typings:expect-error
   configureStore({
     reducer: () => 0,
+    // @ts-expect-error
     middleware: ['not middleware']
   })
 }
@@ -91,9 +93,9 @@ function expectType<T>(t: T) {
     devTools: true
   })
 
-  // typings:expect-error
   configureStore({
     reducer: () => 0,
+    // @ts-expect-error
     devTools: 'true'
   })
 }
@@ -107,9 +109,9 @@ function expectType<T>(t: T) {
     devTools: { name: 'myApp' }
   })
 
-  // typings:expect-error
   configureStore({
     reducer: () => 0,
+    // @ts-expect-error
     devTools: { appname: 'myApp' }
   })
 }
@@ -123,9 +125,9 @@ function expectType<T>(t: T) {
     preloadedState: 0
   })
 
-  // typings:expect-error
   configureStore({
     reducer: () => 0,
+    // @ts-expect-error
     preloadedState: 'non-matching state type'
   })
 }
@@ -139,9 +141,9 @@ function expectType<T>(t: T) {
     enhancers: [applyMiddleware(store => next => next)]
   })
 
-  // typings:expect-error
   configureStore({
     reducer: () => 0,
+    // @ts-expect-error
     enhancers: ['not a store enhancer']
   })
 }
@@ -191,7 +193,7 @@ function expectType<T>(t: T) {
     })
 
     store.dispatch(thunkA())
-    // typings:expect-error
+    // @ts-expect-error
     store.dispatch(thunkB())
   }
   /**
@@ -202,9 +204,9 @@ function expectType<T>(t: T) {
       reducer: reducerA,
       middleware: []
     })
-    // typings:expect-error
+    // @ts-expect-error
     store.dispatch(thunkA())
-    // typings:expect-error
+    // @ts-expect-error
     store.dispatch(thunkB())
   }
   /**
@@ -216,7 +218,7 @@ function expectType<T>(t: T) {
       middleware: [thunk] as [ThunkMiddleware<StateA>]
     })
     store.dispatch(thunkA())
-    // typings:expect-error
+    // @ts-expect-error
     store.dispatch(thunkB())
   }
   /**
@@ -229,7 +231,7 @@ function expectType<T>(t: T) {
     })
 
     store.dispatch(thunkA())
-    // typings:expect-error
+    // @ts-expect-error
     store.dispatch(thunkB())
   }
   /**
@@ -241,7 +243,7 @@ function expectType<T>(t: T) {
       middleware: ([] as any) as [Middleware<(a: StateA) => boolean, StateA>]
     })
     const result: boolean = store.dispatch(5)
-    // typings:expect-error
+    // @ts-expect-error
     const result2: string = store.dispatch(5)
   }
   /**
@@ -273,7 +275,7 @@ function expectType<T>(t: T) {
     // about the value of the extraArgument, as it will always work with every
     // ThunkMiddleware, no matter the actual extraArgument type
     store.dispatch(function() {} as ThunkAction<void, {}, unknown, AnyAction>)
-    // typings:expect-error
+    // @ts-expect-error
     store.dispatch(function() {} as ThunkAction<void, {}, boolean, AnyAction>)
   }
 
@@ -290,7 +292,7 @@ function expectType<T>(t: T) {
     })
     const result1: 'A' = store.dispatch('a')
     const result2: Promise<'A'> = store.dispatch(thunkA())
-    // typings:expect-error
+    // @ts-expect-error
     store.dispatch(thunkB())
   }
 
@@ -311,7 +313,7 @@ function expectType<T>(t: T) {
     })
     const result1: 'A' = store.dispatch('a')
     const result2: Promise<'A'> = store.dispatch(thunkA())
-    // typings:expect-error
+    // @ts-expect-error
     store.dispatch(thunkB())
   }
 
@@ -332,7 +334,7 @@ function expectType<T>(t: T) {
     })
     const result1: 'A' = store.dispatch('a')
     const result2: Promise<'A'> = store.dispatch(thunkA())
-    // typings:expect-error
+    // @ts-expect-error
     store.dispatch(thunkB())
   }
 
@@ -350,7 +352,7 @@ function expectType<T>(t: T) {
     })
     const result1: 'A' = store.dispatch('a')
     const result2: Promise<'A'> = store.dispatch(thunkA())
-    // typings:expect-error
+    // @ts-expect-error
     store.dispatch(thunkB())
   }
 
@@ -370,7 +372,7 @@ function expectType<T>(t: T) {
     const result1: 'A' = store.dispatch('a')
     const result2: Promise<'A'> = store.dispatch(thunkA())
     const result3: 'B' = store.dispatch('b')
-    // typings:expect-error
+    // @ts-expect-error
     store.dispatch(thunkB())
   }
 
@@ -387,7 +389,7 @@ function expectType<T>(t: T) {
         ] as const
     })
     const result1: 'A' = store.dispatch('a')
-    // typings:expect-error
+    // @ts-expect-error
     store.dispatch(thunkA())
   }
 }
diff --git a/type-tests/files/createAction.typetest.ts b/type-tests/files/createAction.typetest.ts
index e702070b..af17726d 100644
--- a/type-tests/files/createAction.typetest.ts
+++ b/type-tests/files/createAction.typetest.ts
@@ -8,12 +8,9 @@ import {
   ActionCreatorWithPayload,
   ActionCreatorWithNonInferrablePayload,
   ActionCreatorWithPreparedPayload
-} from '../../src'
-import { IsAny } from 'src/tsHelpers'
-
-function expectType<T>(p: T): T {
-  return p
-}
+} from '@reduxjs/toolkit'
+import { IsAny } from '@internal/tsHelpers'
+import { expectType } from './helpers'
 
 /* PayloadAction */
 
@@ -24,7 +21,7 @@ function expectType<T>(p: T): T {
   const action: PayloadAction<number> = { type: '', payload: 5 }
   const numberPayload: number = action.payload
 
-  // typings:expect-error
+  // @ts-expect-error
   const stringPayload: string = action.payload
 }
 
@@ -32,11 +29,11 @@ function expectType<T>(p: T): T {
  * Test: PayloadAction type parameter is required.
  */
 {
-  // typings:expect-error
+  // @ts-expect-error
   const action: PayloadAction = { type: '', payload: 5 }
-  // typings:expect-error
+  // @ts-expect-error
   const numberPayload: number = action.payload
-  // typings:expect-error
+  // @ts-expect-error
   const stringPayload: string = action.payload
 }
 
@@ -46,7 +43,7 @@ function expectType<T>(p: T): T {
 {
   const action: PayloadAction<number> = { type: '', payload: 5 }
 
-  // typings:expect-error
+  // @ts-expect-error
   const action2: PayloadAction = { type: 1, payload: 5 }
 }
 
@@ -77,9 +74,9 @@ function expectType<T>(p: T): T {
   expectType<PayloadAction<number | undefined>>(actionCreator())
   expectType<PayloadAction<number | undefined>>(actionCreator(undefined))
 
-  // typings:expect-error
+  // @ts-expect-error
   expectType<PayloadAction<number>>(actionCreator())
-  // typings:expect-error
+  // @ts-expect-error
   expectType<PayloadAction<undefined>>(actionCreator(1))
 }
 
@@ -118,7 +115,7 @@ function expectType<T>(p: T): T {
   const increment = createAction<number, 'increment'>('increment')
   const n: number = increment(1).payload
 
-  // typings:expect-error
+  // @ts-expect-error
   increment('').payload
 }
 
@@ -127,7 +124,7 @@ function expectType<T>(p: T): T {
  */
 {
   const increment = createAction('increment')
-  // typings:expect-error
+  // @ts-expect-error
   const n: number = increment(1).payload
 }
 /*
@@ -138,9 +135,9 @@ function expectType<T>(p: T): T {
   const n: string = increment(1).type
   const s: 'increment' = increment(1).type
 
-  // typings:expect-error
+  // @ts-expect-error
   const r: 'other' = increment(1).type
-  // typings:expect-error
+  // @ts-expect-error
   const q: number = increment(1).type
 }
 
@@ -164,9 +161,9 @@ function expectType<T>(p: T): T {
   }))
   expectType<number>(strLenAction('test').payload)
 
-  // typings:expect-error
+  // @ts-expect-error
   expectType<string>(strLenAction('test').payload)
-  // typings:expect-error
+  // @ts-expect-error
   const error: any = strLenAction('test').error
 }
 
@@ -181,9 +178,9 @@ function expectType<T>(p: T): T {
 
   expectType<number>(strLenMetaAction('test').meta)
 
-  // typings:expect-error
+  // @ts-expect-error
   expectType<string>(strLenMetaAction('test').meta)
-  // typings:expect-error
+  // @ts-expect-error
   const error: any = strLenMetaAction('test').error
 }
 
@@ -198,7 +195,7 @@ function expectType<T>(p: T): T {
 
   expectType<boolean>(boolErrorAction('test').error)
 
-  // typings:expect-error
+  // @ts-expect-error
   expectType<string>(boolErrorAction('test').error)
 }
 
@@ -213,7 +210,7 @@ function expectType<T>(p: T): T {
 
   expectType<string>(strErrorAction('test').error)
 
-  // typings:expect-error
+  // @ts-expect-error
   expectType<boolean>(strErrorAction('test').error)
 }
 
@@ -224,9 +221,9 @@ function expectType<T>(p: T): T {
   const action = createAction<{ input?: string }>('ACTION')
   const t: string | undefined = action({ input: '' }).payload.input
 
-  // typings:expect-error
+  // @ts-expect-error
   const u: number = action({ input: '' }).payload.input
-  // typings:expect-error
+  // @ts-expect-error
   const v: number = action({ input: 3 }).payload.input
 }
 
@@ -246,11 +243,11 @@ function expectType<T>(p: T): T {
   const error: IsAny<Ret['error'], true, false> = true
   const meta: IsAny<Ret['meta'], true, false> = true
 
-  // typings:expect-error
+  // @ts-expect-error
   const payloadNotAny: IsAny<Ret['payload'], true, false> = false
-  // typings:expect-error
+  // @ts-expect-error
   const errorNotAny: IsAny<Ret['error'], true, false> = false
-  // typings:expect-error
+  // @ts-expect-error
   const metaNotAny: IsAny<Ret['meta'], true, false> = false
 }
 
@@ -266,9 +263,9 @@ function expectType<T>(p: T): T {
       expectType<'test'>(x.type)
       expectType<string>(x.payload)
     } else {
-      // typings:expect-error
+      // @ts-expect-error
       expectType<'test'>(x.type)
-      // typings:expect-error
+      // @ts-expect-error
       expectType<any>(x.payload)
     }
   }
@@ -289,7 +286,7 @@ function expectType<T>(p: T): T {
     const x: Action<unknown> = {} as any
     if (actionCreator.match(x)) {
       expectType<'test'>(x.type)
-      // typings:expect-error
+      // @ts-expect-error
       expectType<{}>(x.payload)
     }
   }
@@ -307,11 +304,11 @@ function expectType<T>(p: T): T {
       expectType<string>(x.payload)
       expectType<string>(x.meta)
       expectType<boolean>(x.error)
-      // typings:expect-error
+      // @ts-expect-error
       expectType<number>(x.payload)
-      // typings:expect-error
+      // @ts-expect-error
       expectType<number>(x.meta)
-      // typings:expect-error
+      // @ts-expect-error
       expectType<number>(x.error)
     }
   }
@@ -322,8 +319,9 @@ function expectType<T>(p: T): T {
     expectType<Array<PayloadAction<string, 'test'>>>(
       x.filter(actionCreator.match)
     )
-    // typings:expect-error
+
     expectType<Array<PayloadAction<number, 'test'>>>(
+      // @ts-expect-error
       x.filter(actionCreator.match)
     )
   }
diff --git a/type-tests/files/createAsyncThunk.typetest.ts b/type-tests/files/createAsyncThunk.typetest.ts
index 6c344372..890fa74d 100644
--- a/type-tests/files/createAsyncThunk.typetest.ts
+++ b/type-tests/files/createAsyncThunk.typetest.ts
@@ -1,14 +1,19 @@
-import { createAsyncThunk, Dispatch, createReducer, AnyAction } from 'src'
+/* eslint-disable no-lone-blocks */
+import {
+  createAsyncThunk,
+  createReducer,
+  AnyAction,
+  unwrapResult,
+  SerializedError
+} from '@reduxjs/toolkit'
 import { ThunkDispatch } from 'redux-thunk'
-import { unwrapResult, SerializedError } from 'src/createAsyncThunk'
 
 import apiRequest, { AxiosError } from 'axios'
-import { IsAny, IsUnknown } from 'src/tsHelpers'
+import { IsAny, IsUnknown } from '@internal/tsHelpers'
+import { expectType } from './helpers'
 
-function expectType<T>(t: T) {
-  return t
-}
 const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction>
+const anyAction = { type: 'foo' } as AnyAction
 
   // basic usage
 ;(async function() {
@@ -41,11 +46,11 @@ const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction>
 
   if (async.fulfilled.match(result)) {
     expectType<ReturnType<typeof async['fulfilled']>>(result)
-    // typings:expect-error
+    // @ts-expect-error
     expectType<ReturnType<typeof async['rejected']>>(result)
   } else {
     expectType<ReturnType<typeof async['rejected']>>(result)
-    // typings:expect-error
+    // @ts-expect-error
     expectType<ReturnType<typeof async['fulfilled']>>(result)
   }
 
@@ -53,7 +58,7 @@ const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction>
     .then(unwrapResult)
     .then(result => {
       expectType<number>(result)
-      // typings:expect-error
+      // @ts-expect-error
       expectType<Error>(result)
     })
     .catch(error => {
@@ -102,7 +107,7 @@ const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction>
   )
 
   correctDispatch(fetchBooksTAC(1))
-  // typings:expect-error
+  // @ts-expect-error
   defaultDispatch(fetchBooksTAC(1))
 })()
 /**
@@ -129,6 +134,10 @@ const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction>
   } else {
     expectType<ReturnValue>(returned.payload)
   }
+
+  expectType<ReturnValue>(unwrapResult(returned))
+  // @ts-expect-error
+  expectType<RejectValue>(unwrapResult(returned))
 })()
 
 {
@@ -180,10 +189,23 @@ const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction>
         // rejected by throw
         expectType<undefined>(result.payload)
         expectType<SerializedError>(result.error)
-        // typings:expect-error
+        // @ts-expect-error
         expectType<IsAny<typeof result['error'], true, false>>(true)
       }
     }
+    defaultDispatch(fetchLiveCallsError('asd'))
+      .then(result => {
+        expectType<Item[] | ErrorFromServer | undefined>(result.payload)
+        // @ts-expect-error
+        expectType<Item[]>(unwrapped)
+        return result
+      })
+      .then(unwrapResult)
+      .then(unwrapped => {
+        expectType<Item[]>(unwrapped)
+        // @ts-expect-error
+        expectType<ErrorFromServer>(unwrapResult(unwrapped))
+      })
   })
 }
 
@@ -195,7 +217,7 @@ const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction>
   {
     const asyncThunk = createAsyncThunk('test', () => 0)
     expectType<() => any>(asyncThunk)
-    // typings:expect-error cannot be called with an argument
+    // @ts-expect-error cannot be called with an argument
     asyncThunk(0 as any)
   }
 
@@ -203,7 +225,7 @@ const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction>
   {
     const asyncThunk = createAsyncThunk('test', (arg: undefined) => 0)
     expectType<() => any>(asyncThunk)
-    // typings:expect-error cannot be called with an argument
+    // @ts-expect-error cannot be called with an argument
     asyncThunk(0 as any)
   }
 
@@ -211,7 +233,7 @@ const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction>
   {
     const asyncThunk = createAsyncThunk('test', (arg: void) => 0)
     expectType<() => any>(asyncThunk)
-    // typings:expect-error cannot be called with an argument
+    // @ts-expect-error cannot be called with an argument
     asyncThunk(0 as any)
   }
 
@@ -223,7 +245,7 @@ const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction>
     expectType<(arg?: number) => any>(asyncThunk)
     asyncThunk()
     asyncThunk(5)
-    // typings:expect-error
+    // @ts-expect-error
     asyncThunk('string')
   }
 
@@ -235,7 +257,7 @@ const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction>
     expectType<(arg?: number) => any>(asyncThunk)
     asyncThunk()
     asyncThunk(5)
-    // typings:expect-error
+    // @ts-expect-error
     asyncThunk('string')
   }
 
@@ -245,7 +267,7 @@ const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction>
     expectType<(arg?: number) => any>(asyncThunk)
     asyncThunk()
     asyncThunk(5)
-    // typings:expect-error
+    // @ts-expect-error
     asyncThunk('string')
   }
 
@@ -254,7 +276,7 @@ const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction>
     const asyncThunk = createAsyncThunk('test', (arg: any) => 0)
     expectType<IsAny<Parameters<typeof asyncThunk>[0], true, false>>(true)
     asyncThunk(5)
-    // typings:expect-error
+    // @ts-expect-error
     asyncThunk()
   }
 
@@ -263,7 +285,7 @@ const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction>
     const asyncThunk = createAsyncThunk('test', (arg: unknown) => 0)
     expectType<IsUnknown<Parameters<typeof asyncThunk>[0], true, false>>(true)
     asyncThunk(5)
-    // typings:expect-error
+    // @ts-expect-error
     asyncThunk()
   }
 
@@ -272,7 +294,7 @@ const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction>
     const asyncThunk = createAsyncThunk('test', (arg: number) => 0)
     expectType<(arg: number) => any>(asyncThunk)
     asyncThunk(5)
-    // typings:expect-error
+    // @ts-expect-error
     asyncThunk()
   }
 
@@ -280,7 +302,7 @@ const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction>
   {
     const asyncThunk = createAsyncThunk('test', (arg: undefined, thunkApi) => 0)
     expectType<() => any>(asyncThunk)
-    // typings:expect-error cannot be called with an argument
+    // @ts-expect-error cannot be called with an argument
     asyncThunk(0 as any)
   }
 
@@ -288,7 +310,7 @@ const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction>
   {
     const asyncThunk = createAsyncThunk('test', (arg: void, thunkApi) => 0)
     expectType<() => any>(asyncThunk)
-    // typings:expect-error cannot be called with an argument
+    // @ts-expect-error cannot be called with an argument
     asyncThunk(0 as any)
   }
 
@@ -303,7 +325,7 @@ const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction>
     expectType<(arg?: number) => any>(asyncThunk)
     asyncThunk()
     asyncThunk(5)
-    // typings:expect-error
+    // @ts-expect-error
     asyncThunk('string')
   }
 
@@ -316,7 +338,7 @@ const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction>
     expectType<(arg?: number) => any>(asyncThunk)
     asyncThunk()
     asyncThunk(5)
-    // typings:expect-error
+    // @ts-expect-error
     asyncThunk('string')
   }
 
@@ -325,7 +347,7 @@ const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction>
     const asyncThunk = createAsyncThunk('test', (arg: any, thunkApi) => 0)
     expectType<IsAny<Parameters<typeof asyncThunk>[0], true, false>>(true)
     asyncThunk(5)
-    // typings:expect-error
+    // @ts-expect-error
     asyncThunk()
   }
 
@@ -334,7 +356,7 @@ const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction>
     const asyncThunk = createAsyncThunk('test', (arg: unknown, thunkApi) => 0)
     expectType<IsUnknown<Parameters<typeof asyncThunk>[0], true, false>>(true)
     asyncThunk(5)
-    // typings:expect-error
+    // @ts-expect-error
     asyncThunk()
   }
 
@@ -343,7 +365,31 @@ const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction>
     const asyncThunk = createAsyncThunk('test', (arg: number, thunkApi) => 0)
     expectType<(arg: number) => any>(asyncThunk)
     asyncThunk(5)
-    // typings:expect-error
+    // @ts-expect-error
     asyncThunk()
   }
 }
+
+{
+  type Funky = { somethingElse: 'Funky!' }
+  function funkySerializeError(err: any): Funky {
+    return { somethingElse: 'Funky!' }
+  }
+
+  // has to stay on one line or type tests fail in older TS versions
+  // prettier-ignore
+  // @ts-expect-error
+  const shouldFail = createAsyncThunk('without generics', () => {}, { serializeError: funkySerializeError })
+
+  const shouldWork = createAsyncThunk<
+    any,
+    void,
+    { serializedErrorType: Funky }
+  >('with generics', () => {}, {
+    serializeError: funkySerializeError
+  })
+
+  if (shouldWork.rejected.match(anyAction)) {
+    expectType<Funky>(anyAction.error)
+  }
+}
diff --git a/type-tests/files/createEntityAdapter.typetest.ts b/type-tests/files/createEntityAdapter.typetest.ts
index 5e00b95e..5754761c 100644
--- a/type-tests/files/createEntityAdapter.typetest.ts
+++ b/type-tests/files/createEntityAdapter.typetest.ts
@@ -3,13 +3,12 @@ import {
   createEntityAdapter,
   EntityAdapter,
   ActionCreatorWithPayload,
-  ActionCreatorWithoutPayload
-} from 'src'
-import { EntityStateAdapter, EntityId, Update } from 'src/entities/models'
-
-function expectType<T>(t: T) {
-  return t
-}
+  ActionCreatorWithoutPayload,
+  EntityStateAdapter,
+  EntityId,
+  Update
+} from '@reduxjs/toolkit'
+import { expectType } from './helpers'
 
 function extractReducers<T>(
   adapter: EntityAdapter<T>
@@ -77,7 +76,7 @@ function extractReducers<T>(
     initialState: adapter.getInitialState(),
     reducers: {
       addOne: adapter.addOne,
-      // typings:expect-error
+      // @ts-expect-error
       addOne2: adapter2.addOne
     }
   })
@@ -112,7 +111,7 @@ function extractReducers<T>(
     name: 'test',
     initialState: { somethingElse: '' },
     reducers: {
-      // typings:expect-error
+      // @ts-expect-error
       addOne: adapter.addOne
     }
   })
diff --git a/type-tests/files/createReducer.typetest.ts b/type-tests/files/createReducer.typetest.ts
index e3a594c4..14edaa19 100644
--- a/type-tests/files/createReducer.typetest.ts
+++ b/type-tests/files/createReducer.typetest.ts
@@ -1,7 +1,10 @@
 import { Reducer } from 'redux'
-import { createReducer, createAction, ActionReducerMapBuilder } from '../../src'
-
-function expectType<T>(p: T) {}
+import {
+  createReducer,
+  createAction,
+  ActionReducerMapBuilder
+} from '@reduxjs/toolkit'
+import { expectType } from './helpers'
 
 /*
  * Test: createReducer() infers type of returned reducer.
@@ -21,7 +24,7 @@ function expectType<T>(p: T) {}
 
   const numberReducer: Reducer<number> = reducer
 
-  // typings:expect-error
+  // @ts-expect-error
   const stringReducer: Reducer<string> = reducer
 }
 
@@ -44,7 +47,7 @@ function expectType<T>(p: T) {}
     decrement: decrementHandler
   })
 
-  // typings:expect-error
+  // @ts-expect-error
   createReducer<string>(0, {
     increment: incrementHandler,
     decrement: decrementHandler
@@ -73,6 +76,6 @@ function expectType<T>(p: T) {}
   )
 
   expectType<number>(reducer(0, increment(5)))
-  // typings:expect-error
+  // @ts-expect-error
   expectType<string>(reducer(0, increment(5)))
 }
diff --git a/type-tests/files/createSlice.typetest.ts b/type-tests/files/createSlice.typetest.ts
index 5e6c03a4..1039a8a4 100644
--- a/type-tests/files/createSlice.typetest.ts
+++ b/type-tests/files/createSlice.typetest.ts
@@ -1,5 +1,4 @@
 import { Action, AnyAction, Reducer } from 'redux'
-import { ValidateSliceCaseReducers } from 'src/createSlice'
 import {
   ActionCreatorWithNonInferrablePayload,
   ActionCreatorWithOptionalPayload,
@@ -10,12 +9,10 @@ import {
   createAction,
   createSlice,
   PayloadAction,
-  SliceCaseReducers
-} from '../../src'
-
-function expectType<T>(t: T) {
-  return t
-}
+  SliceCaseReducers,
+  ValidateSliceCaseReducers
+} from '@reduxjs/toolkit'
+import { expectType } from './helpers'
 
 /*
  * Test: Slice name is strongly typed.
@@ -47,7 +44,7 @@ const actionCreators = {
 expectType<typeof counterSlice.actions>(actionCreators.counter)
 expectType<typeof uiSlice.actions>(actionCreators.ui)
 
-// typings:expect-error
+// @ts-expect-error
 const value = actionCreators.anyKey
 
 /*
@@ -73,9 +70,9 @@ const value = actionCreators.anyKey
 
   const reducer: Reducer<number, PayloadAction> = slice.reducer
 
-  // typings:expect-error
+  // @ts-expect-error
   const stringReducer: Reducer<string, PayloadAction> = slice.reducer
-  // typings:expect-error
+  // @ts-expect-error
   const anyActionReducer: Reducer<string, AnyAction> = slice.reducer
 
   /* Actions */
@@ -83,7 +80,7 @@ const value = actionCreators.anyKey
   slice.actions.increment(1)
   slice.actions.decrement(1)
 
-  // typings:expect-error
+  // @ts-expect-error
   slice.actions.other(1)
 }
 
@@ -131,13 +128,13 @@ const value = actionCreators.anyKey
   )
   counter.actions.addTwo(1, 2)
 
-  // typings:expect-error
+  // @ts-expect-error
   counter.actions.multiply()
 
-  // typings:expect-error
+  // @ts-expect-error
   counter.actions.multiply('2')
 
-  // typings:expect-error
+  // @ts-expect-error
   counter.actions.addTwo(1)
 }
 
@@ -162,9 +159,9 @@ const value = actionCreators.anyKey
   const t: string = counter.actions.decrement.type
   const u: string = counter.actions.multiply.type
 
-  // typings:expect-error
+  // @ts-expect-error
   const x: 'counter/increment' = counter.actions.increment.type
-  // typings:expect-error
+  // @ts-expect-error
   const y: 'increment' = counter.actions.increment.type
 }
 
@@ -201,10 +198,10 @@ const value = actionCreators.anyKey
   expectType<string>(counter.actions.concatMetaStrLen('test').payload)
   expectType<number>(counter.actions.concatMetaStrLen('test').meta)
 
-  // typings:expect-error
+  // @ts-expect-error
   expectType<string>(counter.actions.incrementByStrLen('test').payload)
 
-  // typings:expect-error
+  // @ts-expect-error
   expectType<string>(counter.actions.concatMetaStrLen('test').meta)
 }
 
@@ -246,7 +243,7 @@ const value = actionCreators.anyKey
       // case: meta is typed differently in the reducer than returned from prepare
       testErroneousMeta: {
         reducer(_, action: PayloadAction<number, string, 'meta', 'error'>) {},
-        // typings:expect-error
+        // @ts-expect-error
         prepare: (payload: number) => ({
           payload,
           meta: 1,
@@ -256,7 +253,7 @@ const value = actionCreators.anyKey
       // case: error is typed differently in the reducer than returned from prepare
       testErroneousError: {
         reducer(_, action: PayloadAction<number, string, 'meta', 'error'>) {},
-        // typings:expect-error
+        // @ts-expect-error
         prepare: (payload: number) => ({
           payload,
           meta: 'meta' as 'meta',
@@ -300,20 +297,23 @@ const value = actionCreators.anyKey
   )
 
   // Should not mismatch the payload if it's a simple reducer
-  // typings:expect-error
+
   expectType<(state: number, action: PayloadAction<string>) => number | void>(
+    // @ts-expect-error
     counter.caseReducers.increment
   )
 
   // Should not mismatch the payload if it's a reducer with a prepare callback
-  // typings:expect-error
+
   expectType<(state: number, action: PayloadAction<string>) => number | void>(
+    // @ts-expect-error
     counter.caseReducers.decrement
   )
 
   // Should not include entries that don't exist
-  // typings:expect-error
+
   expectType<(state: number, action: PayloadAction<string>) => number | void>(
+    // @ts-expect-error
     counter.caseReducers.someThingNonExistant
   )
 }
@@ -322,7 +322,6 @@ const value = actionCreators.anyKey
  * Test: prepared payload does not match action payload - should cause an error.
  */
 {
-  // typings:expect-error
   const counter = createSlice({
     name: 'counter',
     initialState: { counter: 0 },
@@ -331,6 +330,7 @@ const value = actionCreators.anyKey
         reducer(state, action: PayloadAction<string>) {
           state.counter += action.payload.length
         },
+        // @ts-expect-error
         prepare(x: string) {
           return {
             payload: 6
@@ -388,9 +388,9 @@ const value = actionCreators.anyKey
     expectType<string>(x.type)
     expectType<string>(x.payload)
   } else {
-    // typings:expect-error
+    // @ts-expect-error
     expectType<string>(x.type)
-    // typings:expect-error
+    // @ts-expect-error
     expectType<string>(x.payload)
   }
 }
@@ -448,7 +448,7 @@ const value = actionCreators.anyKey
     reducers: {
       magic(state) {
         expectType<GenericState<string>>(state)
-        // typings:expect-error
+        // @ts-expect-error
         expectType<GenericState<number>>(state)
 
         state.status = 'finished'
diff --git a/type-tests/files/helpers.ts b/type-tests/files/helpers.ts
new file mode 100644
index 00000000..ee067b37
--- /dev/null
+++ b/type-tests/files/helpers.ts
@@ -0,0 +1,48 @@
+import { IsAny, IsUnknown } from '../../src/tsHelpers'
+
+export function expectType<T>(t: T): T {
+  return t
+}
+
+type Equals<T, U> = IsAny<
+  T,
+  never,
+  IsAny<U, never, [T] extends [U] ? ([U] extends [T] ? any : never) : never>
+>
+export function expectExactType<T>(t: T) {
+  return <U extends Equals<T, U>>(u: U) => {}
+}
+
+type EnsureUnknown<T extends any> = IsUnknown<T, any, never>
+export function expectUnknown<T extends EnsureUnknown<T>>(t: T) {
+  return t
+}
+
+type EnsureAny<T extends any> = IsAny<T, any, never>
+export function expectExactAny<T extends EnsureAny<T>>(t: T) {
+  return t
+}
+
+type IsNotAny<T> = IsAny<T, never, any>
+export function expectNotAny<T extends IsNotAny<T>>(t: T): T {
+  return t
+}
+
+expectType<string>('5' as string)
+expectType<string>('5' as const)
+expectType<string>('5' as any)
+expectExactType('5' as const)('5' as const)
+// @ts-expect-error
+expectExactType('5' as string)('5' as const)
+// @ts-expect-error
+expectExactType('5' as any)('5' as const)
+expectUnknown('5' as unknown)
+// @ts-expect-error
+expectUnknown('5' as const)
+// @ts-expect-error
+expectUnknown('5' as any)
+expectExactAny('5' as any)
+// @ts-expect-error
+expectExactAny('5' as const)
+// @ts-expect-error
+expectExactAny('5' as unknown)
diff --git a/type-tests/files/mapBuilders.typetest.ts b/type-tests/files/mapBuilders.typetest.ts
index 6fe4671c..2516da60 100644
--- a/type-tests/files/mapBuilders.typetest.ts
+++ b/type-tests/files/mapBuilders.typetest.ts
@@ -1,9 +1,6 @@
-import { executeReducerBuilderCallback } from 'src/mapBuilders'
-import { createAction, AnyAction } from 'src'
-
-function expectType<T>(t: T) {
-  return t
-}
+import { executeReducerBuilderCallback } from '@internal/mapBuilders'
+import { createAction, AnyAction } from '@reduxjs/toolkit'
+import { expectType } from './helpers'
 
 /** Test:  alternative builder callback for actionMap */
 {
@@ -14,20 +11,20 @@ function expectType<T>(t: T) {
     builder.addCase(increment, (state, action) => {
       expectType<number>(state)
       expectType<{ type: 'increment'; payload: number }>(action)
-      // typings:expect-error
+      // @ts-expect-error
       expectType<string>(state)
-      // typings:expect-error
+      // @ts-expect-error
       expectType<{ type: 'increment'; payload: string }>(action)
-      // typings:expect-error
+      // @ts-expect-error
       expectType<{ type: 'decrement'; payload: number }>(action)
     })
 
     builder.addCase('increment', (state, action) => {
       expectType<number>(state)
       expectType<{ type: 'increment' }>(action)
-      // typings:expect-error
+      // @ts-expect-error
       expectType<{ type: 'decrement' }>(action)
-      // typings:expect-error - this cannot be inferred and has to be manually specified
+      // @ts-expect-error - this cannot be inferred and has to be manually specified
       expectType<{ type: 'increment'; payload: number }>(action)
     })
 
@@ -35,7 +32,7 @@ function expectType<T>(t: T) {
       increment,
       (state, action: ReturnType<typeof increment>) => state
     )
-    // typings:expect-error
+    // @ts-expect-error
     builder.addCase(
       increment,
       (state, action: ReturnType<typeof decrement>) => state
@@ -45,7 +42,7 @@ function expectType<T>(t: T) {
       'increment',
       (state, action: ReturnType<typeof increment>) => state
     )
-    // typings:expect-error
+    // @ts-expect-error
     builder.addCase(
       'decrement',
       (state, action: ReturnType<typeof increment>) => state
@@ -87,7 +84,7 @@ function expectType<T>(t: T) {
     {
       // addMatcher() should prevent further calls to addCase()
       const b = builder.addMatcher(increment.match, () => {})
-      // typings:expect-error
+      // @ts-expect-error
       b.addCase(increment, () => {})
       b.addMatcher(increment.match, () => {})
       b.addDefaultCase(() => {})
@@ -96,11 +93,11 @@ function expectType<T>(t: T) {
     {
       // addDefaultCase() should prevent further calls to addCase(), addMatcher() and addDefaultCase
       const b = builder.addDefaultCase(() => {})
-      // typings:expect-error
+      // @ts-expect-error
       b.addCase(increment, () => {})
-      // typings:expect-error
+      // @ts-expect-error
       b.addMatcher(increment.match, () => {})
-      // typings:expect-error
+      // @ts-expect-error
       b.addDefaultCase(() => {})
     }
   })
diff --git a/type-tests/files/matchers.typetest.ts b/type-tests/files/matchers.typetest.ts
new file mode 100644
index 00000000..83a9edb4
--- /dev/null
+++ b/type-tests/files/matchers.typetest.ts
@@ -0,0 +1,315 @@
+import { IsUnknown } from '@internal/tsHelpers'
+import { AnyAction } from 'redux'
+import {
+  createAction,
+  createAsyncThunk,
+  isAllOf,
+  isAnyOf,
+  isAsyncThunkAction,
+  isFulfilled,
+  isPending,
+  isRejected,
+  isRejectedWithValue,
+  SerializedError
+} from '../../src'
+
+/* isAnyOf */
+
+/*
+ * Test: isAnyOf correctly narrows types when used with action creators
+ */
+function isAnyOfActionTest(action: AnyAction) {
+  const actionA = createAction('a', () => {
+    return {
+      payload: {
+        prop1: 1,
+        prop3: 2
+      }
+    }
+  })
+
+  const actionB = createAction('b', () => {
+    return {
+      payload: {
+        prop1: 1,
+        prop2: 2
+      }
+    }
+  })
+
+  if (isAnyOf(actionA, actionB)(action)) {
+    return {
+      prop1: action.payload.prop1,
+      // @ts-expect-error
+      prop2: action.payload.prop2,
+      // @ts-expect-error
+      prop3: action.payload.prop3
+    }
+  }
+}
+
+/*
+ * Test: isAnyOf correctly narrows types when used with async thunks
+ */
+function isAnyOfThunkTest(action: AnyAction) {
+  const asyncThunk1 = createAsyncThunk<{ prop1: number; prop3: number }>(
+    'asyncThunk1',
+
+    async () => {
+      return {
+        prop1: 1,
+        prop3: 3
+      }
+    }
+  )
+
+  const asyncThunk2 = createAsyncThunk<{ prop1: number; prop2: number }>(
+    'asyncThunk2',
+
+    async () => {
+      return {
+        prop1: 1,
+        prop2: 2
+      }
+    }
+  )
+
+  if (isAnyOf(asyncThunk1.fulfilled, asyncThunk2.fulfilled)(action)) {
+    return {
+      prop1: action.payload.prop1,
+      // @ts-expect-error
+      prop2: action.payload.prop2,
+      // @ts-expect-error
+      prop3: action.payload.prop3
+    }
+  }
+}
+
+/*
+ * Test: isAnyOf correctly narrows types when used with type guards
+ */
+function isAnyOfTypeGuardTest(action: AnyAction) {
+  interface ActionA {
+    type: 'a'
+    payload: {
+      prop1: 1
+      prop3: 2
+    }
+  }
+
+  interface ActionB {
+    type: 'b'
+    payload: {
+      prop1: 1
+      prop2: 2
+    }
+  }
+
+  const guardA = (v: any): v is ActionA => {
+    return v.type === 'a'
+  }
+
+  const guardB = (v: any): v is ActionB => {
+    return v.type === 'b'
+  }
+
+  if (isAnyOf(guardA, guardB)(action)) {
+    return {
+      prop1: action.payload.prop1,
+      // @ts-expect-error
+      prop2: action.payload.prop2,
+      // @ts-expect-error
+      prop3: action.payload.prop3
+    }
+  }
+}
+
+/* isAllOf */
+
+interface SpecialAction {
+  payload: {
+    special: boolean
+  }
+}
+
+const isSpecialAction = (v: any): v is SpecialAction => {
+  return v.meta.isSpecial
+}
+
+/*
+ * Test: isAllOf correctly narrows types when used with action creators
+ *       and type guards
+ */
+function isAllOfActionTest(action: AnyAction) {
+  const actionA = createAction('a', () => {
+    return {
+      payload: {
+        prop1: 1,
+        prop3: 2
+      }
+    }
+  })
+
+  if (isAllOf(actionA, isSpecialAction)(action)) {
+    return {
+      prop1: action.payload.prop1,
+      // @ts-expect-error
+      prop2: action.payload.prop2,
+      prop3: action.payload.prop3,
+      special: action.payload.special
+    }
+  }
+}
+
+/*
+ * Test: isAllOf correctly narrows types when used with async thunks
+ *       and type guards
+ */
+function isAllOfThunkTest(action: AnyAction) {
+  const asyncThunk1 = createAsyncThunk<{ prop1: number; prop3: number }>(
+    'asyncThunk1',
+
+    async () => {
+      return {
+        prop1: 1,
+        prop3: 3
+      }
+    }
+  )
+
+  if (isAllOf(asyncThunk1.fulfilled, isSpecialAction)(action)) {
+    return {
+      prop1: action.payload.prop1,
+      // @ts-expect-error
+      prop2: action.payload.prop2,
+      prop3: action.payload.prop3,
+      special: action.payload.special
+    }
+  }
+}
+
+/*
+ * Test: isAnyOf correctly narrows types when used with type guards
+ */
+function isAllOfTypeGuardTest(action: AnyAction) {
+  interface ActionA {
+    type: 'a'
+    payload: {
+      prop1: 1
+      prop3: 2
+    }
+  }
+
+  const guardA = (v: any): v is ActionA => {
+    return v.type === 'a'
+  }
+
+  if (isAllOf(guardA, isSpecialAction)(action)) {
+    return {
+      prop1: action.payload.prop1,
+      // @ts-expect-error
+      prop2: action.payload.prop2,
+      prop3: action.payload.prop3,
+      special: action.payload.special
+    }
+  }
+}
+
+/*
+ * Test: isPending correctly narrows types
+ */
+function isPendingTest(action: AnyAction) {
+  if (isPending(action)) {
+    expectExactType<undefined>(action.payload)
+    // @ts-expect-error
+    action.error
+  }
+
+  const thunk = createAsyncThunk<string>('a', () => 'result')
+
+  if (isPending(thunk)(action)) {
+    expectExactType<undefined>(action.payload)
+    // @ts-expect-error
+    action.error
+  }
+}
+
+/*
+ * Test: isRejected correctly narrows types
+ */
+function isRejectedTest(action: AnyAction) {
+  if (isRejected(action)) {
+    // might be there if rejected with payload
+    expectUnknown(action.payload)
+    expectExactType<SerializedError>(action.error)
+  }
+
+  const thunk = createAsyncThunk<string>('a', () => 'result')
+
+  if (isRejected(thunk)(action)) {
+    // might be there if rejected with payload
+    expectUnknown(action.payload)
+    expectExactType<SerializedError>(action.error)
+  }
+}
+
+/*
+ * Test: isFulfilled correctly narrows types
+ */
+function isFulfilledTest(action: AnyAction) {
+  if (isFulfilled(action)) {
+    expectUnknown(action.payload)
+    // @ts-expect-error
+    action.error
+  }
+
+  const thunk = createAsyncThunk<string>('a', () => 'result')
+  if (isFulfilled(thunk)(action)) {
+    expectExactType('' as string)(action.payload)
+    // @ts-expect-error
+    action.error
+  }
+}
+
+/*
+ * Test: isAsyncThunkAction correctly narrows types
+ */
+function isAsyncThunkActionTest(action: AnyAction) {
+  if (isAsyncThunkAction(action)) {
+    expectUnknown(action.payload)
+    // do not expect an error property because pending/fulfilled lack it
+    // @ts-expect-error
+    action.error
+  }
+
+  const thunk = createAsyncThunk<string>('a', () => 'result')
+  if (isAsyncThunkAction(thunk)(action)) {
+    // we should expect the payload to be available, but of unknown type because the action may be pending/rejected
+    expectUnknown(action.payload)
+    // do not expect an error property because pending/fulfilled lack it
+    // @ts-expect-error
+    action.error
+  }
+}
+
+/*
+ * Test: isRejectedWithValue correctly narrows types
+ */
+function isRejectedWithValueTest(action: AnyAction) {
+  if (isRejectedWithValue(action)) {
+    expectUnknown(action.payload)
+    expectExactType<SerializedError>(action.error)
+  }
+
+  const thunk = createAsyncThunk<
+    string,
+    void,
+    { rejectValue: { message: string } }
+  >('a', () => 'result')
+  if (isRejectedWithValue(thunk)(action)) {
+    expectExactType({ message: '' as string })(action.payload)
+    expectExactType<SerializedError>(action.error)
+  }
+}
+
+import { expectExactType, expectUnknown } from './helpers'
diff --git a/type-tests/files/tsconfig.json b/type-tests/files/tsconfig.json
index 169a3d63..b0a5d437 100644
--- a/type-tests/files/tsconfig.json
+++ b/type-tests/files/tsconfig.json
@@ -5,10 +5,12 @@
     "module": "es2015",
     "moduleResolution": "node",
     "strict": true,
+    "noEmit": true,
     "target": "es2018",
     "baseUrl": "../..",
     "paths": {
-      "redux-toolkit": ["src/typings.d.ts"]
+      "@reduxjs/toolkit": ["./src"],
+      "@internal/*": ["./src/*"]
     }
   }
 }