diff --git a/.changeset/true-cameras-wash.md b/.changeset/true-cameras-wash.md
new file mode 100644
index 00000000000..84fbe3fa981
--- /dev/null
+++ b/.changeset/true-cameras-wash.md
@@ -0,0 +1,5 @@
+---
+'@tanstack/query-core': minor
+---
+
+add query and infiniteQuery methods, deprecate old imperative methods
diff --git a/packages/angular-query-experimental/src/__tests__/infinite-query-options.test-d.ts b/packages/angular-query-experimental/src/__tests__/infinite-query-options.test-d.ts
index d50b8c6b814..11c2482bf8a 100644
--- a/packages/angular-query-experimental/src/__tests__/infinite-query-options.test-d.ts
+++ b/packages/angular-query-experimental/src/__tests__/infinite-query-options.test-d.ts
@@ -67,6 +67,34 @@ describe('infiniteQueryOptions', () => {
 
     expectTypeOf(data).toEqualTypeOf<InfiniteData<string, number>>()
   })
+
+  it('should work when passed to infiniteQuery', async () => {
+    const options = infiniteQueryOptions({
+      queryKey: ['key'],
+      queryFn: () => Promise.resolve('string'),
+      getNextPageParam: () => 1,
+      initialPageParam: 1,
+    })
+
+    const data = await new QueryClient().infiniteQuery(options)
+
+    expectTypeOf(data).toEqualTypeOf<InfiniteData<string, number>>()
+  })
+
+  it('should work when passed to infiniteQuery with select', async () => {
+    const options = infiniteQueryOptions({
+      queryKey: ['key'],
+      queryFn: () => Promise.resolve('string'),
+      getNextPageParam: () => 1,
+      initialPageParam: 1,
+      select: (data) => data.pages,
+    })
+
+    const data = await new QueryClient().infiniteQuery(options)
+
+    expectTypeOf(data).toEqualTypeOf<Array<string>>()
+  })
+
   it('should tag the queryKey with the result type of the QueryFn', () => {
     const key = queryKey()
     const { queryKey: tagged } = infiniteQueryOptions({
diff --git a/packages/angular-query-experimental/src/__tests__/query-options.test-d.ts b/packages/angular-query-experimental/src/__tests__/query-options.test-d.ts
index 8642cf07d72..049003797c6 100644
--- a/packages/angular-query-experimental/src/__tests__/query-options.test-d.ts
+++ b/packages/angular-query-experimental/src/__tests__/query-options.test-d.ts
@@ -67,6 +67,27 @@ it('should work when passed to fetchQuery', () => {
   assertType<Promise<number>>(data)
 })
 
+it('should work when passed to query', () => {
+  const options = queryOptions({
+    queryKey: ['key'],
+    queryFn: () => Promise.resolve(5),
+  })
+
+  const data = new QueryClient().query(options)
+  assertType<Promise<number>>(data)
+})
+
+it('should work when passed to query with select', () => {
+  const options = queryOptions({
+    queryKey: ['key'],
+    queryFn: () => Promise.resolve(5),
+    select: (data) => data.toString(),
+  })
+
+  const data = new QueryClient().query(options)
+  assertType<Promise<string>>(data)
+})
+
 it('should tag the queryKey with the result type of the QueryFn', () => {
   const key = queryKey()
   const { queryKey: tagged } = queryOptions({
diff --git a/packages/query-core/src/__tests__/queryClient.test-d.tsx b/packages/query-core/src/__tests__/queryClient.test-d.tsx
index 4cd092ddd64..a88544075b2 100644
--- a/packages/query-core/src/__tests__/queryClient.test-d.tsx
+++ b/packages/query-core/src/__tests__/queryClient.test-d.tsx
@@ -1,6 +1,7 @@
 import { assertType, describe, expectTypeOf, it } from 'vitest'
 import { queryKey } from '@tanstack/query-test-utils'
 import { QueryClient } from '../queryClient'
+import { skipToken } from '../utils'
 import type { MutationFilters, QueryFilters, Updater } from '../utils'
 import type { Mutation } from '../mutation'
 import type { Query, QueryState } from '../query'
@@ -11,6 +12,7 @@ import type {
   EnsureQueryDataOptions,
   FetchInfiniteQueryOptions,
   InfiniteData,
+  InfiniteQueryExecuteOptions,
   MutationOptions,
   OmitKeyof,
   QueryKey,
@@ -158,7 +160,38 @@ describe('getQueryState', () => {
   })
 })
 
+describe('fetchQuery', () => {
+  it('should not allow passing select option', () => {
+    assertType<Parameters<QueryClient['fetchQuery']>>([
+      {
+        queryKey: ['key'],
+        queryFn: () => Promise.resolve('string'),
+        // @ts-expect-error `select` is not supported on fetchQuery options
+        select: (data: string) => data.length,
+      },
+    ])
+  })
+})
+
 describe('fetchInfiniteQuery', () => {
+  it('should not allow passing select option', () => {
+    assertType<Parameters<QueryClient['fetchInfiniteQuery']>>([
+      {
+        queryKey: ['key'],
+        queryFn: () => Promise.resolve({ count: 1 }),
+        initialPageParam: 1,
+        getNextPageParam: () => 2,
+        // @ts-expect-error `select` is not supported on fetchInfiniteQuery options
+        select: (data) => ({
+          pages: data.pages.map(
+            (x: unknown) => `count: ${(x as { count: number }).count}`,
+          ),
+          pageParams: data.pageParams,
+        }),
+      },
+    ])
+  })
+
   it('should allow passing pages', async () => {
     const data = await new QueryClient().fetchInfiniteQuery({
       queryKey: queryKey(),
@@ -171,7 +204,7 @@ describe('fetchInfiniteQuery', () => {
     expectTypeOf(data).toEqualTypeOf<InfiniteData<string, number>>()
   })
 
-  it('should not allow passing getNextPageParam without pages', () => {
+  it('should allow passing getNextPageParam without pages', () => {
     assertType<Parameters<QueryClient['fetchInfiniteQuery']>>([
       {
         queryKey: ['key'],
@@ -195,6 +228,105 @@ describe('fetchInfiniteQuery', () => {
   })
 })
 
+describe('query', () => {
+  it('should allow passing select option', () => {
+    const result = new QueryClient().query({
+      queryKey: ['key'],
+      queryFn: () => Promise.resolve('string'),
+      select: (data) => data.length,
+    })
+
+    expectTypeOf(result).toEqualTypeOf<Promise<number>>()
+  })
+
+  it('should infer select type with skipToken queryFn', () => {
+    const result = new QueryClient().query({
+      queryKey: ['key'],
+      queryFn: skipToken,
+      select: (data: string) => data.length,
+    })
+
+    expectTypeOf(result).toEqualTypeOf<Promise<number>>()
+  })
+
+  it('should infer select type with skipToken queryFn and enabled false', () => {
+    const result = new QueryClient().query({
+      queryKey: ['key'],
+      queryFn: skipToken,
+      enabled: false,
+      select: (data: string) => data.length,
+    })
+
+    expectTypeOf(result).toEqualTypeOf<Promise<number>>()
+  })
+
+  it('should infer select type with skipToken queryFn and enabled true', () => {
+    const result = new QueryClient().query({
+      queryKey: ['key'],
+      queryFn: skipToken,
+      enabled: true,
+      select: (data: string) => data.length,
+    })
+
+    expectTypeOf(result).toEqualTypeOf<Promise<number>>()
+  })
+})
+
+describe('infiniteQuery', () => {
+  it('should allow passing select option', () => {
+    const result = new QueryClient().infiniteQuery({
+      queryKey: ['key'],
+      queryFn: () => Promise.resolve({ count: 1 }),
+      initialPageParam: 1,
+      getNextPageParam: () => 2,
+      select: (data) => ({
+        pages: data.pages.map(
+          (x) => `count: ${(x as { count: number }).count}`,
+        ),
+      }),
+    })
+
+    expectTypeOf(result).toEqualTypeOf<Promise<{ pages: Array<string> }>>()
+  })
+
+  it('should allow passing pages', async () => {
+    const result = await new QueryClient().infiniteQuery({
+      queryKey: ['key'],
+      queryFn: () => Promise.resolve({ count: 1 }),
+      getNextPageParam: () => 1,
+      initialPageParam: 1,
+      pages: 5,
+    })
+
+    expectTypeOf(result).toEqualTypeOf<
+      InfiniteData<{ count: number }, number>
+    >()
+  })
+
+  it('should allow passing getNextPageParam without pages', () => {
+    assertType<Parameters<QueryClient['infiniteQuery']>>([
+      {
+        queryKey: ['key'],
+        queryFn: () => Promise.resolve({ count: 1 }),
+        initialPageParam: 1,
+        getNextPageParam: () => 1,
+      },
+    ])
+  })
+
+  it('should not allow passing pages without getNextPageParam', () => {
+    assertType<Parameters<QueryClient['infiniteQuery']>>([
+      // @ts-expect-error Property 'getNextPageParam' is missing
+      {
+        queryKey: ['key'],
+        queryFn: () => Promise.resolve('string'),
+        initialPageParam: 1,
+        pages: 5,
+      },
+    ])
+  })
+})
+
 describe('defaultOptions', () => {
   it('should have a typed QueryFunctionContext', () => {
     new QueryClient({
@@ -228,12 +360,27 @@ describe('fully typed usage', () => {
     // Construct typed arguments
     //
 
+    const infiniteQueryOptions: InfiniteQueryExecuteOptions<
+      TData,
+      TError,
+      InfiniteData<TData>
+    > = {
+      queryKey: ['key', 'infinite'],
+      pages: 5,
+      getNextPageParam: (lastPage) => {
+        expectTypeOf(lastPage).toEqualTypeOf<TData>()
+        return 0
+      },
+      initialPageParam: 0,
+    }
+
     const queryOptions: EnsureQueryDataOptions<TData, TError> = {
-      queryKey: ['key'] as any,
+      queryKey: ['key', 'query'],
     }
+
     const fetchInfiniteQueryOptions: FetchInfiniteQueryOptions<TData, TError> =
       {
-        queryKey: ['key'] as any,
+        queryKey: ['key', 'infinite'],
         pages: 5,
         getNextPageParam: (lastPage) => {
           expectTypeOf(lastPage).toEqualTypeOf<TData>()
@@ -241,6 +388,7 @@ describe('fully typed usage', () => {
         },
         initialPageParam: 0,
       }
+
     const mutationOptions: MutationOptions<TData, TError> = {}
 
     const queryFilters: QueryFilters<DataTag<QueryKey, TData, TError>> = {
@@ -311,11 +459,19 @@ describe('fully typed usage', () => {
     const fetchedQuery = await queryClient.fetchQuery(queryOptions)
     expectTypeOf(fetchedQuery).toEqualTypeOf<TData>()
 
+    const queriedData = await queryClient.query(queryOptions)
+    expectTypeOf(queriedData).toEqualTypeOf<TData>()
+
     queryClient.prefetchQuery(queryOptions)
 
-    const infiniteQuery = await queryClient.fetchInfiniteQuery(
+    const fetchInfiniteQueryResult = await queryClient.fetchInfiniteQuery(
       fetchInfiniteQueryOptions,
     )
+    expectTypeOf(fetchInfiniteQueryResult).toEqualTypeOf<
+      InfiniteData<TData, unknown>
+    >()
+
+    const infiniteQuery = await queryClient.infiniteQuery(infiniteQueryOptions)
     expectTypeOf(infiniteQuery).toEqualTypeOf<InfiniteData<TData, unknown>>()
 
     const infiniteQueryData = await queryClient.ensureInfiniteQueryData(
@@ -450,9 +606,19 @@ describe('fully typed usage', () => {
     const fetchedQuery = await queryClient.fetchQuery(queryOptions)
     expectTypeOf(fetchedQuery).toEqualTypeOf<unknown>()
 
+    const queriedData = await queryClient.query(queryOptions)
+    expectTypeOf(queriedData).toEqualTypeOf<unknown>()
+
     queryClient.prefetchQuery(queryOptions)
 
-    const infiniteQuery = await queryClient.fetchInfiniteQuery(
+    const fetchInfiniteQueryResult = await queryClient.fetchInfiniteQuery(
+      fetchInfiniteQueryOptions,
+    )
+    expectTypeOf(fetchInfiniteQueryResult).toEqualTypeOf<
+      InfiniteData<unknown, unknown>
+    >()
+
+    const infiniteQuery = await queryClient.infiniteQuery(
       fetchInfiniteQueryOptions,
     )
     expectTypeOf(infiniteQuery).toEqualTypeOf<InfiniteData<unknown, unknown>>()
diff --git a/packages/query-core/src/__tests__/queryClient.test.tsx b/packages/query-core/src/__tests__/queryClient.test.tsx
index c09db304467..923d113eb54 100644
--- a/packages/query-core/src/__tests__/queryClient.test.tsx
+++ b/packages/query-core/src/__tests__/queryClient.test.tsx
@@ -8,11 +8,18 @@ import {
   dehydrate,
   focusManager,
   hydrate,
+  noop,
   onlineManager,
   skipToken,
 } from '..'
 import { mockOnlineManagerIsOnline } from './utils'
-import type { QueryCache, QueryFunction, QueryObserverOptions } from '..'
+import type {
+  InfiniteData,
+  Query,
+  QueryCache,
+  QueryFunction,
+  QueryObserverOptions,
+} from '..'
 
 describe('queryClient', () => {
   let queryClient: QueryClient
@@ -449,6 +456,7 @@ describe('queryClient', () => {
     })
   })
 
+  /** @deprecated */
   describe('ensureQueryData', () => {
     it('should return the cached query data if the query is found', async () => {
       const key = queryKey()
@@ -524,6 +532,100 @@ describe('queryClient', () => {
     })
   })
 
+  describe('query with static staleTime', () => {
+    it('should return the cached query data if the query is found', async () => {
+      const key = queryKey()
+      const queryFn = vi.fn(() => Promise.resolve('data'))
+
+      queryClient.setQueryData([key, 'id'], 'bar')
+
+      await expect(
+        queryClient.query({
+          queryKey: [key, 'id'],
+          queryFn,
+          staleTime: 'static',
+        }),
+      ).resolves.toEqual('bar')
+      expect(queryFn).not.toHaveBeenCalled()
+    })
+
+    it('should return the cached query data if the query is found and cached query data is falsy', async () => {
+      const key = queryKey()
+      const queryFn = vi.fn(() => Promise.resolve(0))
+
+      queryClient.setQueryData([key, 'id'], null)
+
+      await expect(
+        queryClient.query({
+          queryKey: [key, 'id'],
+          queryFn,
+          staleTime: 'static',
+        }),
+      ).resolves.toEqual(null)
+      expect(queryFn).not.toHaveBeenCalled()
+    })
+
+    it('should call queryFn and return its results if the query is not found', async () => {
+      const key = queryKey()
+      const queryFn = vi.fn(() => Promise.resolve('data'))
+
+      await expect(
+        queryClient.query({
+          queryKey: [key],
+          queryFn,
+          staleTime: 'static',
+        }),
+      ).resolves.toEqual('data')
+      expect(queryFn).toHaveBeenCalledTimes(1)
+    })
+
+    it('should not fetch when initialData is provided', async () => {
+      const key = queryKey()
+      const queryFn = vi.fn(() => Promise.resolve('data'))
+
+      await expect(
+        queryClient.query({
+          queryKey: [key, 'id'],
+          queryFn,
+          staleTime: 'static',
+          initialData: 'initial',
+        }),
+      ).resolves.toEqual('initial')
+
+      expect(queryFn).not.toHaveBeenCalled()
+    })
+
+    it('supports manual background revalidation via a second query call', async () => {
+      const key = queryKey()
+      let value = 'data-1'
+      const queryFn = vi.fn(() => Promise.resolve(value))
+
+      await expect(
+        queryClient.query({
+          queryKey: key,
+          queryFn,
+          staleTime: 'static',
+        }),
+      ).resolves.toEqual('data-1')
+      expect(queryFn).toHaveBeenCalledTimes(1)
+
+      value = 'data-2'
+      void queryClient
+        .query({
+          queryKey: key,
+          queryFn,
+          staleTime: 0,
+        })
+        .catch(noop)
+
+      await vi.advanceTimersByTimeAsync(0)
+
+      expect(queryFn).toHaveBeenCalledTimes(2)
+      expect(queryClient.getQueryData(key)).toBe('data-2')
+    })
+  })
+
+  /** @deprecated */
   describe('ensureInfiniteQueryData', () => {
     it('should return the cached query data if the query is found', async () => {
       const key = queryKey()
@@ -584,6 +686,45 @@ describe('queryClient', () => {
     })
   })
 
+  describe('infiniteQuery with static staleTime', () => {
+    it('should return the cached query data if the query is found', async () => {
+      const key = queryKey()
+      const queryFn = vi.fn(() => Promise.resolve('data'))
+
+      queryClient.setQueryData([key, 'id'], {
+        pages: ['bar'],
+        pageParams: [0],
+      })
+
+      await expect(
+        queryClient.infiniteQuery({
+          queryKey: [key, 'id'],
+          queryFn,
+          staleTime: 'static',
+          initialPageParam: 1,
+          getNextPageParam: () => undefined,
+        }),
+      ).resolves.toEqual({ pages: ['bar'], pageParams: [0] })
+      expect(queryFn).not.toHaveBeenCalled()
+    })
+
+    it('should fetch the query and return its results if the query is not found', async () => {
+      const key = queryKey()
+      const queryFn = vi.fn(() => Promise.resolve('data'))
+
+      await expect(
+        queryClient.infiniteQuery({
+          queryKey: [key, 'id'],
+          queryFn,
+          staleTime: 'static',
+          initialPageParam: 1,
+          getNextPageParam: () => undefined,
+        }),
+      ).resolves.toEqual({ pages: ['data'], pageParams: [1] })
+      expect(queryFn).toHaveBeenCalledTimes(1)
+    })
+  })
+
   describe('getQueriesData', () => {
     it('should return the query data for all matched queries', () => {
       const key1 = queryKey()
@@ -615,6 +756,7 @@ describe('queryClient', () => {
     })
   })
 
+  /** @deprecated */
   describe('fetchQuery', () => {
     it('should not type-error with strict query key', async () => {
       type StrictData = 'data'
@@ -789,161 +931,937 @@ describe('queryClient', () => {
     })
   })
 
-  describe('fetchInfiniteQuery', () => {
+  describe('query', () => {
     it('should not type-error with strict query key', async () => {
-      type StrictData = string
+      type StrictData = 'data'
       type StrictQueryKey = ['strict', ...ReturnType<typeof queryKey>]
       const key: StrictQueryKey = ['strict', ...queryKey()]
 
-      const data = {
-        pages: ['data'],
-        pageParams: [0],
-      } as const
-
-      const fetchFn: QueryFunction<StrictData, StrictQueryKey, number> = () =>
-        Promise.resolve(data.pages[0])
+      const fetchFn: QueryFunction<StrictData, StrictQueryKey> = () =>
+        Promise.resolve('data')
 
       await expect(
-        queryClient.fetchInfiniteQuery<
+        queryClient.query<
           StrictData,
           any,
           StrictData,
-          StrictQueryKey,
-          number
-        >({ queryKey: key, queryFn: fetchFn, initialPageParam: 0 }),
-      ).resolves.toEqual(data)
+          StrictData,
+          StrictQueryKey
+        >({
+          queryKey: key,
+          queryFn: fetchFn,
+        }),
+      ).resolves.toEqual('data')
     })
 
-    it('should return infinite query data', async () => {
+    // https://github.com/tannerlinsley/react-query/issues/652
+    it('should not retry by default', async () => {
       const key = queryKey()
-      const result = await queryClient.fetchInfiniteQuery({
-        queryKey: key,
-        initialPageParam: 10,
-        queryFn: ({ pageParam }) => Number(pageParam),
-      })
-      const result2 = queryClient.getQueryData(key)
-
-      const expected = {
-        pages: [10],
-        pageParams: [10],
-      }
 
-      expect(result).toEqual(expected)
-      expect(result2).toEqual(expected)
+      await expect(
+        queryClient.query({
+          queryKey: key,
+          queryFn: (): Promise<unknown> => {
+            throw new Error('error')
+          },
+        }),
+      ).rejects.toEqual(new Error('error'))
     })
-  })
 
-  describe('prefetchInfiniteQuery', () => {
-    it('should not type-error with strict query key', async () => {
-      type StrictData = 'data'
-      type StrictQueryKey = ['strict', ...ReturnType<typeof queryKey>]
-      const key: StrictQueryKey = ['strict', ...queryKey()]
+    it('should return the cached data on cache hit', async () => {
+      const key = queryKey()
 
-      const fetchFn: QueryFunction<StrictData, StrictQueryKey, number> = () =>
-        Promise.resolve('data')
+      const fetchFn = () => Promise.resolve('data')
+      const first = await queryClient.query({
+        queryKey: key,
+        queryFn: fetchFn,
+      })
+      const second = await queryClient.query({
+        queryKey: key,
+        queryFn: fetchFn,
+      })
 
-      await queryClient.prefetchInfiniteQuery<
-        StrictData,
-        any,
-        StrictData,
-        StrictQueryKey,
-        number
-      >({ queryKey: key, queryFn: fetchFn, initialPageParam: 0 })
+      expect(second).toBe(first)
+    })
 
-      const result = queryClient.getQueryData(key)
+    it('should throw when disabled and no cached data exists', async () => {
+      const key = queryKey()
+      const queryFn = vi.fn(() => Promise.resolve('data'))
+      const errorMsg = `Query is disabled and no cached data is available for key: '${JSON.stringify(key)}'`
 
-      expect(result).toEqual({
-        pages: ['data'],
-        pageParams: [0],
-      })
+      await expect(
+        queryClient.query({
+          queryKey: key,
+          queryFn,
+          enabled: false,
+        }),
+      ).rejects.toThrowError(errorMsg)
+
+      expect(queryFn).not.toHaveBeenCalled()
     })
 
-    it('should return infinite query data', async () => {
+    it('should return cached data when disabled and apply select', async () => {
       const key = queryKey()
+      const queryFn = vi.fn(() => Promise.resolve('fetched-data'))
 
-      await queryClient.prefetchInfiniteQuery({
+      queryClient.setQueryData(key, 'cached-data')
+
+      const result = await queryClient.query({
         queryKey: key,
-        queryFn: ({ pageParam }) => Number(pageParam),
-        initialPageParam: 10,
+        queryFn,
+        enabled: false,
+        staleTime: 0,
+        select: (data) => `${data}-selected`,
       })
 
-      const result = queryClient.getQueryData(key)
-
-      expect(result).toEqual({
-        pages: [10],
-        pageParams: [10],
-      })
+      expect(result).toBe('cached-data-selected')
+      expect(queryFn).not.toHaveBeenCalled()
     })
 
-    it('should prefetch multiple pages', async () => {
+    it('should throw when skipToken is provided and no cached data exists', async () => {
       const key = queryKey()
+      const select = vi.fn((data: unknown) => (data as string).length)
 
-      await queryClient.prefetchInfiniteQuery({
-        queryKey: key,
-        queryFn: ({ pageParam }) => String(pageParam),
-        getNextPageParam: (_lastPage, _pages, lastPageParam) =>
-          lastPageParam + 5,
-        initialPageParam: 10,
-        pages: 3,
-      })
-
-      const result = queryClient.getQueryData(key)
+      await expect(
+        queryClient.query({
+          queryKey: key,
+          queryFn: skipToken,
+          select,
+        }),
+      ).rejects.toThrowError()
 
-      expect(result).toEqual({
-        pages: ['10', '15', '20'],
-        pageParams: [10, 15, 20],
-      })
+      expect(select).not.toHaveBeenCalled()
     })
 
-    it('should stop prefetching if getNextPageParam returns undefined', async () => {
+    it('should return cached data when skipToken is provided', async () => {
       const key = queryKey()
-      let count = 0
 
-      await queryClient.prefetchInfiniteQuery({
+      queryClient.setQueryData(key, 'cached-data')
+
+      const result = await queryClient.query({
         queryKey: key,
-        queryFn: ({ pageParam }) => String(pageParam),
-        getNextPageParam: (_lastPage, _pages, lastPageParam) => {
-          count++
-          return lastPageParam >= 20 ? undefined : lastPageParam + 5
-        },
-        initialPageParam: 10,
-        pages: 5,
+        queryFn: skipToken,
+        select: (data: unknown) => (data as string).length,
       })
 
-      const result = queryClient.getQueryData(key)
+      expect(result).toBe('cached-data'.length)
+    })
 
-      expect(result).toEqual({
-        pages: ['10', '15', '20'],
-        pageParams: [10, 15, 20],
+    it('should return cached data when skipToken and enabled false are both provided', async () => {
+      const key = queryKey()
+
+      queryClient.setQueryData(key, { value: 'cached-data' })
+
+      const result = await queryClient.query({
+        queryKey: key,
+        queryFn: skipToken,
+        enabled: false,
+        select: (data: { value: string }) => data.value.toUpperCase(),
       })
 
-      // this check ensures we're exiting the fetch loop early
-      expect(count).toBe(3)
+      expect(result).toBe('CACHED-DATA')
     })
-  })
-
-  describe('prefetchQuery', () => {
-    it('should not type-error with strict query key', async () => {
-      type StrictData = 'data'
-      type StrictQueryKey = ['strict', ...ReturnType<typeof queryKey>]
-      const key: StrictQueryKey = ['strict', ...queryKey()]
 
-      const fetchFn: QueryFunction<StrictData, StrictQueryKey> = () =>
-        Promise.resolve('data')
+    it('should throw when enabled is true and skipToken are provided with no cached data', async () => {
+      await expect(
+        queryClient.query({
+          queryKey: queryKey(),
+          queryFn: skipToken,
+          enabled: true,
+        }),
+      ).rejects.toThrowError()
+    })
 
-      await queryClient.prefetchQuery<
-        StrictData,
-        any,
-        StrictData,
-        StrictQueryKey
-      >({ queryKey: key, queryFn: fetchFn })
+    it('should return cached data when enabled is false and skipToken are provided', async () => {
+      const key1 = queryKey()
+      queryClient.setQueryData(key1, { value: 'cached-data' })
 
-      const result = queryClient.getQueryData(key)
+      const booleanDisabledResult = await queryClient.query({
+        queryKey: key1,
+        queryFn: skipToken,
+        enabled: false,
+        select: (data: { value: string }) => data.value.length,
+      })
 
-      expect(result).toEqual('data')
+      expect(booleanDisabledResult).toBe('cached-data'.length)
     })
 
-    it('should return undefined when an error is thrown', async () => {
+    it('should return cached data when enabled callback returns false even if queryFn would return different data', async () => {
+      const key = queryKey()
+      const queryFn = vi.fn(() => Promise.resolve('fetched-data'))
+
+      queryClient.setQueryData(key, 'cached-data')
+
+      const result = await queryClient.query({
+        queryKey: key,
+        queryFn,
+        enabled: () => false,
+      })
+
+      expect(result).toBe('cached-data')
+      expect(queryFn).not.toHaveBeenCalled()
+    })
+
+    it('should fetch when enabled callback returns true and cache is stale', async () => {
+      const key = queryKey()
+
+      queryClient.setQueryData(key, 'old-data')
+
+      await vi.advanceTimersByTimeAsync(1)
+
+      const queryFn = vi.fn(() => Promise.resolve('new-data'))
+
+      const result = await queryClient.query({
+        queryKey: key,
+        queryFn,
+        enabled: () => true,
+        staleTime: 0,
+      })
+
+      expect(result).toBe('new-data')
+      expect(queryFn).toHaveBeenCalledTimes(1)
+    })
+
+    it('should read from cache with static staleTime even if invalidated', async () => {
+      const key = queryKey()
+
+      const fetchFn = vi.fn(() => Promise.resolve({ data: 'data' }))
+      const first = await queryClient.query({
+        queryKey: key,
+        queryFn: fetchFn,
+        staleTime: 'static',
+      })
+
+      expect(first.data).toBe('data')
+      expect(fetchFn).toHaveBeenCalledTimes(1)
+
+      await queryClient.invalidateQueries({
+        queryKey: key,
+        refetchType: 'none',
+      })
+
+      const second = await queryClient.query({
+        queryKey: key,
+        queryFn: fetchFn,
+        staleTime: 'static',
+      })
+
+      expect(fetchFn).toHaveBeenCalledTimes(1)
+
+      expect(second).toBe(first)
+    })
+
+    it('should be able to fetch when garbage collection time is set to 0 and then be removed', async () => {
+      const key1 = queryKey()
+      const promise = queryClient.query({
+        queryKey: key1,
+        queryFn: () => sleep(10).then(() => 1),
+        gcTime: 0,
+      })
+      await vi.advanceTimersByTimeAsync(10)
+      await expect(promise).resolves.toEqual(1)
+      await vi.advanceTimersByTimeAsync(1)
+      expect(queryClient.getQueryData(key1)).toEqual(undefined)
+    })
+
+    it('should keep a query in cache if garbage collection time is Infinity', async () => {
+      const key1 = queryKey()
+      const promise = queryClient.query({
+        queryKey: key1,
+        queryFn: () => sleep(10).then(() => 1),
+        gcTime: Infinity,
+      })
+      await vi.advanceTimersByTimeAsync(10)
+      const result2 = queryClient.getQueryData(key1)
+      await expect(promise).resolves.toEqual(1)
+      expect(result2).toEqual(1)
+    })
+
+    it('should not force fetch', async () => {
+      const key = queryKey()
+
+      queryClient.setQueryData(key, 'og')
+      const fetchFn = () => Promise.resolve('new')
+      const first = await queryClient.query({
+        queryKey: key,
+        queryFn: fetchFn,
+        initialData: 'initial',
+        staleTime: 100,
+      })
+      expect(first).toBe('og')
+    })
+
+    it('should only fetch if the data is older then the given stale time', async () => {
+      const key = queryKey()
+
+      let count = 0
+      const queryFn = () => ++count
+
+      queryClient.setQueryData(key, count)
+      const firstPromise = queryClient.query({
+        queryKey: key,
+        queryFn,
+        staleTime: 100,
+      })
+      await expect(firstPromise).resolves.toBe(0)
+      await vi.advanceTimersByTimeAsync(10)
+      const secondPromise = queryClient.query({
+        queryKey: key,
+        queryFn,
+        staleTime: 10,
+      })
+      await expect(secondPromise).resolves.toBe(1)
+      const thirdPromise = queryClient.query({
+        queryKey: key,
+        queryFn,
+        staleTime: 10,
+      })
+      await expect(thirdPromise).resolves.toBe(1)
+      await vi.advanceTimersByTimeAsync(10)
+      const fourthPromise = queryClient.query({
+        queryKey: key,
+        queryFn,
+        staleTime: 10,
+      })
+      await expect(fourthPromise).resolves.toBe(2)
+    })
+
+    it('should evaluate staleTime when provided as a function', async () => {
+      const key = queryKey()
+      const staleTime = vi.fn(() => 0)
+
+      queryClient.setQueryData(key, 'old-data')
+
+      await vi.advanceTimersByTimeAsync(1)
+
+      const queryFn = vi.fn(() => Promise.resolve('new-data'))
+
+      const result = await queryClient.query({
+        queryKey: key,
+        queryFn,
+        staleTime,
+      })
+
+      expect(result).toBe('new-data')
+      expect(queryFn).toHaveBeenCalledTimes(1)
+      expect(staleTime).toHaveBeenCalledTimes(1)
+    })
+
+    it('should allow new meta', async () => {
+      const key = queryKey()
+
+      const first = await queryClient.query({
+        queryKey: key,
+        queryFn: ({ meta }) => Promise.resolve(meta),
+        meta: {
+          foo: true,
+        },
+      })
+      expect(first).toStrictEqual({ foo: true })
+
+      const second = await queryClient.query({
+        queryKey: key,
+        queryFn: ({ meta }) => Promise.resolve(meta),
+        meta: {
+          foo: false,
+        },
+      })
+      expect(second).toStrictEqual({ foo: false })
+    })
+
+    it('should fetch when enabled is true and cache is stale', async () => {
+      const key = queryKey()
+
+      queryClient.setQueryData(key, 'old-data')
+
+      await vi.advanceTimersByTimeAsync(1)
+
+      const queryFn = vi.fn(() => Promise.resolve('new-data'))
+
+      const result = await queryClient.query({
+        queryKey: key,
+        queryFn,
+        enabled: true,
+        staleTime: 0,
+      })
+
+      expect(result).toBe('new-data')
+      expect(queryFn).toHaveBeenCalledTimes(1)
+    })
+
+    it('should propagate errors', async () => {
+      const key = queryKey()
+
+      await expect(
+        queryClient.query({
+          queryKey: key,
+          queryFn: (): Promise<unknown> => {
+            throw new Error('error')
+          },
+        }),
+      ).rejects.toEqual(new Error('error'))
+    })
+
+    it('should apply select when data is fresh in cache', async () => {
+      const key = queryKey()
+      const queryFn = vi.fn(() => Promise.resolve('fetched-data'))
+
+      queryClient.setQueryData(key, 'cached-data')
+
+      const result = await queryClient.query({
+        queryKey: key,
+        queryFn,
+        staleTime: Infinity,
+        select: (data) => `${data}-selected`,
+      })
+
+      expect(result).toBe('cached-data-selected')
+      expect(queryFn).not.toHaveBeenCalled()
+    })
+
+    it('should apply select to freshly fetched data', async () => {
+      const key = queryKey()
+      const queryFn = vi.fn(() => Promise.resolve({ value: 'fetched-data' }))
+
+      const result = await queryClient.query({
+        queryKey: key,
+        queryFn,
+        select: (data) => data.value.toUpperCase(),
+      })
+
+      expect(result).toBe('FETCHED-DATA')
+      expect(queryFn).toHaveBeenCalledTimes(1)
+    })
+  })
+
+  /** @deprecated */
+  describe('fetchInfiniteQuery', () => {
+    it('should not type-error with strict query key', async () => {
+      type StrictData = string
+      type StrictQueryKey = ['strict', ...ReturnType<typeof queryKey>]
+      const key: StrictQueryKey = ['strict', ...queryKey()]
+
+      const data = {
+        pages: ['data'],
+        pageParams: [0],
+      } as const
+
+      const fetchFn: QueryFunction<StrictData, StrictQueryKey, number> = () =>
+        Promise.resolve(data.pages[0])
+
+      await expect(
+        queryClient.fetchInfiniteQuery<
+          StrictData,
+          any,
+          StrictData,
+          StrictQueryKey,
+          number
+        >({ queryKey: key, queryFn: fetchFn, initialPageParam: 0 }),
+      ).resolves.toEqual(data)
+    })
+
+    it('should return infinite query data', async () => {
+      const key = queryKey()
+      const result = await queryClient.fetchInfiniteQuery({
+        queryKey: key,
+        initialPageParam: 10,
+        queryFn: ({ pageParam }) => Number(pageParam),
+      })
+      const result2 = queryClient.getQueryData(key)
+
+      const expected = {
+        pages: [10],
+        pageParams: [10],
+      }
+
+      expect(result).toEqual(expected)
+      expect(result2).toEqual(expected)
+    })
+  })
+
+  describe('infiniteQuery', () => {
+    it('should not type-error with strict query key', async () => {
+      type StrictData = string
+      type StrictQueryKey = ['strict', ...ReturnType<typeof queryKey>]
+      const key: StrictQueryKey = ['strict', ...queryKey()]
+
+      const data = {
+        pages: ['data'],
+        pageParams: [0],
+      } as const
+
+      const fetchFn: QueryFunction<StrictData, StrictQueryKey, number> = () =>
+        Promise.resolve(data.pages[0])
+
+      await expect(
+        queryClient.infiniteQuery<
+          StrictData,
+          any,
+          StrictData,
+          StrictQueryKey,
+          number
+        >({ queryKey: key, queryFn: fetchFn, initialPageParam: 0 }),
+      ).resolves.toEqual(data)
+    })
+
+    it('should return infinite query data', async () => {
+      const key = queryKey()
+      const result = await queryClient.infiniteQuery({
+        queryKey: key,
+        initialPageParam: 10,
+        queryFn: ({ pageParam }) => Number(pageParam),
+      })
+      const result2 = queryClient.getQueryData(key)
+
+      const expected = {
+        pages: [10],
+        pageParams: [10],
+      }
+
+      expect(result).toEqual(expected)
+      expect(result2).toEqual(expected)
+    })
+
+    it('should throw when disabled and no cached data exists', async () => {
+      const key = queryKey()
+      const queryFn = vi.fn(({ pageParam }: { pageParam: number }) =>
+        Promise.resolve(pageParam),
+      )
+      const errorMsg = `Query is disabled and no cached data is available for key: '${JSON.stringify(key)}'`
+
+      await expect(
+        queryClient.infiniteQuery({
+          queryKey: key,
+          queryFn,
+          initialPageParam: 0,
+          enabled: false,
+        }),
+      ).rejects.toThrow(errorMsg)
+
+      expect(queryFn).not.toHaveBeenCalled()
+    })
+
+    it('should return cached data when disabled and apply select', async () => {
+      const key = queryKey()
+      const queryFn = vi.fn(({ pageParam }: { pageParam: number }) =>
+        Promise.resolve(`'fetched-${String(pageParam)}`),
+      )
+
+      queryClient.setQueryData(key, {
+        pages: ['cached-page'],
+        pageParams: [0],
+      })
+
+      const result = await queryClient.infiniteQuery({
+        queryKey: key,
+        queryFn,
+        initialPageParam: 0,
+        enabled: false,
+        select: (data) => data.pages.map((page) => `${page}-selected`),
+      })
+
+      expect(result).toEqual(['cached-page-selected'])
+      expect(queryFn).not.toHaveBeenCalled()
+    })
+
+    it('should return cached data when skipToken is provided', async () => {
+      const key = queryKey()
+
+      queryClient.setQueryData(key, {
+        pages: ['page-1'],
+        pageParams: [0],
+      })
+
+      const result = await queryClient.infiniteQuery({
+        queryKey: key,
+        queryFn: skipToken,
+        initialPageParam: 0,
+      })
+
+      expect(result).toEqual({
+        pages: ['page-1'],
+        pageParams: [0],
+      })
+    })
+
+    it('should throw when skipToken is provided and no cached data exists', async () => {
+      const key = queryKey()
+      const select = vi.fn(
+        (data: { pages: Array<string> }) => data.pages.length,
+      )
+
+      await expect(
+        queryClient.infiniteQuery({
+          queryKey: key,
+          queryFn: skipToken,
+          initialPageParam: 0,
+          select,
+        }),
+      ).rejects.toThrowError()
+
+      expect(select).not.toHaveBeenCalled()
+    })
+
+    it('should throw when enabled is true and skipToken are provided with no cached data', async () => {
+      await expect(
+        queryClient.infiniteQuery({
+          queryKey: queryKey(),
+          queryFn: skipToken,
+          initialPageParam: 0,
+          enabled: true,
+        }),
+      ).rejects.toThrowError()
+    })
+
+    it('should return cached data when enabled resolves false and skipToken are provided', async () => {
+      const key = queryKey()
+
+      queryClient.setQueryData(key, {
+        pages: [{ value: 'cached-page' }],
+        pageParams: [0],
+      })
+
+      const result = await queryClient.infiniteQuery({
+        queryKey: key,
+        queryFn: skipToken,
+        initialPageParam: 0,
+        enabled: () => false,
+        select: (data: { pages: Array<{ value: string }> }) =>
+          data.pages[0]?.value.length,
+      })
+
+      expect(result).toBe('cached-page'.length)
+    })
+
+    it('should fetch when enabled callback returns true and cache is stale', async () => {
+      const key = queryKey()
+
+      queryClient.setQueryData(key, {
+        pages: ['old-page'],
+        pageParams: [0],
+      })
+
+      await vi.advanceTimersByTimeAsync(1)
+
+      const queryFn = vi.fn(({ pageParam }: { pageParam: number }) =>
+        Promise.resolve(`new-page-${String(pageParam)}`),
+      )
+
+      const result = await queryClient.infiniteQuery({
+        queryKey: key,
+        queryFn,
+        initialPageParam: 0,
+        enabled: () => true,
+        staleTime: 0,
+      })
+
+      expect(result).toEqual({
+        pages: ['new-page-0'],
+        pageParams: [0],
+      })
+      expect(queryFn).toHaveBeenCalledTimes(1)
+    })
+
+    it('should evaluate staleTime callback and refetch when it returns stale', async () => {
+      const key = queryKey()
+
+      queryClient.setQueryData(key, {
+        pages: [{ value: 'old-page', staleTime: 0 }],
+        pageParams: [0],
+      })
+
+      await vi.advanceTimersByTimeAsync(1)
+
+      const queryFn = vi.fn(({ pageParam }: { pageParam: number }) =>
+        Promise.resolve({
+          value: `new-page-${String(pageParam)}`,
+          staleTime: 0,
+        }),
+      )
+      const staleTimeSpy = vi.fn()
+      const staleTime = (
+        query: Query<
+          { value: string; staleTime: number },
+          Error,
+          InfiniteData<{ value: string; staleTime: number }, number>
+        >,
+      ) => {
+        staleTimeSpy()
+        return query.state.data?.pages[0]?.staleTime ?? 0
+      }
+
+      const result = await queryClient.infiniteQuery({
+        queryKey: key,
+        queryFn,
+        initialPageParam: 0,
+        staleTime,
+      })
+
+      expect(result).toEqual({
+        pages: [{ value: 'new-page-0', staleTime: 0 }],
+        pageParams: [0],
+      })
+      expect(staleTimeSpy).toHaveBeenCalled()
+      expect(queryFn).toHaveBeenCalledTimes(1)
+    })
+
+    it('should read from cache with static staleTime even if invalidated', async () => {
+      const key = queryKey()
+
+      const queryFn = vi.fn(({ pageParam }: { pageParam: number }) =>
+        Promise.resolve({ value: `fetched-${String(pageParam)}` }),
+      )
+      const first = await queryClient.infiniteQuery({
+        queryKey: key,
+        queryFn,
+        initialPageParam: 0,
+        staleTime: 'static',
+      })
+
+      expect(first).toEqual({
+        pages: [{ value: 'fetched-0' }],
+        pageParams: [0],
+      })
+      expect(queryFn).toHaveBeenCalledTimes(1)
+
+      await queryClient.invalidateQueries({
+        queryKey: key,
+        refetchType: 'none',
+      })
+
+      const second = await queryClient.infiniteQuery({
+        queryKey: key,
+        queryFn,
+        initialPageParam: 0,
+        staleTime: 'static',
+      })
+
+      expect(queryFn).toHaveBeenCalledTimes(1)
+      expect(second).toBe(first)
+    })
+
+    it('should apply select to infinite query data', async () => {
+      const key = queryKey()
+
+      const result = await queryClient.infiniteQuery({
+        queryKey: key,
+        initialPageParam: 10,
+        queryFn: ({ pageParam }) => Number(pageParam),
+        select: (data) => data.pages.map((page) => page * 2),
+      })
+
+      expect(result).toEqual([20])
+    })
+  })
+
+  /** @deprecated */
+  describe('prefetchInfiniteQuery', () => {
+    it('should not type-error with strict query key', async () => {
+      type StrictData = 'data'
+      type StrictQueryKey = ['strict', ...ReturnType<typeof queryKey>]
+      const key: StrictQueryKey = ['strict', ...queryKey()]
+
+      const fetchFn: QueryFunction<StrictData, StrictQueryKey, number> = () =>
+        Promise.resolve('data')
+
+      await queryClient.prefetchInfiniteQuery<
+        StrictData,
+        any,
+        StrictData,
+        StrictQueryKey,
+        number
+      >({ queryKey: key, queryFn: fetchFn, initialPageParam: 0 })
+
+      const result = queryClient.getQueryData(key)
+
+      expect(result).toEqual({
+        pages: ['data'],
+        pageParams: [0],
+      })
+    })
+
+    it('should return infinite query data', async () => {
+      const key = queryKey()
+
+      await queryClient.prefetchInfiniteQuery({
+        queryKey: key,
+        queryFn: ({ pageParam }) => Number(pageParam),
+        initialPageParam: 10,
+      })
+
+      const result = queryClient.getQueryData(key)
+
+      expect(result).toEqual({
+        pages: [10],
+        pageParams: [10],
+      })
+    })
+
+    it('should prefetch multiple pages', async () => {
+      const key = queryKey()
+
+      await queryClient.prefetchInfiniteQuery({
+        queryKey: key,
+        queryFn: ({ pageParam }) => String(pageParam),
+        getNextPageParam: (_lastPage, _pages, lastPageParam) =>
+          lastPageParam + 5,
+        initialPageParam: 10,
+        pages: 3,
+      })
+
+      const result = queryClient.getQueryData(key)
+
+      expect(result).toEqual({
+        pages: ['10', '15', '20'],
+        pageParams: [10, 15, 20],
+      })
+    })
+
+    it('should stop prefetching if getNextPageParam returns undefined', async () => {
+      const key = queryKey()
+      let count = 0
+
+      await queryClient.prefetchInfiniteQuery({
+        queryKey: key,
+        queryFn: ({ pageParam }) => String(pageParam),
+        getNextPageParam: (_lastPage, _pages, lastPageParam) => {
+          count++
+          return lastPageParam >= 20 ? undefined : lastPageParam + 5
+        },
+        initialPageParam: 10,
+        pages: 5,
+      })
+
+      const result = queryClient.getQueryData(key)
+
+      expect(result).toEqual({
+        pages: ['10', '15', '20'],
+        pageParams: [10, 15, 20],
+      })
+
+      // this check ensures we're exiting the fetch loop early
+      expect(count).toBe(3)
+    })
+  })
+
+  describe('infiniteQuery used for prefetching', () => {
+    it('should not type-error with strict query key', async () => {
+      type StrictData = 'data'
+      type StrictQueryKey = ['strict', ...ReturnType<typeof queryKey>]
+      const key: StrictQueryKey = ['strict', ...queryKey()]
+
+      const fetchFn: QueryFunction<StrictData, StrictQueryKey, number> = () =>
+        Promise.resolve('data')
+
+      await queryClient
+        .infiniteQuery<StrictData, any, StrictData, StrictQueryKey, number>({
+          queryKey: key,
+          queryFn: fetchFn,
+          initialPageParam: 0,
+        })
+        .catch(noop)
+
+      const result = queryClient.getQueryData(key)
+
+      expect(result).toEqual({
+        pages: ['data'],
+        pageParams: [0],
+      })
+    })
+
+    it('should return infinite query data', async () => {
+      const key = queryKey()
+
+      await queryClient
+        .infiniteQuery({
+          queryKey: key,
+          queryFn: ({ pageParam }) => Number(pageParam),
+          initialPageParam: 10,
+        })
+        .catch(noop)
+
+      const result = queryClient.getQueryData(key)
+
+      expect(result).toEqual({
+        pages: [10],
+        pageParams: [10],
+      })
+    })
+
+    it('should prefetch multiple pages', async () => {
+      const key = queryKey()
+
+      await queryClient
+        .infiniteQuery({
+          queryKey: key,
+          queryFn: ({ pageParam }) => String(pageParam),
+          getNextPageParam: (_lastPage, _pages, lastPageParam) =>
+            lastPageParam + 5,
+          initialPageParam: 10,
+          pages: 3,
+        })
+        .catch(noop)
+
+      const result = queryClient.getQueryData(key)
+
+      expect(result).toEqual({
+        pages: ['10', '15', '20'],
+        pageParams: [10, 15, 20],
+      })
+    })
+
+    it('should stop prefetching if getNextPageParam returns undefined', async () => {
+      const key = queryKey()
+      let count = 0
+
+      await queryClient
+        .infiniteQuery({
+          queryKey: key,
+          queryFn: ({ pageParam }) => String(pageParam),
+          getNextPageParam: (_lastPage, _pages, lastPageParam) => {
+            count++
+            return lastPageParam >= 20 ? undefined : lastPageParam + 5
+          },
+          initialPageParam: 10,
+          pages: 5,
+        })
+        .catch(noop)
+
+      const result = queryClient.getQueryData(key)
+
+      expect(result).toEqual({
+        pages: ['10', '15', '20'],
+        pageParams: [10, 15, 20],
+      })
+
+      // this check ensures we're exiting the fetch loop early
+      expect(count).toBe(3)
+    })
+  })
+
+  /** @deprecated */
+  describe('prefetchQuery', () => {
+    it('should not type-error with strict query key', async () => {
+      type StrictData = 'data'
+      type StrictQueryKey = ['strict', ...ReturnType<typeof queryKey>]
+      const key: StrictQueryKey = ['strict', ...queryKey()]
+
+      const fetchFn: QueryFunction<StrictData, StrictQueryKey> = () =>
+        Promise.resolve('data')
+
+      await queryClient.prefetchQuery<
+        StrictData,
+        any,
+        StrictData,
+        StrictQueryKey
+      >({ queryKey: key, queryFn: fetchFn })
+
+      const result = queryClient.getQueryData(key)
+
+      expect(result).toEqual('data')
+    })
+
+    it('should return undefined when an error is thrown', async () => {
       const key = queryKey()
 
       const result = await queryClient.prefetchQuery({
@@ -971,6 +1889,59 @@ describe('queryClient', () => {
     })
   })
 
+  describe('query used for prefetching', () => {
+    it('should not type-error with strict query key', async () => {
+      type StrictData = 'data'
+      type StrictQueryKey = ['strict', ...ReturnType<typeof queryKey>]
+      const key: StrictQueryKey = ['strict', ...queryKey()]
+
+      const fetchFn: QueryFunction<StrictData, StrictQueryKey> = () =>
+        Promise.resolve('data')
+
+      await queryClient
+        .query<StrictData, any, StrictData, StrictData, StrictQueryKey>({
+          queryKey: key,
+          queryFn: fetchFn,
+        })
+        .catch(noop)
+
+      const result = queryClient.getQueryData(key)
+
+      expect(result).toEqual('data')
+    })
+
+    it('should resolve to undefined when error is caught with noop', async () => {
+      const key = queryKey()
+
+      const result = await queryClient
+        .query({
+          queryKey: key,
+          queryFn: (): Promise<unknown> => {
+            throw new Error('error')
+          },
+          retry: false,
+        })
+        .catch(noop)
+
+      expect(result).toBeUndefined()
+    })
+
+    it('should be garbage collected after gcTime if unused', async () => {
+      const key = queryKey()
+
+      await queryClient
+        .query({
+          queryKey: key,
+          queryFn: () => 'data',
+          gcTime: 10,
+        })
+        .catch(noop)
+      expect(queryCache.find({ queryKey: key })).toBeDefined()
+      await vi.advanceTimersByTimeAsync(15)
+      expect(queryCache.find({ queryKey: key })).not.toBeDefined()
+    })
+  })
+
   describe('removeQueries', () => {
     it('should not crash when exact is provided', async () => {
       const key = queryKey()
diff --git a/packages/query-core/src/queryClient.ts b/packages/query-core/src/queryClient.ts
index d82106c7375..2f1711ce9bf 100644
--- a/packages/query-core/src/queryClient.ts
+++ b/packages/query-core/src/queryClient.ts
@@ -4,6 +4,7 @@ import {
   hashQueryKeyByOptions,
   noop,
   partialMatchKey,
+  resolveQueryBoolean,
   resolveStaleTime,
   skipToken,
 } from './utils'
@@ -24,6 +25,7 @@ import type {
   InferDataFromTag,
   InferErrorFromTag,
   InfiniteData,
+  InfiniteQueryExecuteOptions,
   InvalidateOptions,
   InvalidateQueryFilters,
   MutationKey,
@@ -31,6 +33,7 @@ import type {
   MutationOptions,
   OmitKeyof,
   QueryClientConfig,
+  QueryExecuteOptions,
   QueryKey,
   QueryObserverOptions,
   QueryOptions,
@@ -135,6 +138,9 @@ export class QueryClient {
       .data
   }
 
+  /**
+   * @deprecated Use queryClient.query({ ...options, staleTime: 'static' }) instead. This method will be removed in the next major version.
+   */
   ensureQueryData<
     TQueryFnData,
     TError = DefaultError,
@@ -336,6 +342,72 @@ export class QueryClient {
     return Promise.all(promises).then(noop)
   }
 
+  async query<
+    TQueryFnData,
+    TError = DefaultError,
+    TData = TQueryFnData,
+    TQueryData = TQueryFnData,
+    TQueryKey extends QueryKey = QueryKey,
+    TPageParam = never,
+  >(
+    options: QueryExecuteOptions<
+      TQueryFnData,
+      TError,
+      TData,
+      TQueryData,
+      TQueryKey,
+      TPageParam
+    >,
+  ): Promise<TData> {
+    const defaultedOptions = this.defaultQueryOptions(options)
+    const disabledErrorMessage = `Query is disabled and no cached data is available for key: '${defaultedOptions.queryHash}'`
+
+    // https://github.com/tannerlinsley/react-query/issues/652
+    if (defaultedOptions.retry === undefined) {
+      defaultedOptions.retry = false
+    }
+
+    const cachedQuery = this.#queryCache.get<TQueryData>(
+      defaultedOptions.queryHash,
+    )
+
+    if (
+      typeof defaultedOptions.enabled !== 'function' &&
+      defaultedOptions.enabled === false &&
+      cachedQuery?.state.data === undefined
+    ) {
+      throw new Error(disabledErrorMessage)
+    }
+
+    const query = this.#queryCache.build(this, defaultedOptions)
+    const isEnabled =
+      resolveQueryBoolean(defaultedOptions.enabled, query) !== false
+
+    if (!isEnabled && query.state.data === undefined) {
+      throw new Error(disabledErrorMessage)
+    }
+
+    const isStale = query.isStaleByTime(
+      resolveStaleTime(defaultedOptions.staleTime, query),
+    )
+
+    const queryData =
+      isStale && isEnabled
+        ? await query.fetch(defaultedOptions)
+        : (query.state.data as TQueryData)
+
+    const select = defaultedOptions.select
+
+    if (select) {
+      return select(queryData)
+    }
+
+    return queryData as unknown as TData
+  }
+
+  /**
+   * @deprecated Use queryClient.query(options) instead. This method will be removed in the next major version.
+   */
   fetchQuery<
     TQueryFnData,
     TError = DefaultError,
@@ -367,6 +439,9 @@ export class QueryClient {
       : Promise.resolve(query.state.data as TData)
   }
 
+  /**
+   * @deprecated Use queryClient.query(options) instead. You can swallow errors with `.catch(noop)`. This method will be removed in the next major version.
+   */
   prefetchQuery<
     TQueryFnData = unknown,
     TError = DefaultError,
@@ -378,6 +453,32 @@ export class QueryClient {
     return this.fetchQuery(options).then(noop).catch(noop)
   }
 
+  infiniteQuery<
+    TQueryFnData,
+    TError = DefaultError,
+    TData = InfiniteData<TQueryFnData>,
+    TQueryKey extends QueryKey = QueryKey,
+    TPageParam = unknown,
+  >(
+    options: InfiniteQueryExecuteOptions<
+      TQueryFnData,
+      TError,
+      TData,
+      TQueryKey,
+      TPageParam
+    >,
+  ): Promise<
+    Array<TData> extends Array<InfiniteData<TQueryFnData>>
+      ? InfiniteData<TQueryFnData, TPageParam>
+      : TData
+  > {
+    options._type = 'infinite'
+    return this.query(options as any)
+  }
+
+  /**
+   * @deprecated Use queryClient.infiniteQuery(options) instead. This method will be removed in the next major version.
+   */
   fetchInfiniteQuery<
     TQueryFnData,
     TError = DefaultError,
@@ -397,6 +498,9 @@ export class QueryClient {
     return this.fetchQuery(options as any)
   }
 
+  /**
+   * @deprecated Use queryClient.infiniteQuery(options) instead. You can swallow errors with `.catch(noop)`. This method will be removed in the next major version.
+   */
   prefetchInfiniteQuery<
     TQueryFnData,
     TError = DefaultError,
@@ -415,6 +519,9 @@ export class QueryClient {
     return this.fetchInfiniteQuery(options).then(noop).catch(noop)
   }
 
+  /**
+   * @deprecated Use queryClient.infiniteQuery({ ...options, staleTime: 'static' }) instead. This method will be removed in the next major version.
+   */
   ensureInfiniteQueryData<
     TQueryFnData,
     TError = DefaultError,
diff --git a/packages/query-core/src/types.ts b/packages/query-core/src/types.ts
index b2e80f2a144..a16f3b2d70b 100644
--- a/packages/query-core/src/types.ts
+++ b/packages/query-core/src/types.ts
@@ -486,6 +486,32 @@ export type DefaultedInfiniteQueryObserverOptions<
   'throwOnError' | 'refetchOnReconnect' | 'queryHash'
 >
 
+export interface QueryExecuteOptions<
+  TQueryFnData = unknown,
+  TError = DefaultError,
+  TData = TQueryFnData,
+  TQueryData = TQueryFnData,
+  TQueryKey extends QueryKey = QueryKey,
+  TPageParam = never,
+> extends WithRequired<
+  QueryOptions<TQueryFnData, TError, TQueryData, TQueryKey, TPageParam>,
+  'queryKey'
+> {
+  initialPageParam?: never
+  /**
+   * Set this to `false` or a function that returns `false` to disable fetching.
+   * If cached data exists, it will be returned.
+   */
+  enabled?: QueryBooleanOption<TQueryFnData, TError, TQueryData, TQueryKey>
+  select?: (data: TQueryData) => TData
+  /**
+   * The time in milliseconds after data is considered stale.
+   * If the data is fresh it will be returned from the cache.
+   */
+  staleTime?: StaleTimeFunction<TQueryFnData, TError, TQueryData, TQueryKey>
+}
+
+/** @deprecated */
 export interface FetchQueryOptions<
   TQueryFnData = unknown,
   TError = DefaultError,
@@ -504,6 +530,7 @@ export interface FetchQueryOptions<
   staleTime?: StaleTimeFunction<TQueryFnData, TError, TData, TQueryKey>
 }
 
+/** @deprecated */
 export interface EnsureQueryDataOptions<
   TQueryFnData = unknown,
   TError = DefaultError,
@@ -520,6 +547,7 @@ export interface EnsureQueryDataOptions<
   revalidateIfStale?: boolean
 }
 
+/** @deprecated */
 export type EnsureInfiniteQueryDataOptions<
   TQueryFnData = unknown,
   TError = DefaultError,
@@ -536,13 +564,34 @@ export type EnsureInfiniteQueryDataOptions<
   revalidateIfStale?: boolean
 }
 
-type FetchInfiniteQueryPages<TQueryFnData = unknown, TPageParam = unknown> =
+type InfiniteQueryPages<TQueryFnData = unknown, TPageParam = unknown> =
   | { pages?: never }
   | {
       pages: number
       getNextPageParam: GetNextPageParamFunction<TPageParam, TQueryFnData>
     }
 
+export type InfiniteQueryExecuteOptions<
+  TQueryFnData = unknown,
+  TError = DefaultError,
+  TData = InfiniteData<TQueryFnData>,
+  TQueryKey extends QueryKey = QueryKey,
+  TPageParam = unknown,
+> = Omit<
+  QueryExecuteOptions<
+    TQueryFnData,
+    TError,
+    TData,
+    InfiniteData<TQueryFnData, TPageParam>,
+    TQueryKey,
+    TPageParam
+  >,
+  'initialPageParam'
+> &
+  InitialPageParam<TPageParam> &
+  InfiniteQueryPages<TQueryFnData, TPageParam>
+
+/** @deprecated */
 export type FetchInfiniteQueryOptions<
   TQueryFnData = unknown,
   TError = DefaultError,
@@ -560,7 +609,7 @@ export type FetchInfiniteQueryOptions<
   'initialPageParam'
 > &
   InitialPageParam<TPageParam> &
-  FetchInfiniteQueryPages<TQueryFnData, TPageParam>
+  InfiniteQueryPages<TQueryFnData, TPageParam>
 
 export interface ResultOptions {
   throwOnError?: boolean
