Repository: reduxjs/redux-toolkit
Pull request #1007 (2021-04-20 03:39:14)

Restructure RTK files
This PR:

- Moves the existing RTK tests into nested `./tests` subfolders, instead of being side-by-side with the source files
- Moves all the RTKQ source files into `src/query/`
- Reformats all the RTKQ source files to match the RTK formatting


Patch:
diff --git a/package.json b/package.json
index fdc02e3c..52296243 100644
--- a/package.json
+++ b/package.json
@@ -56,8 +56,8 @@
     "build-ci": "node scripts/build.js && tsc && tsc -p src/query/tsconfig.json && api-extractor run",
     "build": "node scripts/build.js && tsc && tsc -p src/query/tsconfig.json && 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\"",
+    "format": "prettier --write \"src/**/*.{ts,tsx}\" \"**/*.md\"",
+    "format:check": "prettier --list-different \"src/**/*.{ts,tsx}\" \"docs/*/**.md\"",
     "lint": "tsdx lint src",
     "prepare": "npm run lint && npm run format:check && npm test && npm run type-tests && npm run build-ci",
     "test": "tsdx test",
diff --git a/query/src/HandledError.ts b/query/src/HandledError.ts
deleted file mode 100644
index e61d77c2..00000000
--- a/query/src/HandledError.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export class HandledError {
-  constructor(public readonly value: any, public readonly meta: any = undefined) {}
-}
diff --git a/query/src/baseQueryTypes.ts b/query/src/baseQueryTypes.ts
deleted file mode 100644
index a6a433ce..00000000
--- a/query/src/baseQueryTypes.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import { ThunkDispatch } from '@reduxjs/toolkit';
-import { MaybePromise, UnwrapPromise } from './tsHelpers';
-
-export interface BaseQueryApi {
-  signal?: AbortSignal;
-  dispatch: ThunkDispatch<any, any, any>;
-  getState: () => unknown;
-}
-
-export type QueryReturnValue<T = unknown, E = unknown, M = unknown> =
-  | {
-      error: E;
-      data?: undefined;
-      meta?: M;
-    }
-  | {
-      error?: undefined;
-      data: T;
-      meta?: M;
-    };
-
-export type BaseQueryFn<Args = any, Result = unknown, Error = unknown, DefinitionExtraOptions = {}, Meta = {}> = (
-  args: Args,
-  api: BaseQueryApi,
-  extraOptions: DefinitionExtraOptions
-) => MaybePromise<QueryReturnValue<Result, Error, Meta>>;
-
-export type BaseQueryEnhancer<AdditionalArgs = unknown, AdditionalDefinitionExtraOptions = unknown, Config = void> = <
-  BaseQuery extends BaseQueryFn
->(
-  baseQuery: BaseQuery,
-  config: Config
-) => BaseQueryFn<
-  BaseQueryArg<BaseQuery> & AdditionalArgs,
-  BaseQueryResult<BaseQuery>,
-  BaseQueryError<BaseQuery>,
-  BaseQueryExtraOptions<BaseQuery> & AdditionalDefinitionExtraOptions
->;
-
-export type BaseQueryResult<BaseQuery extends BaseQueryFn> = UnwrapPromise<
-  ReturnType<BaseQuery>
-> extends infer Unwrapped
-  ? Unwrapped extends { data: any }
-    ? Unwrapped['data']
-    : never
-  : never;
-
-export type BaseQueryMeta<BaseQuery extends BaseQueryFn> = UnwrapPromise<ReturnType<BaseQuery>>['meta'];
-
-export type BaseQueryError<BaseQuery extends BaseQueryFn> = Exclude<
-  UnwrapPromise<ReturnType<BaseQuery>>,
-  { error: undefined }
->['error'];
-
-export type BaseQueryArg<T extends (arg: any, ...args: any[]) => any> = T extends (arg: infer A, ...args: any[]) => any
-  ? A
-  : any;
-
-export type BaseQueryExtraOptions<BaseQuery extends BaseQueryFn> = Parameters<BaseQuery>[2];
diff --git a/query/src/constants.ts b/query/src/constants.ts
deleted file mode 100644
index 7e44189d..00000000
--- a/query/src/constants.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export const UNINITIALIZED_VALUE = Symbol();
-export type UninitializedValue = typeof UNINITIALIZED_VALUE;
diff --git a/query/src/core/buildMiddleware.ts b/query/src/core/buildMiddleware.ts
deleted file mode 100644
index 236ff675..00000000
--- a/query/src/core/buildMiddleware.ts
+++ /dev/null
@@ -1,258 +0,0 @@
-import {
-  AnyAction,
-  AsyncThunk,
-  createAction,
-  isAnyOf,
-  isFulfilled,
-  isRejectedWithValue,
-  Middleware,
-  MiddlewareAPI,
-  ThunkDispatch,
-} from '@reduxjs/toolkit';
-import { QueryCacheKey, QueryStatus, QuerySubState, QuerySubstateIdentifier, RootState, Subscribers } from './apiState';
-import { Api, ApiContext } from '../apiTypes';
-import { calculateProvidedByThunk, MutationThunkArg, QueryThunkArg, ThunkResult } from './buildThunks';
-import { AssertTagTypes, calculateProvidedBy, EndpointDefinitions, FullTagDescription } from '../endpointDefinitions';
-import { onFocus, onOnline } from './setupListeners';
-import { flatten } from '../utils';
-
-type QueryStateMeta<T> = Record<string, undefined | T>;
-type TimeoutId = ReturnType<typeof setTimeout>;
-
-export function buildMiddleware<
-  Definitions extends EndpointDefinitions,
-  ReducerPath extends string,
-  TagTypes extends string
->({
-  reducerPath,
-  context,
-  context: { endpointDefinitions },
-  queryThunk,
-  mutationThunk,
-  api,
-  assertTagType,
-}: {
-  reducerPath: ReducerPath;
-  context: ApiContext<Definitions>;
-  queryThunk: AsyncThunk<ThunkResult, QueryThunkArg<any>, {}>;
-  mutationThunk: AsyncThunk<ThunkResult, MutationThunkArg<any>, {}>;
-  api: Api<any, EndpointDefinitions, ReducerPath, TagTypes>;
-  assertTagType: AssertTagTypes;
-}) {
-  type MWApi = MiddlewareAPI<ThunkDispatch<any, any, AnyAction>, RootState<Definitions, string, ReducerPath>>;
-  const { removeQueryResult, unsubscribeQueryResult, updateSubscriptionOptions, resetApiState } = api.internalActions;
-
-  const currentRemovalTimeouts: QueryStateMeta<TimeoutId> = {};
-  const currentPolls: QueryStateMeta<{ nextPollTimestamp: number; timeout?: TimeoutId; pollingInterval: number }> = {};
-
-  const actions = {
-    invalidateTags: createAction<Array<TagTypes | FullTagDescription<TagTypes>>>(`${reducerPath}/invalidateTags`),
-  };
-
-  const middleware: Middleware<{}, RootState<Definitions, string, ReducerPath>, ThunkDispatch<any, any, AnyAction>> = (
-    mwApi
-  ) => (next) => (action) => {
-    const result = next(action);
-
-    if (isAnyOf(isFulfilled(mutationThunk), isRejectedWithValue(mutationThunk))(action)) {
-      invalidateTags(calculateProvidedByThunk(action, 'invalidatesTags', endpointDefinitions, assertTagType), mwApi);
-    }
-
-    if (actions.invalidateTags.match(action)) {
-      invalidateTags(calculateProvidedBy(action.payload, undefined, undefined, undefined, assertTagType), mwApi);
-    }
-
-    if (unsubscribeQueryResult.match(action)) {
-      handleUnsubscribe(action.payload, mwApi);
-    }
-
-    if (updateSubscriptionOptions.match(action)) {
-      updatePollingInterval(action.payload, mwApi);
-    }
-    if (queryThunk.pending.match(action) || (queryThunk.rejected.match(action) && action.meta.condition)) {
-      updatePollingInterval(action.meta.arg, mwApi);
-    }
-    if (queryThunk.fulfilled.match(action) || (queryThunk.rejected.match(action) && !action.meta.condition)) {
-      startNextPoll(action.meta.arg, mwApi);
-    }
-
-    if (onFocus.match(action)) {
-      refetchValidQueries(mwApi, 'refetchOnFocus');
-    }
-    if (onOnline.match(action)) {
-      refetchValidQueries(mwApi, 'refetchOnReconnect');
-    }
-
-    if (resetApiState.match(action)) {
-      for (const [key, poll] of Object.entries(currentPolls)) {
-        if (poll?.timeout) clearTimeout(poll.timeout);
-        delete currentPolls[key];
-      }
-      for (const [key, timeout] of Object.entries(currentRemovalTimeouts)) {
-        if (timeout) clearTimeout(timeout);
-        delete currentRemovalTimeouts[key];
-      }
-    }
-
-    return result;
-  };
-
-  return { middleware, actions };
-
-  function refetchQuery(
-    querySubState: Exclude<QuerySubState<any>, { status: QueryStatus.uninitialized }>,
-    queryCacheKey: string,
-    override: Partial<QueryThunkArg<any>> = {}
-  ) {
-    return queryThunk({
-      endpointName: querySubState.endpointName,
-      originalArgs: querySubState.originalArgs,
-      subscribe: false,
-      forceRefetch: true,
-      startedTimeStamp: Date.now(),
-      queryCacheKey: queryCacheKey as any,
-      ...override,
-    });
-  }
-
-  function refetchValidQueries(api: MWApi, type: 'refetchOnFocus' | 'refetchOnReconnect') {
-    const state = api.getState()[reducerPath];
-    const queries = state.queries;
-    const subscriptions = state.subscriptions;
-
-    context.batch(() => {
-      for (const queryCacheKey of Object.keys(subscriptions)) {
-        const querySubState = queries[queryCacheKey];
-        const subscriptionSubState = subscriptions[queryCacheKey];
-
-        if (!subscriptionSubState || !querySubState || querySubState.status === QueryStatus.uninitialized) return;
-
-        const shouldRefetch =
-          Object.values(subscriptionSubState).some((sub) => sub[type] === true) ||
-          (Object.values(subscriptionSubState).every((sub) => sub[type] === undefined) && state.config[type]);
-
-        if (shouldRefetch) {
-          api.dispatch(refetchQuery(querySubState, queryCacheKey));
-        }
-      }
-    });
-  }
-
-  function invalidateTags(tags: readonly FullTagDescription<string>[], api: MWApi) {
-    const state = api.getState()[reducerPath];
-
-    const toInvalidate = new Set<QueryCacheKey>();
-    for (const tag of tags) {
-      const provided = state.provided[tag.type];
-      if (!provided) {
-        continue;
-      }
-
-      let invalidateSubscriptions =
-        (tag.id !== undefined
-          ? // id given: invalidate all queries that provide this type & id
-            provided[tag.id]
-          : // no id: invalidate all queries that provide this type
-            flatten(Object.values(provided))) ?? [];
-
-      for (const invalidate of invalidateSubscriptions) {
-        toInvalidate.add(invalidate);
-      }
-    }
-
-    context.batch(() => {
-      for (const queryCacheKey of toInvalidate.values()) {
-        const querySubState = state.queries[queryCacheKey];
-        const subscriptionSubState = state.subscriptions[queryCacheKey];
-        if (querySubState && subscriptionSubState) {
-          if (Object.keys(subscriptionSubState).length === 0) {
-            api.dispatch(removeQueryResult({ queryCacheKey }));
-          } else if (querySubState.status !== QueryStatus.uninitialized) {
-            api.dispatch(refetchQuery(querySubState, queryCacheKey));
-          } else {
-          }
-        }
-      }
-    });
-  }
-
-  function handleUnsubscribe({ queryCacheKey }: QuerySubstateIdentifier, api: MWApi) {
-    const keepUnusedDataFor = api.getState()[reducerPath].config.keepUnusedDataFor;
-    const currentTimeout = currentRemovalTimeouts[queryCacheKey];
-    if (currentTimeout) {
-      clearTimeout(currentTimeout);
-    }
-    currentRemovalTimeouts[queryCacheKey] = setTimeout(() => {
-      const subscriptions = api.getState()[reducerPath].subscriptions[queryCacheKey];
-      if (!subscriptions || Object.keys(subscriptions).length === 0) {
-        api.dispatch(removeQueryResult({ queryCacheKey }));
-      }
-      delete currentRemovalTimeouts![queryCacheKey];
-    }, keepUnusedDataFor * 1000);
-  }
-
-  function startNextPoll({ queryCacheKey }: QuerySubstateIdentifier, api: MWApi) {
-    const state = api.getState()[reducerPath];
-    const querySubState = state.queries[queryCacheKey];
-    const subscriptions = state.subscriptions[queryCacheKey];
-
-    if (!querySubState || querySubState.status === QueryStatus.uninitialized) return;
-
-    const lowestPollingInterval = findLowestPollingInterval(subscriptions);
-    if (!Number.isFinite(lowestPollingInterval)) return;
-
-    const currentPoll = currentPolls[queryCacheKey];
-
-    if (currentPoll?.timeout) {
-      clearTimeout(currentPoll.timeout);
-      currentPoll.timeout = undefined;
-    }
-
-    const nextPollTimestamp = Date.now() + lowestPollingInterval;
-
-    const currentInterval: typeof currentPolls[number] = (currentPolls[queryCacheKey] = {
-      nextPollTimestamp,
-      pollingInterval: lowestPollingInterval,
-      timeout: setTimeout(() => {
-        currentInterval!.timeout = undefined;
-        api.dispatch(refetchQuery(querySubState, queryCacheKey));
-      }, lowestPollingInterval),
-    });
-  }
-
-  function updatePollingInterval({ queryCacheKey }: QuerySubstateIdentifier, api: MWApi) {
-    const state = api.getState()[reducerPath];
-    const querySubState = state.queries[queryCacheKey];
-    const subscriptions = state.subscriptions[queryCacheKey];
-
-    if (!querySubState || querySubState.status === QueryStatus.uninitialized) {
-      return;
-    }
-
-    const lowestPollingInterval = findLowestPollingInterval(subscriptions);
-    const currentPoll = currentPolls[queryCacheKey];
-
-    if (!Number.isFinite(lowestPollingInterval)) {
-      if (currentPoll?.timeout) {
-        clearTimeout(currentPoll.timeout);
-      }
-      delete currentPolls[queryCacheKey];
-      return;
-    }
-
-    const nextPollTimestamp = Date.now() + lowestPollingInterval;
-
-    if (!currentPoll || nextPollTimestamp < currentPoll.nextPollTimestamp) {
-      startNextPoll({ queryCacheKey }, api);
-    }
-  }
-}
-
-function findLowestPollingInterval(subscribers: Subscribers = {}) {
-  let lowestPollingInterval = Number.POSITIVE_INFINITY;
-  for (const subscription of Object.values(subscribers)) {
-    if (!!subscription.pollingInterval)
-      lowestPollingInterval = Math.min(subscription.pollingInterval, lowestPollingInterval);
-  }
-  return lowestPollingInterval;
-}
diff --git a/query/src/core/buildSlice.ts b/query/src/core/buildSlice.ts
deleted file mode 100644
index bf94b35d..00000000
--- a/query/src/core/buildSlice.ts
+++ /dev/null
@@ -1,296 +0,0 @@
-import {
-  AsyncThunk,
-  combineReducers,
-  createAction,
-  createSlice,
-  isAnyOf,
-  isFulfilled,
-  isRejectedWithValue,
-  PayloadAction,
-} from '@reduxjs/toolkit';
-import {
-  CombinedState,
-  QuerySubstateIdentifier,
-  QuerySubState,
-  MutationSubstateIdentifier,
-  MutationSubState,
-  QueryStatus,
-  MutationState,
-  QueryState,
-  InvalidationState,
-  Subscribers,
-  QueryCacheKey,
-  SubscriptionState,
-  ConfigState,
-} from './apiState';
-import { calculateProvidedByThunk, MutationThunkArg, QueryThunkArg, ThunkResult } from './buildThunks';
-import { AssertTagTypes, EndpointDefinitions } from '../endpointDefinitions';
-import { applyPatches, Patch } from 'immer';
-import { onFocus, onFocusLost, onOffline, onOnline } from './setupListeners';
-import { isDocumentVisible, isOnline, copyWithStructuralSharing } from '../utils';
-import { ApiContext } from '../apiTypes';
-
-function updateQuerySubstateIfExists(
-  state: QueryState<any>,
-  queryCacheKey: QueryCacheKey,
-  update: (substate: QuerySubState<any>) => void
-) {
-  const substate = state[queryCacheKey];
-  if (substate) {
-    update(substate);
-  }
-}
-
-function updateMutationSubstateIfExists(
-  state: MutationState<any>,
-  { requestId }: MutationSubstateIdentifier,
-  update: (substate: MutationSubState<any>) => void
-) {
-  const substate = state[requestId];
-  if (substate) {
-    update(substate);
-  }
-}
-
-const initialState = {} as any;
-
-export function buildSlice({
-  reducerPath,
-  queryThunk,
-  mutationThunk,
-  context: { endpointDefinitions: definitions },
-  assertTagType,
-  config,
-}: {
-  reducerPath: string;
-  queryThunk: AsyncThunk<ThunkResult, QueryThunkArg<any>, {}>;
-  mutationThunk: AsyncThunk<ThunkResult, MutationThunkArg<any>, {}>;
-  context: ApiContext<EndpointDefinitions>;
-  assertTagType: AssertTagTypes;
-  config: Omit<ConfigState<string>, 'online' | 'focused'>;
-}) {
-  const resetApiState = createAction(`${reducerPath}/resetApiState`);
-  const querySlice = createSlice({
-    name: `${reducerPath}/queries`,
-    initialState: initialState as QueryState<any>,
-    reducers: {
-      removeQueryResult(draft, { payload: { queryCacheKey } }: PayloadAction<QuerySubstateIdentifier>) {
-        delete draft[queryCacheKey];
-      },
-      queryResultPatched(
-        draft,
-        { payload: { queryCacheKey, patches } }: PayloadAction<QuerySubstateIdentifier & { patches: Patch[] }>
-      ) {
-        updateQuerySubstateIfExists(draft, queryCacheKey, (substate) => {
-          substate.data = applyPatches(substate.data as any, patches);
-        });
-      },
-    },
-    extraReducers(builder) {
-      builder
-        .addCase(queryThunk.pending, (draft, { meta: { arg, requestId } }) => {
-          if (arg.subscribe) {
-            // only initialize substate if we want to subscribe to it
-            draft[arg.queryCacheKey] ??= {
-              status: QueryStatus.uninitialized,
-              endpointName: arg.endpointName,
-            };
-          }
-
-          updateQuerySubstateIfExists(draft, arg.queryCacheKey, (substate) => {
-            substate.status = QueryStatus.pending;
-            substate.requestId = requestId;
-            substate.originalArgs = arg.originalArgs;
-            substate.startedTimeStamp = arg.startedTimeStamp;
-          });
-        })
-        .addCase(queryThunk.fulfilled, (draft, { meta, payload }) => {
-          updateQuerySubstateIfExists(draft, meta.arg.queryCacheKey, (substate) => {
-            if (substate.requestId !== meta.requestId) return;
-            substate.status = QueryStatus.fulfilled;
-            substate.data = copyWithStructuralSharing(substate.data, payload.result);
-            delete substate.error;
-            substate.fulfilledTimeStamp = payload.fulfilledTimeStamp;
-          });
-        })
-        .addCase(queryThunk.rejected, (draft, { meta: { condition, arg, requestId }, error, payload }) => {
-          updateQuerySubstateIfExists(draft, arg.queryCacheKey, (substate) => {
-            if (condition) {
-              // request was aborted due to condition (another query already running)
-            } else {
-              // request failed
-              if (substate.requestId !== requestId) return;
-              substate.status = QueryStatus.rejected;
-              substate.error = (payload ?? error) as any;
-            }
-          });
-        });
-    },
-  });
-  const mutationSlice = createSlice({
-    name: `${reducerPath}/mutations`,
-    initialState: initialState as MutationState<any>,
-    reducers: {
-      unsubscribeResult(draft, action: PayloadAction<MutationSubstateIdentifier>) {
-        if (action.payload.requestId in draft) {
-          delete draft[action.payload.requestId];
-        }
-      },
-    },
-    extraReducers(builder) {
-      builder
-        .addCase(mutationThunk.pending, (draft, { meta: { arg, requestId } }) => {
-          if (!arg.track) return;
-
-          draft[requestId] = {
-            status: QueryStatus.pending,
-            originalArgs: arg.originalArgs,
-            endpointName: arg.endpointName,
-            startedTimeStamp: arg.startedTimeStamp,
-          };
-        })
-        .addCase(mutationThunk.fulfilled, (draft, { payload, meta: { requestId, arg } }) => {
-          if (!arg.track) return;
-
-          updateMutationSubstateIfExists(draft, { requestId }, (substate) => {
-            substate.status = QueryStatus.fulfilled;
-            substate.data = payload.result;
-            substate.fulfilledTimeStamp = payload.fulfilledTimeStamp;
-          });
-        })
-        .addCase(mutationThunk.rejected, (draft, { payload, error, meta: { requestId, arg } }) => {
-          if (!arg.track) return;
-
-          updateMutationSubstateIfExists(draft, { requestId }, (substate) => {
-            substate.status = QueryStatus.rejected;
-            substate.error = (payload ?? error) as any;
-          });
-        });
-    },
-  });
-
-  const invalidationSlice = createSlice({
-    name: `${reducerPath}/invalidation`,
-    initialState: initialState as InvalidationState<string>,
-    reducers: {},
-    extraReducers(builder) {
-      builder
-        .addCase(querySlice.actions.removeQueryResult, (draft, { payload: { queryCacheKey } }) => {
-          for (const tagTypeSubscriptions of Object.values(draft)) {
-            for (const idSubscriptions of Object.values(tagTypeSubscriptions)) {
-              const foundAt = idSubscriptions.indexOf(queryCacheKey);
-              if (foundAt !== -1) {
-                idSubscriptions.splice(foundAt, 1);
-              }
-            }
-          }
-        })
-        .addMatcher(isAnyOf(isFulfilled(queryThunk), isRejectedWithValue(queryThunk)), (draft, action) => {
-          const providedTags = calculateProvidedByThunk(action, 'providesTags', definitions, assertTagType);
-          const { queryCacheKey } = action.meta.arg;
-
-          for (const { type, id } of providedTags) {
-            const subscribedQueries = ((draft[type] ??= {})[id || '__internal_without_id'] ??= []);
-            const alreadySubscribed = subscribedQueries.includes(queryCacheKey);
-            if (!alreadySubscribed) {
-              subscribedQueries.push(queryCacheKey);
-            }
-          }
-        });
-    },
-  });
-
-  const subscriptionSlice = createSlice({
-    name: `${reducerPath}/subscriptions`,
-    initialState: initialState as SubscriptionState,
-    reducers: {
-      updateSubscriptionOptions(
-        draft,
-        {
-          payload: { queryCacheKey, requestId, options },
-        }: PayloadAction<
-          { endpointName: string; requestId: string; options: Subscribers[number] } & QuerySubstateIdentifier
-        >
-      ) {
-        if (draft?.[queryCacheKey]?.[requestId]) {
-          draft[queryCacheKey]![requestId] = options;
-        }
-      },
-      unsubscribeResult(
-        draft,
-        { payload: { queryCacheKey, requestId } }: PayloadAction<{ requestId: string } & QuerySubstateIdentifier>
-      ) {
-        if (draft[queryCacheKey]) {
-          delete draft[queryCacheKey]![requestId];
-        }
-      },
-    },
-    extraReducers: (builder) => {
-      builder
-        .addCase(querySlice.actions.removeQueryResult, (draft, { payload: { queryCacheKey } }) => {
-          delete draft[queryCacheKey];
-        })
-        .addCase(queryThunk.pending, (draft, { meta: { arg, requestId } }) => {
-          if (arg.subscribe) {
-            const substate = (draft[arg.queryCacheKey] ??= {});
-            substate[requestId] = arg.subscriptionOptions ?? substate[requestId] ?? {};
-          }
-        })
-        .addCase(queryThunk.rejected, (draft, { meta: { condition, arg, requestId }, error, payload }) => {
-          const substate = draft[arg.queryCacheKey];
-          // request was aborted due to condition (another query already running)
-          if (condition && arg.subscribe && substate) {
-            substate[requestId] = arg.subscriptionOptions ?? substate[requestId] ?? {};
-          }
-        });
-    },
-  });
-
-  const configSlice = createSlice({
-    name: `${reducerPath}/config`,
-    initialState: {
-      online: isOnline(),
-      focused: isDocumentVisible(),
-      ...config,
-    } as ConfigState<string>,
-    reducers: {},
-    extraReducers: (builder) => {
-      builder
-        .addCase(onOnline, (state) => {
-          state.online = true;
-        })
-        .addCase(onOffline, (state) => {
-          state.online = false;
-        })
-        .addCase(onFocus, (state) => {
-          state.focused = true;
-        })
-        .addCase(onFocusLost, (state) => {
-          state.focused = false;
-        });
-    },
-  });
-
-  const combinedReducer = combineReducers<CombinedState<any, string, string>>({
-    queries: querySlice.reducer,
-    mutations: mutationSlice.reducer,
-    provided: invalidationSlice.reducer,
-    subscriptions: subscriptionSlice.reducer,
-    config: configSlice.reducer,
-  });
-
-  const reducer: typeof combinedReducer = (state, action) =>
-    combinedReducer(resetApiState.match(action) ? undefined : state, action);
-
-  const actions = {
-    updateSubscriptionOptions: subscriptionSlice.actions.updateSubscriptionOptions,
-    queryResultPatched: querySlice.actions.queryResultPatched,
-    removeQueryResult: querySlice.actions.removeQueryResult,
-    unsubscribeQueryResult: subscriptionSlice.actions.unsubscribeResult,
-    unsubscribeMutationResult: mutationSlice.actions.unsubscribeResult,
-    resetApiState,
-  };
-
-  return { reducer, actions };
-}
-export type SliceActions = ReturnType<typeof buildSlice>['actions'];
diff --git a/query/src/core/index.ts b/query/src/core/index.ts
deleted file mode 100644
index 41c34654..00000000
--- a/query/src/core/index.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { buildCreateApi } from '../createApi';
-import { coreModule } from './module';
-
-const createApi = buildCreateApi(coreModule());
-
-export { createApi, coreModule };
diff --git a/query/src/defaultSerializeQueryArgs.ts b/query/src/defaultSerializeQueryArgs.ts
deleted file mode 100644
index 5143cef8..00000000
--- a/query/src/defaultSerializeQueryArgs.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { QueryCacheKey } from './core/apiState';
-import { EndpointDefinition } from './endpointDefinitions';
-
-export const defaultSerializeQueryArgs: SerializeQueryArgs<any> = ({ endpointName, queryArgs }) => {
-  // Sort the object keys before stringifying, to prevent useQuery({ a: 1, b: 2 }) having a different cache key than useQuery({ b: 2, a: 1 })
-  return `${endpointName}(${JSON.stringify(queryArgs, Object.keys(queryArgs || {}).sort())})`;
-};
-
-export type SerializeQueryArgs<_InternalQueryArgs> = (_: {
-  queryArgs: any;
-  endpointDefinition: EndpointDefinition<any, any, any, any>;
-  endpointName: string;
-}) => string;
-
-export type InternalSerializeQueryArgs<_InternalQueryArgs> = (_: {
-  queryArgs: any;
-  endpointDefinition: EndpointDefinition<any, any, any, any>;
-  endpointName: string;
-}) => QueryCacheKey;
diff --git a/query/src/fakeBaseQuery.ts b/query/src/fakeBaseQuery.ts
deleted file mode 100644
index 61666df8..00000000
--- a/query/src/fakeBaseQuery.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { BaseQueryFn } from './baseQueryTypes';
-
-const _NEVER = Symbol();
-export type NEVER = typeof _NEVER;
-
-/**
- * Creates a "fake" baseQuery to be used if your api *only* uses the `queryFn` definition syntax.
- * This also allows you to specify a specific error type to be shared by all your `queryFn` definitions.
- */
-export function fakeBaseQuery<ErrorType>(): BaseQueryFn<void, NEVER, ErrorType, {}> {
-  return function () {
-    throw new Error('When using `fakeBaseQuery`, all queries & mutations must use the `queryFn` definition syntax.');
-  };
-}
diff --git a/query/src/index.ts b/query/src/index.ts
deleted file mode 100644
index c015befe..00000000
--- a/query/src/index.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-export { QueryStatus } from './core/apiState';
-export type { Api, ApiWithInjectedEndpoints, Module, ApiModules } from './apiTypes';
-export type { BaseQueryEnhancer, BaseQueryFn } from './baseQueryTypes';
-export type {
-  EndpointDefinitions,
-  EndpointDefinition,
-  QueryDefinition,
-  MutationDefinition,
-} from './endpointDefinitions';
-export { fetchBaseQuery } from './fetchBaseQuery';
-export type { FetchBaseQueryError, FetchArgs } from './fetchBaseQuery';
-export { retry } from './retry';
-export { setupListeners } from './core/setupListeners';
-export { skipSelector } from './core/buildSelectors';
-export type { CreateApi, CreateApiOptions } from './createApi';
-export { buildCreateApi } from './createApi';
-export { fakeBaseQuery } from './fakeBaseQuery';
-
-export { createApi, coreModule } from './core';
diff --git a/query/src/react-hooks/index.ts b/query/src/react-hooks/index.ts
deleted file mode 100644
index 499c446d..00000000
--- a/query/src/react-hooks/index.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { coreModule } from '../core/module';
-import { buildCreateApi } from '../createApi';
-import { reactHooksModule } from './module';
-
-const createApi = buildCreateApi(coreModule(), reactHooksModule());
-
-export { createApi, reactHooksModule };
diff --git a/query/src/react.ts b/query/src/react.ts
deleted file mode 100644
index 3da8fc31..00000000
--- a/query/src/react.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export * from './.';
-export { ApiProvider } from './react-hooks/ApiProvider';
-export { createApi, reactHooksModule } from './react-hooks';
diff --git a/query/src/tsHelpers.ts b/query/src/tsHelpers.ts
deleted file mode 100644
index d982520d..00000000
--- a/query/src/tsHelpers.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-export type Id<T> = { [K in keyof T]: T[K] } & {};
-export type WithRequiredProp<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
-export type Override<T1, T2> = T2 extends any ? Omit<T1, keyof T2> & T2 : never;
-export function assertCast<T>(v: any): asserts v is T {}
-
-export function safeAssign<T extends object>(target: T, ...args: Array<Partial<NoInfer<T>>>) {
-  Object.assign(target, ...args);
-}
-
-/**
- * Convert a Union type `(A|B)` to an intersection type `(A&B)`
- */
-export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
-
-export type NonOptionalKeys<T> = { [K in keyof T]-?: undefined extends T[K] ? never : K }[keyof T];
-
-export type HasRequiredProps<T, True, False> = NonOptionalKeys<T> extends never ? False : True;
-
-export type OptionalIfAllPropsOptional<T> = HasRequiredProps<T, T, T | never>;
-
-export type NoInfer<T> = [T][T extends any ? 0 : never];
-
-export type UnwrapPromise<T> = T extends PromiseLike<infer V> ? V : T;
-
-export type MaybePromise<T> = T | PromiseLike<T>;
-
-export type OmitFromUnion<T, K extends keyof T> = T extends any ? Omit<T, K> : never;
-
-export type IsAny<T, True, False = never> = true | false extends (T extends never ? true : false) ? True : False;
-
-export type CastAny<T, CastTo> = IsAny<T, CastTo, T>;
diff --git a/query/src/utils/capitalize.ts b/query/src/utils/capitalize.ts
deleted file mode 100644
index b7aa41b3..00000000
--- a/query/src/utils/capitalize.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export function capitalize(str: string) {
-  return str.replace(str[0], str[0].toUpperCase());
-}
diff --git a/query/src/utils/copyWithStructuralSharing.ts b/query/src/utils/copyWithStructuralSharing.ts
deleted file mode 100644
index 01874366..00000000
--- a/query/src/utils/copyWithStructuralSharing.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { isPlainObject as _iPO } from '@reduxjs/toolkit';
-
-// remove type guard
-const isPlainObject: (_: any) => boolean = _iPO;
-
-export function copyWithStructuralSharing<T>(oldObj: any, newObj: T): T;
-export function copyWithStructuralSharing(oldObj: any, newObj: any): any {
-  if (
-    oldObj === newObj ||
-    !((isPlainObject(oldObj) && isPlainObject(newObj)) || (Array.isArray(oldObj) && Array.isArray(newObj)))
-  ) {
-    return newObj;
-  }
-  const newKeys = Object.keys(newObj);
-  const oldKeys = Object.keys(oldObj);
-
-  let isSameObject = newKeys.length === oldKeys.length;
-  const mergeObj: any = Array.isArray(newObj) ? [] : {};
-  for (const key of newKeys) {
-    mergeObj[key] = copyWithStructuralSharing(oldObj[key], newObj[key]);
-    if (isSameObject) isSameObject = oldObj[key] === mergeObj[key];
-  }
-  return isSameObject ? oldObj : mergeObj;
-}
diff --git a/query/src/utils/index.ts b/query/src/utils/index.ts
deleted file mode 100644
index d350cc2a..00000000
--- a/query/src/utils/index.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export * from './isAbsoluteUrl';
-export * from './isValidUrl';
-export * from './joinUrls';
-export * from './flatten';
-export * from './shallowEqual';
-export * from './capitalize';
-export * from './isOnline';
-export * from './isDocumentVisible';
-export * from './copyWithStructuralSharing';
diff --git a/query/src/utils/joinUrls.ts b/query/src/utils/joinUrls.ts
deleted file mode 100644
index 1d66bf94..00000000
--- a/query/src/utils/joinUrls.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { isAbsoluteUrl } from './isAbsoluteUrl';
-
-const withoutTrailingSlash = (url: string) => url.replace(/\/$/, '');
-const withoutLeadingSlash = (url: string) => url.replace(/^\//, '');
-
-export function joinUrls(base: string | undefined, url: string | undefined): string {
-  if (!base) {
-    return url!;
-  }
-  if (!url) {
-    return base;
-  }
-
-  if (isAbsoluteUrl(url)) {
-    return url;
-  }
-
-  base = withoutTrailingSlash(base);
-  url = withoutLeadingSlash(url);
-
-  return `${base}/${url}`;
-}
diff --git a/query/src/utils/shallowEqual.ts b/query/src/utils/shallowEqual.ts
deleted file mode 100644
index 8e09a464..00000000
--- a/query/src/utils/shallowEqual.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-/* eslint-disable no-self-compare */
-
-function is(x: any, y: any) {
-  if (x === y) {
-    return x !== 0 || y !== 0 || 1 / x === 1 / y;
-  } else {
-    return x !== x && y !== y;
-  }
-}
-
-export function shallowEqual(objA: any, objB: any) {
-  if (is(objA, objB)) return true;
-
-  if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
-    return false;
-  }
-
-  const keysA = Object.keys(objA);
-  const keysB = Object.keys(objB);
-
-  if (keysA.length !== keysB.length) return false;
-
-  for (let i = 0; i < keysA.length; i++) {
-    if (!Object.prototype.hasOwnProperty.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) {
-      return false;
-    }
-  }
-
-  return true;
-}
diff --git a/src/entities/utils.spec.ts b/src/entities/tests/utils.spec.ts
similarity index 97%
rename from src/entities/utils.spec.ts
rename to src/entities/tests/utils.spec.ts
index c8ce49ce..e42c0d85 100644
--- a/src/entities/utils.spec.ts
+++ b/src/entities/tests/utils.spec.ts
@@ -1,4 +1,4 @@
-import { selectIdValue } from './utils'
+import { selectIdValue } from '../utils'
 import { AClockworkOrange } from './fixtures/book'
 
 describe('Entity utils', () => {
diff --git a/src/query/HandledError.ts b/src/query/HandledError.ts
new file mode 100644
index 00000000..dcbf936b
--- /dev/null
+++ b/src/query/HandledError.ts
@@ -0,0 +1,6 @@
+export class HandledError {
+  constructor(
+    public readonly value: any,
+    public readonly meta: any = undefined
+  ) {}
+}
diff --git a/query/src/apiTypes.ts b/src/query/apiTypes.ts
similarity index 55%
rename from query/src/apiTypes.ts
rename to src/query/apiTypes.ts
index 4e8c918b..83e5cace 100644
--- a/query/src/apiTypes.ts
+++ b/src/query/apiTypes.ts
@@ -1,8 +1,13 @@
-import { EndpointDefinitions, EndpointBuilder, EndpointDefinition, ReplaceTagTypes } from './endpointDefinitions';
-import { UnionToIntersection, Id, NoInfer } from './tsHelpers';
-import { CoreModule } from './core/module';
-import { CreateApiOptions } from './createApi';
-import { BaseQueryFn } from './baseQueryTypes';
+import {
+  EndpointDefinitions,
+  EndpointBuilder,
+  EndpointDefinition,
+  ReplaceTagTypes,
+} from './endpointDefinitions'
+import { UnionToIntersection, Id, NoInfer } from './tsHelpers'
+import { CoreModule } from './core/module'
+import { CreateApiOptions } from './createApi'
+import { BaseQueryFn } from './baseQueryTypes'
 
 export interface ApiModules<
   // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -15,10 +20,10 @@ export interface ApiModules<
   TagTypes extends string
 > {}
 
-export type ModuleName = keyof ApiModules<any, any, any, any>;
+export type ModuleName = keyof ApiModules<any, any, any, any>
 
 export type Module<Name extends ModuleName> = {
-  name: Name;
+  name: Name
   init<
     BaseQuery extends BaseQueryFn,
     Definitions extends EndpointDefinitions,
@@ -26,16 +31,21 @@ export type Module<Name extends ModuleName> = {
     TagTypes extends string
   >(
     api: Api<BaseQuery, EndpointDefinitions, ReducerPath, TagTypes, ModuleName>,
-    options: Required<CreateApiOptions<BaseQuery, Definitions, ReducerPath, TagTypes>>,
+    options: Required<
+      CreateApiOptions<BaseQuery, Definitions, ReducerPath, TagTypes>
+    >,
     context: ApiContext<Definitions>
   ): {
-    injectEndpoint(endpointName: string, definition: EndpointDefinition<any, any, any, any>): void;
-  };
-};
+    injectEndpoint(
+      endpointName: string,
+      definition: EndpointDefinition<any, any, any, any>
+    ): void
+  }
+}
 
 export interface ApiContext<Definitions extends EndpointDefinitions> {
-  endpointDefinitions: Definitions;
-  batch(cb: () => void): void;
+  endpointDefinitions: Definitions
+  batch(cb: () => void): void
 }
 
 export type Api<
@@ -45,35 +55,52 @@ export type Api<
   TagTypes extends string,
   Enhancers extends ModuleName = CoreModule
 > = Id<
-  Id<UnionToIntersection<ApiModules<BaseQuery, Definitions, ReducerPath, TagTypes>[Enhancers]>> & {
+  Id<
+    UnionToIntersection<
+      ApiModules<BaseQuery, Definitions, ReducerPath, TagTypes>[Enhancers]
+    >
+  > & {
     /**
      * A function to inject the endpoints into the original API, but also give you that same API with correct types for these endpoints back. Useful with code-splitting.
      */
     injectEndpoints<NewDefinitions extends EndpointDefinitions>(_: {
-      endpoints: (build: EndpointBuilder<BaseQuery, TagTypes, ReducerPath>) => NewDefinitions;
-      overrideExisting?: boolean;
-    }): Api<BaseQuery, Definitions & NewDefinitions, ReducerPath, TagTypes, Enhancers>;
+      endpoints: (
+        build: EndpointBuilder<BaseQuery, TagTypes, ReducerPath>
+      ) => NewDefinitions
+      overrideExisting?: boolean
+    }): Api<
+      BaseQuery,
+      Definitions & NewDefinitions,
+      ReducerPath,
+      TagTypes,
+      Enhancers
+    >
     /**
      *A function to enhance a generated API with additional information. Useful with code-generation.
      */
     enhanceEndpoints<NewTagTypes extends string = never>(_: {
       /** @deprecated */
-      addEntityTypes?: readonly NewTagTypes[];
-      addTagTypes?: readonly NewTagTypes[];
-      endpoints?: ReplaceTagTypes<Definitions, TagTypes | NoInfer<NewTagTypes>> extends infer NewDefinitions
+      addEntityTypes?: readonly NewTagTypes[]
+      addTagTypes?: readonly NewTagTypes[]
+      endpoints?: ReplaceTagTypes<
+        Definitions,
+        TagTypes | NoInfer<NewTagTypes>
+      > extends infer NewDefinitions
         ? {
-            [K in keyof NewDefinitions]?: Partial<NewDefinitions[K]> | ((definition: NewDefinitions[K]) => void);
+            [K in keyof NewDefinitions]?:
+              | Partial<NewDefinitions[K]>
+              | ((definition: NewDefinitions[K]) => void)
           }
-        : never;
+        : never
     }): Api<
       BaseQuery,
       ReplaceTagTypes<Definitions, TagTypes | NewTagTypes>,
       ReducerPath,
       TagTypes | NewTagTypes,
       Enhancers
-    >;
+    >
   }
->;
+>
 
 export type ApiWithInjectedEndpoints<
   ApiDefinition extends Api<any, any, any, any>,
@@ -82,5 +109,6 @@ export type ApiWithInjectedEndpoints<
     : never
 > = Omit<ApiDefinition, 'endpoints'> &
   Omit<Injections, 'endpoints'> & {
-    endpoints: ApiDefinition['endpoints'] & Partial<UnionToIntersection<Injections[number]['endpoints']>>;
-  };
+    endpoints: ApiDefinition['endpoints'] &
+      Partial<UnionToIntersection<Injections[number]['endpoints']>>
+  }
diff --git a/src/query/baseQueryTypes.ts b/src/query/baseQueryTypes.ts
new file mode 100644
index 00000000..4d2a783a
--- /dev/null
+++ b/src/query/baseQueryTypes.ts
@@ -0,0 +1,71 @@
+import { ThunkDispatch } from '@reduxjs/toolkit'
+import { MaybePromise, UnwrapPromise } from './tsHelpers'
+
+export interface BaseQueryApi {
+  signal?: AbortSignal
+  dispatch: ThunkDispatch<any, any, any>
+  getState: () => unknown
+}
+
+export type QueryReturnValue<T = unknown, E = unknown, M = unknown> =
+  | {
+      error: E
+      data?: undefined
+      meta?: M
+    }
+  | {
+      error?: undefined
+      data: T
+      meta?: M
+    }
+
+export type BaseQueryFn<
+  Args = any,
+  Result = unknown,
+  Error = unknown,
+  DefinitionExtraOptions = {},
+  Meta = {}
+> = (
+  args: Args,
+  api: BaseQueryApi,
+  extraOptions: DefinitionExtraOptions
+) => MaybePromise<QueryReturnValue<Result, Error, Meta>>
+
+export type BaseQueryEnhancer<
+  AdditionalArgs = unknown,
+  AdditionalDefinitionExtraOptions = unknown,
+  Config = void
+> = <BaseQuery extends BaseQueryFn>(
+  baseQuery: BaseQuery,
+  config: Config
+) => BaseQueryFn<
+  BaseQueryArg<BaseQuery> & AdditionalArgs,
+  BaseQueryResult<BaseQuery>,
+  BaseQueryError<BaseQuery>,
+  BaseQueryExtraOptions<BaseQuery> & AdditionalDefinitionExtraOptions
+>
+
+export type BaseQueryResult<BaseQuery extends BaseQueryFn> = UnwrapPromise<
+  ReturnType<BaseQuery>
+> extends infer Unwrapped
+  ? Unwrapped extends { data: any }
+    ? Unwrapped['data']
+    : never
+  : never
+
+export type BaseQueryMeta<BaseQuery extends BaseQueryFn> = UnwrapPromise<
+  ReturnType<BaseQuery>
+>['meta']
+
+export type BaseQueryError<BaseQuery extends BaseQueryFn> = Exclude<
+  UnwrapPromise<ReturnType<BaseQuery>>,
+  { error: undefined }
+>['error']
+
+export type BaseQueryArg<
+  T extends (arg: any, ...args: any[]) => any
+> = T extends (arg: infer A, ...args: any[]) => any ? A : any
+
+export type BaseQueryExtraOptions<
+  BaseQuery extends BaseQueryFn
+> = Parameters<BaseQuery>[2]
diff --git a/src/query/constants.ts b/src/query/constants.ts
new file mode 100644
index 00000000..d0e0fb45
--- /dev/null
+++ b/src/query/constants.ts
@@ -0,0 +1,2 @@
+export const UNINITIALIZED_VALUE = Symbol()
+export type UninitializedValue = typeof UNINITIALIZED_VALUE
diff --git a/query/src/core/apiState.ts b/src/query/core/apiState.ts
similarity index 50%
rename from query/src/core/apiState.ts
rename to src/query/core/apiState.ts
index 7709c33f..23b21450 100644
--- a/query/src/core/apiState.ts
+++ b/src/query/core/apiState.ts
@@ -1,5 +1,5 @@
-import { SerializedError } from '@reduxjs/toolkit';
-import { BaseQueryError } from '../baseQueryTypes';
+import { SerializedError } from '@reduxjs/toolkit'
+import { BaseQueryError } from '../baseQueryTypes'
 import {
   QueryDefinition,
   MutationDefinition,
@@ -7,18 +7,18 @@ import {
   BaseEndpointDefinition,
   ResultTypeFrom,
   QueryArgFrom,
-} from '../endpointDefinitions';
-import { Id, WithRequiredProp } from '../tsHelpers';
+} from '../endpointDefinitions'
+import { Id, WithRequiredProp } from '../tsHelpers'
 
-export type QueryCacheKey = string & { _type: 'queryCacheKey' };
-export type QuerySubstateIdentifier = { queryCacheKey: QueryCacheKey };
-export type MutationSubstateIdentifier = { requestId: string };
+export type QueryCacheKey = string & { _type: 'queryCacheKey' }
+export type QuerySubstateIdentifier = { queryCacheKey: QueryCacheKey }
+export type MutationSubstateIdentifier = { requestId: string }
 
 export type RefetchConfigOptions = {
-  refetchOnMountOrArgChange: boolean | number;
-  refetchOnReconnect: boolean;
-  refetchOnFocus: boolean;
-};
+  refetchOnMountOrArgChange: boolean | number
+  refetchOnReconnect: boolean
+  refetchOnFocus: boolean
+}
 
 /**
  * Strings describing the query state at any given time.
@@ -32,33 +32,33 @@ export enum QueryStatus {
 
 export type RequestStatusFlags =
   | {
-      status: QueryStatus.uninitialized;
-      isUninitialized: true;
-      isLoading: false;
-      isSuccess: false;
-      isError: false;
+      status: QueryStatus.uninitialized
+      isUninitialized: true
+      isLoading: false
+      isSuccess: false
+      isError: false
     }
   | {
-      status: QueryStatus.pending;
-      isUninitialized: false;
-      isLoading: true;
-      isSuccess: false;
-      isError: false;
+      status: QueryStatus.pending
+      isUninitialized: false
+      isLoading: true
+      isSuccess: false
+      isError: false
     }
   | {
-      status: QueryStatus.fulfilled;
-      isUninitialized: false;
-      isLoading: false;
-      isSuccess: true;
-      isError: false;
+      status: QueryStatus.fulfilled
+      isUninitialized: false
+      isLoading: false
+      isSuccess: true
+      isError: false
     }
   | {
-      status: QueryStatus.rejected;
-      isUninitialized: false;
-      isLoading: false;
-      isSuccess: false;
-      isError: true;
-    };
+      status: QueryStatus.rejected
+      isUninitialized: false
+      isLoading: false
+      isSuccess: false
+      isError: true
+    }
 
 export function getRequestStatusFlags(status: QueryStatus): RequestStatusFlags {
   return {
@@ -67,14 +67,14 @@ export function getRequestStatusFlags(status: QueryStatus): RequestStatusFlags {
     isLoading: status === QueryStatus.pending,
     isSuccess: status === QueryStatus.fulfilled,
     isError: status === QueryStatus.rejected,
-  } as any;
+  } as any
 }
 
 export type SubscriptionOptions = {
   /**
    * How frequently to automatically re-fetch data (in milliseconds). Defaults to `0` (off).
    */
-  pollingInterval?: number;
+  pollingInterval?: number
   /**
    * Defaults to `false`. This setting allows you to control whether RTK Query will try to refetch all subscribed queries after regaining a network connection.
    *
@@ -82,7 +82,7 @@ export type SubscriptionOptions = {
    *
    * Note: requires `setupListeners` to have been called.
    */
-  refetchOnReconnect?: boolean;
+  refetchOnReconnect?: boolean
   /**
    * Defaults to `false`. This setting allows you to control whether RTK Query will try to refetch all subscribed queries after the application window regains focus.
    *
@@ -90,139 +90,171 @@ export type SubscriptionOptions = {
    *
    * Note: requires `setupListeners` to have been called.
    */
-  refetchOnFocus?: boolean;
-};
-export type Subscribers = { [requestId: string]: SubscriptionOptions };
+  refetchOnFocus?: boolean
+}
+export type Subscribers = { [requestId: string]: SubscriptionOptions }
 export type QueryKeys<Definitions extends EndpointDefinitions> = {
-  [K in keyof Definitions]: Definitions[K] extends QueryDefinition<any, any, any, any> ? K : never;
-}[keyof Definitions];
+  [K in keyof Definitions]: Definitions[K] extends QueryDefinition<
+    any,
+    any,
+    any,
+    any
+  >
+    ? K
+    : never
+}[keyof Definitions]
 export type MutationKeys<Definitions extends EndpointDefinitions> = {
-  [K in keyof Definitions]: Definitions[K] extends MutationDefinition<any, any, any, any> ? K : never;
-}[keyof Definitions];
+  [K in keyof Definitions]: Definitions[K] extends MutationDefinition<
+    any,
+    any,
+    any,
+    any
+  >
+    ? K
+    : never
+}[keyof Definitions]
 
 type BaseQuerySubState<D extends BaseEndpointDefinition<any, any, any>> = {
   /**
    * The argument originally passed into the hook or `initiate` action call
    */
-  originalArgs: QueryArgFrom<D>;
+  originalArgs: QueryArgFrom<D>
   /**
    * A unique ID associated with the request
    */
-  requestId: string;
+  requestId: string
   /**
    * The received data from the query
    */
-  data?: ResultTypeFrom<D>;
+  data?: ResultTypeFrom<D>
   /**
    * The received error if applicable
    */
   error?:
     | SerializedError
-    | (D extends QueryDefinition<any, infer BaseQuery, any, any> ? BaseQueryError<BaseQuery> : never);
+    | (D extends QueryDefinition<any, infer BaseQuery, any, any>
+        ? BaseQueryError<BaseQuery>
+        : never)
   /**
    * The name of the endpoint associated with the query
    */
-  endpointName: string;
+  endpointName: string
   /**
    * Time that the latest query started
    */
-  startedTimeStamp: number;
+  startedTimeStamp: number
   /**
    * Time that the latest query was fulfilled
    */
-  fulfilledTimeStamp?: number;
-};
+  fulfilledTimeStamp?: number
+}
 
 export type QuerySubState<D extends BaseEndpointDefinition<any, any, any>> = Id<
   | ({
-      status: QueryStatus.fulfilled;
-    } & WithRequiredProp<BaseQuerySubState<D>, 'data' | 'fulfilledTimeStamp'> & { error: undefined })
+      status: QueryStatus.fulfilled
+    } & WithRequiredProp<
+      BaseQuerySubState<D>,
+      'data' | 'fulfilledTimeStamp'
+    > & { error: undefined })
   | ({
-      status: QueryStatus.pending;
+      status: QueryStatus.pending
     } & BaseQuerySubState<D>)
   | ({
-      status: QueryStatus.rejected;
+      status: QueryStatus.rejected
     } & WithRequiredProp<BaseQuerySubState<D>, 'error'>)
   | {
-      status: QueryStatus.uninitialized;
-      originalArgs?: undefined;
-      data?: undefined;
-      error?: undefined;
-      requestId?: undefined;
-      endpointName?: string;
-      startedTimeStamp?: undefined;
-      fulfilledTimeStamp?: undefined;
+      status: QueryStatus.uninitialized
+      originalArgs?: undefined
+      data?: undefined
+      error?: undefined
+      requestId?: undefined
+      endpointName?: string
+      startedTimeStamp?: undefined
+      fulfilledTimeStamp?: undefined
     }
->;
+>
 
 type BaseMutationSubState<D extends BaseEndpointDefinition<any, any, any>> = {
-  originalArgs?: QueryArgFrom<D>;
-  data?: ResultTypeFrom<D>;
+  originalArgs?: QueryArgFrom<D>
+  data?: ResultTypeFrom<D>
   error?:
     | SerializedError
-    | (D extends MutationDefinition<any, infer BaseQuery, any, any> ? BaseQueryError<BaseQuery> : never);
-  endpointName: string;
-  startedTimeStamp: number;
-  fulfilledTimeStamp?: number;
-};
+    | (D extends MutationDefinition<any, infer BaseQuery, any, any>
+        ? BaseQueryError<BaseQuery>
+        : never)
+  endpointName: string
+  startedTimeStamp: number
+  fulfilledTimeStamp?: number
+}
 
 export type MutationSubState<D extends BaseEndpointDefinition<any, any, any>> =
   | (({
-      status: QueryStatus.fulfilled;
-    } & WithRequiredProp<BaseMutationSubState<D>, 'data' | 'fulfilledTimeStamp'>) & { error: undefined })
+      status: QueryStatus.fulfilled
+    } & WithRequiredProp<
+      BaseMutationSubState<D>,
+      'data' | 'fulfilledTimeStamp'
+    >) & { error: undefined })
   | (({
-      status: QueryStatus.pending;
+      status: QueryStatus.pending
     } & BaseMutationSubState<D>) & { data?: undefined })
   | ({
-      status: QueryStatus.rejected;
+      status: QueryStatus.rejected
     } & WithRequiredProp<BaseMutationSubState<D>, 'error'>)
   | {
-      status: QueryStatus.uninitialized;
-      originalArgs?: undefined;
-      data?: undefined;
-      error?: undefined;
-      endpointName?: string;
-      startedTimeStamp?: undefined;
-      fulfilledTimeStamp?: undefined;
-    };
-
-export type CombinedState<D extends EndpointDefinitions, E extends string, ReducerPath extends string> = {
-  queries: QueryState<D>;
-  mutations: MutationState<D>;
-  provided: InvalidationState<E>;
-  subscriptions: SubscriptionState;
-  config: ConfigState<ReducerPath>;
-};
+      status: QueryStatus.uninitialized
+      originalArgs?: undefined
+      data?: undefined
+      error?: undefined
+      endpointName?: string
+      startedTimeStamp?: undefined
+      fulfilledTimeStamp?: undefined
+    }
+
+export type CombinedState<
+  D extends EndpointDefinitions,
+  E extends string,
+  ReducerPath extends string
+> = {
+  queries: QueryState<D>
+  mutations: MutationState<D>
+  provided: InvalidationState<E>
+  subscriptions: SubscriptionState
+  config: ConfigState<ReducerPath>
+}
 
 export type InvalidationState<TagTypes extends string> = {
   [_ in TagTypes]: {
-    [id: string]: Array<QueryCacheKey>;
-    [id: number]: Array<QueryCacheKey>;
-  };
-};
+    [id: string]: Array<QueryCacheKey>
+    [id: number]: Array<QueryCacheKey>
+  }
+}
 
 export type QueryState<D extends EndpointDefinitions> = {
-  [queryCacheKey: string]: QuerySubState<D[string]> | undefined;
-};
+  [queryCacheKey: string]: QuerySubState<D[string]> | undefined
+}
 
 export type SubscriptionState = {
-  [queryCacheKey: string]: Subscribers | undefined;
-};
+  [queryCacheKey: string]: Subscribers | undefined
+}
 
 export type ConfigState<ReducerPath> = RefetchConfigOptions & {
-  reducerPath: ReducerPath;
-  online: boolean;
-  focused: boolean;
-} & ModifiableConfigState;
+  reducerPath: ReducerPath
+  online: boolean
+  focused: boolean
+} & ModifiableConfigState
 
 export type ModifiableConfigState = {
-  keepUnusedDataFor: number;
-} & RefetchConfigOptions;
+  keepUnusedDataFor: number
+} & RefetchConfigOptions
 
 export type MutationState<D extends EndpointDefinitions> = {
-  [requestId: string]: MutationSubState<D[string]> | undefined;
-};
+  [requestId: string]: MutationSubState<D[string]> | undefined
+}
 
-export type RootState<Definitions extends EndpointDefinitions, TagTypes extends string, ReducerPath extends string> = {
-  [P in ReducerPath]: CombinedState<Definitions, TagTypes, P>;
-};
+export type RootState<
+  Definitions extends EndpointDefinitions,
+  TagTypes extends string,
+  ReducerPath extends string
+> = {
+  [P in ReducerPath]: CombinedState<Definitions, TagTypes, P>
+}
diff --git a/query/src/core/buildInitiate.ts b/src/query/core/buildInitiate.ts
similarity index 51%
rename from query/src/core/buildInitiate.ts
rename to src/query/core/buildInitiate.ts
index 8a905269..8efb99fe 100644
--- a/query/src/core/buildInitiate.ts
+++ b/src/query/core/buildInitiate.ts
@@ -4,14 +4,19 @@ import {
   MutationDefinition,
   QueryArgFrom,
   ResultTypeFrom,
-} from '../endpointDefinitions';
-import type { QueryThunkArg, MutationThunkArg } from './buildThunks';
-import { AnyAction, AsyncThunk, ThunkAction, unwrapResult } from '@reduxjs/toolkit';
-import { QuerySubState, SubscriptionOptions } from './apiState';
-import { InternalSerializeQueryArgs } from '../defaultSerializeQueryArgs';
-import { Api } from '../apiTypes';
-import { ApiEndpointQuery } from './module';
-import { BaseQueryResult } from '../baseQueryTypes';
+} from '../endpointDefinitions'
+import type { QueryThunkArg, MutationThunkArg } from './buildThunks'
+import {
+  AnyAction,
+  AsyncThunk,
+  ThunkAction,
+  unwrapResult,
+} from '@reduxjs/toolkit'
+import { QuerySubState, SubscriptionOptions } from './apiState'
+import { InternalSerializeQueryArgs } from '../defaultSerializeQueryArgs'
+import { Api } from '../apiTypes'
+import { ApiEndpointQuery } from './module'
+import { BaseQueryResult } from '../baseQueryTypes'
 
 declare module './module' {
   export interface ApiEndpointQuery<
@@ -19,7 +24,7 @@ declare module './module' {
     // eslint-disable-next-line @typescript-eslint/no-unused-vars
     Definitions extends EndpointDefinitions
   > {
-    initiate: StartQueryActionCreator<Definition>;
+    initiate: StartQueryActionCreator<Definition>
   }
 
   export interface ApiEndpointMutation<
@@ -27,32 +32,38 @@ declare module './module' {
     // eslint-disable-next-line @typescript-eslint/no-unused-vars
     Definitions extends EndpointDefinitions
   > {
-    initiate: StartMutationActionCreator<Definition>;
+    initiate: StartMutationActionCreator<Definition>
   }
 }
 
 export interface StartQueryActionCreatorOptions {
-  subscribe?: boolean;
-  forceRefetch?: boolean | number;
-  subscriptionOptions?: SubscriptionOptions;
+  subscribe?: boolean
+  forceRefetch?: boolean | number
+  subscriptionOptions?: SubscriptionOptions
 }
 
-type StartQueryActionCreator<D extends QueryDefinition<any, any, any, any, any>> = (
+type StartQueryActionCreator<
+  D extends QueryDefinition<any, any, any, any, any>
+> = (
   arg: QueryArgFrom<D>,
   options?: StartQueryActionCreatorOptions
-) => ThunkAction<QueryActionCreatorResult<D>, any, any, AnyAction>;
+) => ThunkAction<QueryActionCreatorResult<D>, any, any, AnyAction>
 
-export type QueryActionCreatorResult<D extends QueryDefinition<any, any, any, any>> = Promise<QuerySubState<D>> & {
-  arg: QueryArgFrom<D>;
-  requestId: string;
-  subscriptionOptions: SubscriptionOptions | undefined;
-  abort(): void;
-  unsubscribe(): void;
-  refetch(): void;
-  updateSubscriptionOptions(options: SubscriptionOptions): void;
-};
+export type QueryActionCreatorResult<
+  D extends QueryDefinition<any, any, any, any>
+> = Promise<QuerySubState<D>> & {
+  arg: QueryArgFrom<D>
+  requestId: string
+  subscriptionOptions: SubscriptionOptions | undefined
+  abort(): void
+  unsubscribe(): void
+  refetch(): void
+  updateSubscriptionOptions(options: SubscriptionOptions): void
+}
 
-type StartMutationActionCreator<D extends MutationDefinition<any, any, any, any>> = (
+type StartMutationActionCreator<
+  D extends MutationDefinition<any, any, any, any>
+> = (
   arg: QueryArgFrom<D>,
   options?: {
     /**
@@ -61,19 +72,27 @@ type StartMutationActionCreator<D extends MutationDefinition<any, any, any, any>
      * result, state & potential errors being held in store, you can set this to false.
      * (defaults to `true`)
      */
-    track?: boolean;
+    track?: boolean
   }
-) => ThunkAction<MutationActionCreatorResult<D>, any, any, AnyAction>;
+) => ThunkAction<MutationActionCreatorResult<D>, any, any, AnyAction>
 
-export type MutationActionCreatorResult<D extends MutationDefinition<any, any, any, any>> = Promise<
-  ReturnType<BaseQueryResult<D extends MutationDefinition<any, infer BaseQuery, any, any> ? BaseQuery : never>>
+export type MutationActionCreatorResult<
+  D extends MutationDefinition<any, any, any, any>
+> = Promise<
+  ReturnType<
+    BaseQueryResult<
+      D extends MutationDefinition<any, infer BaseQuery, any, any>
+        ? BaseQuery
+        : never
+    >
+  >
 > & {
-  arg: QueryArgFrom<D>;
-  requestId: string;
-  abort(): void;
-  unwrap(): Promise<ResultTypeFrom<D>>;
-  unsubscribe(): void;
-};
+  arg: QueryArgFrom<D>
+  requestId: string
+  abort(): void
+  unwrap(): Promise<ResultTypeFrom<D>>
+  unsubscribe(): void
+}
 
 export function buildInitiate<InternalQueryArgs>({
   serializeQueryArgs,
@@ -81,20 +100,31 @@ export function buildInitiate<InternalQueryArgs>({
   mutationThunk,
   api,
 }: {
-  serializeQueryArgs: InternalSerializeQueryArgs<InternalQueryArgs>;
-  queryThunk: AsyncThunk<any, QueryThunkArg<any>, {}>;
-  mutationThunk: AsyncThunk<any, MutationThunkArg<any>, {}>;
-  api: Api<any, EndpointDefinitions, any, any>;
+  serializeQueryArgs: InternalSerializeQueryArgs<InternalQueryArgs>
+  queryThunk: AsyncThunk<any, QueryThunkArg<any>, {}>
+  mutationThunk: AsyncThunk<any, MutationThunkArg<any>, {}>
+  api: Api<any, EndpointDefinitions, any, any>
 }) {
-  const { unsubscribeQueryResult, unsubscribeMutationResult, updateSubscriptionOptions } = api.internalActions;
-  return { buildInitiateQuery, buildInitiateMutation };
+  const {
+    unsubscribeQueryResult,
+    unsubscribeMutationResult,
+    updateSubscriptionOptions,
+  } = api.internalActions
+  return { buildInitiateQuery, buildInitiateMutation }
 
-  function buildInitiateQuery(endpointName: string, endpointDefinition: QueryDefinition<any, any, any, any>) {
+  function buildInitiateQuery(
+    endpointName: string,
+    endpointDefinition: QueryDefinition<any, any, any, any>
+  ) {
     const queryAction: StartQueryActionCreator<any> = (
       arg,
       { subscribe = true, forceRefetch, subscriptionOptions } = {}
     ) => (dispatch, getState) => {
-      const queryCacheKey = serializeQueryArgs({ queryArgs: arg, endpointDefinition, endpointName });
+      const queryCacheKey = serializeQueryArgs({
+        queryArgs: arg,
+        endpointDefinition,
+        endpointName,
+      })
       const thunk = queryThunk({
         subscribe,
         forceRefetch,
@@ -103,18 +133,22 @@ export function buildInitiate<InternalQueryArgs>({
         originalArgs: arg,
         queryCacheKey,
         startedTimeStamp: Date.now(),
-      });
-      const thunkResult = dispatch(thunk);
-      const { requestId, abort } = thunkResult;
+      })
+      const thunkResult = dispatch(thunk)
+      const { requestId, abort } = thunkResult
       const statePromise = Object.assign(
-        thunkResult.then(() => (api.endpoints[endpointName] as ApiEndpointQuery<any, any>).select(arg)(getState())),
+        thunkResult.then(() =>
+          (api.endpoints[endpointName] as ApiEndpointQuery<any, any>).select(
+            arg
+          )(getState())
+        ),
         {
           arg,
           requestId,
           subscriptionOptions,
           abort,
           refetch() {
-            dispatch(queryAction(arg, { subscribe: false, forceRefetch: true }));
+            dispatch(queryAction(arg, { subscribe: false, forceRefetch: true }))
           },
           unsubscribe() {
             if (subscribe)
@@ -123,17 +157,24 @@ export function buildInitiate<InternalQueryArgs>({
                   queryCacheKey,
                   requestId,
                 })
-              );
+              )
           },
           updateSubscriptionOptions(options: SubscriptionOptions) {
-            statePromise.subscriptionOptions = options;
-            dispatch(updateSubscriptionOptions({ endpointName, requestId, queryCacheKey, options }));
+            statePromise.subscriptionOptions = options
+            dispatch(
+              updateSubscriptionOptions({
+                endpointName,
+                requestId,
+                queryCacheKey,
+                options,
+              })
+            )
           },
         }
-      );
-      return statePromise;
-    };
-    return queryAction;
+      )
+      return statePromise
+    }
+    return queryAction
   }
 
   function buildInitiateMutation(
@@ -146,26 +187,28 @@ export function buildInitiate<InternalQueryArgs>({
         originalArgs: arg,
         track,
         startedTimeStamp: Date.now(),
-      });
-      const thunkResult = dispatch(thunk);
-      const { requestId, abort } = thunkResult;
+      })
+      const thunkResult = dispatch(thunk)
+      const { requestId, abort } = thunkResult
       const returnValuePromise = thunkResult
         .then(unwrapResult)
         .then((unwrapped) => ({
           data: unwrapped.result,
         }))
-        .catch((error) => ({ error }));
+        .catch((error) => ({ error }))
       return Object.assign(returnValuePromise, {
         arg: thunkResult.arg,
         requestId,
         abort,
         unwrap() {
-          return thunkResult.then(unwrapResult).then((unwrapped) => unwrapped.result);
+          return thunkResult
+            .then(unwrapResult)
+            .then((unwrapped) => unwrapped.result)
         },
         unsubscribe() {
-          if (track) dispatch(unsubscribeMutationResult({ requestId }));
+          if (track) dispatch(unsubscribeMutationResult({ requestId }))
         },
-      });
-    };
+      })
+    }
   }
 }
diff --git a/src/query/core/buildMiddleware.ts b/src/query/core/buildMiddleware.ts
new file mode 100644
index 00000000..d473076b
--- /dev/null
+++ b/src/query/core/buildMiddleware.ts
@@ -0,0 +1,356 @@
+import {
+  AnyAction,
+  AsyncThunk,
+  createAction,
+  isAnyOf,
+  isFulfilled,
+  isRejectedWithValue,
+  Middleware,
+  MiddlewareAPI,
+  ThunkDispatch,
+} from '@reduxjs/toolkit'
+import {
+  QueryCacheKey,
+  QueryStatus,
+  QuerySubState,
+  QuerySubstateIdentifier,
+  RootState,
+  Subscribers,
+} from './apiState'
+import { Api, ApiContext } from '../apiTypes'
+import {
+  calculateProvidedByThunk,
+  MutationThunkArg,
+  QueryThunkArg,
+  ThunkResult,
+} from './buildThunks'
+import {
+  AssertTagTypes,
+  calculateProvidedBy,
+  EndpointDefinitions,
+  FullTagDescription,
+} from '../endpointDefinitions'
+import { onFocus, onOnline } from './setupListeners'
+import { flatten } from '../utils'
+
+type QueryStateMeta<T> = Record<string, undefined | T>
+type TimeoutId = ReturnType<typeof setTimeout>
+
+export function buildMiddleware<
+  Definitions extends EndpointDefinitions,
+  ReducerPath extends string,
+  TagTypes extends string
+>({
+  reducerPath,
+  context,
+  context: { endpointDefinitions },
+  queryThunk,
+  mutationThunk,
+  api,
+  assertTagType,
+}: {
+  reducerPath: ReducerPath
+  context: ApiContext<Definitions>
+  queryThunk: AsyncThunk<ThunkResult, QueryThunkArg<any>, {}>
+  mutationThunk: AsyncThunk<ThunkResult, MutationThunkArg<any>, {}>
+  api: Api<any, EndpointDefinitions, ReducerPath, TagTypes>
+  assertTagType: AssertTagTypes
+}) {
+  type MWApi = MiddlewareAPI<
+    ThunkDispatch<any, any, AnyAction>,
+    RootState<Definitions, string, ReducerPath>
+  >
+  const {
+    removeQueryResult,
+    unsubscribeQueryResult,
+    updateSubscriptionOptions,
+    resetApiState,
+  } = api.internalActions
+
+  const currentRemovalTimeouts: QueryStateMeta<TimeoutId> = {}
+  const currentPolls: QueryStateMeta<{
+    nextPollTimestamp: number
+    timeout?: TimeoutId
+    pollingInterval: number
+  }> = {}
+
+  const actions = {
+    invalidateTags: createAction<
+      Array<TagTypes | FullTagDescription<TagTypes>>
+    >(`${reducerPath}/invalidateTags`),
+  }
+
+  const middleware: Middleware<
+    {},
+    RootState<Definitions, string, ReducerPath>,
+    ThunkDispatch<any, any, AnyAction>
+  > = (mwApi) => (next) => (action) => {
+    const result = next(action)
+
+    if (
+      isAnyOf(
+        isFulfilled(mutationThunk),
+        isRejectedWithValue(mutationThunk)
+      )(action)
+    ) {
+      invalidateTags(
+        calculateProvidedByThunk(
+          action,
+          'invalidatesTags',
+          endpointDefinitions,
+          assertTagType
+        ),
+        mwApi
+      )
+    }
+
+    if (actions.invalidateTags.match(action)) {
+      invalidateTags(
+        calculateProvidedBy(
+          action.payload,
+          undefined,
+          undefined,
+          undefined,
+          assertTagType
+        ),
+        mwApi
+      )
+    }
+
+    if (unsubscribeQueryResult.match(action)) {
+      handleUnsubscribe(action.payload, mwApi)
+    }
+
+    if (updateSubscriptionOptions.match(action)) {
+      updatePollingInterval(action.payload, mwApi)
+    }
+    if (
+      queryThunk.pending.match(action) ||
+      (queryThunk.rejected.match(action) && action.meta.condition)
+    ) {
+      updatePollingInterval(action.meta.arg, mwApi)
+    }
+    if (
+      queryThunk.fulfilled.match(action) ||
+      (queryThunk.rejected.match(action) && !action.meta.condition)
+    ) {
+      startNextPoll(action.meta.arg, mwApi)
+    }
+
+    if (onFocus.match(action)) {
+      refetchValidQueries(mwApi, 'refetchOnFocus')
+    }
+    if (onOnline.match(action)) {
+      refetchValidQueries(mwApi, 'refetchOnReconnect')
+    }
+
+    if (resetApiState.match(action)) {
+      for (const [key, poll] of Object.entries(currentPolls)) {
+        if (poll?.timeout) clearTimeout(poll.timeout)
+        delete currentPolls[key]
+      }
+      for (const [key, timeout] of Object.entries(currentRemovalTimeouts)) {
+        if (timeout) clearTimeout(timeout)
+        delete currentRemovalTimeouts[key]
+      }
+    }
+
+    return result
+  }
+
+  return { middleware, actions }
+
+  function refetchQuery(
+    querySubState: Exclude<
+      QuerySubState<any>,
+      { status: QueryStatus.uninitialized }
+    >,
+    queryCacheKey: string,
+    override: Partial<QueryThunkArg<any>> = {}
+  ) {
+    return queryThunk({
+      endpointName: querySubState.endpointName,
+      originalArgs: querySubState.originalArgs,
+      subscribe: false,
+      forceRefetch: true,
+      startedTimeStamp: Date.now(),
+      queryCacheKey: queryCacheKey as any,
+      ...override,
+    })
+  }
+
+  function refetchValidQueries(
+    api: MWApi,
+    type: 'refetchOnFocus' | 'refetchOnReconnect'
+  ) {
+    const state = api.getState()[reducerPath]
+    const queries = state.queries
+    const subscriptions = state.subscriptions
+
+    context.batch(() => {
+      for (const queryCacheKey of Object.keys(subscriptions)) {
+        const querySubState = queries[queryCacheKey]
+        const subscriptionSubState = subscriptions[queryCacheKey]
+
+        if (
+          !subscriptionSubState ||
+          !querySubState ||
+          querySubState.status === QueryStatus.uninitialized
+        )
+          return
+
+        const shouldRefetch =
+          Object.values(subscriptionSubState).some(
+            (sub) => sub[type] === true
+          ) ||
+          (Object.values(subscriptionSubState).every(
+            (sub) => sub[type] === undefined
+          ) &&
+            state.config[type])
+
+        if (shouldRefetch) {
+          api.dispatch(refetchQuery(querySubState, queryCacheKey))
+        }
+      }
+    })
+  }
+
+  function invalidateTags(
+    tags: readonly FullTagDescription<string>[],
+    api: MWApi
+  ) {
+    const state = api.getState()[reducerPath]
+
+    const toInvalidate = new Set<QueryCacheKey>()
+    for (const tag of tags) {
+      const provided = state.provided[tag.type]
+      if (!provided) {
+        continue
+      }
+
+      let invalidateSubscriptions =
+        (tag.id !== undefined
+          ? // id given: invalidate all queries that provide this type & id
+            provided[tag.id]
+          : // no id: invalidate all queries that provide this type
+            flatten(Object.values(provided))) ?? []
+
+      for (const invalidate of invalidateSubscriptions) {
+        toInvalidate.add(invalidate)
+      }
+    }
+
+    context.batch(() => {
+      for (const queryCacheKey of toInvalidate.values()) {
+        const querySubState = state.queries[queryCacheKey]
+        const subscriptionSubState = state.subscriptions[queryCacheKey]
+        if (querySubState && subscriptionSubState) {
+          if (Object.keys(subscriptionSubState).length === 0) {
+            api.dispatch(removeQueryResult({ queryCacheKey }))
+          } else if (querySubState.status !== QueryStatus.uninitialized) {
+            api.dispatch(refetchQuery(querySubState, queryCacheKey))
+          } else {
+          }
+        }
+      }
+    })
+  }
+
+  function handleUnsubscribe(
+    { queryCacheKey }: QuerySubstateIdentifier,
+    api: MWApi
+  ) {
+    const keepUnusedDataFor = api.getState()[reducerPath].config
+      .keepUnusedDataFor
+    const currentTimeout = currentRemovalTimeouts[queryCacheKey]
+    if (currentTimeout) {
+      clearTimeout(currentTimeout)
+    }
+    currentRemovalTimeouts[queryCacheKey] = setTimeout(() => {
+      const subscriptions = api.getState()[reducerPath].subscriptions[
+        queryCacheKey
+      ]
+      if (!subscriptions || Object.keys(subscriptions).length === 0) {
+        api.dispatch(removeQueryResult({ queryCacheKey }))
+      }
+      delete currentRemovalTimeouts![queryCacheKey]
+    }, keepUnusedDataFor * 1000)
+  }
+
+  function startNextPoll(
+    { queryCacheKey }: QuerySubstateIdentifier,
+    api: MWApi
+  ) {
+    const state = api.getState()[reducerPath]
+    const querySubState = state.queries[queryCacheKey]
+    const subscriptions = state.subscriptions[queryCacheKey]
+
+    if (!querySubState || querySubState.status === QueryStatus.uninitialized)
+      return
+
+    const lowestPollingInterval = findLowestPollingInterval(subscriptions)
+    if (!Number.isFinite(lowestPollingInterval)) return
+
+    const currentPoll = currentPolls[queryCacheKey]
+
+    if (currentPoll?.timeout) {
+      clearTimeout(currentPoll.timeout)
+      currentPoll.timeout = undefined
+    }
+
+    const nextPollTimestamp = Date.now() + lowestPollingInterval
+
+    const currentInterval: typeof currentPolls[number] = (currentPolls[
+      queryCacheKey
+    ] = {
+      nextPollTimestamp,
+      pollingInterval: lowestPollingInterval,
+      timeout: setTimeout(() => {
+        currentInterval!.timeout = undefined
+        api.dispatch(refetchQuery(querySubState, queryCacheKey))
+      }, lowestPollingInterval),
+    })
+  }
+
+  function updatePollingInterval(
+    { queryCacheKey }: QuerySubstateIdentifier,
+    api: MWApi
+  ) {
+    const state = api.getState()[reducerPath]
+    const querySubState = state.queries[queryCacheKey]
+    const subscriptions = state.subscriptions[queryCacheKey]
+
+    if (!querySubState || querySubState.status === QueryStatus.uninitialized) {
+      return
+    }
+
+    const lowestPollingInterval = findLowestPollingInterval(subscriptions)
+    const currentPoll = currentPolls[queryCacheKey]
+
+    if (!Number.isFinite(lowestPollingInterval)) {
+      if (currentPoll?.timeout) {
+        clearTimeout(currentPoll.timeout)
+      }
+      delete currentPolls[queryCacheKey]
+      return
+    }
+
+    const nextPollTimestamp = Date.now() + lowestPollingInterval
+
+    if (!currentPoll || nextPollTimestamp < currentPoll.nextPollTimestamp) {
+      startNextPoll({ queryCacheKey }, api)
+    }
+  }
+}
+
+function findLowestPollingInterval(subscribers: Subscribers = {}) {
+  let lowestPollingInterval = Number.POSITIVE_INFINITY
+  for (const subscription of Object.values(subscribers)) {
+    if (!!subscription.pollingInterval)
+      lowestPollingInterval = Math.min(
+        subscription.pollingInterval,
+        lowestPollingInterval
+      )
+  }
+  return lowestPollingInterval
+}
diff --git a/query/src/core/buildSelectors.ts b/src/query/core/buildSelectors.ts
similarity index 57%
rename from query/src/core/buildSelectors.ts
rename to src/query/core/buildSelectors.ts
index 44b47513..e641415f 100644
--- a/query/src/core/buildSelectors.ts
+++ b/src/query/core/buildSelectors.ts
@@ -1,4 +1,4 @@
-import { createNextState, createSelector } from '@reduxjs/toolkit';
+import { createNextState, createSelector } from '@reduxjs/toolkit'
 import {
   MutationSubState,
   QueryStatus,
@@ -6,7 +6,7 @@ import {
   RootState as _RootState,
   getRequestStatusFlags,
   RequestStatusFlags,
-} from './apiState';
+} from './apiState'
 import {
   EndpointDefinitions,
   QueryDefinition,
@@ -14,10 +14,10 @@ import {
   QueryArgFrom,
   TagTypesFrom,
   ReducerPathFrom,
-} from '../endpointDefinitions';
-import { InternalSerializeQueryArgs } from '../defaultSerializeQueryArgs';
+} from '../endpointDefinitions'
+import { InternalSerializeQueryArgs } from '../defaultSerializeQueryArgs'
 
-export const skipSelector = Symbol('skip selector');
+export const skipSelector = Symbol('skip selector')
 
 declare module './module' {
   export interface ApiEndpointQuery<
@@ -26,8 +26,12 @@ declare module './module' {
   > {
     select: QueryResultSelectorFactory<
       Definition,
-      _RootState<Definitions, TagTypesFrom<Definition>, ReducerPathFrom<Definition>>
-    >;
+      _RootState<
+        Definitions,
+        TagTypesFrom<Definition>,
+        ReducerPathFrom<Definition>
+      >
+    >
   }
 
   export interface ApiEndpointMutation<
@@ -36,55 +40,77 @@ declare module './module' {
   > {
     select: MutationResultSelectorFactory<
       Definition,
-      _RootState<Definitions, TagTypesFrom<Definition>, ReducerPathFrom<Definition>>
-    >;
+      _RootState<
+        Definitions,
+        TagTypesFrom<Definition>,
+        ReducerPathFrom<Definition>
+      >
+    >
   }
 }
 
-type QueryResultSelectorFactory<Definition extends QueryDefinition<any, any, any, any>, RootState> = (
+type QueryResultSelectorFactory<
+  Definition extends QueryDefinition<any, any, any, any>,
+  RootState
+> = (
   queryArg: QueryArgFrom<Definition> | typeof skipSelector
-) => (state: RootState) => QueryResultSelectorResult<Definition>;
+) => (state: RootState) => QueryResultSelectorResult<Definition>
 
 export type QueryResultSelectorResult<
   Definition extends QueryDefinition<any, any, any, any>
-> = QuerySubState<Definition> & RequestStatusFlags;
+> = QuerySubState<Definition> & RequestStatusFlags
 
-type MutationResultSelectorFactory<Definition extends MutationDefinition<any, any, any, any>, RootState> = (
+type MutationResultSelectorFactory<
+  Definition extends MutationDefinition<any, any, any, any>,
+  RootState
+> = (
   requestId: string | typeof skipSelector
-) => (state: RootState) => MutationResultSelectorResult<Definition>;
+) => (state: RootState) => MutationResultSelectorResult<Definition>
 
 export type MutationResultSelectorResult<
   Definition extends MutationDefinition<any, any, any, any>
-> = MutationSubState<Definition> & RequestStatusFlags;
+> = MutationSubState<Definition> & RequestStatusFlags
 
 const initialSubState = {
   status: QueryStatus.uninitialized as const,
-};
+}
 
 // abuse immer to freeze default states
-const defaultQuerySubState = createNextState({}, (): QuerySubState<any> => initialSubState);
-const defaultMutationSubState = createNextState({}, (): MutationSubState<any> => initialSubState);
+const defaultQuerySubState = createNextState(
+  {},
+  (): QuerySubState<any> => initialSubState
+)
+const defaultMutationSubState = createNextState(
+  {},
+  (): MutationSubState<any> => initialSubState
+)
 
-export function buildSelectors<InternalQueryArgs, Definitions extends EndpointDefinitions, ReducerPath extends string>({
+export function buildSelectors<
+  InternalQueryArgs,
+  Definitions extends EndpointDefinitions,
+  ReducerPath extends string
+>({
   serializeQueryArgs,
   reducerPath,
 }: {
-  serializeQueryArgs: InternalSerializeQueryArgs<InternalQueryArgs>;
-  reducerPath: ReducerPath;
+  serializeQueryArgs: InternalSerializeQueryArgs<InternalQueryArgs>
+  reducerPath: ReducerPath
 }) {
-  type RootState = _RootState<Definitions, string, string>;
+  type RootState = _RootState<Definitions, string, string>
 
-  return { buildQuerySelector, buildMutationSelector };
+  return { buildQuerySelector, buildMutationSelector }
 
-  function withRequestFlags<T extends { status: QueryStatus }>(substate: T): T & RequestStatusFlags {
+  function withRequestFlags<T extends { status: QueryStatus }>(
+    substate: T
+  ): T & RequestStatusFlags {
     return {
       ...substate,
       ...getRequestStatusFlags(substate.status),
-    };
+    }
   }
 
   function selectInternalState(rootState: RootState) {
-    return rootState[reducerPath];
+    return rootState[reducerPath]
   }
 
   function buildQuerySelector(
@@ -97,21 +123,31 @@ export function buildSelectors<InternalQueryArgs, Definitions extends EndpointDe
         (internalState) =>
           (queryArgs === skipSelector
             ? undefined
-            : internalState.queries[serializeQueryArgs({ queryArgs, endpointDefinition, endpointName })]) ??
-          defaultQuerySubState
-      );
-      return createSelector(selectQuerySubState, withRequestFlags);
-    };
+            : internalState.queries[
+                serializeQueryArgs({
+                  queryArgs,
+                  endpointDefinition,
+                  endpointName,
+                })
+              ]) ?? defaultQuerySubState
+      )
+      return createSelector(selectQuerySubState, withRequestFlags)
+    }
   }
 
-  function buildMutationSelector(): MutationResultSelectorFactory<any, RootState> {
+  function buildMutationSelector(): MutationResultSelectorFactory<
+    any,
+    RootState
+  > {
     return (mutationId) => {
       const selectMutationSubstate = createSelector(
         selectInternalState,
         (internalState) =>
-          (mutationId === skipSelector ? undefined : internalState.mutations[mutationId]) ?? defaultMutationSubState
-      );
-      return createSelector(selectMutationSubstate, withRequestFlags);
-    };
+          (mutationId === skipSelector
+            ? undefined
+            : internalState.mutations[mutationId]) ?? defaultMutationSubState
+      )
+      return createSelector(selectMutationSubstate, withRequestFlags)
+    }
   }
 }
diff --git a/src/query/core/buildSlice.ts b/src/query/core/buildSlice.ts
new file mode 100644
index 00000000..fabcc9fc
--- /dev/null
+++ b/src/query/core/buildSlice.ts
@@ -0,0 +1,368 @@
+import {
+  AsyncThunk,
+  combineReducers,
+  createAction,
+  createSlice,
+  isAnyOf,
+  isFulfilled,
+  isRejectedWithValue,
+  PayloadAction,
+} from '@reduxjs/toolkit'
+import {
+  CombinedState,
+  QuerySubstateIdentifier,
+  QuerySubState,
+  MutationSubstateIdentifier,
+  MutationSubState,
+  QueryStatus,
+  MutationState,
+  QueryState,
+  InvalidationState,
+  Subscribers,
+  QueryCacheKey,
+  SubscriptionState,
+  ConfigState,
+} from './apiState'
+import {
+  calculateProvidedByThunk,
+  MutationThunkArg,
+  QueryThunkArg,
+  ThunkResult,
+} from './buildThunks'
+import { AssertTagTypes, EndpointDefinitions } from '../endpointDefinitions'
+import { applyPatches, Patch } from 'immer'
+import { onFocus, onFocusLost, onOffline, onOnline } from './setupListeners'
+import {
+  isDocumentVisible,
+  isOnline,
+  copyWithStructuralSharing,
+} from '../utils'
+import { ApiContext } from '../apiTypes'
+
+function updateQuerySubstateIfExists(
+  state: QueryState<any>,
+  queryCacheKey: QueryCacheKey,
+  update: (substate: QuerySubState<any>) => void
+) {
+  const substate = state[queryCacheKey]
+  if (substate) {
+    update(substate)
+  }
+}
+
+function updateMutationSubstateIfExists(
+  state: MutationState<any>,
+  { requestId }: MutationSubstateIdentifier,
+  update: (substate: MutationSubState<any>) => void
+) {
+  const substate = state[requestId]
+  if (substate) {
+    update(substate)
+  }
+}
+
+const initialState = {} as any
+
+export function buildSlice({
+  reducerPath,
+  queryThunk,
+  mutationThunk,
+  context: { endpointDefinitions: definitions },
+  assertTagType,
+  config,
+}: {
+  reducerPath: string
+  queryThunk: AsyncThunk<ThunkResult, QueryThunkArg<any>, {}>
+  mutationThunk: AsyncThunk<ThunkResult, MutationThunkArg<any>, {}>
+  context: ApiContext<EndpointDefinitions>
+  assertTagType: AssertTagTypes
+  config: Omit<ConfigState<string>, 'online' | 'focused'>
+}) {
+  const resetApiState = createAction(`${reducerPath}/resetApiState`)
+  const querySlice = createSlice({
+    name: `${reducerPath}/queries`,
+    initialState: initialState as QueryState<any>,
+    reducers: {
+      removeQueryResult(
+        draft,
+        { payload: { queryCacheKey } }: PayloadAction<QuerySubstateIdentifier>
+      ) {
+        delete draft[queryCacheKey]
+      },
+      queryResultPatched(
+        draft,
+        {
+          payload: { queryCacheKey, patches },
+        }: PayloadAction<QuerySubstateIdentifier & { patches: Patch[] }>
+      ) {
+        updateQuerySubstateIfExists(draft, queryCacheKey, (substate) => {
+          substate.data = applyPatches(substate.data as any, patches)
+        })
+      },
+    },
+    extraReducers(builder) {
+      builder
+        .addCase(queryThunk.pending, (draft, { meta: { arg, requestId } }) => {
+          if (arg.subscribe) {
+            // only initialize substate if we want to subscribe to it
+            draft[arg.queryCacheKey] ??= {
+              status: QueryStatus.uninitialized,
+              endpointName: arg.endpointName,
+            }
+          }
+
+          updateQuerySubstateIfExists(draft, arg.queryCacheKey, (substate) => {
+            substate.status = QueryStatus.pending
+            substate.requestId = requestId
+            substate.originalArgs = arg.originalArgs
+            substate.startedTimeStamp = arg.startedTimeStamp
+          })
+        })
+        .addCase(queryThunk.fulfilled, (draft, { meta, payload }) => {
+          updateQuerySubstateIfExists(
+            draft,
+            meta.arg.queryCacheKey,
+            (substate) => {
+              if (substate.requestId !== meta.requestId) return
+              substate.status = QueryStatus.fulfilled
+              substate.data = copyWithStructuralSharing(
+                substate.data,
+                payload.result
+              )
+              delete substate.error
+              substate.fulfilledTimeStamp = payload.fulfilledTimeStamp
+            }
+          )
+        })
+        .addCase(
+          queryThunk.rejected,
+          (draft, { meta: { condition, arg, requestId }, error, payload }) => {
+            updateQuerySubstateIfExists(
+              draft,
+              arg.queryCacheKey,
+              (substate) => {
+                if (condition) {
+                  // request was aborted due to condition (another query already running)
+                } else {
+                  // request failed
+                  if (substate.requestId !== requestId) return
+                  substate.status = QueryStatus.rejected
+                  substate.error = (payload ?? error) as any
+                }
+              }
+            )
+          }
+        )
+    },
+  })
+  const mutationSlice = createSlice({
+    name: `${reducerPath}/mutations`,
+    initialState: initialState as MutationState<any>,
+    reducers: {
+      unsubscribeResult(
+        draft,
+        action: PayloadAction<MutationSubstateIdentifier>
+      ) {
+        if (action.payload.requestId in draft) {
+          delete draft[action.payload.requestId]
+        }
+      },
+    },
+    extraReducers(builder) {
+      builder
+        .addCase(
+          mutationThunk.pending,
+          (draft, { meta: { arg, requestId } }) => {
+            if (!arg.track) return
+
+            draft[requestId] = {
+              status: QueryStatus.pending,
+              originalArgs: arg.originalArgs,
+              endpointName: arg.endpointName,
+              startedTimeStamp: arg.startedTimeStamp,
+            }
+          }
+        )
+        .addCase(
+          mutationThunk.fulfilled,
+          (draft, { payload, meta: { requestId, arg } }) => {
+            if (!arg.track) return
+
+            updateMutationSubstateIfExists(draft, { requestId }, (substate) => {
+              substate.status = QueryStatus.fulfilled
+              substate.data = payload.result
+              substate.fulfilledTimeStamp = payload.fulfilledTimeStamp
+            })
+          }
+        )
+        .addCase(
+          mutationThunk.rejected,
+          (draft, { payload, error, meta: { requestId, arg } }) => {
+            if (!arg.track) return
+
+            updateMutationSubstateIfExists(draft, { requestId }, (substate) => {
+              substate.status = QueryStatus.rejected
+              substate.error = (payload ?? error) as any
+            })
+          }
+        )
+    },
+  })
+
+  const invalidationSlice = createSlice({
+    name: `${reducerPath}/invalidation`,
+    initialState: initialState as InvalidationState<string>,
+    reducers: {},
+    extraReducers(builder) {
+      builder
+        .addCase(
+          querySlice.actions.removeQueryResult,
+          (draft, { payload: { queryCacheKey } }) => {
+            for (const tagTypeSubscriptions of Object.values(draft)) {
+              for (const idSubscriptions of Object.values(
+                tagTypeSubscriptions
+              )) {
+                const foundAt = idSubscriptions.indexOf(queryCacheKey)
+                if (foundAt !== -1) {
+                  idSubscriptions.splice(foundAt, 1)
+                }
+              }
+            }
+          }
+        )
+        .addMatcher(
+          isAnyOf(isFulfilled(queryThunk), isRejectedWithValue(queryThunk)),
+          (draft, action) => {
+            const providedTags = calculateProvidedByThunk(
+              action,
+              'providesTags',
+              definitions,
+              assertTagType
+            )
+            const { queryCacheKey } = action.meta.arg
+
+            for (const { type, id } of providedTags) {
+              const subscribedQueries = ((draft[type] ??= {})[
+                id || '__internal_without_id'
+              ] ??= [])
+              const alreadySubscribed = subscribedQueries.includes(
+                queryCacheKey
+              )
+              if (!alreadySubscribed) {
+                subscribedQueries.push(queryCacheKey)
+              }
+            }
+          }
+        )
+    },
+  })
+
+  const subscriptionSlice = createSlice({
+    name: `${reducerPath}/subscriptions`,
+    initialState: initialState as SubscriptionState,
+    reducers: {
+      updateSubscriptionOptions(
+        draft,
+        {
+          payload: { queryCacheKey, requestId, options },
+        }: PayloadAction<
+          {
+            endpointName: string
+            requestId: string
+            options: Subscribers[number]
+          } & QuerySubstateIdentifier
+        >
+      ) {
+        if (draft?.[queryCacheKey]?.[requestId]) {
+          draft[queryCacheKey]![requestId] = options
+        }
+      },
+      unsubscribeResult(
+        draft,
+        {
+          payload: { queryCacheKey, requestId },
+        }: PayloadAction<{ requestId: string } & QuerySubstateIdentifier>
+      ) {
+        if (draft[queryCacheKey]) {
+          delete draft[queryCacheKey]![requestId]
+        }
+      },
+    },
+    extraReducers: (builder) => {
+      builder
+        .addCase(
+          querySlice.actions.removeQueryResult,
+          (draft, { payload: { queryCacheKey } }) => {
+            delete draft[queryCacheKey]
+          }
+        )
+        .addCase(queryThunk.pending, (draft, { meta: { arg, requestId } }) => {
+          if (arg.subscribe) {
+            const substate = (draft[arg.queryCacheKey] ??= {})
+            substate[requestId] =
+              arg.subscriptionOptions ?? substate[requestId] ?? {}
+          }
+        })
+        .addCase(
+          queryThunk.rejected,
+          (draft, { meta: { condition, arg, requestId }, error, payload }) => {
+            const substate = draft[arg.queryCacheKey]
+            // request was aborted due to condition (another query already running)
+            if (condition && arg.subscribe && substate) {
+              substate[requestId] =
+                arg.subscriptionOptions ?? substate[requestId] ?? {}
+            }
+          }
+        )
+    },
+  })
+
+  const configSlice = createSlice({
+    name: `${reducerPath}/config`,
+    initialState: {
+      online: isOnline(),
+      focused: isDocumentVisible(),
+      ...config,
+    } as ConfigState<string>,
+    reducers: {},
+    extraReducers: (builder) => {
+      builder
+        .addCase(onOnline, (state) => {
+          state.online = true
+        })
+        .addCase(onOffline, (state) => {
+          state.online = false
+        })
+        .addCase(onFocus, (state) => {
+          state.focused = true
+        })
+        .addCase(onFocusLost, (state) => {
+          state.focused = false
+        })
+    },
+  })
+
+  const combinedReducer = combineReducers<CombinedState<any, string, string>>({
+    queries: querySlice.reducer,
+    mutations: mutationSlice.reducer,
+    provided: invalidationSlice.reducer,
+    subscriptions: subscriptionSlice.reducer,
+    config: configSlice.reducer,
+  })
+
+  const reducer: typeof combinedReducer = (state, action) =>
+    combinedReducer(resetApiState.match(action) ? undefined : state, action)
+
+  const actions = {
+    updateSubscriptionOptions:
+      subscriptionSlice.actions.updateSubscriptionOptions,
+    queryResultPatched: querySlice.actions.queryResultPatched,
+    removeQueryResult: querySlice.actions.removeQueryResult,
+    unsubscribeQueryResult: subscriptionSlice.actions.unsubscribeResult,
+    unsubscribeMutationResult: mutationSlice.actions.unsubscribeResult,
+    resetApiState,
+  }
+
+  return { reducer, actions }
+}
+export type SliceActions = ReturnType<typeof buildSlice>['actions']
diff --git a/query/src/core/buildThunks.ts b/src/query/core/buildThunks.ts
similarity index 55%
rename from query/src/core/buildThunks.ts
rename to src/query/core/buildThunks.ts
index a2f1d8f4..f01ace70 100644
--- a/query/src/core/buildThunks.ts
+++ b/src/query/core/buildThunks.ts
@@ -1,8 +1,18 @@
-import { InternalSerializeQueryArgs } from '../defaultSerializeQueryArgs';
-import { Api, ApiContext } from '../apiTypes';
-import { BaseQueryFn, BaseQueryArg, BaseQueryError, QueryReturnValue } from '../baseQueryTypes';
-import { RootState, QueryKeys, QueryStatus, QuerySubstateIdentifier } from './apiState';
-import { StartQueryActionCreatorOptions } from './buildInitiate';
+import { InternalSerializeQueryArgs } from '../defaultSerializeQueryArgs'
+import { Api, ApiContext } from '../apiTypes'
+import {
+  BaseQueryFn,
+  BaseQueryArg,
+  BaseQueryError,
+  QueryReturnValue,
+} from '../baseQueryTypes'
+import {
+  RootState,
+  QueryKeys,
+  QueryStatus,
+  QuerySubstateIdentifier,
+} from './apiState'
+import { StartQueryActionCreatorOptions } from './buildInitiate'
 import {
   AssertTagTypes,
   calculateProvidedBy,
@@ -14,7 +24,7 @@ import {
   QueryArgFrom,
   QueryDefinition,
   ResultTypeFrom,
-} from '../endpointDefinitions';
+} from '../endpointDefinitions'
 import {
   AsyncThunkPayloadCreator,
   Draft,
@@ -23,14 +33,20 @@ import {
   isPending,
   isRejected,
   isRejectedWithValue,
-} from '@reduxjs/toolkit';
-import { Patch, isDraftable, produceWithPatches, enablePatches } from 'immer';
-import { AnyAction, createAsyncThunk, ThunkAction, ThunkDispatch, AsyncThunk } from '@reduxjs/toolkit';
+} from '@reduxjs/toolkit'
+import { Patch, isDraftable, produceWithPatches, enablePatches } from 'immer'
+import {
+  AnyAction,
+  createAsyncThunk,
+  ThunkAction,
+  ThunkDispatch,
+  AsyncThunk,
+} from '@reduxjs/toolkit'
 
-import { HandledError } from '../HandledError';
+import { HandledError } from '../HandledError'
 
-import { ApiEndpointQuery, PrefetchOptions } from './module';
-import { UnwrapPromise } from '../tsHelpers';
+import { ApiEndpointQuery, PrefetchOptions } from './module'
+import { UnwrapPromise } from '../tsHelpers'
 
 declare module './module' {
   export interface ApiEndpointQuery<
@@ -49,7 +65,12 @@ declare module './module' {
 type EndpointThunk<
   Thunk extends AsyncThunk<any, any, any>,
   Definition extends EndpointDefinition<any, any, any, any>
-> = Definition extends EndpointDefinition<infer QueryArg, infer BaseQueryFn, any, infer ResultType>
+> = Definition extends EndpointDefinition<
+  infer QueryArg,
+  infer BaseQueryFn,
+  any,
+  infer ResultType
+>
   ? Thunk extends AsyncThunk<infer ATResult, infer ATArg, infer ATConfig>
     ? AsyncThunk<
         ATResult & { result: ResultType },
@@ -57,79 +78,83 @@ type EndpointThunk<
         ATConfig & { rejectValue: BaseQueryError<BaseQueryFn> }
       >
     : never
-  : never;
+  : never
 
 export type PendingAction<
   Thunk extends AsyncThunk<any, any, any>,
   Definition extends EndpointDefinition<any, any, any, any>
-> = ReturnType<EndpointThunk<Thunk, Definition>['pending']>;
+> = ReturnType<EndpointThunk<Thunk, Definition>['pending']>
 
 export type FulfilledAction<
   Thunk extends AsyncThunk<any, any, any>,
   Definition extends EndpointDefinition<any, any, any, any>
-> = ReturnType<EndpointThunk<Thunk, Definition>['fulfilled']>;
+> = ReturnType<EndpointThunk<Thunk, Definition>['fulfilled']>
 
 export type RejectedAction<
   Thunk extends AsyncThunk<any, any, any>,
   Definition extends EndpointDefinition<any, any, any, any>
-> = ReturnType<EndpointThunk<Thunk, Definition>['rejected']>;
+> = ReturnType<EndpointThunk<Thunk, Definition>['rejected']>
 
-export type Matcher<M> = (value: any) => value is M;
+export type Matcher<M> = (value: any) => value is M
 
 export interface Matchers<
   Thunk extends AsyncThunk<any, any, any>,
   Definition extends EndpointDefinition<any, any, any, any>
 > {
-  matchPending: Matcher<PendingAction<Thunk, Definition>>;
-  matchFulfilled: Matcher<FulfilledAction<Thunk, Definition>>;
-  matchRejected: Matcher<RejectedAction<Thunk, Definition>>;
+  matchPending: Matcher<PendingAction<Thunk, Definition>>
+  matchFulfilled: Matcher<FulfilledAction<Thunk, Definition>>
+  matchRejected: Matcher<RejectedAction<Thunk, Definition>>
 }
 
-export interface QueryThunkArg<_InternalQueryArgs> extends QuerySubstateIdentifier, StartQueryActionCreatorOptions {
-  originalArgs: unknown;
-  endpointName: string;
-  startedTimeStamp: number;
+export interface QueryThunkArg<_InternalQueryArgs>
+  extends QuerySubstateIdentifier,
+    StartQueryActionCreatorOptions {
+  originalArgs: unknown
+  endpointName: string
+  startedTimeStamp: number
 }
 
 export interface MutationThunkArg<_InternalQueryArgs> {
-  originalArgs: unknown;
-  endpointName: string;
-  track?: boolean;
-  startedTimeStamp: number;
+  originalArgs: unknown
+  endpointName: string
+  track?: boolean
+  startedTimeStamp: number
 }
 
 export interface ThunkResult {
-  fulfilledTimeStamp: number;
-  result: unknown;
+  fulfilledTimeStamp: number
+  result: unknown
 }
 
-export type QueryThunk = AsyncThunk<ThunkResult, QueryThunkArg<any>, {}>;
-export type MutationThunk = AsyncThunk<ThunkResult, MutationThunkArg<any>, {}>;
+export type QueryThunk = AsyncThunk<ThunkResult, QueryThunkArg<any>, {}>
+export type MutationThunk = AsyncThunk<ThunkResult, MutationThunkArg<any>, {}>
 
 function defaultTransformResponse(baseQueryReturnValue: unknown) {
-  return baseQueryReturnValue;
+  return baseQueryReturnValue
 }
 
-type MaybeDrafted<T> = T | Draft<T>;
-type Recipe<T> = (data: MaybeDrafted<T>) => void | MaybeDrafted<T>;
+type MaybeDrafted<T> = T | Draft<T>
+type Recipe<T> = (data: MaybeDrafted<T>) => void | MaybeDrafted<T>
 
-export type PatchQueryResultThunk<Definitions extends EndpointDefinitions, PartialState> = <
-  EndpointName extends QueryKeys<Definitions>
->(
+export type PatchQueryResultThunk<
+  Definitions extends EndpointDefinitions,
+  PartialState
+> = <EndpointName extends QueryKeys<Definitions>>(
   endpointName: EndpointName,
   args: QueryArgFrom<Definitions[EndpointName]>,
   patches: Patch[]
-) => ThunkAction<void, PartialState, any, AnyAction>;
+) => ThunkAction<void, PartialState, any, AnyAction>
 
-export type UpdateQueryResultThunk<Definitions extends EndpointDefinitions, PartialState> = <
-  EndpointName extends QueryKeys<Definitions>
->(
+export type UpdateQueryResultThunk<
+  Definitions extends EndpointDefinitions,
+  PartialState
+> = <EndpointName extends QueryKeys<Definitions>>(
   endpointName: EndpointName,
   args: QueryArgFrom<Definitions[EndpointName]>,
   updateRecipe: Recipe<ResultTypeFrom<Definitions[EndpointName]>>
-) => ThunkAction<PatchCollection, PartialState, any, AnyAction>;
+) => ThunkAction<PatchCollection, PartialState, any, AnyAction>
 
-type PatchCollection = { patches: Patch[]; inversePatches: Patch[] };
+type PatchCollection = { patches: Patch[]; inversePatches: Patch[] }
 
 export function buildThunks<
   BaseQuery extends BaseQueryFn,
@@ -142,19 +167,21 @@ export function buildThunks<
   serializeQueryArgs,
   api,
 }: {
-  baseQuery: BaseQuery;
-  reducerPath: ReducerPath;
-  context: ApiContext<Definitions>;
-  serializeQueryArgs: InternalSerializeQueryArgs<BaseQueryArg<BaseQuery>>;
-  api: Api<BaseQuery, EndpointDefinitions, ReducerPath, any>;
+  baseQuery: BaseQuery
+  reducerPath: ReducerPath
+  context: ApiContext<Definitions>
+  serializeQueryArgs: InternalSerializeQueryArgs<BaseQueryArg<BaseQuery>>
+  api: Api<BaseQuery, EndpointDefinitions, ReducerPath, any>
 }) {
-  type InternalQueryArgs = BaseQueryArg<BaseQuery>;
-  type State = RootState<any, string, ReducerPath>;
-
-  const patchQueryResult: PatchQueryResultThunk<EndpointDefinitions, State> = (endpointName, args, patches) => (
-    dispatch
-  ) => {
-    const endpointDefinition = endpointDefinitions[endpointName];
+  type InternalQueryArgs = BaseQueryArg<BaseQuery>
+  type State = RootState<any, string, ReducerPath>
+
+  const patchQueryResult: PatchQueryResultThunk<EndpointDefinitions, State> = (
+    endpointName,
+    args,
+    patches
+  ) => (dispatch) => {
+    const endpointDefinition = endpointDefinitions[endpointName]
     dispatch(
       api.internalActions.queryResultPatched({
         queryCacheKey: serializeQueryArgs({
@@ -164,81 +191,107 @@ export function buildThunks<
         }),
         patches,
       })
-    );
-  };
+    )
+  }
 
-  const updateQueryResult: UpdateQueryResultThunk<EndpointDefinitions, State> = (endpointName, args, updateRecipe) => (
-    dispatch,
-    getState
-  ) => {
-    const currentState = (api.endpoints[endpointName] as ApiEndpointQuery<any, any>).select(args)(getState());
-    let ret: PatchCollection = { patches: [], inversePatches: [] };
+  const updateQueryResult: UpdateQueryResultThunk<
+    EndpointDefinitions,
+    State
+  > = (endpointName, args, updateRecipe) => (dispatch, getState) => {
+    const currentState = (api.endpoints[endpointName] as ApiEndpointQuery<
+      any,
+      any
+    >).select(args)(getState())
+    let ret: PatchCollection = { patches: [], inversePatches: [] }
     if (currentState.status === QueryStatus.uninitialized) {
-      return ret;
+      return ret
     }
     if ('data' in currentState) {
       if (isDraftable(currentState.data)) {
         // call "enablePatches" as late as possible
-        enablePatches();
-        const [, patches, inversePatches] = produceWithPatches(currentState.data, updateRecipe);
-        ret.patches.push(...patches);
-        ret.inversePatches.push(...inversePatches);
+        enablePatches()
+        const [, patches, inversePatches] = produceWithPatches(
+          currentState.data,
+          updateRecipe
+        )
+        ret.patches.push(...patches)
+        ret.inversePatches.push(...inversePatches)
       } else {
-        const value = updateRecipe(currentState.data);
-        ret.patches.push({ op: 'replace', path: [], value });
-        ret.inversePatches.push({ op: 'replace', path: [], value: currentState.data });
+        const value = updateRecipe(currentState.data)
+        ret.patches.push({ op: 'replace', path: [], value })
+        ret.inversePatches.push({
+          op: 'replace',
+          path: [],
+          value: currentState.data,
+        })
       }
     }
 
-    dispatch(patchQueryResult(endpointName, args, ret.patches));
+    dispatch(patchQueryResult(endpointName, args, ret.patches))
 
-    return ret;
-  };
+    return ret
+  }
 
   const executeEndpoint: AsyncThunkPayloadCreator<
     ThunkResult,
     QueryThunkArg<InternalQueryArgs> | MutationThunkArg<InternalQueryArgs>,
     { state: RootState<any, string, ReducerPath> }
   > = async (arg, { signal, rejectWithValue, ...api }) => {
-    const endpointDefinition = endpointDefinitions[arg.endpointName];
+    const endpointDefinition = endpointDefinitions[arg.endpointName]
 
-    const context: Record<string, any> = {};
-    const queryApi: QueryApi<ReducerPath, any> | MutationApi<ReducerPath, any> = {
+    const context: Record<string, any> = {}
+    const queryApi:
+      | QueryApi<ReducerPath, any>
+      | MutationApi<ReducerPath, any> = {
       ...api,
       context,
-    };
+    }
 
-    if (endpointDefinition.onStart) endpointDefinition.onStart(arg.originalArgs, queryApi);
+    if (endpointDefinition.onStart)
+      endpointDefinition.onStart(arg.originalArgs, queryApi)
 
     try {
-      let transformResponse: (baseQueryReturnValue: any, meta: any) => any = defaultTransformResponse;
-      let result: QueryReturnValue;
-      const baseQueryApi = { signal, dispatch: api.dispatch, getState: api.getState };
+      let transformResponse: (
+        baseQueryReturnValue: any,
+        meta: any
+      ) => any = defaultTransformResponse
+      let result: QueryReturnValue
+      const baseQueryApi = {
+        signal,
+        dispatch: api.dispatch,
+        getState: api.getState,
+      }
       if (endpointDefinition.query) {
         result = await baseQuery(
           endpointDefinition.query(arg.originalArgs),
           baseQueryApi,
           endpointDefinition.extraOptions as any
-        );
+        )
 
         if (endpointDefinition.transformResponse) {
-          transformResponse = endpointDefinition.transformResponse;
+          transformResponse = endpointDefinition.transformResponse
         }
       } else {
         result = await endpointDefinition.queryFn(
           arg.originalArgs,
           baseQueryApi,
           endpointDefinition.extraOptions as any,
-          (arg) => baseQuery(arg, baseQueryApi, endpointDefinition.extraOptions as any)
-        );
+          (arg) =>
+            baseQuery(arg, baseQueryApi, endpointDefinition.extraOptions as any)
+        )
       }
-      if (result.error) throw new HandledError(result.error, result.meta);
+      if (result.error) throw new HandledError(result.error, result.meta)
       if (endpointDefinition.onSuccess)
-        endpointDefinition.onSuccess(arg.originalArgs, queryApi, result.data, result.meta);
+        endpointDefinition.onSuccess(
+          arg.originalArgs,
+          queryApi,
+          result.data,
+          result.meta
+        )
       return {
         fulfilledTimeStamp: Date.now(),
         result: await transformResponse(result.data, result.meta),
-      };
+      }
     } catch (error) {
       if (endpointDefinition.onError)
         endpointDefinition.onError(
@@ -246,13 +299,13 @@ export function buildThunks<
           queryApi,
           error instanceof HandledError ? error.value : error,
           error instanceof HandledError ? error.meta : undefined
-        );
+        )
       if (error instanceof HandledError) {
-        return rejectWithValue(error.value);
+        return rejectWithValue(error.value)
       }
-      throw error;
+      throw error
     }
-  };
+  }
 
   const queryThunk = createAsyncThunk<
     ThunkResult,
@@ -260,89 +313,122 @@ export function buildThunks<
     { state: RootState<any, string, ReducerPath> }
   >(`${reducerPath}/executeQuery`, executeEndpoint, {
     condition(arg, { getState }) {
-      const state = getState()[reducerPath];
-      const requestState = state?.queries?.[arg.queryCacheKey];
-      const baseFetchOnMountOrArgChange = state.config.refetchOnMountOrArgChange;
+      const state = getState()[reducerPath]
+      const requestState = state?.queries?.[arg.queryCacheKey]
+      const baseFetchOnMountOrArgChange = state.config.refetchOnMountOrArgChange
 
-      const fulfilledVal = requestState?.fulfilledTimeStamp;
-      const refetchVal = arg.forceRefetch ?? (arg.subscribe && baseFetchOnMountOrArgChange);
+      const fulfilledVal = requestState?.fulfilledTimeStamp
+      const refetchVal =
+        arg.forceRefetch ?? (arg.subscribe && baseFetchOnMountOrArgChange)
 
       // Don't retry a request that's currently in-flight
-      if (requestState?.status === 'pending') return false;
+      if (requestState?.status === 'pending') return false
 
       // Pull from the cache unless we explicitly force refetch or qualify based on time
       if (fulfilledVal) {
         if (refetchVal) {
           // Return if its true or compare the dates because it must be a number
-          return refetchVal === true || (Number(new Date()) - Number(fulfilledVal)) / 1000 >= refetchVal;
+          return (
+            refetchVal === true ||
+            (Number(new Date()) - Number(fulfilledVal)) / 1000 >= refetchVal
+          )
         }
         // Value is cached and we didn't specify to refresh, skip it.
-        return false;
+        return false
       }
 
-      return true;
+      return true
     },
     dispatchConditionRejection: true,
-  });
+  })
 
   const mutationThunk = createAsyncThunk<
     ThunkResult,
     MutationThunkArg<InternalQueryArgs>,
     { state: RootState<any, string, ReducerPath> }
-  >(`${reducerPath}/executeMutation`, executeEndpoint);
+  >(`${reducerPath}/executeMutation`, executeEndpoint)
 
-  const hasTheForce = (options: any): options is { force: boolean } => 'force' in options;
-  const hasMaxAge = (options: any): options is { ifOlderThan: false | number } => 'ifOlderThan' in options;
+  const hasTheForce = (options: any): options is { force: boolean } =>
+    'force' in options
+  const hasMaxAge = (
+    options: any
+  ): options is { ifOlderThan: false | number } => 'ifOlderThan' in options
 
   const prefetch = <EndpointName extends QueryKeys<EndpointDefinitions>>(
     endpointName: EndpointName,
     arg: any,
     options: PrefetchOptions
-  ): ThunkAction<void, any, any, AnyAction> => (dispatch: ThunkDispatch<any, any, any>, getState: () => any) => {
-    const force = hasTheForce(options) && options.force;
-    const maxAge = hasMaxAge(options) && options.ifOlderThan;
+  ): ThunkAction<void, any, any, AnyAction> => (
+    dispatch: ThunkDispatch<any, any, any>,
+    getState: () => any
+  ) => {
+    const force = hasTheForce(options) && options.force
+    const maxAge = hasMaxAge(options) && options.ifOlderThan
 
     const queryAction = (force: boolean = true) =>
-      (api.endpoints[endpointName] as ApiEndpointQuery<any, any>).initiate(arg, { forceRefetch: force });
-    const latestStateValue = (api.endpoints[endpointName] as ApiEndpointQuery<any, any>).select(arg)(getState());
+      (api.endpoints[endpointName] as ApiEndpointQuery<any, any>).initiate(
+        arg,
+        { forceRefetch: force }
+      )
+    const latestStateValue = (api.endpoints[endpointName] as ApiEndpointQuery<
+      any,
+      any
+    >).select(arg)(getState())
 
     if (force) {
-      dispatch(queryAction());
+      dispatch(queryAction())
     } else if (maxAge) {
-      const lastFulfilledTs = latestStateValue?.fulfilledTimeStamp;
+      const lastFulfilledTs = latestStateValue?.fulfilledTimeStamp
       if (!lastFulfilledTs) {
-        dispatch(queryAction());
-        return;
+        dispatch(queryAction())
+        return
       }
-      const shouldRetrigger = (Number(new Date()) - Number(new Date(lastFulfilledTs))) / 1000 >= maxAge;
+      const shouldRetrigger =
+        (Number(new Date()) - Number(new Date(lastFulfilledTs))) / 1000 >=
+        maxAge
       if (shouldRetrigger) {
-        dispatch(queryAction());
+        dispatch(queryAction())
       }
     } else {
       // If prefetching with no options, just let it try
-      dispatch(queryAction(false));
+      dispatch(queryAction(false))
     }
-  };
+  }
 
   function matchesEndpoint(endpointName: string) {
-    return (action: any): action is AnyAction => action?.meta?.arg?.endpointName === endpointName;
+    return (action: any): action is AnyAction =>
+      action?.meta?.arg?.endpointName === endpointName
   }
 
   function buildMatchThunkActions<
-    Thunk extends AsyncThunk<any, QueryThunkArg<any>, any> | AsyncThunk<any, MutationThunkArg<any>, any>
+    Thunk extends
+      | AsyncThunk<any, QueryThunkArg<any>, any>
+      | AsyncThunk<any, MutationThunkArg<any>, any>
   >(thunk: Thunk, endpointName: string) {
     return {
       matchPending: isAllOf(isPending(thunk), matchesEndpoint(endpointName)),
-      matchFulfilled: isAllOf(isFulfilled(thunk), matchesEndpoint(endpointName)),
+      matchFulfilled: isAllOf(
+        isFulfilled(thunk),
+        matchesEndpoint(endpointName)
+      ),
       matchRejected: isAllOf(isRejected(thunk), matchesEndpoint(endpointName)),
-    } as Matchers<Thunk, any>;
+    } as Matchers<Thunk, any>
   }
 
-  return { queryThunk, mutationThunk, prefetch, updateQueryResult, patchQueryResult, buildMatchThunkActions };
+  return {
+    queryThunk,
+    mutationThunk,
+    prefetch,
+    updateQueryResult,
+    patchQueryResult,
+    buildMatchThunkActions,
+  }
 }
 
 export function calculateProvidedByThunk(
-  action: UnwrapPromise<ReturnType<ReturnType<QueryThunk>> | ReturnType<ReturnType<MutationThunk>>>,
+  action: UnwrapPromise<
+    ReturnType<ReturnType<QueryThunk>> | ReturnType<ReturnType<MutationThunk>>
+  >,
   type: 'providesTags' | 'invalidatesTags',
   endpointDefinitions: EndpointDefinitions,
   assertTagType: AssertTagTypes
@@ -353,5 +439,5 @@ export function calculateProvidedByThunk(
     isRejectedWithValue(action) ? action.payload : undefined,
     action.meta.arg.originalArgs,
     assertTagType
-  );
+  )
 }
diff --git a/src/query/core/index.ts b/src/query/core/index.ts
new file mode 100644
index 00000000..a396c4b3
--- /dev/null
+++ b/src/query/core/index.ts
@@ -0,0 +1,6 @@
+import { buildCreateApi } from '../createApi'
+import { coreModule } from './module'
+
+const createApi = buildCreateApi(coreModule())
+
+export { createApi, coreModule }
diff --git a/query/src/core/module.ts b/src/query/core/module.ts
similarity index 70%
rename from query/src/core/module.ts
rename to src/query/core/module.ts
index 31fd1681..3dcd8631 100644
--- a/query/src/core/module.ts
+++ b/src/query/core/module.ts
@@ -1,8 +1,19 @@
 /**
  * Note: this file should import all other files for type discovery and declaration merging
  */
-import { buildThunks, PatchQueryResultThunk, UpdateQueryResultThunk } from './buildThunks';
-import { ActionCreatorWithPayload, AnyAction, Middleware, Reducer, ThunkAction, ThunkDispatch } from '@reduxjs/toolkit';
+import {
+  buildThunks,
+  PatchQueryResultThunk,
+  UpdateQueryResultThunk,
+} from './buildThunks'
+import {
+  ActionCreatorWithPayload,
+  AnyAction,
+  Middleware,
+  Reducer,
+  ThunkAction,
+  ThunkDispatch,
+} from '@reduxjs/toolkit'
 import {
   EndpointDefinitions,
   QueryArgFrom,
@@ -12,19 +23,19 @@ import {
   isQueryDefinition,
   isMutationDefinition,
   FullTagDescription,
-} from '../endpointDefinitions';
-import { CombinedState, QueryKeys, RootState } from './apiState';
-import './buildSelectors';
-import { Api, Module } from '../apiTypes';
-import { onFocus, onFocusLost, onOnline, onOffline } from './setupListeners';
-import { buildSlice } from './buildSlice';
-import { buildMiddleware } from './buildMiddleware';
-import { buildSelectors } from './buildSelectors';
-import { buildInitiate } from './buildInitiate';
-import { assertCast, Id, safeAssign } from '../tsHelpers';
-import { InternalSerializeQueryArgs } from '../defaultSerializeQueryArgs';
-import { SliceActions } from './buildSlice';
-import { BaseQueryFn } from '../baseQueryTypes';
+} from '../endpointDefinitions'
+import { CombinedState, QueryKeys, RootState } from './apiState'
+import './buildSelectors'
+import { Api, Module } from '../apiTypes'
+import { onFocus, onFocusLost, onOnline, onOffline } from './setupListeners'
+import { buildSlice } from './buildSlice'
+import { buildMiddleware } from './buildMiddleware'
+import { buildSelectors } from './buildSelectors'
+import { buildInitiate } from './buildInitiate'
+import { assertCast, Id, safeAssign } from '../tsHelpers'
+import { InternalSerializeQueryArgs } from '../defaultSerializeQueryArgs'
+import { SliceActions } from './buildSlice'
+import { BaseQueryFn } from '../baseQueryTypes'
 
 /**
  * `ifOlderThan` - (default: `false` | `number`) - _number is value in seconds_
@@ -36,12 +47,12 @@ import { BaseQueryFn } from '../baseQueryTypes';
  */
 export type PrefetchOptions =
   | {
-      ifOlderThan?: false | number;
+      ifOlderThan?: false | number
     }
-  | { force?: boolean };
+  | { force?: boolean }
 
-export const coreModuleName = Symbol();
-export type CoreModule = typeof coreModuleName;
+export const coreModuleName = Symbol()
+export type CoreModule = typeof coreModuleName
 
 declare module '../apiTypes' {
   export interface ApiModules<
@@ -65,11 +76,11 @@ declare module '../apiTypes' {
        * })
        * ```
        */
-      reducerPath: ReducerPath;
+      reducerPath: ReducerPath
       /**
        * Internal actions not part of the public API. Note: These are subject to change at any given time.
        */
-      internalActions: InternalActions;
+      internalActions: InternalActions
       /**
        *  A standard redux reducer that enables core functionality. Make sure it's included in your store.
        *
@@ -83,7 +94,10 @@ declare module '../apiTypes' {
        * })
        * ```
        */
-      reducer: Reducer<CombinedState<Definitions, TagTypes, ReducerPath>, AnyAction>;
+      reducer: Reducer<
+        CombinedState<Definitions, TagTypes, ReducerPath>,
+        AnyAction
+      >
       /**
        * This is a standard redux middleware and is responsible for things like polling, garbage collection and a handful of other things. Make sure it's included in your store.
        *
@@ -97,7 +111,11 @@ declare module '../apiTypes' {
        * })
        * ```
        */
-      middleware: Middleware<{}, RootState<Definitions, string, ReducerPath>, ThunkDispatch<any, any, AnyAction>>;
+      middleware: Middleware<
+        {},
+        RootState<Definitions, string, ReducerPath>,
+        ThunkDispatch<any, any, AnyAction>
+      >
       /**
        * TODO
        */
@@ -109,43 +127,61 @@ declare module '../apiTypes' {
           endpointName: EndpointName,
           arg: QueryArgFrom<Definitions[EndpointName]>,
           options: PrefetchOptions
-        ): ThunkAction<void, any, any, AnyAction>;
+        ): ThunkAction<void, any, any, AnyAction>
         /* @deprecated */
         prefetchThunk<EndpointName extends QueryKeys<EndpointDefinitions>>(
           endpointName: EndpointName,
           arg: QueryArgFrom<Definitions[EndpointName]>,
           options: PrefetchOptions
-        ): ThunkAction<void, any, any, AnyAction>;
+        ): ThunkAction<void, any, any, AnyAction>
         /**
          * TODO
          */
-        updateQueryResult: UpdateQueryResultThunk<Definitions, RootState<Definitions, string, ReducerPath>>;
+        updateQueryResult: UpdateQueryResultThunk<
+          Definitions,
+          RootState<Definitions, string, ReducerPath>
+        >
         /**
          * TODO
          */
-        patchQueryResult: PatchQueryResultThunk<Definitions, RootState<Definitions, string, ReducerPath>>;
+        patchQueryResult: PatchQueryResultThunk<
+          Definitions,
+          RootState<Definitions, string, ReducerPath>
+        >
         /**
          * TODO
          */
-        resetApiState: SliceActions['resetApiState'];
+        resetApiState: SliceActions['resetApiState']
         /**
          * TODO
          */
-        invalidateTags: ActionCreatorWithPayload<Array<TagTypes | FullTagDescription<TagTypes>>, string>;
+        invalidateTags: ActionCreatorWithPayload<
+          Array<TagTypes | FullTagDescription<TagTypes>>,
+          string
+        >
         /** @deprecated renamed to `invalidateTags` */
-        invalidateEntities: ActionCreatorWithPayload<Array<TagTypes | FullTagDescription<TagTypes>>, string>;
-      };
+        invalidateEntities: ActionCreatorWithPayload<
+          Array<TagTypes | FullTagDescription<TagTypes>>,
+          string
+        >
+      }
       /**
        * Endpoints based on the input endpoints provided to `createApi`, containing `select` and `action matchers`.
        */
       endpoints: {
-        [K in keyof Definitions]: Definitions[K] extends QueryDefinition<any, any, any, any, any>
+        [K in keyof Definitions]: Definitions[K] extends QueryDefinition<
+          any,
+          any,
+          any,
+          any,
+          any
+        >
           ? Id<ApiEndpointQuery<Definitions[K], Definitions>>
           : Definitions[K] extends MutationDefinition<any, any, any, any, any>
           ? Id<ApiEndpointMutation<Definitions[K], Definitions>>
-          : never;
-      };
-    };
+          : never
+      }
+    }
   }
 }
 
@@ -169,17 +205,17 @@ export type ListenerActions = {
    * Will cause the RTK Query middleware to trigger any refetchOnReconnect-related behavior
    * @link https://rtk-query-docs.netlify.app/api/setupListeners
    */
-  onOnline: typeof onOnline;
-  onOffline: typeof onOffline;
+  onOnline: typeof onOnline
+  onOffline: typeof onOffline
   /**
    * Will cause the RTK Query middleware to trigger any refetchOnFocus-related behavior
    * @link https://rtk-query-docs.netlify.app/api/setupListeners
    */
-  onFocus: typeof onFocus;
-  onFocusLost: typeof onFocusLost;
-};
+  onFocus: typeof onFocus
+  onFocusLost: typeof onFocusLost
+}
 
-export type InternalActions = SliceActions & ListenerActions;
+export type InternalActions = SliceActions & ListenerActions
 
 /**
  * Creates a module containing the basic redux logic for use with `buildCreateApi`.
@@ -205,16 +241,21 @@ export const coreModule = (): Module<CoreModule> => ({
     },
     context
   ) {
-    assertCast<InternalSerializeQueryArgs<any>>(serializeQueryArgs);
+    assertCast<InternalSerializeQueryArgs<any>>(serializeQueryArgs)
 
     const assertTagType: AssertTagTypes = (tag) => {
-      if (typeof process !== 'undefined' && process.env.NODE_ENV === 'development') {
+      if (
+        typeof process !== 'undefined' &&
+        process.env.NODE_ENV === 'development'
+      ) {
         if (!tagTypes.includes(tag.type as any)) {
-          console.error(`Tag type '${tag.type}' was used, but not specified in \`tagTypes\`!`);
+          console.error(
+            `Tag type '${tag.type}' was used, but not specified in \`tagTypes\`!`
+          )
         }
       }
-      return tag;
-    };
+      return tag
+    }
 
     Object.assign(api, {
       reducerPath,
@@ -226,7 +267,7 @@ export const coreModule = (): Module<CoreModule> => ({
         onFocusLost,
       },
       util: {},
-    });
+    })
 
     const {
       queryThunk,
@@ -241,7 +282,7 @@ export const coreModule = (): Module<CoreModule> => ({
       context,
       api,
       serializeQueryArgs,
-    });
+    })
 
     const { reducer, actions: sliceActions } = buildSlice({
       context,
@@ -249,16 +290,22 @@ export const coreModule = (): Module<CoreModule> => ({
       mutationThunk,
       reducerPath,
       assertTagType,
-      config: { refetchOnFocus, refetchOnReconnect, refetchOnMountOrArgChange, keepUnusedDataFor, reducerPath },
-    });
+      config: {
+        refetchOnFocus,
+        refetchOnReconnect,
+        refetchOnMountOrArgChange,
+        keepUnusedDataFor,
+        reducerPath,
+      },
+    })
 
     safeAssign(api.util, {
       patchQueryResult,
       updateQueryResult,
       prefetch,
       resetApiState: sliceActions.resetApiState,
-    });
-    safeAssign(api.internalActions, sliceActions);
+    })
+    safeAssign(api.internalActions, sliceActions)
 
     const { middleware, actions: middlewareActions } = buildMiddleware({
       reducerPath,
@@ -267,50 +314,62 @@ export const coreModule = (): Module<CoreModule> => ({
       mutationThunk,
       api,
       assertTagType,
-    });
-    safeAssign(api.util, middlewareActions);
+    })
+    safeAssign(api.util, middlewareActions)
 
-    safeAssign(api, { reducer: reducer as any, middleware });
+    safeAssign(api, { reducer: reducer as any, middleware })
 
     const { buildQuerySelector, buildMutationSelector } = buildSelectors({
       serializeQueryArgs: serializeQueryArgs as any,
       reducerPath,
-    });
+    })
 
     const { buildInitiateQuery, buildInitiateMutation } = buildInitiate({
       queryThunk,
       mutationThunk,
       api,
       serializeQueryArgs: serializeQueryArgs as any,
-    });
+    })
 
     // remove in final release
     Object.defineProperty(api.util, 'invalidateEntities', {
       get() {
-        if (typeof process !== 'undefined' && process.env.NODE_ENV === 'development') {
+        if (
+          typeof process !== 'undefined' &&
+          process.env.NODE_ENV === 'development'
+        ) {
           console.warn(
             '`api.util.invalidateEntities` has been renamed to `api.util.invalidateTags`, please change your code accordingly'
-          );
+          )
         }
-        return api.util.invalidateTags;
+        return api.util.invalidateTags
       },
-    });
+    })
     Object.defineProperty(api.util, 'prefetchThunk', {
       get() {
-        if (typeof process !== 'undefined' && process.env.NODE_ENV === 'development') {
+        if (
+          typeof process !== 'undefined' &&
+          process.env.NODE_ENV === 'development'
+        ) {
           console.warn(
             '`api.util.prefetchThunk` has been renamed to `api.util.prefetch`, please change your code accordingly'
-          );
+          )
         }
-        return api.util.prefetch;
+        return api.util.prefetch
       },
-    });
+    })
 
     return {
       name: coreModuleName,
       injectEndpoint(endpointName, definition) {
-        const anyApi = (api as any) as Api<any, Record<string, any>, string, string, CoreModule>;
-        anyApi.endpoints[endpointName] ??= {} as any;
+        const anyApi = (api as any) as Api<
+          any,
+          Record<string, any>,
+          string,
+          string,
+          CoreModule
+        >
+        anyApi.endpoints[endpointName] ??= {} as any
         if (isQueryDefinition(definition)) {
           safeAssign(
             anyApi.endpoints[endpointName],
@@ -319,7 +378,7 @@ export const coreModule = (): Module<CoreModule> => ({
               initiate: buildInitiateQuery(endpointName, definition),
             },
             buildMatchThunkActions(queryThunk, endpointName)
-          );
+          )
         } else if (isMutationDefinition(definition)) {
           safeAssign(
             anyApi.endpoints[endpointName],
@@ -328,9 +387,9 @@ export const coreModule = (): Module<CoreModule> => ({
               initiate: buildInitiateMutation(endpointName, definition),
             },
             buildMatchThunkActions(mutationThunk, endpointName)
-          );
+          )
         }
       },
-    };
+    }
   },
-});
+})
diff --git a/query/src/core/setupListeners.ts b/src/query/core/setupListeners.ts
similarity index 50%
rename from query/src/core/setupListeners.ts
rename to src/query/core/setupListeners.ts
index 91ac81eb..21465286 100644
--- a/query/src/core/setupListeners.ts
+++ b/src/query/core/setupListeners.ts
@@ -1,11 +1,11 @@
-import { createAction, ThunkDispatch } from '@reduxjs/toolkit';
+import { createAction, ThunkDispatch } from '@reduxjs/toolkit'
 
-export const onFocus = createAction('__rtkq/focused');
-export const onFocusLost = createAction('__rtkq/unfocused');
-export const onOnline = createAction('__rtkq/online');
-export const onOffline = createAction('__rtkq/offline');
+export const onFocus = createAction('__rtkq/focused')
+export const onFocusLost = createAction('__rtkq/unfocused')
+export const onOnline = createAction('__rtkq/online')
+export const onOffline = createAction('__rtkq/offline')
 
-let initialized = false;
+let initialized = false
 
 /**
  * A utility used to enable `refetchOnMount` and `refetchOnReconnect` behaviors.
@@ -28,47 +28,53 @@ export function setupListeners(
   customHandler?: (
     dispatch: ThunkDispatch<any, any, any>,
     actions: {
-      onFocus: typeof onFocus;
-      onFocusLost: typeof onFocusLost;
-      onOnline: typeof onOnline;
-      onOffline: typeof onOffline;
+      onFocus: typeof onFocus
+      onFocusLost: typeof onFocusLost
+      onOnline: typeof onOnline
+      onOffline: typeof onOffline
     }
   ) => () => void
 ) {
   function defaultHandler() {
-    const handleFocus = () => dispatch(onFocus());
-    const handleFocusLost = () => dispatch(onFocusLost());
-    const handleOnline = () => dispatch(onOnline());
-    const handleOffline = () => dispatch(onOffline());
+    const handleFocus = () => dispatch(onFocus())
+    const handleFocusLost = () => dispatch(onFocusLost())
+    const handleOnline = () => dispatch(onOnline())
+    const handleOffline = () => dispatch(onOffline())
     const handleVisibilityChange = () => {
       if (window.document.visibilityState === 'visible') {
-        handleFocus();
+        handleFocus()
       } else {
-        handleFocusLost();
+        handleFocusLost()
       }
-    };
+    }
 
     if (!initialized) {
       if (typeof window !== 'undefined' && window.addEventListener) {
         // Handle focus events
-        window.addEventListener('visibilitychange', handleVisibilityChange, false);
-        window.addEventListener('focus', handleFocus, false);
+        window.addEventListener(
+          'visibilitychange',
+          handleVisibilityChange,
+          false
+        )
+        window.addEventListener('focus', handleFocus, false)
 
         // Handle connection events
-        window.addEventListener('online', handleOnline, false);
-        window.addEventListener('offline', handleOffline, false);
-        initialized = true;
+        window.addEventListener('online', handleOnline, false)
+        window.addEventListener('offline', handleOffline, false)
+        initialized = true
       }
     }
     const unsubscribe = () => {
-      window.removeEventListener('focus', handleFocus);
-      window.removeEventListener('visibilitychange', handleVisibilityChange);
-      window.removeEventListener('online', handleOnline);
-      window.removeEventListener('offline', handleOffline);
-      initialized = false;
-    };
-    return unsubscribe;
+      window.removeEventListener('focus', handleFocus)
+      window.removeEventListener('visibilitychange', handleVisibilityChange)
+      window.removeEventListener('online', handleOnline)
+      window.removeEventListener('offline', handleOffline)
+      initialized = false
+    }
+    return unsubscribe
   }
 
-  return customHandler ? customHandler(dispatch, { onFocus, onFocusLost, onOffline, onOnline }) : defaultHandler();
+  return customHandler
+    ? customHandler(dispatch, { onFocus, onFocusLost, onOffline, onOnline })
+    : defaultHandler()
 }
diff --git a/query/src/createApi.ts b/src/query/createApi.ts
similarity index 70%
rename from query/src/createApi.ts
rename to src/query/createApi.ts
index 6339b854..9e640362 100644
--- a/query/src/createApi.ts
+++ b/src/query/createApi.ts
@@ -1,7 +1,14 @@
-import type { Api, ApiContext, Module, ModuleName } from './apiTypes';
-import type { BaseQueryArg, BaseQueryFn } from './baseQueryTypes';
-import { defaultSerializeQueryArgs, SerializeQueryArgs } from './defaultSerializeQueryArgs';
-import { DefinitionType, EndpointBuilder, EndpointDefinitions } from './endpointDefinitions';
+import type { Api, ApiContext, Module, ModuleName } from './apiTypes'
+import type { BaseQueryArg, BaseQueryFn } from './baseQueryTypes'
+import {
+  defaultSerializeQueryArgs,
+  SerializeQueryArgs,
+} from './defaultSerializeQueryArgs'
+import {
+  DefinitionType,
+  EndpointBuilder,
+  EndpointDefinitions,
+} from './endpointDefinitions'
 
 export interface CreateApiOptions<
   BaseQuery extends BaseQueryFn,
@@ -40,13 +47,13 @@ export interface CreateApiOptions<
    * };
    * ```
    */
-  baseQuery: BaseQuery;
+  baseQuery: BaseQuery
   /**
    * An array of string tag type names. Specifying tag types is optional, but you should define them so that they can be used for caching and invalidation. When defining an tag type, you will be able to [provide](../concepts/mutations#provides) them with `provides` and [invalidate](../concepts/mutations#advanced-mutations-with-revalidation) them with `invalidates` when configuring [endpoints](#endpoints).
    */
-  tagTypes?: readonly TagTypes[];
+  tagTypes?: readonly TagTypes[]
   /** @deprecated renamed to `tagTypes` */
-  entityTypes?: readonly TagTypes[];
+  entityTypes?: readonly TagTypes[]
   /**
    * The `reducerPath` is a _unique_ key that your service will be mounted to in your store. If you call `createApi` more than once in your application, you will need to provide a unique value each time. Defaults to `'api'`.
    *
@@ -73,19 +80,21 @@ export interface CreateApiOptions<
    * });
    * ```
    */
-  reducerPath?: ReducerPath;
+  reducerPath?: ReducerPath
   /**
    * Accepts a custom function if you have a need to change the creation of cache keys for any reason.
    */
-  serializeQueryArgs?: SerializeQueryArgs<BaseQueryArg<BaseQuery>>;
+  serializeQueryArgs?: SerializeQueryArgs<BaseQueryArg<BaseQuery>>
   /**
    * Endpoints are just a set of operations that you want to perform against your server. You define them as an object using the builder syntax. There are two basic endpoint types: [`query`](../concepts/queries) and [`mutation`](../concepts/mutations).
    */
-  endpoints(build: EndpointBuilder<BaseQuery, TagTypes, ReducerPath>): Definitions;
+  endpoints(
+    build: EndpointBuilder<BaseQuery, TagTypes, ReducerPath>
+  ): Definitions
   /**
    * Defaults to `60` _(this value is in seconds)_. This is how long RTK Query will keep your data cached for **after** the last component unsubscribes. For example, if you query an endpoint, then unmount the component, then mount another component that makes the same request within the given time frame, the most recent value will be served from the cache.
    */
-  keepUnusedDataFor?: number;
+  keepUnusedDataFor?: number
   /**
    * Defaults to `false`. This setting allows you to control whether if a cached result is already available RTK Query will only serve a cached result, or if it should `refetch` when set to `true` or if an adequate amount of time has passed since the last successful query result.
    * - `false` - Will not cause a query to be performed _unless_ it does not exist yet.
@@ -94,7 +103,7 @@ export interface CreateApiOptions<
    *
    * If you specify this option alongside `skip: true`, this **will not be evaluated** until `skip` is false.
    */
-  refetchOnMountOrArgChange?: boolean | number;
+  refetchOnMountOrArgChange?: boolean | number
   /**
    * Defaults to `false`. This setting allows you to control whether RTK Query will try to refetch all subscribed queries after the application window regains focus.
    *
@@ -102,7 +111,7 @@ export interface CreateApiOptions<
    *
    * Note: requires `setupListeners` to have been called.
    */
-  refetchOnFocus?: boolean;
+  refetchOnFocus?: boolean
   /**
    * Defaults to `false`. This setting allows you to control whether RTK Query will try to refetch all subscribed queries after regaining a network connection.
    *
@@ -110,7 +119,7 @@ export interface CreateApiOptions<
    *
    * Note: requires `setupListeners` to have been called.
    */
-  refetchOnReconnect?: boolean;
+  refetchOnReconnect?: boolean
 }
 
 export type CreateApi<Modules extends ModuleName> = {
@@ -126,8 +135,8 @@ export type CreateApi<Modules extends ModuleName> = {
     TagTypes extends string = never
   >(
     options: CreateApiOptions<BaseQuery, Definitions, ReducerPath, TagTypes>
-  ): Api<BaseQuery, Definitions, ReducerPath, TagTypes, Modules>;
-};
+  ): Api<BaseQuery, Definitions, ReducerPath, TagTypes, Modules>
+}
 
 /**
  * Builds a `createApi` method based on the provided `modules`.
@@ -152,10 +161,15 @@ export function buildCreateApi<Modules extends [Module<any>, ...Module<any>[]]>(
   return function baseCreateApi(options) {
     // remove in final release
     if (options.entityTypes) {
-      if (typeof process !== 'undefined' && process.env.NODE_ENV === 'development') {
-        console.warn('`entityTypes` has been renamed to `tagTypes`, please change your code accordingly');
+      if (
+        typeof process !== 'undefined' &&
+        process.env.NODE_ENV === 'development'
+      ) {
+        console.warn(
+          '`entityTypes` has been renamed to `tagTypes`, please change your code accordingly'
+        )
       }
-      options.tagTypes ??= options.entityTypes;
+      options.tagTypes ??= options.entityTypes
     }
 
     const optionsWithDefaults = {
@@ -168,106 +182,146 @@ export function buildCreateApi<Modules extends [Module<any>, ...Module<any>[]]>(
       ...options,
       entityTypes: [], // remove in final release
       tagTypes: [...(options.tagTypes || [])],
-    };
+    }
 
     const context: ApiContext<EndpointDefinitions> = {
       endpointDefinitions: {},
       batch(fn) {
         // placeholder "batch" method to be overridden by plugins, for example with React.unstable_batchedUpdate
-        fn();
+        fn()
       },
-    };
+    }
 
     const api = {
       injectEndpoints,
       enhanceEndpoints({ addTagTypes, endpoints, addEntityTypes }) {
         // remove in final release
         if (addEntityTypes) {
-          if (typeof process !== 'undefined' && process.env.NODE_ENV === 'development') {
-            console.warn('`addEntityTypes` has been renamed to `addTagTypes`, please change your code accordingly');
+          if (
+            typeof process !== 'undefined' &&
+            process.env.NODE_ENV === 'development'
+          ) {
+            console.warn(
+              '`addEntityTypes` has been renamed to `addTagTypes`, please change your code accordingly'
+            )
           }
-          addTagTypes ??= addEntityTypes;
+          addTagTypes ??= addEntityTypes
         }
 
         if (addTagTypes) {
           for (const eT of addTagTypes) {
             if (!optionsWithDefaults.tagTypes.includes(eT as any)) {
-              optionsWithDefaults.tagTypes.push(eT as any);
+              optionsWithDefaults.tagTypes.push(eT as any)
             }
           }
         }
         if (endpoints) {
-          for (const [endpointName, partialDefinition] of Object.entries(endpoints)) {
+          for (const [endpointName, partialDefinition] of Object.entries(
+            endpoints
+          )) {
             if (typeof partialDefinition === 'function') {
-              partialDefinition(context.endpointDefinitions[endpointName]);
+              partialDefinition(context.endpointDefinitions[endpointName])
             }
-            Object.assign(context.endpointDefinitions[endpointName] || {}, partialDefinition);
+            Object.assign(
+              context.endpointDefinitions[endpointName] || {},
+              partialDefinition
+            )
             // remove in final release
-            const x = context.endpointDefinitions[endpointName];
+            const x = context.endpointDefinitions[endpointName]
             if (x?.provides) {
-              if (typeof process !== 'undefined' && process.env.NODE_ENV === 'development') {
-                console.warn('`provides` has been renamed to `providesTags`, please change your code accordingly');
+              if (
+                typeof process !== 'undefined' &&
+                process.env.NODE_ENV === 'development'
+              ) {
+                console.warn(
+                  '`provides` has been renamed to `providesTags`, please change your code accordingly'
+                )
               }
-              x.providesTags = x.provides;
+              x.providesTags = x.provides
             }
             if (x?.invalidates) {
-              if (typeof process !== 'undefined' && process.env.NODE_ENV === 'development') {
+              if (
+                typeof process !== 'undefined' &&
+                process.env.NODE_ENV === 'development'
+              ) {
                 console.warn(
                   '`invalidates` has been renamed to `invalidatesTags`, please change your code accordingly'
-                );
+                )
               }
-              x.invalidatesTags = x.invalidates;
+              x.invalidatesTags = x.invalidates
             }
           }
         }
-        return api;
+        return api
       },
-    } as Api<BaseQueryFn, {}, string, string, Modules[number]['name']>;
+    } as Api<BaseQueryFn, {}, string, string, Modules[number]['name']>
 
-    const initializedModules = modules.map((m) => m.init(api as any, optionsWithDefaults, context));
+    const initializedModules = modules.map((m) =>
+      m.init(api as any, optionsWithDefaults, context)
+    )
 
-    function injectEndpoints(inject: Parameters<typeof api.injectEndpoints>[0]) {
+    function injectEndpoints(
+      inject: Parameters<typeof api.injectEndpoints>[0]
+    ) {
       const evaluatedEndpoints = inject.endpoints({
         query: (x) => {
           // remove in final release
           if (x.provides) {
-            if (typeof process !== 'undefined' && process.env.NODE_ENV === 'development') {
-              console.warn('`provides` has been renamed to `providesTags`, please change your code accordingly');
+            if (
+              typeof process !== 'undefined' &&
+              process.env.NODE_ENV === 'development'
+            ) {
+              console.warn(
+                '`provides` has been renamed to `providesTags`, please change your code accordingly'
+              )
             }
-            x.providesTags ??= x.provides;
+            x.providesTags ??= x.provides
           }
-          return { ...x, type: DefinitionType.query } as any;
+          return { ...x, type: DefinitionType.query } as any
         },
         mutation: (x) => {
           // remove in final release
           if (x.invalidates) {
-            if (typeof process !== 'undefined' && process.env.NODE_ENV === 'development') {
-              console.warn('`invalidates` has been renamed to `invalidatesTags`, please change your code accordingly');
+            if (
+              typeof process !== 'undefined' &&
+              process.env.NODE_ENV === 'development'
+            ) {
+              console.warn(
+                '`invalidates` has been renamed to `invalidatesTags`, please change your code accordingly'
+              )
             }
-            x.invalidatesTags ??= x.invalidates;
+            x.invalidatesTags ??= x.invalidates
           }
-          return { ...x, type: DefinitionType.mutation } as any;
+          return { ...x, type: DefinitionType.mutation } as any
         },
-      });
+      })
 
-      for (const [endpointName, definition] of Object.entries(evaluatedEndpoints)) {
-        if (typeof process !== 'undefined' && process.env.NODE_ENV === 'development') {
-          if (!inject.overrideExisting && endpointName in context.endpointDefinitions) {
+      for (const [endpointName, definition] of Object.entries(
+        evaluatedEndpoints
+      )) {
+        if (
+          typeof process !== 'undefined' &&
+          process.env.NODE_ENV === 'development'
+        ) {
+          if (
+            !inject.overrideExisting &&
+            endpointName in context.endpointDefinitions
+          ) {
             console.error(
               `called \`injectEndpoints\` to override already-existing endpointName ${endpointName} without specifying \`overrideExisting: true\``
-            );
-            continue;
+            )
+            continue
           }
         }
-        context.endpointDefinitions[endpointName] = definition;
+        context.endpointDefinitions[endpointName] = definition
         for (const m of initializedModules) {
-          m.injectEndpoint(endpointName, definition);
+          m.injectEndpoint(endpointName, definition)
         }
       }
 
-      return api as any;
+      return api as any
     }
 
-    return api.injectEndpoints({ endpoints: options.endpoints as any });
-  };
+    return api.injectEndpoints({ endpoints: options.endpoints as any })
+  }
 }
diff --git a/src/query/defaultSerializeQueryArgs.ts b/src/query/defaultSerializeQueryArgs.ts
new file mode 100644
index 00000000..10ea6d74
--- /dev/null
+++ b/src/query/defaultSerializeQueryArgs.ts
@@ -0,0 +1,25 @@
+import { QueryCacheKey } from './core/apiState'
+import { EndpointDefinition } from './endpointDefinitions'
+
+export const defaultSerializeQueryArgs: SerializeQueryArgs<any> = ({
+  endpointName,
+  queryArgs,
+}) => {
+  // Sort the object keys before stringifying, to prevent useQuery({ a: 1, b: 2 }) having a different cache key than useQuery({ b: 2, a: 1 })
+  return `${endpointName}(${JSON.stringify(
+    queryArgs,
+    Object.keys(queryArgs || {}).sort()
+  )})`
+}
+
+export type SerializeQueryArgs<_InternalQueryArgs> = (_: {
+  queryArgs: any
+  endpointDefinition: EndpointDefinition<any, any, any, any>
+  endpointName: string
+}) => string
+
+export type InternalSerializeQueryArgs<_InternalQueryArgs> = (_: {
+  queryArgs: any
+  endpointDefinition: EndpointDefinition<any, any, any, any>
+  endpointName: string
+}) => QueryCacheKey
diff --git a/query/src/endpointDefinitions.ts b/src/query/endpointDefinitions.ts
similarity index 70%
rename from query/src/endpointDefinitions.ts
rename to src/query/endpointDefinitions.ts
index 6b05217a..7310ddcc 100644
--- a/query/src/endpointDefinitions.ts
+++ b/src/query/endpointDefinitions.ts
@@ -1,5 +1,5 @@
-import { AnyAction, ThunkDispatch } from '@reduxjs/toolkit';
-import { RootState } from './core/apiState';
+import { AnyAction, ThunkDispatch } from '@reduxjs/toolkit'
+import { RootState } from './core/apiState'
 import {
   BaseQueryExtraOptions,
   BaseQueryFn,
@@ -9,93 +9,123 @@ import {
   QueryReturnValue,
   BaseQueryError,
   BaseQueryMeta,
-} from './baseQueryTypes';
-import { HasRequiredProps, MaybePromise, OmitFromUnion, CastAny } from './tsHelpers';
-import { NEVER } from './fakeBaseQuery';
+} from './baseQueryTypes'
+import {
+  HasRequiredProps,
+  MaybePromise,
+  OmitFromUnion,
+  CastAny,
+} from './tsHelpers'
+import { NEVER } from './fakeBaseQuery'
 
-const resultType = Symbol();
-const baseQuery = Symbol();
+const resultType = Symbol()
+const baseQuery = Symbol()
 
-interface EndpointDefinitionWithQuery<QueryArg, BaseQuery extends BaseQueryFn, ResultType> {
+interface EndpointDefinitionWithQuery<
+  QueryArg,
+  BaseQuery extends BaseQueryFn,
+  ResultType
+> {
   /**
    * `query` is the only required property, and can be a function that returns either a `string` or an `object` which is passed to your `baseQuery`. If you are using [fetchBaseQuery](./fetchBaseQuery), this can return either a `string` or an `object` of properties in `FetchArgs`. If you use your own custom `baseQuery`, you can customize this behavior to your liking
    */
-  query(arg: QueryArg): BaseQueryArg<BaseQuery>;
-  queryFn?: never;
+  query(arg: QueryArg): BaseQueryArg<BaseQuery>
+  queryFn?: never
   /**
    * A function to manipulate the data returned by a query or mutation
    */
   transformResponse?(
     baseQueryReturnValue: BaseQueryResult<BaseQuery>,
     meta: BaseQueryMeta<BaseQuery>
-  ): ResultType | Promise<ResultType>;
+  ): ResultType | Promise<ResultType>
 }
 
-interface EndpointDefinitionWithQueryFn<QueryArg, BaseQuery extends BaseQueryFn, ResultType> {
+interface EndpointDefinitionWithQueryFn<
+  QueryArg,
+  BaseQuery extends BaseQueryFn,
+  ResultType
+> {
   queryFn(
     arg: QueryArg,
     api: BaseQueryApi,
     extraOptions: BaseQueryExtraOptions<BaseQuery>,
     baseQuery: (arg: Parameters<BaseQuery>[0]) => ReturnType<BaseQuery>
-  ): MaybePromise<QueryReturnValue<ResultType, BaseQueryError<BaseQuery>>>;
-  query?: never;
-  transformResponse?: never;
+  ): MaybePromise<QueryReturnValue<ResultType, BaseQueryError<BaseQuery>>>
+  query?: never
+  transformResponse?: never
 }
 
-export type BaseEndpointDefinition<QueryArg, BaseQuery extends BaseQueryFn, ResultType> = (
+export type BaseEndpointDefinition<
+  QueryArg,
+  BaseQuery extends BaseQueryFn,
+  ResultType
+> = (
   | ([CastAny<BaseQueryResult<BaseQuery>, {}>] extends [NEVER]
       ? never
       : EndpointDefinitionWithQuery<QueryArg, BaseQuery, ResultType>)
   | EndpointDefinitionWithQueryFn<QueryArg, BaseQuery, ResultType>
 ) & {
   /* phantom type */
-  [resultType]?: ResultType;
+  [resultType]?: ResultType
   /* phantom type */
-  [baseQuery]?: BaseQuery;
+  [baseQuery]?: BaseQuery
 } & HasRequiredProps<
     BaseQueryExtraOptions<BaseQuery>,
     { extraOptions: BaseQueryExtraOptions<BaseQuery> },
     { extraOptions?: BaseQueryExtraOptions<BaseQuery> }
-  >;
+  >
 
 export enum DefinitionType {
   query = 'query',
   mutation = 'mutation',
 }
 
-type GetResultDescriptionFn<TagTypes extends string, ResultType, QueryArg, ErrorType> = (
+type GetResultDescriptionFn<
+  TagTypes extends string,
+  ResultType,
+  QueryArg,
+  ErrorType
+> = (
   result: ResultType | undefined,
   error: ErrorType | undefined,
   arg: QueryArg
-) => ReadonlyArray<TagDescription<TagTypes>>;
+) => ReadonlyArray<TagDescription<TagTypes>>
 
-export type FullTagDescription<TagType> = { type: TagType; id?: number | string };
-type TagDescription<TagType> = TagType | FullTagDescription<TagType>;
-type ResultDescription<TagTypes extends string, ResultType, QueryArg, ErrorType> =
+export type FullTagDescription<TagType> = {
+  type: TagType
+  id?: number | string
+}
+type TagDescription<TagType> = TagType | FullTagDescription<TagType>
+type ResultDescription<
+  TagTypes extends string,
+  ResultType,
+  QueryArg,
+  ErrorType
+> =
   | ReadonlyArray<TagDescription<TagTypes>>
-  | GetResultDescriptionFn<TagTypes, ResultType, QueryArg, ErrorType>;
+  | GetResultDescriptionFn<TagTypes, ResultType, QueryArg, ErrorType>
 
 export interface QueryApi<ReducerPath extends string, Context extends {}> {
   /**
    * The dispatch method for the store
    */
-  dispatch: ThunkDispatch<any, any, AnyAction>;
+  dispatch: ThunkDispatch<any, any, AnyAction>
   /**
    * A method to get the current state
    */
-  getState(): RootState<any, any, ReducerPath>;
+  getState(): RootState<any, any, ReducerPath>
   /**
    * `extra` as provided as `thunk.extraArgument` to the `configureStore` `getDefaultMiddleware` option.
    */
-  extra: unknown;
+  extra: unknown
   /**
    * A unique ID generated for the mutation
    */
-  requestId: string;
+  requestId: string
   /**
    * A variable shared between `onStart`, `onError` and `onSuccess` of one request to pass data forward between them
    */
-  context: Context;
+  context: Context
 }
 
 interface QueryExtraOptions<
@@ -106,7 +136,7 @@ interface QueryExtraOptions<
   ReducerPath extends string = string,
   Context = Record<string, any>
 > {
-  type: DefinitionType.query;
+  type: DefinitionType.query
   /**
    * - Used by `queries` to provide tags to the cache.
    * - Expects an array of tag type strings, or an array of objects of tag types with ids.
@@ -114,21 +144,31 @@ interface QueryExtraOptions<
    *   2.  `[{ type: 'Post' }]` - equivalent to `a`
    *   3.  `[{ type: 'Post', id: 1 }]`
    */
-  providesTags?: ResultDescription<TagTypes, ResultType, QueryArg, BaseQueryError<BaseQuery>>;
+  providesTags?: ResultDescription<
+    TagTypes,
+    ResultType,
+    QueryArg,
+    BaseQueryError<BaseQuery>
+  >
   /** @deprecated renamed to `providesTags` */
-  provides?: ResultDescription<TagTypes, ResultType, QueryArg, BaseQueryError<BaseQuery>>;
+  provides?: ResultDescription<
+    TagTypes,
+    ResultType,
+    QueryArg,
+    BaseQueryError<BaseQuery>
+  >
   /**
    * Not to be used. A query should not invalidate tags in the cache.
    */
-  invalidatesTags?: never;
+  invalidatesTags?: never
   /** @deprecated */
-  invalidates?: never;
+  invalidates?: never
   /**
    * Called when the query is triggered.
    * @param arg - The argument supplied to the query
    * @param queryApi - An object containing `dispatch`, `getState()`, `extra`, `request`Id`, `context`
    */
-  onStart?(arg: QueryArg, queryApi: QueryApi<ReducerPath, Context>): void;
+  onStart?(arg: QueryArg, queryApi: QueryApi<ReducerPath, Context>): void
   /**
    * Called when an error response is returned by the query.
    * @param arg - The argument supplied to the query
@@ -141,7 +181,7 @@ interface QueryExtraOptions<
     queryApi: QueryApi<ReducerPath, Context>,
     error: unknown,
     meta: BaseQueryMeta<BaseQuery>
-  ): void;
+  ): void
   /**
    * Called when a successful response is returned by the query.
    * @param arg - The argument supplied to the query
@@ -154,7 +194,7 @@ interface QueryExtraOptions<
     queryApi: QueryApi<ReducerPath, Context>,
     result: ResultType,
     meta: BaseQueryMeta<BaseQuery> | undefined
-  ): void;
+  ): void
 }
 
 export type QueryDefinition<
@@ -165,29 +205,36 @@ export type QueryDefinition<
   ReducerPath extends string = string,
   Context = Record<string, any>
 > = BaseEndpointDefinition<QueryArg, BaseQuery, ResultType> &
-  QueryExtraOptions<TagTypes, ResultType, QueryArg, BaseQuery, ReducerPath, Context>;
+  QueryExtraOptions<
+    TagTypes,
+    ResultType,
+    QueryArg,
+    BaseQuery,
+    ReducerPath,
+    Context
+  >
 
 export interface MutationApi<ReducerPath extends string, Context extends {}> {
   /**
    * The dispatch method for the store
    */
-  dispatch: ThunkDispatch<any, any, AnyAction>;
+  dispatch: ThunkDispatch<any, any, AnyAction>
   /**
    * A method to get the current state
    */
-  getState(): RootState<any, any, ReducerPath>;
+  getState(): RootState<any, any, ReducerPath>
   /**
    * `extra` as provided as `thunk.extraArgument` to the `configureStore` `getDefaultMiddleware` option.
    */
-  extra: unknown;
+  extra: unknown
   /**
    * A unique ID generated for the mutation
    */
-  requestId: string;
+  requestId: string
   /**
    * A variable shared between `onStart`, `onError` and `onSuccess` of one request to pass data forward between them
    */
-  context: Context;
+  context: Context
 }
 
 interface MutationExtraOptions<
@@ -198,26 +245,36 @@ interface MutationExtraOptions<
   ReducerPath extends string = string,
   Context = Record<string, any>
 > {
-  type: DefinitionType.mutation;
+  type: DefinitionType.mutation
   /**
    * - Used by `mutations` for [cache invalidation](../concepts/mutations#advanced-mutations-with-revalidation) purposes.
    * - Expects the same shapes as `provides`.
    */
-  invalidatesTags?: ResultDescription<TagTypes, ResultType, QueryArg, BaseQueryError<BaseQuery>>;
+  invalidatesTags?: ResultDescription<
+    TagTypes,
+    ResultType,
+    QueryArg,
+    BaseQueryError<BaseQuery>
+  >
   /** @deprecated renamed to `invalidatesTags` */
-  invalidates?: ResultDescription<TagTypes, ResultType, QueryArg, BaseQueryError<BaseQuery>>;
+  invalidates?: ResultDescription<
+    TagTypes,
+    ResultType,
+    QueryArg,
+    BaseQueryError<BaseQuery>
+  >
   /**
    * Not to be used. A mutation should not provide tags to the cache.
    */
-  providesTags?: never;
+  providesTags?: never
   /** @deprecated */
-  provides?: never;
+  provides?: never
   /**
    * Called when the mutation is triggered.
    * @param arg - The argument supplied to the query
    * @param mutationApi - An object containing `dispatch`, `getState()`, `extra`, `request`Id`, `context`
    */
-  onStart?(arg: QueryArg, mutationApi: MutationApi<ReducerPath, Context>): void;
+  onStart?(arg: QueryArg, mutationApi: MutationApi<ReducerPath, Context>): void
   /**
    * Called when an error response is returned by the mutation.
    * @param arg - The argument supplied to the query
@@ -230,7 +287,7 @@ interface MutationExtraOptions<
     mutationApi: MutationApi<ReducerPath, Context>,
     error: unknown,
     meta: BaseQueryMeta<BaseQuery>
-  ): void;
+  ): void
   /**
    * Called when a successful response is returned by the mutation.
    * @param arg - The argument supplied to the query
@@ -243,7 +300,7 @@ interface MutationExtraOptions<
     mutationApi: MutationApi<ReducerPath, Context>,
     result: ResultType,
     meta: BaseQueryMeta<BaseQuery> | undefined
-  ): void;
+  ): void
 }
 
 export type MutationDefinition<
@@ -254,7 +311,14 @@ export type MutationDefinition<
   ReducerPath extends string = string,
   Context = Record<string, any>
 > = BaseEndpointDefinition<QueryArg, BaseQuery, ResultType> &
-  MutationExtraOptions<TagTypes, ResultType, QueryArg, BaseQuery, ReducerPath, Context>;
+  MutationExtraOptions<
+    TagTypes,
+    ResultType,
+    QueryArg,
+    BaseQuery,
+    ReducerPath,
+    Context
+  >
 
 export type EndpointDefinition<
   QueryArg,
@@ -264,21 +328,30 @@ export type EndpointDefinition<
   ReducerPath extends string = string
 > =
   | QueryDefinition<QueryArg, BaseQuery, TagTypes, ResultType, ReducerPath>
-  | MutationDefinition<QueryArg, BaseQuery, TagTypes, ResultType, ReducerPath>;
+  | MutationDefinition<QueryArg, BaseQuery, TagTypes, ResultType, ReducerPath>
 
-export type EndpointDefinitions = Record<string, EndpointDefinition<any, any, any, any>>;
+export type EndpointDefinitions = Record<
+  string,
+  EndpointDefinition<any, any, any, any>
+>
 
-export function isQueryDefinition(e: EndpointDefinition<any, any, any, any>): e is QueryDefinition<any, any, any, any> {
-  return e.type === DefinitionType.query;
+export function isQueryDefinition(
+  e: EndpointDefinition<any, any, any, any>
+): e is QueryDefinition<any, any, any, any> {
+  return e.type === DefinitionType.query
 }
 
 export function isMutationDefinition(
   e: EndpointDefinition<any, any, any, any>
 ): e is MutationDefinition<any, any, any, any> {
-  return e.type === DefinitionType.mutation;
+  return e.type === DefinitionType.mutation
 }
 
-export type EndpointBuilder<BaseQuery extends BaseQueryFn, TagTypes extends string, ReducerPath extends string> = {
+export type EndpointBuilder<
+  BaseQuery extends BaseQueryFn,
+  TagTypes extends string,
+  ReducerPath extends string
+> = {
   /**
    * An endpoint definition that retrieves data, and may provide tags to the cache.
    *
@@ -304,8 +377,11 @@ export type EndpointBuilder<BaseQuery extends BaseQueryFn, TagTypes extends stri
    *```
    */
   query<ResultType, QueryArg>(
-    definition: OmitFromUnion<QueryDefinition<QueryArg, BaseQuery, TagTypes, ResultType>, 'type'>
-  ): QueryDefinition<QueryArg, BaseQuery, TagTypes, ResultType>;
+    definition: OmitFromUnion<
+      QueryDefinition<QueryArg, BaseQuery, TagTypes, ResultType>,
+      'type'
+    >
+  ): QueryDefinition<QueryArg, BaseQuery, TagTypes, ResultType>
   /**
    * An endpoint definition that alters data on the server or will possibly invalidate the cache.
    *
@@ -333,16 +409,32 @@ export type EndpointBuilder<BaseQuery extends BaseQueryFn, TagTypes extends stri
    */
   mutation<ResultType, QueryArg, Context = Record<string, any>>(
     definition: OmitFromUnion<
-      MutationDefinition<QueryArg, BaseQuery, TagTypes, ResultType, ReducerPath, Context>,
+      MutationDefinition<
+        QueryArg,
+        BaseQuery,
+        TagTypes,
+        ResultType,
+        ReducerPath,
+        Context
+      >,
       'type'
     >
-  ): MutationDefinition<QueryArg, BaseQuery, TagTypes, ResultType, ReducerPath, Context>;
-};
+  ): MutationDefinition<
+    QueryArg,
+    BaseQuery,
+    TagTypes,
+    ResultType,
+    ReducerPath,
+    Context
+  >
+}
 
-export type AssertTagTypes = <T extends FullTagDescription<string>>(t: T) => T;
+export type AssertTagTypes = <T extends FullTagDescription<string>>(t: T) => T
 
 export function calculateProvidedBy<ResultType, QueryArg, ErrorType>(
-  description: ResultDescription<string, ResultType, QueryArg, ErrorType> | undefined,
+  description:
+    | ResultDescription<string, ResultType, QueryArg, ErrorType>
+    | undefined,
   result: ResultType | undefined,
   error: ErrorType | undefined,
   queryArg: QueryArg,
@@ -351,56 +443,43 @@ export function calculateProvidedBy<ResultType, QueryArg, ErrorType>(
   if (isFunction(description)) {
     return description(result as ResultType, error as undefined, queryArg)
       .map(expandTagDescription)
-      .map(assertTagTypes);
+      .map(assertTagTypes)
   }
   if (Array.isArray(description)) {
-    return description.map(expandTagDescription).map(assertTagTypes);
+    return description.map(expandTagDescription).map(assertTagTypes)
   }
-  return [];
+  return []
 }
 
 function isFunction<T>(t: T): t is Extract<T, Function> {
-  return typeof t === 'function';
+  return typeof t === 'function'
 }
 
-function expandTagDescription(description: TagDescription<string>): FullTagDescription<string> {
-  return typeof description === 'string' ? { type: description } : description;
+function expandTagDescription(
+  description: TagDescription<string>
+): FullTagDescription<string> {
+  return typeof description === 'string' ? { type: description } : description
 }
 
-export type QueryArgFrom<D extends BaseEndpointDefinition<any, any, any>> = D extends BaseEndpointDefinition<
-  infer QA,
-  any,
-  any
->
-  ? QA
-  : unknown;
-export type ResultTypeFrom<D extends BaseEndpointDefinition<any, any, any>> = D extends BaseEndpointDefinition<
-  any,
-  any,
-  infer RT
->
-  ? RT
-  : unknown;
+export type QueryArgFrom<
+  D extends BaseEndpointDefinition<any, any, any>
+> = D extends BaseEndpointDefinition<infer QA, any, any> ? QA : unknown
+export type ResultTypeFrom<
+  D extends BaseEndpointDefinition<any, any, any>
+> = D extends BaseEndpointDefinition<any, any, infer RT> ? RT : unknown
 
-export type ReducerPathFrom<D extends EndpointDefinition<any, any, any, any>> = D extends EndpointDefinition<
-  any,
-  any,
-  any,
-  infer RP
->
-  ? RP
-  : unknown;
+export type ReducerPathFrom<
+  D extends EndpointDefinition<any, any, any, any>
+> = D extends EndpointDefinition<any, any, any, infer RP> ? RP : unknown
 
-export type TagTypesFrom<D extends EndpointDefinition<any, any, any, any>> = D extends EndpointDefinition<
-  any,
-  any,
-  infer RP,
-  any
->
-  ? RP
-  : unknown;
+export type TagTypesFrom<
+  D extends EndpointDefinition<any, any, any, any>
+> = D extends EndpointDefinition<any, any, infer RP, any> ? RP : unknown
 
-export type ReplaceTagTypes<Definitions extends EndpointDefinitions, NewTagTypes extends string> = {
+export type ReplaceTagTypes<
+  Definitions extends EndpointDefinitions,
+  NewTagTypes extends string
+> = {
   [K in keyof Definitions]: Definitions[K] extends QueryDefinition<
     infer QueryArg,
     infer BaseQuery,
@@ -416,6 +495,12 @@ export type ReplaceTagTypes<Definitions extends EndpointDefinitions, NewTagTypes
         infer ResultType,
         infer ReducerPath
       >
-    ? MutationDefinition<QueryArg, BaseQuery, NewTagTypes, ResultType, ReducerPath>
-    : never;
-};
+    ? MutationDefinition<
+        QueryArg,
+        BaseQuery,
+        NewTagTypes,
+        ResultType,
+        ReducerPath
+      >
+    : never
+}
diff --git a/src/query/fakeBaseQuery.ts b/src/query/fakeBaseQuery.ts
new file mode 100644
index 00000000..c5e4646e
--- /dev/null
+++ b/src/query/fakeBaseQuery.ts
@@ -0,0 +1,21 @@
+import { BaseQueryFn } from './baseQueryTypes'
+
+const _NEVER = Symbol()
+export type NEVER = typeof _NEVER
+
+/**
+ * Creates a "fake" baseQuery to be used if your api *only* uses the `queryFn` definition syntax.
+ * This also allows you to specify a specific error type to be shared by all your `queryFn` definitions.
+ */
+export function fakeBaseQuery<ErrorType>(): BaseQueryFn<
+  void,
+  NEVER,
+  ErrorType,
+  {}
+> {
+  return function () {
+    throw new Error(
+      'When using `fakeBaseQuery`, all queries & mutations must use the `queryFn` definition syntax.'
+    )
+  }
+}
diff --git a/query/src/fetchBaseQuery.ts b/src/query/fetchBaseQuery.ts
similarity index 56%
rename from query/src/fetchBaseQuery.ts
rename to src/query/fetchBaseQuery.ts
index 00ea6320..e8d152db 100644
--- a/query/src/fetchBaseQuery.ts
+++ b/src/query/fetchBaseQuery.ts
@@ -1,65 +1,85 @@
-import { joinUrls } from './utils';
-import { isPlainObject } from '@reduxjs/toolkit';
-import type { BaseQueryFn } from './baseQueryTypes';
-import type { MaybePromise, Override } from './tsHelpers';
+import { joinUrls } from './utils'
+import { isPlainObject } from '@reduxjs/toolkit'
+import type { BaseQueryFn } from './baseQueryTypes'
+import type { MaybePromise, Override } from './tsHelpers'
 
-export type ResponseHandler = 'json' | 'text' | ((response: Response) => Promise<any>);
+export type ResponseHandler =
+  | 'json'
+  | 'text'
+  | ((response: Response) => Promise<any>)
 
 type CustomRequestInit = Override<
   RequestInit,
-  { headers?: Headers | string[][] | Record<string, string | undefined> | undefined }
->;
+  {
+    headers?:
+      | Headers
+      | string[][]
+      | Record<string, string | undefined>
+      | undefined
+  }
+>
 
 export interface FetchArgs extends CustomRequestInit {
-  url: string;
-  params?: Record<string, any>;
-  body?: any;
-  responseHandler?: ResponseHandler;
-  validateStatus?: (response: Response, body: any) => boolean;
+  url: string
+  params?: Record<string, any>
+  body?: any
+  responseHandler?: ResponseHandler
+  validateStatus?: (response: Response, body: any) => boolean
 }
 
-const defaultValidateStatus = (response: Response) => response.status >= 200 && response.status <= 299;
+const defaultValidateStatus = (response: Response) =>
+  response.status >= 200 && response.status <= 299
 
-const isJsonContentType = (headers: Headers) => headers.get('content-type')?.trim()?.startsWith('application/json');
+const isJsonContentType = (headers: Headers) =>
+  headers.get('content-type')?.trim()?.startsWith('application/json')
 
-const handleResponse = async (response: Response, responseHandler: ResponseHandler) => {
+const handleResponse = async (
+  response: Response,
+  responseHandler: ResponseHandler
+) => {
   if (typeof responseHandler === 'function') {
-    return responseHandler(response);
+    return responseHandler(response)
   }
 
   if (responseHandler === 'text') {
-    return response.text();
+    return response.text()
   }
 
   if (responseHandler === 'json') {
-    const text = await response.text();
-    return text.length ? JSON.parse(text) : undefined;
+    const text = await response.text()
+    return text.length ? JSON.parse(text) : undefined
   }
-};
+}
 
 export interface FetchBaseQueryError {
-  status: number;
-  data: unknown;
+  status: number
+  data: unknown
 }
 
 function stripUndefined(obj: any) {
   if (!isPlainObject(obj)) {
-    return obj;
+    return obj
   }
-  const copy: Record<string, any> = { ...obj };
+  const copy: Record<string, any> = { ...obj }
   for (const [k, v] of Object.entries(copy)) {
-    if (typeof v === 'undefined') delete copy[k];
+    if (typeof v === 'undefined') delete copy[k]
   }
-  return copy;
+  return copy
 }
 
 export type FetchBaseQueryArgs = {
-  baseUrl?: string;
-  prepareHeaders?: (headers: Headers, api: { getState: () => unknown }) => MaybePromise<Headers>;
-  fetchFn?: (input: RequestInfo, init?: RequestInit | undefined) => Promise<Response>;
-} & RequestInit;
-
-export type FetchBaseQueryMeta = { request: Request; response: Response };
+  baseUrl?: string
+  prepareHeaders?: (
+    headers: Headers,
+    api: { getState: () => unknown }
+  ) => MaybePromise<Headers>
+  fetchFn?: (
+    input: RequestInfo,
+    init?: RequestInit | undefined
+  ) => Promise<Response>
+} & RequestInit
+
+export type FetchBaseQueryMeta = { request: Request; response: Response }
 
 /**
  * This is a very small wrapper around fetch that aims to simplify requests.
@@ -100,7 +120,13 @@ export function fetchBaseQuery({
   prepareHeaders = (x) => x,
   fetchFn = fetch,
   ...baseFetchOptions
-}: FetchBaseQueryArgs = {}): BaseQueryFn<string | FetchArgs, unknown, FetchBaseQueryError, {}, FetchBaseQueryMeta> {
+}: FetchBaseQueryArgs = {}): BaseQueryFn<
+  string | FetchArgs,
+  unknown,
+  FetchBaseQueryError,
+  {},
+  FetchBaseQueryMeta
+> {
   return async (arg, { signal, getState }) => {
     let {
       url,
@@ -111,46 +137,52 @@ export function fetchBaseQuery({
       responseHandler = 'json' as const,
       validateStatus = defaultValidateStatus,
       ...rest
-    } = typeof arg == 'string' ? { url: arg } : arg;
+    } = typeof arg == 'string' ? { url: arg } : arg
     let config: RequestInit = {
       ...baseFetchOptions,
       method,
       signal,
       body,
       ...rest,
-    };
+    }
 
-    config.headers = await prepareHeaders(new Headers(stripUndefined(headers)), { getState });
+    config.headers = await prepareHeaders(
+      new Headers(stripUndefined(headers)),
+      { getState }
+    )
 
     // Only set the content-type to json if appropriate. Will not be true for FormData, ArrayBuffer, Blob, etc.
     const isJsonifiable = (body: any) =>
-      typeof body === 'object' && (isPlainObject(body) || Array.isArray(body) || typeof body.toJSON === 'function');
+      typeof body === 'object' &&
+      (isPlainObject(body) ||
+        Array.isArray(body) ||
+        typeof body.toJSON === 'function')
 
     if (!config.headers.has('content-type') && isJsonifiable(body)) {
-      config.headers.set('content-type', 'application/json');
+      config.headers.set('content-type', 'application/json')
     }
 
     if (body && isJsonContentType(config.headers)) {
-      config.body = JSON.stringify(body);
+      config.body = JSON.stringify(body)
     }
 
     if (params) {
-      const divider = ~url.indexOf('?') ? '&' : '?';
-      const query = new URLSearchParams(stripUndefined(params));
-      url += divider + query;
+      const divider = ~url.indexOf('?') ? '&' : '?'
+      const query = new URLSearchParams(stripUndefined(params))
+      url += divider + query
     }
 
-    url = joinUrls(baseUrl, url);
+    url = joinUrls(baseUrl, url)
 
-    const request = new Request(url, config);
-    const requestClone = request.clone();
+    const request = new Request(url, config)
+    const requestClone = request.clone()
 
-    const response = await fetchFn(request);
-    const responseClone = response.clone();
+    const response = await fetchFn(request)
+    const responseClone = response.clone()
 
-    const meta = { request: requestClone, response: responseClone };
+    const meta = { request: requestClone, response: responseClone }
 
-    const resultData = await handleResponse(response, responseHandler);
+    const resultData = await handleResponse(response, responseHandler)
 
     return validateStatus(response, resultData)
       ? {
@@ -163,6 +195,6 @@ export function fetchBaseQuery({
             data: resultData,
           },
           meta,
-        };
-  };
+        }
+  }
 }
diff --git a/src/query/index.ts b/src/query/index.ts
new file mode 100644
index 00000000..8780398e
--- /dev/null
+++ b/src/query/index.ts
@@ -0,0 +1,24 @@
+export { QueryStatus } from './core/apiState'
+export type {
+  Api,
+  ApiWithInjectedEndpoints,
+  Module,
+  ApiModules,
+} from './apiTypes'
+export type { BaseQueryEnhancer, BaseQueryFn } from './baseQueryTypes'
+export type {
+  EndpointDefinitions,
+  EndpointDefinition,
+  QueryDefinition,
+  MutationDefinition,
+} from './endpointDefinitions'
+export { fetchBaseQuery } from './fetchBaseQuery'
+export type { FetchBaseQueryError, FetchArgs } from './fetchBaseQuery'
+export { retry } from './retry'
+export { setupListeners } from './core/setupListeners'
+export { skipSelector } from './core/buildSelectors'
+export type { CreateApi, CreateApiOptions } from './createApi'
+export { buildCreateApi } from './createApi'
+export { fakeBaseQuery } from './fakeBaseQuery'
+
+export { createApi, coreModule } from './core'
diff --git a/query/src/react-hooks/ApiProvider.tsx b/src/query/react-hooks/ApiProvider.tsx
similarity index 69%
rename from query/src/react-hooks/ApiProvider.tsx
rename to src/query/react-hooks/ApiProvider.tsx
index 0b04c168..ac5d3d22 100644
--- a/query/src/react-hooks/ApiProvider.tsx
+++ b/src/query/react-hooks/ApiProvider.tsx
@@ -1,8 +1,8 @@
-import { configureStore } from '@reduxjs/toolkit';
-import React, { Context } from 'react';
-import { Provider, ReactReduxContextValue } from 'react-redux';
-import { setupListeners } from '../core/setupListeners';
-import { Api } from '../apiTypes';
+import { configureStore } from '@reduxjs/toolkit'
+import React, { Context } from 'react'
+import { Provider, ReactReduxContextValue } from 'react-redux'
+import { setupListeners } from '../core/setupListeners'
+import { Api } from '../apiTypes'
 
 /**
  * Can be used as a `Provider` if you **do not already have a Redux store**.
@@ -28,10 +28,10 @@ import { Api } from '../apiTypes';
  * in that case.
  */
 export function ApiProvider<A extends Api<any, {}, any, any>>(props: {
-  children: any;
-  api: A;
-  setupListeners?: Parameters<typeof setupListeners>[1];
-  context?: Context<ReactReduxContextValue>;
+  children: any
+  api: A
+  setupListeners?: Parameters<typeof setupListeners>[1]
+  context?: Context<ReactReduxContextValue>
 }) {
   const [store] = React.useState(() =>
     configureStore({
@@ -40,13 +40,13 @@ export function ApiProvider<A extends Api<any, {}, any, any>>(props: {
       },
       middleware: (gDM) => gDM().concat(props.api.middleware),
     })
-  );
+  )
   // Adds the event listeners for online/offline/focus/etc
-  setupListeners(store.dispatch, props.setupListeners);
+  setupListeners(store.dispatch, props.setupListeners)
 
   return (
     <Provider store={store} context={props.context}>
       {props.children}
     </Provider>
-  );
+  )
 }
diff --git a/query/src/react-hooks/buildHooks.ts b/src/query/react-hooks/buildHooks.ts
similarity index 65%
rename from query/src/react-hooks/buildHooks.ts
rename to src/query/react-hooks/buildHooks.ts
index 9a38cc12..9e954518 100644
--- a/query/src/react-hooks/buildHooks.ts
+++ b/src/query/react-hooks/buildHooks.ts
@@ -1,33 +1,60 @@
-import { AnyAction, createSelector, ThunkAction, ThunkDispatch } from '@reduxjs/toolkit';
-import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
-import { QueryStatus, QuerySubState, SubscriptionOptions, QueryKeys, RootState } from '../core/apiState';
+import {
+  AnyAction,
+  createSelector,
+  ThunkAction,
+  ThunkDispatch,
+} from '@reduxjs/toolkit'
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import {
+  QueryStatus,
+  QuerySubState,
+  SubscriptionOptions,
+  QueryKeys,
+  RootState,
+} from '../core/apiState'
 import {
   EndpointDefinitions,
   MutationDefinition,
   QueryDefinition,
   QueryArgFrom,
   ResultTypeFrom,
-} from '../endpointDefinitions';
-import { QueryResultSelectorResult, MutationResultSelectorResult, skipSelector } from '../core/buildSelectors';
-import { QueryActionCreatorResult, MutationActionCreatorResult } from '../core/buildInitiate';
-import { shallowEqual } from '../utils';
-import { Api } from '../apiTypes';
-import { Id, NoInfer, Override } from '../tsHelpers';
-import { ApiEndpointMutation, ApiEndpointQuery, CoreModule, PrefetchOptions } from '../core/module';
-import { ReactHooksModuleOptions } from './module';
-import { useShallowStableValue } from './useShallowStableValue';
-import { UninitializedValue, UNINITIALIZED_VALUE } from '../constants';
-
-export interface QueryHooks<Definition extends QueryDefinition<any, any, any, any, any>> {
-  useQuery: UseQuery<Definition>;
-  useLazyQuery: UseLazyQuery<Definition>;
-  useQuerySubscription: UseQuerySubscription<Definition>;
-  useLazyQuerySubscription: UseLazyQuerySubscription<Definition>;
-  useQueryState: UseQueryState<Definition>;
+} from '../endpointDefinitions'
+import {
+  QueryResultSelectorResult,
+  MutationResultSelectorResult,
+  skipSelector,
+} from '../core/buildSelectors'
+import {
+  QueryActionCreatorResult,
+  MutationActionCreatorResult,
+} from '../core/buildInitiate'
+import { shallowEqual } from '../utils'
+import { Api } from '../apiTypes'
+import { Id, NoInfer, Override } from '../tsHelpers'
+import {
+  ApiEndpointMutation,
+  ApiEndpointQuery,
+  CoreModule,
+  PrefetchOptions,
+} from '../core/module'
+import { ReactHooksModuleOptions } from './module'
+import { useShallowStableValue } from './useShallowStableValue'
+import { UninitializedValue, UNINITIALIZED_VALUE } from '../constants'
+
+export interface QueryHooks<
+  Definition extends QueryDefinition<any, any, any, any, any>
+> {
+  useQuery: UseQuery<Definition>
+  useLazyQuery: UseLazyQuery<Definition>
+  useQuerySubscription: UseQuerySubscription<Definition>
+  useLazyQuerySubscription: UseLazyQuerySubscription<Definition>
+  useQueryState: UseQueryState<Definition>
 }
 
-export interface MutationHooks<Definition extends MutationDefinition<any, any, any, any, any>> {
-  useMutation: UseMutation<Definition>;
+export interface MutationHooks<
+  Definition extends MutationDefinition<any, any, any, any, any>
+> {
+  useMutation: UseMutation<Definition>
 }
 
 /**
@@ -38,7 +65,7 @@ export type UseQuery<D extends QueryDefinition<any, any, any, any>> = <
 >(
   arg: QueryArgFrom<D>,
   options?: UseQuerySubscriptionOptions & UseQueryStateOptions<D, R>
-) => UseQueryStateResult<D, R> & ReturnType<UseQuerySubscription<D>>;
+) => UseQueryStateResult<D, R> & ReturnType<UseQuerySubscription<D>>
 
 interface UseQuerySubscriptionOptions extends SubscriptionOptions {
   /**
@@ -73,7 +100,7 @@ interface UseQuerySubscriptionOptions extends SubscriptionOptions {
    * };
    * ```
    */
-  skip?: boolean;
+  skip?: boolean
   /**
    * Defaults to `false`. This setting allows you to control whether if a cached result is already available, RTK Query will only serve a cached result, or if it should `refetch` when set to `true` or if an adequate amount of time has passed since the last successful query result.
    * - `false` - Will not cause a query to be performed _unless_ it does not exist yet.
@@ -82,42 +109,64 @@ interface UseQuerySubscriptionOptions extends SubscriptionOptions {
    *
    * If you specify this option alongside `skip: true`, this **will not be evaluated** until `skip` is false.
    */
-  refetchOnMountOrArgChange?: boolean | number;
+  refetchOnMountOrArgChange?: boolean | number
 }
 
-export type UseQuerySubscription<D extends QueryDefinition<any, any, any, any>> = (
+export type UseQuerySubscription<
+  D extends QueryDefinition<any, any, any, any>
+> = (
   arg: QueryArgFrom<D>,
   options?: UseQuerySubscriptionOptions
-) => Pick<QueryActionCreatorResult<D>, 'refetch'>;
+) => Pick<QueryActionCreatorResult<D>, 'refetch'>
 
-export type UseLazyQueryLastPromiseInfo<D extends QueryDefinition<any, any, any, any>> = {
-  lastArg: QueryArgFrom<D>;
-};
-export type UseLazyQuery<D extends QueryDefinition<any, any, any, any>> = <R = UseQueryStateDefaultResult<D>>(
+export type UseLazyQueryLastPromiseInfo<
+  D extends QueryDefinition<any, any, any, any>
+> = {
+  lastArg: QueryArgFrom<D>
+}
+export type UseLazyQuery<D extends QueryDefinition<any, any, any, any>> = <
+  R = UseQueryStateDefaultResult<D>
+>(
   options?: SubscriptionOptions & Omit<UseQueryStateOptions<D, R>, 'skip'>
-) => [(arg: QueryArgFrom<D>) => void, UseQueryStateResult<D, R>, UseLazyQueryLastPromiseInfo<D>];
-
-export type UseLazyQuerySubscription<D extends QueryDefinition<any, any, any, any>> = (
+) => [
+  (arg: QueryArgFrom<D>) => void,
+  UseQueryStateResult<D, R>,
+  UseLazyQueryLastPromiseInfo<D>
+]
+
+export type UseLazyQuerySubscription<
+  D extends QueryDefinition<any, any, any, any>
+> = (
   options?: SubscriptionOptions
-) => [(arg: QueryArgFrom<D>) => void, QueryArgFrom<D> | UninitializedValue];
+) => [(arg: QueryArgFrom<D>) => void, QueryArgFrom<D> | UninitializedValue]
 
-export type QueryStateSelector<R extends Record<string, any>, D extends QueryDefinition<any, any, any, any>> = (
+export type QueryStateSelector<
+  R extends Record<string, any>,
+  D extends QueryDefinition<any, any, any, any>
+> = (
   state: QueryResultSelectorResult<D>,
   lastResult: R | undefined,
   defaultQueryStateSelector: DefaultQueryStateSelector<D>
-) => R;
+) => R
 
-export type DefaultQueryStateSelector<D extends QueryDefinition<any, any, any, any>> = (
+export type DefaultQueryStateSelector<
+  D extends QueryDefinition<any, any, any, any>
+> = (
   state: QueryResultSelectorResult<D>,
   lastResult: Pick<UseQueryStateDefaultResult<D>, 'data'>
-) => UseQueryStateDefaultResult<D>;
+) => UseQueryStateDefaultResult<D>
 
-export type UseQueryState<D extends QueryDefinition<any, any, any, any>> = <R = UseQueryStateDefaultResult<D>>(
+export type UseQueryState<D extends QueryDefinition<any, any, any, any>> = <
+  R = UseQueryStateDefaultResult<D>
+>(
   arg: QueryArgFrom<D>,
   options?: UseQueryStateOptions<D, R>
-) => UseQueryStateResult<D, R>;
+) => UseQueryStateResult<D, R>
 
-export type UseQueryStateOptions<D extends QueryDefinition<any, any, any, any>, R extends Record<string, any>> = {
+export type UseQueryStateOptions<
+  D extends QueryDefinition<any, any, any, any>,
+  R extends Record<string, any>
+> = {
   /**
    * Prevents a query from automatically running.
    *
@@ -150,7 +199,7 @@ export type UseQueryStateOptions<D extends QueryDefinition<any, any, any, any>,
    * };
    * ```
    */
-  skip?: boolean;
+  skip?: boolean
   /**
    * `selectFromResult` allows you to get a specific segment from a query result in a performant manner.
    * When using this feature, the component will not rerender unless the underlying data of the selected item has changed.
@@ -181,43 +230,58 @@ export type UseQueryStateOptions<D extends QueryDefinition<any, any, any, any>,
    * }
    * ```
    */
-  selectFromResult?: QueryStateSelector<R, D>;
-};
+  selectFromResult?: QueryStateSelector<R, D>
+}
 
-export type UseQueryStateResult<_ extends QueryDefinition<any, any, any, any>, R> = NoInfer<R>;
+export type UseQueryStateResult<
+  _ extends QueryDefinition<any, any, any, any>,
+  R
+> = NoInfer<R>
 
-type UseQueryStateBaseResult<D extends QueryDefinition<any, any, any, any>> = QuerySubState<D> & {
+type UseQueryStateBaseResult<
+  D extends QueryDefinition<any, any, any, any>
+> = QuerySubState<D> & {
   /**
    * Query has not started yet.
    */
-  isUninitialized: false;
+  isUninitialized: false
   /**
    * Query is currently loading for the first time. No data yet.
    */
-  isLoading: false;
+  isLoading: false
   /**
    * Query is currently fetching, but might have data from an earlier request.
    */
-  isFetching: false;
+  isFetching: false
   /**
    * Query has data from a successful load.
    */
-  isSuccess: false;
+  isSuccess: false
   /**
    * Query is currently in "error" state.
    */
-  isError: false;
-};
+  isError: false
+}
 
-type UseQueryStateDefaultResult<D extends QueryDefinition<any, any, any, any>> = Id<
-  | Override<Extract<UseQueryStateBaseResult<D>, { status: QueryStatus.uninitialized }>, { isUninitialized: true }>
+type UseQueryStateDefaultResult<
+  D extends QueryDefinition<any, any, any, any>
+> = Id<
+  | Override<
+      Extract<
+        UseQueryStateBaseResult<D>,
+        { status: QueryStatus.uninitialized }
+      >,
+      { isUninitialized: true }
+    >
   | Override<
       UseQueryStateBaseResult<D>,
       | { isLoading: true; isFetching: boolean; data: undefined }
       | ({ isSuccess: true; isFetching: boolean; error: undefined } & Required<
           Pick<UseQueryStateBaseResult<D>, 'data' | 'fulfilledTimeStamp'>
         >)
-      | ({ isError: true } & Required<Pick<UseQueryStateBaseResult<D>, 'error'>>)
+      | ({ isError: true } & Required<
+          Pick<UseQueryStateBaseResult<D>, 'error'>
+        >)
     >
 > & {
   /**
@@ -225,23 +289,32 @@ type UseQueryStateDefaultResult<D extends QueryDefinition<any, any, any, any>> =
    * please use the `isLoading`, `isFetching`, `isSuccess`, `isError`
    * and `isUninitialized` flags instead
    */
-  status: QueryStatus;
-};
+  status: QueryStatus
+}
 
-export type MutationStateSelector<R extends Record<string, any>, D extends MutationDefinition<any, any, any, any>> = (
+export type MutationStateSelector<
+  R extends Record<string, any>,
+  D extends MutationDefinition<any, any, any, any>
+> = (
   state: MutationResultSelectorResult<D>,
   defaultMutationStateSelector: DefaultMutationStateSelector<D>
-) => R;
+) => R
 
-export type DefaultMutationStateSelector<D extends MutationDefinition<any, any, any, any>> = (
-  state: MutationResultSelectorResult<D>
-) => MutationResultSelectorResult<D>;
+export type DefaultMutationStateSelector<
+  D extends MutationDefinition<any, any, any, any>
+> = (state: MutationResultSelectorResult<D>) => MutationResultSelectorResult<D>
 
-export type UseMutationStateOptions<D extends MutationDefinition<any, any, any, any>, R extends Record<string, any>> = {
-  selectFromResult?: MutationStateSelector<R, D>;
-};
+export type UseMutationStateOptions<
+  D extends MutationDefinition<any, any, any, any>,
+  R extends Record<string, any>
+> = {
+  selectFromResult?: MutationStateSelector<R, D>
+}
 
-export type UseMutationStateResult<_ extends MutationDefinition<any, any, any, any>, R> = NoInfer<R>;
+export type UseMutationStateResult<
+  _ extends MutationDefinition<any, any, any, any>,
+  R
+> = NoInfer<R>
 
 export type UseMutation<D extends MutationDefinition<any, any, any, any>> = <
   R extends Record<string, any> = MutationResultSelectorResult<D>
@@ -277,26 +350,39 @@ export type UseMutation<D extends MutationDefinition<any, any, any, any>> = <
      * }
      * ```
      */
-    unwrap: () => Promise<ResultTypeFrom<D>>;
+    unwrap: () => Promise<ResultTypeFrom<D>>
   },
   UseMutationStateResult<D, R>
-];
+]
 
-const defaultMutationStateSelector: DefaultMutationStateSelector<any> = (currentState) => currentState;
+const defaultMutationStateSelector: DefaultMutationStateSelector<any> = (
+  currentState
+) => currentState
 
-const defaultQueryStateSelector: DefaultQueryStateSelector<any> = (currentState, lastResult) => {
+const defaultQueryStateSelector: DefaultQueryStateSelector<any> = (
+  currentState,
+  lastResult
+) => {
   // data is the last known good request result we have tracked - or if none has been tracked yet the last good result for the current args
-  const data = (currentState.isSuccess ? currentState.data : lastResult?.data) ?? currentState.data;
+  const data =
+    (currentState.isSuccess ? currentState.data : lastResult?.data) ??
+    currentState.data
 
   // isFetching = true any time a request is in flight
-  const isFetching = currentState.isLoading;
+  const isFetching = currentState.isLoading
   // isLoading = true only when loading while no data is present yet (initial load with no data in the cache)
-  const isLoading = !data && isFetching;
+  const isLoading = !data && isFetching
   // isSuccess = true when data is present
-  const isSuccess = currentState.isSuccess || (isFetching && !!data);
-
-  return { ...currentState, data, isFetching, isLoading, isSuccess } as UseQueryStateDefaultResult<any>;
-};
+  const isSuccess = currentState.isSuccess || (isFetching && !!data)
+
+  return {
+    ...currentState,
+    data,
+    isFetching,
+    isLoading,
+    isSuccess,
+  } as UseQueryStateDefaultResult<any>
+}
 
 /**
  * Wrapper around `defaultQueryStateSelector` to be used in `useQuery`.
@@ -304,8 +390,11 @@ const defaultQueryStateSelector: DefaultQueryStateSelector<any> = (currentState,
  * `{ isUninitialized: false, isFetching: true, isLoading: true }`
  * to prevent that the library user has to do an additional check for `isUninitialized`/
  */
-const noPendingQueryStateSelector: DefaultQueryStateSelector<any> = (currentState, lastResult) => {
-  const selected = defaultQueryStateSelector(currentState, lastResult);
+const noPendingQueryStateSelector: DefaultQueryStateSelector<any> = (
+  currentState,
+  lastResult
+) => {
+  const selected = defaultQueryStateSelector(currentState, lastResult)
   if (selected.isUninitialized) {
     return {
       ...selected,
@@ -313,16 +402,16 @@ const noPendingQueryStateSelector: DefaultQueryStateSelector<any> = (currentStat
       isFetching: true,
       isLoading: true,
       status: QueryStatus.pending,
-    };
+    }
   }
-  return selected;
-};
+  return selected
+}
 
 type GenericPrefetchThunk = (
   endpointName: any,
   arg: any,
   options: PrefetchOptions
-) => ThunkAction<void, any, any, AnyAction>;
+) => ThunkAction<void, any, any, AnyAction>
 
 /**
  *
@@ -336,74 +425,90 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
   api,
   moduleOptions: { batch, useDispatch, useSelector },
 }: {
-  api: Api<any, Definitions, any, any, CoreModule>;
-  moduleOptions: Required<ReactHooksModuleOptions>;
+  api: Api<any, Definitions, any, any, CoreModule>
+  moduleOptions: Required<ReactHooksModuleOptions>
 }) {
-  return { buildQueryHooks, buildMutationHook, usePrefetch };
+  return { buildQueryHooks, buildMutationHook, usePrefetch }
 
   function usePrefetch<EndpointName extends QueryKeys<Definitions>>(
     endpointName: EndpointName,
     defaultOptions?: PrefetchOptions
   ) {
-    const dispatch = useDispatch<ThunkDispatch<any, any, AnyAction>>();
-    const stableDefaultOptions = useShallowStableValue(defaultOptions);
+    const dispatch = useDispatch<ThunkDispatch<any, any, AnyAction>>()
+    const stableDefaultOptions = useShallowStableValue(defaultOptions)
 
     return useCallback(
       (arg: any, options?: PrefetchOptions) =>
         dispatch(
-          (api.util.prefetch as GenericPrefetchThunk)(endpointName, arg, { ...stableDefaultOptions, ...options })
+          (api.util.prefetch as GenericPrefetchThunk)(endpointName, arg, {
+            ...stableDefaultOptions,
+            ...options,
+          })
         ),
       [endpointName, dispatch, stableDefaultOptions]
-    );
+    )
   }
 
   function buildQueryHooks(name: string): QueryHooks<any> {
     const useQuerySubscription: UseQuerySubscription<any> = (
       arg: any,
-      { refetchOnReconnect, refetchOnFocus, refetchOnMountOrArgChange, skip = false, pollingInterval = 0 } = {}
+      {
+        refetchOnReconnect,
+        refetchOnFocus,
+        refetchOnMountOrArgChange,
+        skip = false,
+        pollingInterval = 0,
+      } = {}
     ) => {
       const { initiate } = api.endpoints[name] as ApiEndpointQuery<
         QueryDefinition<any, any, any, any, any>,
         Definitions
-      >;
-      const dispatch = useDispatch<ThunkDispatch<any, any, AnyAction>>();
-      const stableArg = useShallowStableValue(arg);
+      >
+      const dispatch = useDispatch<ThunkDispatch<any, any, AnyAction>>()
+      const stableArg = useShallowStableValue(arg)
       const stableSubscriptionOptions = useShallowStableValue({
         refetchOnReconnect,
         refetchOnFocus,
         pollingInterval,
-      });
+      })
 
-      const promiseRef = useRef<QueryActionCreatorResult<any>>();
+      const promiseRef = useRef<QueryActionCreatorResult<any>>()
 
       useEffect(() => {
         if (skip) {
-          return;
+          return
         }
 
-        const lastPromise = promiseRef.current;
-        const lastSubscriptionOptions = promiseRef.current?.subscriptionOptions;
+        const lastPromise = promiseRef.current
+        const lastSubscriptionOptions = promiseRef.current?.subscriptionOptions
 
         if (!lastPromise || lastPromise.arg !== stableArg) {
-          lastPromise?.unsubscribe();
+          lastPromise?.unsubscribe()
           const promise = dispatch(
             initiate(stableArg, {
               subscriptionOptions: stableSubscriptionOptions,
               forceRefetch: refetchOnMountOrArgChange,
             })
-          );
-          promiseRef.current = promise;
+          )
+          promiseRef.current = promise
         } else if (stableSubscriptionOptions !== lastSubscriptionOptions) {
-          lastPromise.updateSubscriptionOptions(stableSubscriptionOptions);
+          lastPromise.updateSubscriptionOptions(stableSubscriptionOptions)
         }
-      }, [dispatch, initiate, refetchOnMountOrArgChange, skip, stableArg, stableSubscriptionOptions]);
+      }, [
+        dispatch,
+        initiate,
+        refetchOnMountOrArgChange,
+        skip,
+        stableArg,
+        stableSubscriptionOptions,
+      ])
 
       useEffect(() => {
         return () => {
-          promiseRef.current?.unsubscribe();
-          promiseRef.current = undefined;
-        };
-      }, []);
+          promiseRef.current?.unsubscribe()
+          promiseRef.current = undefined
+        }
+      }, [])
 
       return useMemo(
         () => ({
@@ -413,8 +518,8 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
           refetch: () => void promiseRef.current?.refetch(),
         }),
         []
-      );
-    };
+      )
+    }
 
     const useLazyQuerySubscription: UseLazyQuerySubscription<any> = ({
       refetchOnReconnect,
@@ -424,154 +529,181 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
       const { initiate } = api.endpoints[name] as ApiEndpointQuery<
         QueryDefinition<any, any, any, any, any>,
         Definitions
-      >;
-      const dispatch = useDispatch<ThunkDispatch<any, any, AnyAction>>();
+      >
+      const dispatch = useDispatch<ThunkDispatch<any, any, AnyAction>>()
 
-      const [arg, setArg] = useState<any>(UNINITIALIZED_VALUE);
-      const promiseRef = useRef<QueryActionCreatorResult<any> | undefined>();
+      const [arg, setArg] = useState<any>(UNINITIALIZED_VALUE)
+      const promiseRef = useRef<QueryActionCreatorResult<any> | undefined>()
 
       const stableSubscriptionOptions = useShallowStableValue({
         refetchOnReconnect,
         refetchOnFocus,
         pollingInterval,
-      });
+      })
 
       useEffect(() => {
-        const lastSubscriptionOptions = promiseRef.current?.subscriptionOptions;
+        const lastSubscriptionOptions = promiseRef.current?.subscriptionOptions
 
         if (stableSubscriptionOptions !== lastSubscriptionOptions) {
-          promiseRef.current?.updateSubscriptionOptions(stableSubscriptionOptions);
+          promiseRef.current?.updateSubscriptionOptions(
+            stableSubscriptionOptions
+          )
         }
-      }, [stableSubscriptionOptions]);
+      }, [stableSubscriptionOptions])
 
-      const subscriptionOptionsRef = useRef(stableSubscriptionOptions);
+      const subscriptionOptionsRef = useRef(stableSubscriptionOptions)
       useEffect(() => {
-        subscriptionOptionsRef.current = stableSubscriptionOptions;
-      }, [stableSubscriptionOptions]);
+        subscriptionOptionsRef.current = stableSubscriptionOptions
+      }, [stableSubscriptionOptions])
 
       const trigger = useCallback(
         function (arg: any, preferCacheValue = false) {
           batch(() => {
-            promiseRef.current?.unsubscribe();
+            promiseRef.current?.unsubscribe()
 
             promiseRef.current = dispatch(
               initiate(arg, {
                 subscriptionOptions: subscriptionOptionsRef.current,
                 forceRefetch: !preferCacheValue,
               })
-            );
-            setArg(arg);
-          });
+            )
+            setArg(arg)
+          })
         },
         [dispatch, initiate]
-      );
+      )
 
       /* cleanup on unmount */
       useEffect(() => {
         return () => {
-          promiseRef?.current?.unsubscribe();
-        };
-      }, []);
+          promiseRef?.current?.unsubscribe()
+        }
+      }, [])
 
       /* if "cleanup on unmount" was triggered from a fast refresh, we want to reinstate the query */
       useEffect(() => {
         if (arg !== UNINITIALIZED_VALUE && !promiseRef.current) {
-          trigger(arg, true);
+          trigger(arg, true)
         }
-      }, [arg, trigger]);
+      }, [arg, trigger])
 
-      return useMemo(() => [trigger, arg], [trigger, arg]);
-    };
+      return useMemo(() => [trigger, arg], [trigger, arg])
+    }
 
     const useQueryState: UseQueryState<any> = (
       arg: any,
-      { skip = false, selectFromResult = defaultQueryStateSelector as QueryStateSelector<any, any> } = {}
+      {
+        skip = false,
+        selectFromResult = defaultQueryStateSelector as QueryStateSelector<
+          any,
+          any
+        >,
+      } = {}
     ) => {
-      const { select } = api.endpoints[name] as ApiEndpointQuery<QueryDefinition<any, any, any, any, any>, Definitions>;
-      const stableArg = useShallowStableValue(arg);
+      const { select } = api.endpoints[name] as ApiEndpointQuery<
+        QueryDefinition<any, any, any, any, any>,
+        Definitions
+      >
+      const stableArg = useShallowStableValue(arg)
 
-      const lastValue = useRef<any>();
+      const lastValue = useRef<any>()
 
       const querySelector = useMemo(
         () =>
           createSelector(
-            [select(skip ? skipSelector : stableArg), (_: any, lastResult: any) => lastResult],
-            (subState, lastResult) => selectFromResult(subState, lastResult, defaultQueryStateSelector)
+            [
+              select(skip ? skipSelector : stableArg),
+              (_: any, lastResult: any) => lastResult,
+            ],
+            (subState, lastResult) =>
+              selectFromResult(subState, lastResult, defaultQueryStateSelector)
           ),
         [select, skip, stableArg, selectFromResult]
-      );
+      )
 
       const currentState = useSelector(
-        (state: RootState<Definitions, any, any>) => querySelector(state, lastValue.current),
+        (state: RootState<Definitions, any, any>) =>
+          querySelector(state, lastValue.current),
         shallowEqual
-      );
+      )
 
       useEffect(() => {
-        lastValue.current = currentState;
-      }, [currentState]);
+        lastValue.current = currentState
+      }, [currentState])
 
-      return currentState;
-    };
+      return currentState
+    }
 
     return {
       useQueryState,
       useQuerySubscription,
       useLazyQuerySubscription,
       useLazyQuery(options) {
-        const [trigger, arg] = useLazyQuerySubscription(options);
+        const [trigger, arg] = useLazyQuerySubscription(options)
         const queryStateResults = useQueryState(arg, {
           ...options,
           skip: arg === UNINITIALIZED_VALUE,
-        });
+        })
 
-        const info = useMemo(() => ({ lastArg: arg }), [arg]);
-        return useMemo(() => [trigger, queryStateResults, info], [trigger, queryStateResults, info]);
+        const info = useMemo(() => ({ lastArg: arg }), [arg])
+        return useMemo(() => [trigger, queryStateResults, info], [
+          trigger,
+          queryStateResults,
+          info,
+        ])
       },
       useQuery(arg, options) {
-        const querySubscriptionResults = useQuerySubscription(arg, options);
+        const querySubscriptionResults = useQuerySubscription(arg, options)
         const queryStateResults = useQueryState(arg, {
-          selectFromResult: options?.skip ? undefined : (noPendingQueryStateSelector as QueryStateSelector<any, any>),
+          selectFromResult: options?.skip
+            ? undefined
+            : (noPendingQueryStateSelector as QueryStateSelector<any, any>),
           ...options,
-        });
-        return useMemo(() => ({ ...queryStateResults, ...querySubscriptionResults }), [
-          queryStateResults,
-          querySubscriptionResults,
-        ]);
+        })
+        return useMemo(
+          () => ({ ...queryStateResults, ...querySubscriptionResults }),
+          [queryStateResults, querySubscriptionResults]
+        )
       },
-    };
+    }
   }
 
   function buildMutationHook(name: string): UseMutation<any> {
-    return ({ selectFromResult = defaultMutationStateSelector as MutationStateSelector<any, any> } = {}) => {
+    return ({
+      selectFromResult = defaultMutationStateSelector as MutationStateSelector<
+        any,
+        any
+      >,
+    } = {}) => {
       const { select, initiate } = api.endpoints[name] as ApiEndpointMutation<
         MutationDefinition<any, any, any, any, any>,
         Definitions
-      >;
-      const dispatch = useDispatch<ThunkDispatch<any, any, AnyAction>>();
-      const [requestId, setRequestId] = useState<string>();
+      >
+      const dispatch = useDispatch<ThunkDispatch<any, any, AnyAction>>()
+      const [requestId, setRequestId] = useState<string>()
 
-      const promiseRef = useRef<MutationActionCreatorResult<any>>();
+      const promiseRef = useRef<MutationActionCreatorResult<any>>()
 
       useEffect(() => {
         return () => {
-          promiseRef.current?.unsubscribe();
-          promiseRef.current = undefined;
-        };
-      }, []);
+          promiseRef.current?.unsubscribe()
+          promiseRef.current = undefined
+        }
+      }, [])
 
       const triggerMutation = useCallback(
         function (arg) {
-          let promise: MutationActionCreatorResult<any>;
+          let promise: MutationActionCreatorResult<any>
           batch(() => {
-            promiseRef?.current?.unsubscribe();
-            promise = dispatch(initiate(arg));
-            promiseRef.current = promise;
-            setRequestId(promise.requestId);
-          });
-          return promise!;
+            promiseRef?.current?.unsubscribe()
+            promise = dispatch(initiate(arg))
+            promiseRef.current = promise
+            setRequestId(promise.requestId)
+          })
+          return promise!
         },
         [dispatch, initiate]
-      );
+      )
 
       const mutationSelector = useMemo(
         () =>
@@ -579,11 +711,14 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
             selectFromResult(subState, defaultMutationStateSelector)
           ),
         [select, requestId, selectFromResult]
-      );
+      )
 
-      const currentState = useSelector(mutationSelector, shallowEqual);
+      const currentState = useSelector(mutationSelector, shallowEqual)
 
-      return useMemo(() => [triggerMutation, currentState], [triggerMutation, currentState]);
-    };
+      return useMemo(() => [triggerMutation, currentState], [
+        triggerMutation,
+        currentState,
+      ])
+    }
   }
 }
diff --git a/src/query/react-hooks/index.ts b/src/query/react-hooks/index.ts
new file mode 100644
index 00000000..67add8e3
--- /dev/null
+++ b/src/query/react-hooks/index.ts
@@ -0,0 +1,7 @@
+import { coreModule } from '../core/module'
+import { buildCreateApi } from '../createApi'
+import { reactHooksModule } from './module'
+
+const createApi = buildCreateApi(coreModule(), reactHooksModule())
+
+export { createApi, reactHooksModule }
diff --git a/query/src/react-hooks/module.ts b/src/query/react-hooks/module.ts
similarity index 64%
rename from query/src/react-hooks/module.ts
rename to src/query/react-hooks/module.ts
index a1d06158..4ad8947b 100644
--- a/query/src/react-hooks/module.ts
+++ b/src/query/react-hooks/module.ts
@@ -1,4 +1,4 @@
-import { buildHooks, MutationHooks, QueryHooks } from './buildHooks';
+import { buildHooks, MutationHooks, QueryHooks } from './buildHooks'
 import {
   EndpointDefinitions,
   QueryDefinition,
@@ -6,24 +6,24 @@ import {
   isQueryDefinition,
   isMutationDefinition,
   QueryArgFrom,
-} from '../endpointDefinitions';
-import { TS41Hooks } from '../ts41Types';
-import { Api, Module } from '../apiTypes';
-import { capitalize } from '../utils';
-import { safeAssign } from '../tsHelpers';
-import { BaseQueryFn } from '../baseQueryTypes';
+} from '../endpointDefinitions'
+import { TS41Hooks } from '../ts41Types'
+import { Api, Module } from '../apiTypes'
+import { capitalize } from '../utils'
+import { safeAssign } from '../tsHelpers'
+import { BaseQueryFn } from '../baseQueryTypes'
 
 import {
   useDispatch as rrUseDispatch,
   useSelector as rrUseSelector,
   useStore as rrUseStore,
   batch as rrBatch,
-} from 'react-redux';
-import { QueryKeys } from '../core/apiState';
-import { PrefetchOptions } from '../core/module';
+} from 'react-redux'
+import { QueryKeys } from '../core/apiState'
+import { PrefetchOptions } from '../core/module'
 
-export const reactHooksModuleName = Symbol();
-export type ReactHooksModule = typeof reactHooksModuleName;
+export const reactHooksModuleName = Symbol()
+export type ReactHooksModule = typeof reactHooksModuleName
 
 declare module '../apiTypes' {
   export interface ApiModules<
@@ -40,42 +40,51 @@ declare module '../apiTypes' {
        *  Endpoints based on the input endpoints provided to `createApi`, containing `select`, `hooks` and `action matchers`.
        */
       endpoints: {
-        [K in keyof Definitions]: Definitions[K] extends QueryDefinition<any, any, any, any, any>
+        [K in keyof Definitions]: Definitions[K] extends QueryDefinition<
+          any,
+          any,
+          any,
+          any,
+          any
+        >
           ? QueryHooks<Definitions[K]>
           : Definitions[K] extends MutationDefinition<any, any, any, any, any>
           ? MutationHooks<Definitions[K]>
-          : never;
-      };
+          : never
+      }
       /**
        * A hook that accepts a string endpoint name, and provides a callback that when called, pre-fetches the data for that endpoint.
        */
       usePrefetch<EndpointName extends QueryKeys<Definitions>>(
         endpointName: EndpointName,
         options?: PrefetchOptions
-      ): (arg: QueryArgFrom<Definitions[EndpointName]>, options?: PrefetchOptions) => void;
-    } & TS41Hooks<Definitions>;
+      ): (
+        arg: QueryArgFrom<Definitions[EndpointName]>,
+        options?: PrefetchOptions
+      ) => void
+    } & TS41Hooks<Definitions>
   }
 }
 
-type RR = typeof import('react-redux');
+type RR = typeof import('react-redux')
 
 export interface ReactHooksModuleOptions {
   /**
    * The version of the `batchedUpdates` function to be used
    */
-  batch?: RR['batch'];
+  batch?: RR['batch']
   /**
    * The version of the `useDispatch` hook to be used
    */
-  useDispatch?: RR['useDispatch'];
+  useDispatch?: RR['useDispatch']
   /**
    * The version of the `useSelector` hook to be used
    */
-  useSelector?: RR['useSelector'];
+  useSelector?: RR['useSelector']
   /**
    * Currently unused - for potential future use
    */
-  useStore?: RR['useStore'];
+  useStore?: RR['useStore']
 }
 
 /**
@@ -100,34 +109,47 @@ export const reactHooksModule = ({
 }: ReactHooksModuleOptions = {}): Module<ReactHooksModule> => ({
   name: reactHooksModuleName,
   init(api, options, context) {
-    const anyApi = (api as any) as Api<any, Record<string, any>, string, string, ReactHooksModule>;
+    const anyApi = (api as any) as Api<
+      any,
+      Record<string, any>,
+      string,
+      string,
+      ReactHooksModule
+    >
     const { buildQueryHooks, buildMutationHook, usePrefetch } = buildHooks({
       api,
       moduleOptions: { batch, useDispatch, useSelector, useStore },
-    });
-    safeAssign(anyApi, { usePrefetch });
-    safeAssign(context, { batch });
+    })
+    safeAssign(anyApi, { usePrefetch })
+    safeAssign(context, { batch })
 
     return {
       injectEndpoint(endpointName, definition) {
         if (isQueryDefinition(definition)) {
-          const { useQuery, useLazyQuery, useQueryState, useQuerySubscription } = buildQueryHooks(endpointName);
+          const {
+            useQuery,
+            useLazyQuery,
+            useQueryState,
+            useQuerySubscription,
+          } = buildQueryHooks(endpointName)
           safeAssign(anyApi.endpoints[endpointName], {
             useQuery,
             useLazyQuery,
             useQueryState,
             useQuerySubscription,
-          });
-          (api as any)[`use${capitalize(endpointName)}Query`] = useQuery;
-          (api as any)[`useLazy${capitalize(endpointName)}Query`] = useLazyQuery;
+          })
+          ;(api as any)[`use${capitalize(endpointName)}Query`] = useQuery
+          ;(api as any)[
+            `useLazy${capitalize(endpointName)}Query`
+          ] = useLazyQuery
         } else if (isMutationDefinition(definition)) {
-          const useMutation = buildMutationHook(endpointName);
+          const useMutation = buildMutationHook(endpointName)
           safeAssign(anyApi.endpoints[endpointName], {
             useMutation,
-          });
-          (api as any)[`use${capitalize(endpointName)}Mutation`] = useMutation;
+          })
+          ;(api as any)[`use${capitalize(endpointName)}Mutation`] = useMutation
         }
       },
-    };
+    }
   },
-});
+})
diff --git a/query/src/react-hooks/useShallowStableValue.ts b/src/query/react-hooks/useShallowStableValue.ts
similarity index 54%
rename from query/src/react-hooks/useShallowStableValue.ts
rename to src/query/react-hooks/useShallowStableValue.ts
index eabbc22a..6f28b0c5 100644
--- a/query/src/react-hooks/useShallowStableValue.ts
+++ b/src/query/react-hooks/useShallowStableValue.ts
@@ -1,13 +1,13 @@
-import { useEffect, useRef } from 'react';
-import { shallowEqual } from '../utils';
+import { useEffect, useRef } from 'react'
+import { shallowEqual } from '../utils'
 
 export function useShallowStableValue<T>(value: T) {
-  const cache = useRef(value);
+  const cache = useRef(value)
   useEffect(() => {
     if (!shallowEqual(cache.current, value)) {
-      cache.current = value;
+      cache.current = value
     }
-  }, [value]);
+  }, [value])
 
-  return shallowEqual(cache.current, value) ? cache.current : value;
+  return shallowEqual(cache.current, value) ? cache.current : value
 }
diff --git a/src/query/react.ts b/src/query/react.ts
new file mode 100644
index 00000000..47227eab
--- /dev/null
+++ b/src/query/react.ts
@@ -0,0 +1,3 @@
+export * from './.'
+export { ApiProvider } from './react-hooks/ApiProvider'
+export { createApi, reactHooksModule } from './react-hooks'
diff --git a/query/src/retry.ts b/src/query/retry.ts
similarity index 67%
rename from query/src/retry.ts
rename to src/query/retry.ts
index 6587f209..244776c1 100644
--- a/query/src/retry.ts
+++ b/src/query/retry.ts
@@ -1,5 +1,5 @@
-import { BaseQueryEnhancer } from './baseQueryTypes';
-import { HandledError } from './HandledError';
+import { BaseQueryEnhancer } from './baseQueryTypes'
+import { HandledError } from './HandledError'
 
 /**
  * Exponential backoff based on the attempt number.
@@ -15,56 +15,64 @@ import { HandledError } from './HandledError';
  * @param maxRetries - Maximum number of retries
  */
 async function defaultBackoff(attempt: number = 0, maxRetries: number = 5) {
-  const attempts = Math.min(attempt, maxRetries);
+  const attempts = Math.min(attempt, maxRetries)
 
-  const timeout = ~~((Math.random() + 0.4) * (300 << attempts)); // Force a positive int in the case we make this an option
-  await new Promise((resolve) => setTimeout((res) => resolve(res), timeout));
+  const timeout = ~~((Math.random() + 0.4) * (300 << attempts)) // Force a positive int in the case we make this an option
+  await new Promise((resolve) => setTimeout((res) => resolve(res), timeout))
 }
 
 interface StaggerOptions {
   /**
    * How many times the query will be retried (default: 5)
    */
-  maxRetries?: number;
+  maxRetries?: number
   /**
    * Function used to determine delay between retries
    */
-  backoff?: (attempt: number, maxRetries: number) => Promise<void>;
+  backoff?: (attempt: number, maxRetries: number) => Promise<void>
 }
 
 function fail(e: any): never {
-  throw Object.assign(new HandledError({ error: e }), { throwImmediately: true });
+  throw Object.assign(new HandledError({ error: e }), {
+    throwImmediately: true,
+  })
 }
 
-const retryWithBackoff: BaseQueryEnhancer<unknown, StaggerOptions, StaggerOptions | void> = (
-  baseQuery,
-  defaultOptions
-) => async (args, api, extraOptions) => {
-  const options = { maxRetries: 5, backoff: defaultBackoff, ...defaultOptions, ...extraOptions };
-  let retry = 0;
+const retryWithBackoff: BaseQueryEnhancer<
+  unknown,
+  StaggerOptions,
+  StaggerOptions | void
+> = (baseQuery, defaultOptions) => async (args, api, extraOptions) => {
+  const options = {
+    maxRetries: 5,
+    backoff: defaultBackoff,
+    ...defaultOptions,
+    ...extraOptions,
+  }
+  let retry = 0
 
   while (true) {
     try {
-      const result = await baseQuery(args, api, extraOptions);
+      const result = await baseQuery(args, api, extraOptions)
       // baseQueries _should_ return an error property, so we should check for that and throw it to continue retrying
       if (result.error) {
-        throw new HandledError(result);
+        throw new HandledError(result)
       }
-      return result;
+      return result
     } catch (e) {
-      retry++;
+      retry++
       if (e.throwImmediately || retry > options.maxRetries) {
         if (e instanceof HandledError) {
-          return e.value;
+          return e.value
         }
 
         // We don't know what this is, so we have to rethrow it
-        throw e;
+        throw e
       }
-      await options.backoff(retry, options.maxRetries);
+      await options.backoff(retry, options.maxRetries)
     }
   }
-};
+}
 
 /**
  * A utility that can wrap `baseQuery` in the API definition to provide retries with a basic exponential backoff.
@@ -92,4 +100,4 @@ const retryWithBackoff: BaseQueryEnhancer<unknown, StaggerOptions, StaggerOption
  * export const { useGetPostsQuery, useGetPostQuery } = api;
  * ```
  */
-export const retry = Object.assign(retryWithBackoff, { fail });
+export const retry = Object.assign(retryWithBackoff, { fail })
diff --git a/query/src/ts41Types.ts b/src/query/ts41Types.ts
similarity index 71%
rename from query/src/ts41Types.ts
rename to src/query/ts41Types.ts
index 34f06d26..27a55f37 100644
--- a/query/src/ts41Types.ts
+++ b/src/query/ts41Types.ts
@@ -1,25 +1,32 @@
-import { UseMutation, UseLazyQuery, UseQuery } from './react-hooks/buildHooks';
-import { DefinitionType, EndpointDefinitions, MutationDefinition, QueryDefinition } from './endpointDefinitions';
+import { UseMutation, UseLazyQuery, UseQuery } from './react-hooks/buildHooks'
+import {
+  DefinitionType,
+  EndpointDefinitions,
+  MutationDefinition,
+  QueryDefinition,
+} from './endpointDefinitions'
 
-export type TS41Hooks<Definitions extends EndpointDefinitions> = keyof Definitions extends infer Keys
+export type TS41Hooks<
+  Definitions extends EndpointDefinitions
+> = keyof Definitions extends infer Keys
   ? Keys extends string
     ? Definitions[Keys] extends { type: DefinitionType.query }
       ? {
           [K in Keys as `use${Capitalize<K>}Query`]: UseQuery<
             Extract<Definitions[K], QueryDefinition<any, any, any, any>>
-          >;
+          >
         } &
           {
             [K in Keys as `useLazy${Capitalize<K>}Query`]: UseLazyQuery<
               Extract<Definitions[K], QueryDefinition<any, any, any, any>>
-            >;
+            >
           }
       : Definitions[Keys] extends { type: DefinitionType.mutation }
       ? {
           [K in Keys as `use${Capitalize<K>}Mutation`]: UseMutation<
             Extract<Definitions[K], MutationDefinition<any, any, any, any>>
-          >;
+          >
         }
       : never
     : never
-  : never;
+  : never
diff --git a/src/query/tsHelpers.ts b/src/query/tsHelpers.ts
new file mode 100644
index 00000000..af79f5bd
--- /dev/null
+++ b/src/query/tsHelpers.ts
@@ -0,0 +1,49 @@
+export type Id<T> = { [K in keyof T]: T[K] } & {}
+export type WithRequiredProp<T, K extends keyof T> = Omit<T, K> &
+  Required<Pick<T, K>>
+export type Override<T1, T2> = T2 extends any ? Omit<T1, keyof T2> & T2 : never
+export function assertCast<T>(v: any): asserts v is T {}
+
+export function safeAssign<T extends object>(
+  target: T,
+  ...args: Array<Partial<NoInfer<T>>>
+) {
+  Object.assign(target, ...args)
+}
+
+/**
+ * Convert a Union type `(A|B)` to an intersection type `(A&B)`
+ */
+export type UnionToIntersection<U> = (
+  U extends any ? (k: U) => void : never
+) extends (k: infer I) => void
+  ? I
+  : never
+
+export type NonOptionalKeys<T> = {
+  [K in keyof T]-?: undefined extends T[K] ? never : K
+}[keyof T]
+
+export type HasRequiredProps<T, True, False> = NonOptionalKeys<T> extends never
+  ? False
+  : True
+
+export type OptionalIfAllPropsOptional<T> = HasRequiredProps<T, T, T | never>
+
+export type NoInfer<T> = [T][T extends any ? 0 : never]
+
+export type UnwrapPromise<T> = T extends PromiseLike<infer V> ? V : T
+
+export type MaybePromise<T> = T | PromiseLike<T>
+
+export type OmitFromUnion<T, K extends keyof T> = T extends any
+  ? Omit<T, K>
+  : never
+
+export type IsAny<T, True, False = never> = true | false extends (
+  T extends never ? true : false
+)
+  ? True
+  : False
+
+export type CastAny<T, CastTo> = IsAny<T, CastTo, T>
diff --git a/src/query/utils/capitalize.ts b/src/query/utils/capitalize.ts
new file mode 100644
index 00000000..cb73b8cb
--- /dev/null
+++ b/src/query/utils/capitalize.ts
@@ -0,0 +1,3 @@
+export function capitalize(str: string) {
+  return str.replace(str[0], str[0].toUpperCase())
+}
diff --git a/src/query/utils/copyWithStructuralSharing.ts b/src/query/utils/copyWithStructuralSharing.ts
new file mode 100644
index 00000000..2d6fdc30
--- /dev/null
+++ b/src/query/utils/copyWithStructuralSharing.ts
@@ -0,0 +1,27 @@
+import { isPlainObject as _iPO } from '@reduxjs/toolkit'
+
+// remove type guard
+const isPlainObject: (_: any) => boolean = _iPO
+
+export function copyWithStructuralSharing<T>(oldObj: any, newObj: T): T
+export function copyWithStructuralSharing(oldObj: any, newObj: any): any {
+  if (
+    oldObj === newObj ||
+    !(
+      (isPlainObject(oldObj) && isPlainObject(newObj)) ||
+      (Array.isArray(oldObj) && Array.isArray(newObj))
+    )
+  ) {
+    return newObj
+  }
+  const newKeys = Object.keys(newObj)
+  const oldKeys = Object.keys(oldObj)
+
+  let isSameObject = newKeys.length === oldKeys.length
+  const mergeObj: any = Array.isArray(newObj) ? [] : {}
+  for (const key of newKeys) {
+    mergeObj[key] = copyWithStructuralSharing(oldObj[key], newObj[key])
+    if (isSameObject) isSameObject = oldObj[key] === mergeObj[key]
+  }
+  return isSameObject ? oldObj : mergeObj
+}
diff --git a/query/src/utils/flatten.ts b/src/query/utils/flatten.ts
similarity index 76%
rename from query/src/utils/flatten.ts
rename to src/query/utils/flatten.ts
index 9d3aae3a..e80e4732 100644
--- a/query/src/utils/flatten.ts
+++ b/src/query/utils/flatten.ts
@@ -3,4 +3,4 @@
  * @param arr An array like [1,2,3,[1,2]]
  * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat
  */
-export const flatten = (arr: any[]) => [].concat(...arr);
+export const flatten = (arr: any[]) => [].concat(...arr)
diff --git a/src/query/utils/index.ts b/src/query/utils/index.ts
new file mode 100644
index 00000000..5c993785
--- /dev/null
+++ b/src/query/utils/index.ts
@@ -0,0 +1,9 @@
+export * from './isAbsoluteUrl'
+export * from './isValidUrl'
+export * from './joinUrls'
+export * from './flatten'
+export * from './shallowEqual'
+export * from './capitalize'
+export * from './isOnline'
+export * from './isDocumentVisible'
+export * from './copyWithStructuralSharing'
diff --git a/query/src/utils/isAbsoluteUrl.ts b/src/query/utils/isAbsoluteUrl.ts
similarity index 77%
rename from query/src/utils/isAbsoluteUrl.ts
rename to src/query/utils/isAbsoluteUrl.ts
index 87e17011..3ad518db 100644
--- a/query/src/utils/isAbsoluteUrl.ts
+++ b/src/query/utils/isAbsoluteUrl.ts
@@ -5,5 +5,5 @@
  */
 
 export function isAbsoluteUrl(url: string) {
-  return new RegExp(`(^|:)//`).test(url);
+  return new RegExp(`(^|:)//`).test(url)
 }
diff --git a/query/src/utils/isDocumentVisible.ts b/src/query/utils/isDocumentVisible.ts
similarity index 84%
rename from query/src/utils/isDocumentVisible.ts
rename to src/query/utils/isDocumentVisible.ts
index 517cde33..7b6dbddb 100644
--- a/query/src/utils/isDocumentVisible.ts
+++ b/src/query/utils/isDocumentVisible.ts
@@ -5,8 +5,8 @@
 export function isDocumentVisible(): boolean {
   // `document` may not exist in non-browser envs (like RN)
   if (typeof document === 'undefined') {
-    return true;
+    return true
   }
   // Match true for visible, prerender, undefined
-  return document.visibilityState !== 'hidden';
+  return document.visibilityState !== 'hidden'
 }
diff --git a/query/src/utils/isOnline.ts b/src/query/utils/isOnline.ts
similarity index 70%
rename from query/src/utils/isOnline.ts
rename to src/query/utils/isOnline.ts
index e34ca81f..af34850c 100644
--- a/query/src/utils/isOnline.ts
+++ b/src/query/utils/isOnline.ts
@@ -4,5 +4,9 @@
  */
 export function isOnline() {
   // We set the default config value in the store, so we'd need to check for this in a SSR env
-  return typeof navigator === 'undefined' ? true : navigator.onLine === undefined ? true : navigator.onLine;
+  return typeof navigator === 'undefined'
+    ? true
+    : navigator.onLine === undefined
+    ? true
+    : navigator.onLine
 }
diff --git a/query/src/utils/isValidUrl.ts b/src/query/utils/isValidUrl.ts
similarity index 58%
rename from query/src/utils/isValidUrl.ts
rename to src/query/utils/isValidUrl.ts
index 7387a16e..05752d79 100644
--- a/query/src/utils/isValidUrl.ts
+++ b/src/query/utils/isValidUrl.ts
@@ -1,9 +1,9 @@
 export function isValidUrl(string: string) {
   try {
-    new URL(string);
+    new URL(string)
   } catch (_) {
-    return false;
+    return false
   }
 
-  return true;
+  return true
 }
diff --git a/src/query/utils/joinUrls.ts b/src/query/utils/joinUrls.ts
new file mode 100644
index 00000000..f6753386
--- /dev/null
+++ b/src/query/utils/joinUrls.ts
@@ -0,0 +1,25 @@
+import { isAbsoluteUrl } from './isAbsoluteUrl'
+
+const withoutTrailingSlash = (url: string) => url.replace(/\/$/, '')
+const withoutLeadingSlash = (url: string) => url.replace(/^\//, '')
+
+export function joinUrls(
+  base: string | undefined,
+  url: string | undefined
+): string {
+  if (!base) {
+    return url!
+  }
+  if (!url) {
+    return base
+  }
+
+  if (isAbsoluteUrl(url)) {
+    return url
+  }
+
+  base = withoutTrailingSlash(base)
+  url = withoutLeadingSlash(url)
+
+  return `${base}/${url}`
+}
diff --git a/src/query/utils/shallowEqual.ts b/src/query/utils/shallowEqual.ts
new file mode 100644
index 00000000..fe752c38
--- /dev/null
+++ b/src/query/utils/shallowEqual.ts
@@ -0,0 +1,38 @@
+/* eslint-disable no-self-compare */
+
+function is(x: any, y: any) {
+  if (x === y) {
+    return x !== 0 || y !== 0 || 1 / x === 1 / y
+  } else {
+    return x !== x && y !== y
+  }
+}
+
+export function shallowEqual(objA: any, objB: any) {
+  if (is(objA, objB)) return true
+
+  if (
+    typeof objA !== 'object' ||
+    objA === null ||
+    typeof objB !== 'object' ||
+    objB === null
+  ) {
+    return false
+  }
+
+  const keysA = Object.keys(objA)
+  const keysB = Object.keys(objB)
+
+  if (keysA.length !== keysB.length) return false
+
+  for (let i = 0; i < keysA.length; i++) {
+    if (
+      !Object.prototype.hasOwnProperty.call(objB, keysA[i]) ||
+      !is(objA[keysA[i]], objB[keysA[i]])
+    ) {
+      return false
+    }
+  }
+
+  return true
+}
diff --git a/tsconfig.json b/tsconfig.json
index c95b8630..11191530 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -7,7 +7,7 @@
     "src"
   ],
   "exclude": [
-    "src/*.test.ts",
+    "src/**/*.test.ts",
     "src/query"
   ]
 }
\ No newline at end of file


Test patch:
diff --git a/src/entities/entity_state.test.ts b/src/entities/tests/entity_state.test.ts
similarity index 93%
rename from src/entities/entity_state.test.ts
rename to src/entities/tests/entity_state.test.ts
index 298a1218..d840d134 100644
--- a/src/entities/entity_state.test.ts
+++ b/src/entities/tests/entity_state.test.ts
@@ -1,6 +1,6 @@
-import { createEntityAdapter, EntityAdapter } from './index'
-import { PayloadAction, createAction } from '../createAction'
-import { createSlice } from '../createSlice'
+import { createEntityAdapter, EntityAdapter } from '../index'
+import { PayloadAction, createAction } from '../../createAction'
+import { createSlice } from '../../createSlice'
 import { BookModel } from './fixtures/book'
 
 describe('Entity State', () => {
diff --git a/src/entities/fixtures/book.ts b/src/entities/tests/fixtures/book.ts
similarity index 100%
rename from src/entities/fixtures/book.ts
rename to src/entities/tests/fixtures/book.ts
diff --git a/src/entities/sorted_state_adapter.test.ts b/src/entities/tests/sorted_state_adapter.test.ts
similarity index 99%
rename from src/entities/sorted_state_adapter.test.ts
rename to src/entities/tests/sorted_state_adapter.test.ts
index 81db212f..5a5f7550 100644
--- a/src/entities/sorted_state_adapter.test.ts
+++ b/src/entities/tests/sorted_state_adapter.test.ts
@@ -1,6 +1,6 @@
-import { EntityStateAdapter, EntityState } from './models'
-import { createEntityAdapter } from './create_adapter'
-import { createAction } from '../createAction'
+import { EntityStateAdapter, EntityState } from '../models'
+import { createEntityAdapter } from '../create_adapter'
+import { createAction } from '../../createAction'
 import {
   BookModel,
   TheGreatGatsby,
@@ -8,7 +8,7 @@ import {
   AnimalFarm,
   TheHobbit,
 } from './fixtures/book'
-import { createNextState } from '..'
+import { createNextState } from '../..'
 
 describe('Sorted State Adapter', () => {
   let adapter: EntityStateAdapter<BookModel>
diff --git a/src/entities/state_adapter.test.ts b/src/entities/tests/state_adapter.test.ts
similarity index 90%
rename from src/entities/state_adapter.test.ts
rename to src/entities/tests/state_adapter.test.ts
index 4d72f51b..e1c3fa8e 100644
--- a/src/entities/state_adapter.test.ts
+++ b/src/entities/tests/state_adapter.test.ts
@@ -1,7 +1,7 @@
-import { createEntityAdapter, EntityAdapter } from './index'
-import { PayloadAction } from '../createAction'
-import { configureStore } from '../configureStore'
-import { createSlice } from '../createSlice'
+import { createEntityAdapter, EntityAdapter } from '../index'
+import { PayloadAction } from '../../createAction'
+import { configureStore } from '../../configureStore'
+import { createSlice } from '../../createSlice'
 import { BookModel } from './fixtures/book'
 
 describe('createStateOperator', () => {
diff --git a/src/entities/state_selectors.test.ts b/src/entities/tests/state_selectors.test.ts
similarity index 98%
rename from src/entities/state_selectors.test.ts
rename to src/entities/tests/state_selectors.test.ts
index d0b1f986..451dc449 100644
--- a/src/entities/state_selectors.test.ts
+++ b/src/entities/tests/state_selectors.test.ts
@@ -1,5 +1,5 @@
-import { createEntityAdapter, EntityAdapter, EntityState } from './index'
-import { EntitySelectors } from './models'
+import { createEntityAdapter, EntityAdapter, EntityState } from '../index'
+import { EntitySelectors } from '../models'
 import {
   BookModel,
   AClockworkOrange,
diff --git a/src/entities/unsorted_state_adapter.test.ts b/src/entities/tests/unsorted_state_adapter.test.ts
similarity index 99%
rename from src/entities/unsorted_state_adapter.test.ts
rename to src/entities/tests/unsorted_state_adapter.test.ts
index 21c789e7..643527b1 100644
--- a/src/entities/unsorted_state_adapter.test.ts
+++ b/src/entities/tests/unsorted_state_adapter.test.ts
@@ -1,5 +1,5 @@
-import { EntityStateAdapter, EntityState } from './models'
-import { createEntityAdapter } from './create_adapter'
+import { EntityStateAdapter, EntityState } from '../models'
+import { createEntityAdapter } from '../create_adapter'
 import {
   BookModel,
   TheGreatGatsby,
@@ -7,7 +7,7 @@ import {
   AnimalFarm,
   TheHobbit,
 } from './fixtures/book'
-import { createNextState } from '..'
+import { createNextState } from '../..'
 
 describe('Unsorted State Adapter', () => {
   let adapter: EntityStateAdapter<BookModel>
diff --git a/src/__snapshots__/createAsyncThunk.test.ts.snap b/src/tests/__snapshots__/createAsyncThunk.test.ts.snap
similarity index 100%
rename from src/__snapshots__/createAsyncThunk.test.ts.snap
rename to src/tests/__snapshots__/createAsyncThunk.test.ts.snap
diff --git a/src/__snapshots__/serializableStateInvariantMiddleware.test.ts.snap b/src/tests/__snapshots__/serializableStateInvariantMiddleware.test.ts.snap
similarity index 100%
rename from src/__snapshots__/serializableStateInvariantMiddleware.test.ts.snap
rename to src/tests/__snapshots__/serializableStateInvariantMiddleware.test.ts.snap
diff --git a/src/combinedTest.test.ts b/src/tests/combinedTest.test.ts
similarity index 90%
rename from src/combinedTest.test.ts
rename to src/tests/combinedTest.test.ts
index 03374f38..6b95423c 100644
--- a/src/combinedTest.test.ts
+++ b/src/tests/combinedTest.test.ts
@@ -1,10 +1,10 @@
-import { createAsyncThunk } from './createAsyncThunk'
-import { createAction, PayloadAction } from './createAction'
-import { createSlice } from './createSlice'
-import { configureStore } from './configureStore'
-import { createEntityAdapter } from './entities/create_adapter'
-import { EntityAdapter } from './entities/models'
-import { BookModel } from './entities/fixtures/book'
+import { createAsyncThunk } from '../createAsyncThunk'
+import { createAction, PayloadAction } from '../createAction'
+import { createSlice } from '../createSlice'
+import { configureStore } from '../configureStore'
+import { createEntityAdapter } from '../entities/create_adapter'
+import { EntityAdapter } from '../entities/models'
+import { BookModel } from '../entities/tests/fixtures/book'
 
 describe('Combined entity slice', () => {
   let adapter: EntityAdapter<BookModel>
diff --git a/src/configureStore.test.ts b/src/tests/configureStore.test.ts
similarity index 98%
rename from src/configureStore.test.ts
rename to src/tests/configureStore.test.ts
index f3705d4f..ed1fca7a 100644
--- a/src/configureStore.test.ts
+++ b/src/tests/configureStore.test.ts
@@ -1,6 +1,6 @@
-import { configureStore } from './configureStore'
+import { configureStore } from '../configureStore'
 import * as redux from 'redux'
-import * as devtools from './devtoolsExtension'
+import * as devtools from '../devtoolsExtension'
 import { StoreEnhancer, StoreEnhancerStoreCreator } from 'redux'
 
 describe('configureStore', () => {
diff --git a/src/createAction.test.ts b/src/tests/createAction.test.ts
similarity index 98%
rename from src/createAction.test.ts
rename to src/tests/createAction.test.ts
index 79f68767..ecd4b42f 100644
--- a/src/createAction.test.ts
+++ b/src/tests/createAction.test.ts
@@ -1,4 +1,4 @@
-import { createAction, getType } from './createAction'
+import { createAction, getType } from '../createAction'
 
 describe('createAction', () => {
   it('should create an action', () => {
diff --git a/src/createAsyncThunk.test.ts b/src/tests/createAsyncThunk.test.ts
similarity index 99%
rename from src/createAsyncThunk.test.ts
rename to src/tests/createAsyncThunk.test.ts
index dfe9d458..b444b782 100644
--- a/src/createAsyncThunk.test.ts
+++ b/src/tests/createAsyncThunk.test.ts
@@ -2,8 +2,8 @@ import {
   createAsyncThunk,
   miniSerializeError,
   unwrapResult,
-} from './createAsyncThunk'
-import { configureStore } from './configureStore'
+} from '../createAsyncThunk'
+import { configureStore } from '../configureStore'
 import { AnyAction } from 'redux'
 
 import {
@@ -435,7 +435,7 @@ describe('createAsyncThunk with abortController', () => {
 
   describe('behaviour with missing AbortController', () => {
     let keepAbortController: typeof window['AbortController']
-    let freshlyLoadedModule: typeof import('./createAsyncThunk')
+    let freshlyLoadedModule: typeof import('../createAsyncThunk')
     let restore: () => void
     let nodeEnv: string
 
@@ -443,7 +443,7 @@ describe('createAsyncThunk with abortController', () => {
       keepAbortController = window.AbortController
       delete (window as any).AbortController
       jest.resetModules()
-      freshlyLoadedModule = require('./createAsyncThunk')
+      freshlyLoadedModule = require('../createAsyncThunk')
       restore = mockConsole(createConsole())
       nodeEnv = process.env.NODE_ENV!
       process.env.NODE_ENV = 'development'
diff --git a/src/createDraftSafeSelector.test.ts b/src/tests/createDraftSafeSelector.test.ts
similarity index 93%
rename from src/createDraftSafeSelector.test.ts
rename to src/tests/createDraftSafeSelector.test.ts
index db25ce07..d4f70fab 100644
--- a/src/createDraftSafeSelector.test.ts
+++ b/src/tests/createDraftSafeSelector.test.ts
@@ -1,5 +1,5 @@
 import { createSelector } from 'reselect'
-import { createDraftSafeSelector } from './createDraftSafeSelector'
+import { createDraftSafeSelector } from '../createDraftSafeSelector'
 import { produce } from 'immer'
 
 type State = { value: number }
diff --git a/src/createReducer.test.ts b/src/tests/createReducer.test.ts
similarity index 98%
rename from src/createReducer.test.ts
rename to src/tests/createReducer.test.ts
index 609ea34c..e1bb80ba 100644
--- a/src/createReducer.test.ts
+++ b/src/tests/createReducer.test.ts
@@ -1,6 +1,6 @@
-import { createReducer, CaseReducer } from './createReducer'
-import { PayloadAction, createAction } from './createAction'
-import { createNextState, Draft } from './'
+import { createReducer, CaseReducer } from '../createReducer'
+import { PayloadAction, createAction } from '../createAction'
+import { createNextState, Draft } from '..'
 import { Reducer, AnyAction } from 'redux'
 
 interface Todo {
@@ -63,7 +63,7 @@ describe('createReducer', () => {
     })
 
     test('Freezes data in production', () => {
-      const { createReducer } = require('./createReducer')
+      const { createReducer } = require('../createReducer')
       const addTodo: AddTodoReducer = (state, action) => {
         const { newTodo } = action.payload
         state.push({ ...newTodo, completed: false })
diff --git a/src/createSlice.test.ts b/src/tests/createSlice.test.ts
similarity index 98%
rename from src/createSlice.test.ts
rename to src/tests/createSlice.test.ts
index bd95a810..1baad4c7 100644
--- a/src/createSlice.test.ts
+++ b/src/tests/createSlice.test.ts
@@ -1,5 +1,5 @@
-import { createSlice } from './createSlice'
-import { createAction, PayloadAction } from './createAction'
+import { createSlice } from '../createSlice'
+import { createAction, PayloadAction } from '../createAction'
 
 describe('createSlice', () => {
   describe('when slice is undefined', () => {
diff --git a/src/getDefaultMiddleware.test.ts b/src/tests/getDefaultMiddleware.test.ts
similarity index 99%
rename from src/getDefaultMiddleware.test.ts
rename to src/tests/getDefaultMiddleware.test.ts
index 199c7493..ca7078f4 100644
--- a/src/getDefaultMiddleware.test.ts
+++ b/src/tests/getDefaultMiddleware.test.ts
@@ -1,5 +1,5 @@
 import { AnyAction, Middleware } from 'redux'
-import { getDefaultMiddleware, MiddlewareArray, configureStore } from '.'
+import { getDefaultMiddleware, MiddlewareArray, configureStore } from '..'
 import thunk, { ThunkAction } from 'redux-thunk'
 
 describe('getDefaultMiddleware', () => {
diff --git a/src/immutableStateInvariantMiddleware.test.ts b/src/tests/immutableStateInvariantMiddleware.test.ts
similarity index 99%
rename from src/immutableStateInvariantMiddleware.test.ts
rename to src/tests/immutableStateInvariantMiddleware.test.ts
index fb2ba1ee..080d25f7 100644
--- a/src/immutableStateInvariantMiddleware.test.ts
+++ b/src/tests/immutableStateInvariantMiddleware.test.ts
@@ -4,7 +4,7 @@ import {
   isImmutableDefault,
   trackForMutations,
   ImmutableStateInvariantMiddlewareOptions,
-} from './immutableStateInvariantMiddleware'
+} from '../immutableStateInvariantMiddleware'
 import { mockConsole, createConsole, getLog } from 'console-testing-library'
 
 describe('createImmutableStateInvariantMiddleware', () => {
diff --git a/src/isPlainObject.test.ts b/src/tests/isPlainObject.test.ts
similarity index 93%
rename from src/isPlainObject.test.ts
rename to src/tests/isPlainObject.test.ts
index 6fb99021..178ad487 100644
--- a/src/isPlainObject.test.ts
+++ b/src/tests/isPlainObject.test.ts
@@ -1,4 +1,4 @@
-import isPlainObject from './isPlainObject'
+import isPlainObject from '../isPlainObject'
 import vm from 'vm'
 
 describe('isPlainObject', () => {
diff --git a/src/matchers.test.ts b/src/tests/matchers.test.ts
similarity index 98%
rename from src/matchers.test.ts
rename to src/tests/matchers.test.ts
index 5644d1c5..7b8b2080 100644
--- a/src/matchers.test.ts
+++ b/src/tests/matchers.test.ts
@@ -6,10 +6,10 @@ import {
   isPending,
   isRejected,
   isRejectedWithValue,
-} from './matchers'
-import { createAction } from './createAction'
-import { createAsyncThunk } from './createAsyncThunk'
-import { createReducer } from './createReducer'
+} from '../matchers'
+import { createAction } from '../createAction'
+import { createAsyncThunk } from '../createAsyncThunk'
+import { createReducer } from '../createReducer'
 import { ThunkAction } from 'redux-thunk'
 import { AnyAction } from 'redux'
 
diff --git a/src/serializableStateInvariantMiddleware.test.ts b/src/tests/serializableStateInvariantMiddleware.test.ts
similarity index 99%
rename from src/serializableStateInvariantMiddleware.test.ts
rename to src/tests/serializableStateInvariantMiddleware.test.ts
index 8f2bbec4..7929fbcb 100644
--- a/src/serializableStateInvariantMiddleware.test.ts
+++ b/src/tests/serializableStateInvariantMiddleware.test.ts
@@ -4,13 +4,13 @@ import {
   createConsole,
   getLog,
 } from 'console-testing-library/pure'
-import { configureStore } from './configureStore'
+import { configureStore } from '../configureStore'
 
 import {
   createSerializableStateInvariantMiddleware,
   findNonSerializableValue,
   isPlain,
-} from './serializableStateInvariantMiddleware'
+} from '../serializableStateInvariantMiddleware'
 
 // Mocking console
 let restore = () => {}