[
  {
    "path": ".github/workflows/cd.yml",
    "content": "name: CD\n\non:\n  release:\n    types: [published]\n\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: pnpm/action-setup@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 22\n          registry-url: 'https://registry.npmjs.org'\n          cache: 'pnpm'\n          cache-dependency-path: '**/pnpm-lock.yaml'\n      - run: pnpm install --frozen-lockfile\n      - run: pnpm run compile\n      - run: pnpm test\n      - run: pnpm publish --no-git-checks\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: pnpm/action-setup@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 22\n          cache: 'pnpm'\n          cache-dependency-path: '**/pnpm-lock.yaml'\n      - run: pnpm install --frozen-lockfile\n      - run: pnpm compile\n      - run: pnpm test\n"
  },
  {
    "path": ".github/workflows/pr.yml",
    "content": "name: Publish Preview\n\non:\n  pull_request:\n    types: [opened, synchronize]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n      - uses: pnpm/action-setup@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 22\n          cache: 'pnpm'\n          cache-dependency-path: '**/pnpm-lock.yaml'\n      - run: pnpm install --frozen-lockfile\n      - run: pnpm run compile\n      - run: pnpm dlx pkg-pr-new publish --template './examples/*'\n"
  },
  {
    "path": ".gitignore",
    "content": "*~\n*.swp\nnode_modules\n/dist\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"semi\": false,\n  \"trailingComma\": \"es5\",\n  \"singleQuote\": true,\n  \"bracketSameLine\": true,\n  \"tabWidth\": 2,\n  \"printWidth\": 80\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Change Log\n\n## [Unreleased]\n\n## [0.11.0] - 2025-08-01\n\n### Changed\n\n- feat: `QueryClientAtomProvider` is a ready-to-use wrapper that combines Jotai Provider and TanStack Query QueryClientProvider.\n- breaking: removed `QueryClientProvider`\n\n## [0.10.1] - 2025-07-24\n\n### Changed\n\n- fix: '/react' import path for QueryClientProvider utils\n\n## [0.10.0] - 2025-07-23\n\n### Changed\n\n- feat: atomWithQueries for tanstack/query's useQueries\n- feat: add QueryClientProvider utils for react\n\n## [0.9.2] - 2025-07-10\n\n### Changed\n\n- upgrade @tankstack/query-core to 5.81.5\n\n## [0.9.1] - 2025-07-09\n\n### Changed\n\n- upgrade @tankstack/query-core to 5.81.2\n\n## [0.9.0] - 2024-09-29\n\n### Added\n\n- add: change atom signature to return a setter function to force re-evaluate an atom. This is to help the atom able to reevaluate when it hits an error boundary by throwing a rejected promise.\n\n## [0.8.8] - 2024-09-15\n\n### Changed\n\n- fix: use query.reset on error instead of client.resetQueries.\n\n## [0.8.7] - 2024-08-18\n\n### Changed\n\n- add: batch calls to improve perf\n- fix: return appropriate cleanup functions\n- fix: cleanup - remove unused code\n\n## [0.8.6] - 2024-08-05\n\n### Changed\n\n- fix: remove wonka as a peer dependency\n\n## [0.8.5] - 2024-02-04\n\n### Changed\n\n- fix: include types.ts in build\n\n## [0.8.4] - 2024-01-31\n\n### Changed\n\n- fix: fix generics\n\n## [0.8.3] - 2024-01-29\n\n### Changed\n\n- fix: update types\n\n## [0.8.2] - 2024-01-15\n\n### Changed\n\n- fix: update jotai peer dependency to v2\n\n## [0.8.1] - 2023-12-23\n\n### Changed\n\n- fix: add default staletime for suspense atoms\n- fix: suspense example\n\n## [0.8.0] - 2023-12-09\n\n### Added\n\n- breaking: update atom api to resemble tanstack/query api\n- add: atomWithSuspenseQuery, atomWithSuspenseInfiniteQuery, atomWithMutationState\n\n## [0.7.2] - 2023-09-08\n\n### Changed\n\n- fix: loading mutation does not call refresh on unmount #38\n\n## [0.7.1] - 2023-05-25\n\n### Changed\n\n- Fix result of statusAtom sometimes not updated #35\n\n## [0.7.0] - 2023-04-03\n\n### Added\n\n- feat: atomsWithQueryAsync, plus example #30\n\n## [0.6.0] - 2023-03-03\n\n### Added\n\n- feat: mark internal atoms as private\n\n## [0.5.0] - 2023-01-31\n\n### Added\n\n- Migrate to Jotai v2 API #18\n\n## [0.4.0] - 2022-10-21\n\n### Changed\n\n- fix: status should change #10\n- breaking: simplify api names #11\n\n## [0.3.0] - 2022-10-11\n\n### Changed\n\n- make mutation atom type correct #7\n- update jotai and fix types #8\n\n## [0.2.1] - 2022-10-04\n\n### Changed\n\n- fix setOptions not to delay #6\n\n## [0.2.0] - 2022-09-27\n\n### Changed\n\n- for dataAtom, re-create observer when options change #5\n\n## [0.1.0] - 2022-09-24\n\n### Added\n\n- implement refetch #1\n- feat: observer cache #2\n- feat: infinite query #3\n- feat: mutation api #4\n\n## [0.0.1] - 2022-09-20\n\n### Added\n\n- Initial experimental release\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2022 Daishi Kato\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Jotai Query 🚀 👻\n\n[jotai-tanstack-query](https://github.com/jotai-labs/jotai-tanstack-query) is a Jotai extension library for TanStack Query. It provides a wonderful interface with all of the TanStack Query features, providing you the ability to use those features in combination with your existing Jotai state.\n\n# Table of contents\n\n- [Support](#support)\n- [Install](#install)\n- [Usage](#usage)\n- [Incremental Adoption](#incremental-adoption)\n- [Exported Provider](#exported-provider)\n- [Exported Functions](#exported-functions)\n  - [atomWithQuery](#atomwithquery-usage)\n  - [atomWithQueries](#atomwithqueries-usage)\n  - [atomWithInfiniteQuery](#atomwithinfinitequery-usage)\n  - [atomWithMutation](#atomwithmutation-usage)\n  - [atomWithMutationState](#atomwithmutationstate-usage)\n  - [Suspense](#suspense)\n    - [atomWithSuspenseQuery](#atomwithsuspensequery-usage)\n    - [atomWithSuspenseInfiniteQuery](#atomwithsuspenseinfinitequery-usage)\n- [SSR Support](#ssr-support)\n  - [Next.js App Router Example](#nextjs-app-router-example)\n- [Error Handling](#error-handling)\n- [Dev Tools](#devtools)\n- [FAQ](#faq)\n- [Migrate to v0.8.0](#migrate-to-v080)\n\n### Support\n\njotai-tanstack-query currently supports [Jotai v2](https://jotai.org) and [TanStack Query v5](https://tanstack.com/query/v5).\n\n### Install\n\n```bash\nnpm i jotai jotai-tanstack-query @tanstack/react-query\n```\n\n### Usage\n\n```jsx\nimport { QueryClient } from '@tanstack/react-query'\nimport { useAtomValue } from 'jotai'\nimport { atomWithQuery } from 'jotai-tanstack-query'\nimport { QueryClientAtomProvider } from 'jotai-tanstack-query/react'\n\nconst queryClient = new QueryClient()\n\nexport const Root = () => {\n  return (\n    <QueryClientAtomProvider client={queryClient}>\n      <App />\n    </QueryClientAtomProvider>\n  )\n}\n\nconst todosAtom = atomWithQuery(() => ({\n  queryKey: ['todos'],\n  queryFn: fetchTodoList,\n}))\n\nconst App = () => {\n  const { data, isPending, isError } = useAtomValue(todosAtom)\n\n  if (isPending) return <div>Loading...</div>\n  if (isError) return <div>Error</div>\n\n  return <div>{JSON.stringify(data)}</div>\n}\n```\n\n### Incremental Adoption\n\nYou can incrementally adopt `jotai-tanstack-query` in your app. It's not an all or nothing solution. You just have to ensure you are using the [same QueryClient instance](#exported-provider).\n\n```jsx\n// TanStack/Query\nconst { data, isPending, isError } = useQuery({\n  queryKey: ['todos'],\n  queryFn: fetchTodoList,\n})\n\n// jotai-tanstack-query\nconst todosAtom = atomWithQuery(() => ({\n  queryKey: ['todos'],\n}))\n\nconst { data, isPending, isError } = useAtomValue(todosAtom)\n```\n\n### Exported provider\n\n[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/jotaijs/jotai-tanstack-query/tree/main/examples/08_query_client_atom_provider)\n\n`QueryClientAtomProvider` is a ready-to-use wrapper that combines Jotai Provider and TanStack Query QueryClientProvider.\n\n```jsx\nimport { QueryClient } from '@tanstack/react-query'\nimport { QueryClientAtomProvider } from 'jotai-tanstack-query/react'\n\nconst queryClient = new QueryClient()\n\nexport const Root = () => {\n  return (\n    <QueryClientAtomProvider client={queryClient}>\n      <App />\n    </QueryClientAtomProvider>\n  )\n}\n```\n\nYes, you can absolutely combine them yourself.\n\n```js\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query'\nimport { Provider } from 'jotai/react'\nimport { queryClientAtom } from 'jotai-tanstack-query'\nimport { useHydrateAtoms } from 'jotai/react/utils'\n\nconst queryClient = new QueryClient()\n\nconst HydrateAtoms = ({ children }) => {\n  useHydrateAtoms([[queryClientAtom, queryClient]])\n  return children\n}\n\nexport const Root = () => {\n  return (\n    <QueryClientProvider client={queryClient}>\n      <Provider>\n        <HydrateAtoms>\n          <App />\n        </HydrateAtoms>\n      </Provider>\n    </QueryClientProvider>\n  )\n}\n```\n\n### Exported functions\n\n- `atomWithQuery` for [useQuery](https://tanstack.com/query/v5/docs/react/reference/useQuery)\n- `atomWithQueries` for [useQueries](https://tanstack.com/query/v5/docs/react/reference/useQueries)\n- `atomWithInfiniteQuery` for [useInfiniteQuery](https://tanstack.com/query/v5/docs/react/reference/useInfiniteQuery)\n- `atomWithMutation` for [useMutation](https://tanstack.com/query/v5/docs/react/reference/useMutation)\n- `atomWithSuspenseQuery` for [useSuspenseQuery](https://tanstack.com/query/v5/docs/react/reference/useSuspenseQuery)\n- `atomWithSuspenseInfiniteQuery` for [useSuspenseInfiniteQuery](https://tanstack.com/query/v5/docs/react/reference/useSuspenseInfiniteQuery)\n- `atomWithMutationState` for [useMutationState](https://tanstack.com/query/v5/docs/react/reference/useMutationState)\n\nAll functions follow the same signature.\n\n```ts\nconst dataAtom = atomWithSomething(getOptions, getQueryClient)\n```\n\nThe first `getOptions` parameter is a function that returns an input to the observer.\nThe second optional `getQueryClient` parameter is a function that return [QueryClient](https://tanstack.com/query/v5/docs/reference/QueryClient).\n\n### atomWithQuery usage\n\n[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/jotaijs/jotai-tanstack-query/tree/main/examples/01_query)\n\n`atomWithQuery` creates a new atom that implements a standard [`Query`](https://tanstack.com/query/v5/docs/react/guides/queries) from TanStack Query.\n\n```jsx\nimport { atom, useAtom } from 'jotai'\nimport { atomWithQuery } from 'jotai-tanstack-query'\n\nconst idAtom = atom(1)\nconst userAtom = atomWithQuery((get) => ({\n  queryKey: ['users', get(idAtom)],\n  queryFn: async ({ queryKey: [, id] }) => {\n    const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)\n    return res.json()\n  },\n}))\n\nconst UserData = () => {\n  const [{ data, isPending, isError }] = useAtom(userAtom)\n\n  if (isPending) return <div>Loading...</div>\n  if (isError) return <div>Error</div>\n\n  return <div>{JSON.stringify(data)}</div>\n}\n```\n\n### atomWithQueries usage\n\n[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/jotaijs/jotai-tanstack-query/tree/main/examples/07_queries)\n\n`atomWithQueries` creates a new atom that implements `Dynamic Parallel Queries` from TanStack Query. It allows you to run multiple queries concurrently and optionally combine their results. You can [read more about Dynamic Parallel Queries here](https://tanstack.com/query/v5/docs/framework/react/guides/parallel-queries#dynamic-parallel-queries-with-usequeries).\n\nThere are two ways to use `atomWithQueries`:\n\n#### Basic usage - Returns an array of query atoms\n\n```jsx\nimport { Atom, atom, useAtom } from 'jotai'\nimport { type AtomWithQueryResult, atomWithQueries } from 'jotai-tanstack-query'\n\nconst userIdsAtom = atom([1, 2, 3])\n\n// Independent atom - encapsulates query logic\nconst userQueryAtomsAtom = atom((get) => {\n  const userIds = get(userIdsAtom)\n  return atomWithQueries({\n    queries: userIds.map((id) => () => ({\n      queryKey: ['user', id],\n      queryFn: async ({ queryKey: [, userId] }) => {\n        const res = await fetch(\n          `https://jsonplaceholder.typicode.com/users/${userId}`\n        )\n        return res.json()\n      },\n    })),\n  })\n})\n\n// Independent UI component\nconst UserData = ({ queryAtom }: { queryAtom: Atom<AtomWithQueryResult> }) => {\n  const [{ data, isPending, isError }] = useAtom(queryAtom)\n\n  if (isPending) return <div>Loading...</div>\n  if (isError) return <div>Error</div>\n  if (!data) return null\n\n  return (\n    <div>\n      {data.name} - {data.email}\n    </div>\n  )\n}\n\n// Component only needs one useAtom call\nconst UsersData = () => {\n  const [userQueryAtoms] = useAtom(userQueryAtomsAtom)\n  return (\n    <div>\n      {userQueryAtoms.map((queryAtom, index) => (\n        <UserData key={index} queryAtom={queryAtom} />\n      ))}\n    </div>\n  )\n}\n```\n\n#### Advanced usage - Combine multiple query results\n\n```jsx\nimport { Atom, atom, useAtom } from 'jotai'\nimport { atomWithQueries } from 'jotai-tanstack-query'\n\nconst userIdsAtom = atom([1, 2, 3])\n\n// Independent atom - encapsulates combined query logic\nconst combinedUsersDataAtom = atom((get) => {\n  const userIds = get(userIdsAtom)\n  return atomWithQueries({\n    queries: userIds.map((id) => () => ({\n      queryKey: ['user', id],\n      queryFn: async ({ queryKey: [, userId] }) => {\n        const res = await fetch(\n          `https://jsonplaceholder.typicode.com/users/${userId}`\n        )\n        return res.json()\n      },\n    })),\n    combine: (results) => ({\n      data: results.map((result) => result.data),\n      isPending: results.some((result) => result.isPending),\n      isError: results.some((result) => result.isError),\n    }),\n  })\n})\n\n// Component only needs one useAtom call\nconst CombinedUsersData = () => {\n  const [combinedUsersDataAtomValue] = useAtom(combinedUsersDataAtom)\n  const [{ data, isPending, isError }] = useAtom(combinedUsersDataAtomValue)\n\n  if (isPending) return <div>Loading...</div>\n  if (isError) return <div>Error</div>\n\n  return (\n    <div>\n      {data.map((user) => (\n        <div key={user.id}>\n          {user.name} - {user.email}\n        </div>\n      ))}\n    </div>\n  )\n}\n```\n\n### atomWithInfiniteQuery usage\n\n[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/jotaijs/jotai-tanstack-query/tree/main/examples/03_infinite)\n\n`atomWithInfiniteQuery` is very similar to `atomWithQuery`, however it is for an `InfiniteQuery`, which is used for data that is meant to be paginated. You can [read more about Infinite Queries here](https://tanstack.com/query/v5/docs/guides/infinite-queries).\n\n> Rendering lists that can additively \"load more\" data onto an existing set of data or \"infinite scroll\" is also a very common UI pattern. React Query supports a useful version of useQuery called useInfiniteQuery for querying these types of lists.\n\nA notable difference between a standard query atom is the additional option `getNextPageParam` and `getPreviousPageParam`, which is what you'll use to instruct the query on how to fetch any additional pages.\n\n```jsx\nimport { atom, useAtom } from 'jotai'\nimport { atomWithInfiniteQuery } from 'jotai-tanstack-query'\n\nconst postsAtom = atomWithInfiniteQuery(() => ({\n  queryKey: ['posts'],\n  queryFn: async ({ pageParam }) => {\n    const res = await fetch(`https://jsonplaceholder.typicode.com/posts?_page=${pageParam}`)\n    return res.json()\n  },\n  getNextPageParam: (lastPage, allPages, lastPageParam) => lastPageParam + 1,\n  initialPageParam: 1,\n}))\n\nconst Posts = () => {\n  const [{ data, fetchNextPage, isPending, isError, isFetching }] =\n    useAtom(postsAtom)\n\n  if (isPending) return <div>Loading...</div>\n  if (isError) return <div>Error</div>\n\n  return (\n    <>\n      {data.pages.map((page, index) => (\n        <div key={index}>\n          {page.map((post: any) => (\n            <div key={post.id}>{post.title}</div>\n          ))}\n        </div>\n      ))}\n      <button onClick={() => fetchNextPage()}>Next</button>\n    </>\n  )\n}\n```\n\n### atomWithMutation usage\n\n[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/jotaijs/jotai-tanstack-query/tree/main/examples/05_mutation)\n\n`atomWithMutation` creates a new atom that implements a standard [`Mutation`](https://tanstack.com/query/v5/docs/guides/mutations) from TanStack Query.\n\n> Unlike queries, mutations are typically used to create/update/delete data or perform server side-effects.\n\n`atomWithMutation` supports all options from TanStack Query's [`useMutation`](https://tanstack.com/query/v5/docs/react/reference/useMutation), including:\n\n- `mutationKey` - A unique key for the mutation\n- `mutationFn` - The function that performs the mutation\n- `onMutate` - Called before the mutation is executed (useful for optimistic updates)\n- `onSuccess` - Called when the mutation succeeds\n- `onError` - Called when the mutation fails\n- `onSettled` - Called when the mutation is settled (either success or error)\n- `retry` - Number of retry attempts\n- `retryDelay` - Delay between retries\n- `gcTime` - Time until inactive mutations are garbage collected\n- And all other [MutationOptions](https://tanstack.com/query/v5/docs/react/reference/useMutation#options)\n\n#### Basic usage\n\n```tsx\nimport { useAtom } from 'jotai/react'\nimport { atomWithMutation } from 'jotai-tanstack-query'\n\nconst postAtom = atomWithMutation(() => ({\n  mutationKey: ['posts'],\n  mutationFn: async ({ title }: { title: string }) => {\n    const res = await fetch(`https://jsonplaceholder.typicode.com/posts`, {\n      method: 'POST',\n      body: JSON.stringify({\n        title,\n        body: 'body',\n        userId: 1,\n      }),\n      headers: {\n        'Content-type': 'application/json; charset=UTF-8',\n      },\n    })\n    const data = await res.json()\n    return data\n  },\n}))\n\nconst Posts = () => {\n  const [{ mutate, isPending, status }] = useAtom(postAtom)\n  return (\n    <div>\n      <button onClick={() => mutate({ title: 'foo' })} disabled={isPending}>\n        {isPending ? 'Creating...' : 'Create Post'}\n      </button>\n      <pre>{JSON.stringify(status, null, 2)}</pre>\n    </div>\n  )\n}\n```\n\n#### Optimistic Updates\n\n`atomWithMutation` fully supports optimistic updates through the `onMutate`, `onError`, and `onSettled` callbacks. This allows you to update the UI immediately before the server responds, and roll back if the mutation fails.\n\n```tsx\nimport { Getter } from 'jotai'\nimport { useAtom } from 'jotai/react'\nimport {\n  atomWithMutation,\n  atomWithQuery,\n  queryClientAtom,\n} from 'jotai-tanstack-query'\n\ninterface Post {\n  id: number\n  title: string\n  body: string\n  userId: number\n}\n\ninterface NewPost {\n  title: string\n}\n\ninterface OptimisticContext {\n  previousPosts: Post[] | undefined\n}\n\n// Query to fetch posts list\nconst postsQueryAtom = atomWithQuery(() => ({\n  queryKey: ['posts'],\n  queryFn: async () => {\n    const res = await fetch(\n      'https://jsonplaceholder.typicode.com/posts?_limit=5'\n    )\n    return res.json() as Promise<Post[]>\n  },\n}))\n\n// Mutation with optimistic updates\nconst postAtom = atomWithMutation<Post, NewPost, Error, OptimisticContext>(\n  (get) => {\n    const queryClient = get(queryClientAtom)\n    return {\n      mutationKey: ['addPost'],\n      mutationFn: async ({ title }: NewPost) => {\n        const res = await fetch(`https://jsonplaceholder.typicode.com/posts`, {\n          method: 'POST',\n          body: JSON.stringify({\n            title,\n            body: 'body',\n            userId: 1,\n          }),\n          headers: {\n            'Content-type': 'application/json; charset=UTF-8',\n          },\n        })\n        const data = await res.json()\n        return data as Post\n      },\n      // When mutate is called:\n      onMutate: async (newPost: NewPost) => {\n        // Cancel any outgoing refetches\n        // (so they don't overwrite our optimistic update)\n        await queryClient.cancelQueries({ queryKey: ['posts'] })\n\n        // Snapshot the previous value\n        const previousPosts = queryClient.getQueryData<Post[]>(['posts'])\n\n        // Optimistically update to the new value\n        queryClient.setQueryData<Post[]>(['posts'], (old) => {\n          const optimisticPost: Post = {\n            id: Date.now(), // Temporary ID\n            title: newPost.title,\n            body: 'body',\n            userId: 1,\n          }\n          return old ? [...old, optimisticPost] : [optimisticPost]\n        })\n\n        // Return a result with the snapshotted value\n        return { previousPosts }\n      },\n      // If the mutation fails, use the result returned from onMutate to roll back\n      onError: (\n        _err: Error,\n        _newPost: NewPost,\n        onMutateResult: OptimisticContext | undefined\n      ) => {\n        if (onMutateResult?.previousPosts) {\n          queryClient.setQueryData(['posts'], onMutateResult.previousPosts)\n        }\n      },\n      // Always refetch after error or success:\n      onSettled: (\n        _data: Post | undefined,\n        _error: Error | null,\n        _variables: NewPost,\n        _onMutateResult: OptimisticContext | undefined\n      ) => {\n        queryClient.invalidateQueries({ queryKey: ['posts'] })\n      },\n    }\n  }\n)\n\nconst PostsList = () => {\n  const [{ data: posts, isPending }] = useAtom(postsQueryAtom)\n\n  if (isPending) return <div>Loading posts...</div>\n\n  return (\n    <div>\n      <h3>Posts:</h3>\n      <ul>\n        {posts?.map((post: Post) => (\n          <li key={post.id}>{post.title}</li>\n        ))}\n      </ul>\n    </div>\n  )\n}\n\nconst AddPost = () => {\n  const [{ mutate, isPending }] = useAtom(postAtom)\n  const [title, setTitle] = React.useState('')\n\n  return (\n    <div>\n      <input\n        value={title}\n        onChange={(e) => setTitle(e.target.value)}\n        placeholder=\"Enter post title\"\n      />\n      <button\n        onClick={() => {\n          if (title) {\n            mutate({ title })\n            setTitle('')\n          }\n        }}\n        disabled={isPending}>\n        {isPending ? 'Adding...' : 'Add Post'}\n      </button>\n    </div>\n  )\n}\n```\n\nFor more details on optimistic updates, see the [TanStack Query Optimistic Updates guide](https://tanstack.com/query/v5/docs/framework/react/guides/optimistic-updates).\n\n### atomWithMutationState usage\n\n`atomWithMutationState` creates a new atom that gives you access to all mutations in the [`MutationCache`](https://tanstack.com/query/v5/docs/react/reference/useMutationState).\n\n```jsx\nconst mutationStateAtom = atomWithMutationState((get) => ({\n  filters: {\n    mutationKey: ['posts'],\n  },\n}))\n```\n\n### Suspense\n\njotai-tanstack-query can also be used with React's Suspense.\n\n### atomWithSuspenseQuery usage\n\n[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/jotaijs/jotai-tanstack-query/tree/main/examples/02_suspense)\n\n```jsx\nimport { atom, useAtom } from 'jotai'\nimport { atomWithSuspenseQuery } from 'jotai-tanstack-query'\n\nconst idAtom = atom(1)\nconst userAtom = atomWithSuspenseQuery((get) => ({\n  queryKey: ['users', get(idAtom)],\n  queryFn: async ({ queryKey: [, id] }) => {\n    const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)\n    return res.json()\n  },\n}))\n\nconst UserData = () => {\n  const [{ data }] = useAtom(userAtom)\n\n  return <div>{JSON.stringify(data)}</div>\n}\n```\n\n### atomWithSuspenseInfiniteQuery usage\n\n[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/jotaijs/jotai-tanstack-query/tree/main/examples/04_infinite_suspense)\n\n```jsx\nimport { atom, useAtom } from 'jotai'\nimport { atomWithSuspenseInfiniteQuery } from 'jotai-tanstack-query'\n\nconst postsAtom = atomWithSuspenseInfiniteQuery(() => ({\n  queryKey: ['posts'],\n  queryFn: async ({ pageParam }) => {\n    const res = await fetch(`https://jsonplaceholder.typicode.com/posts?_page=${pageParam}`)\n    return res.json()\n  },\n  getNextPageParam: (lastPage, allPages, lastPageParam) => lastPageParam + 1,\n  initialPageParam: 1,\n}))\n\nconst Posts = () => {\n  const [{ data, fetchNextPage, isPending, isError, isFetching }] =\n    useAtom(postsAtom)\n\n  return (\n    <>\n      {data.pages.map((page, index) => (\n        <div key={index}>\n          {page.map((post: any) => (\n            <div key={post.id}>{post.title}</div>\n          ))}\n        </div>\n      ))}\n      <button onClick={() => fetchNextPage()}>Next</button>\n    </>\n  )\n}\n```\n\n### SSR support\n\nTo understand if your application can benefit from React Query when also using Server Components, see the article [You Might Not Need React Query](https://tkdodo.eu/blog/you-might-not-need-react-query).\n\nAll atoms can be used within the context of a server side rendered app, such as a next.js app or Gatsby app. You can [use both options](https://tanstack.com/query/v5/docs/framework/react/guides/ssr) that React Query supports for use within SSR apps, [hydration](https://tanstack.com/query/v5/docs/framework/react/guides/ssr#using-the-hydration-apis) or [`initialData`](https://tanstack.com/query/v5/docs/framework/react/guides/ssr#get-started-fast-with-initialdata).\n\n#### Next.js App Router Example\n\n[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/jotaijs/jotai-tanstack-query/tree/main/examples/11_nextjs_app_router)\n\n### Error handling\n\nFetch error will be thrown and can be caught with ErrorBoundary.\nRefetching may recover from a temporary error.\n\nSee [a working example](https://stackblitz.com/github/jotaijs/jotai-tanstack-query/tree/main/examples/09_error_boundary) to learn more.\n\n### Devtools\n\nIn order to use the Devtools, you need to install it additionally.\n\n```bash\n$ npm i @tanstack/react-query-devtools --save-dev\n```\n\nAll you have to do is put the `<ReactQueryDevtools />` within `<QueryClientAtomProvider />`.\n\n```tsx\nimport { QueryClient, QueryCache } from '@tanstack/react-query'\nimport { ReactQueryDevtools } from '@tanstack/react-query-devtools'\nimport { QueryClientAtomProvider } from 'jotai-tanstack-query/react'\n\nconst queryClient = new QueryClient()\n\nexport const Root = () => {\n  return (\n    <QueryClientAtomProvider client={queryClient}>\n      <App />\n      <ReactQueryDevtools />\n    </QueryClientAtomProvider>\n  )\n}\n```\n\n## FAQ\n\n### atomsWithQuery `queryKey` type is always `unknown`?\n\nExplicitly declare the `get:Getter` to get proper type inference for `queryKey`.\n\n```tsx\nimport { Getter } from 'jotai'\n\n// ❌ Without explicit Getter type, queryKey type might be unknown\nconst userAtom = atomWithQuery((get) => ({\n  queryKey: ['users', get(idAtom).toString()],\n  queryFn: async ({ queryKey: [, id] }) => {\n    // typeof id = unknown\n  },\n}))\n\n// ✅ With explicit Getter type, queryKey gets proper type inference\nconst userAtom = atomWithQuery((get: Getter) => ({\n  queryKey: ['users', get(idAtom).toString()],\n  queryFn: async ({ queryKey: [, id] }) => {\n    // typeof id = string\n  },\n}))\n```\n\n### Why “tracked properties” are not supported\n\nTanStack Query provides a feature called [tracked properties](https://tanstack.com/query/v5/docs/framework/react/guides/render-optimizations#tracked-properties), which only triggers a re-render when a property that was actually accessed changes.\n\nFor example:\n\n```tsx\nconst { data } = useQuery(...)\n```\n\nIf only data is accessed, changes to isFetching or status will not cause a re-render.\n\nHowever, atomWithQuery intentionally does **not** implement this behavior.\n\nThe atom approach is to create derived atoms rather than automatic tracking.\n\n```js\nconst queryAtom = atomWithQuery(...)\n\nconst dataAtom = atom((get) => get(queryAtom).data)\n\nconst Component = () => {\n  const data = useAtomValue(dataAtom)\n  ...\n}\n```\n\nThe component only subscribes to data, changes to other properties (e.g. isFetching) won’t trigger re-renders\n\n\n## Migrate to v0.8.0\n\n### Change in atom signature\n\nAll atom signatures have changed to be more consistent with TanStack Query.\nv0.8.0 returns only a single atom, instead of a tuple of atoms, and hence the name change from `atomsWithSomething` to`atomWithSomething`.\n\n```diff\n\n- const [dataAtom, statusAtom] = atomsWithSomething(getOptions, getQueryClient)\n+ const dataAtom = atomWithSomething(getOptions, getQueryClient)\n\n```\n\n### Simplified Return Structure\n\nIn the previous version of `jotai-tanstack-query`, the query atoms `atomsWithQuery` and `atomsWithInfiniteQuery` returned a tuple of atoms: `[dataAtom, statusAtom]`. This design separated the data and its status into two different atoms.\n\n#### atomWithQuery and atomWithInfiniteQuery\n\n- `dataAtom` was used to access the actual data (`TData`).\n- `statusAtom` provided the status object (`QueryObserverResult<TData, TError>`), which included additional attributes like `isPending`, `isError`, etc.\n\nIn v0.8.0, they have been replaced by `atomWithQuery` and `atomWithInfiniteQuery` to return only a single `dataAtom`. This `dataAtom` now directly provides the `QueryObserverResult<TData, TError>`, aligning it closely with the behavior of Tanstack Query's bindings.\n\nTo migrate to the new version, replace the separate `dataAtom` and `statusAtom` usage with the unified `dataAtom` that now contains both data and status information.\n\n```diff\n- const [dataAtom, statusAtom] = atomsWithQuery(/* ... */);\n- const [data] = useAtom(dataAtom);\n- const [status] = useAtom(statusAtom);\n\n+ const dataAtom = atomWithQuery(/* ... */);\n+ const [{ data, isPending, isError }] = useAtom(dataAtom);\n```\n\n#### atomWithMutation\n\nSimilar to `atomsWithQuery` and `atomsWithInfiniteQuery`, `atomWithMutation` also returns a single atom instead of a tuple of atoms. The return type of the atom value is `MutationObserverResult<TData, TError, TVariables, TContext>`.\n\n```diff\n\n- const [, postAtom] = atomsWithMutation(/* ... */);\n- const [post, mutate] = useAtom(postAtom); // Accessing mutation status from post; and mutate() to execute the mutation\n\n+ const postAtom = atomWithMutation(/* ... */);\n+ const [{ data, error, mutate }] = useAtom(postAtom); // Accessing mutation result and mutate method from the same atom\n\n```\n"
  },
  {
    "path": "__tests__/01_basic.spec.tsx",
    "content": "import {\n  atomWithInfiniteQuery,\n  atomWithMutation,\n  atomWithMutationState,\n  atomWithQuery,\n  atomWithSuspenseInfiniteQuery,\n  atomWithSuspenseQuery,\n  queryClientAtom,\n} from '../src/index'\n\ndescribe('basic spec', () => {\n  it('should export functions', () => {\n    expect(queryClientAtom).toBeDefined()\n    expect(atomWithQuery).toBeDefined()\n    expect(atomWithInfiniteQuery).toBeDefined()\n    expect(atomWithMutation).toBeDefined()\n    expect(atomWithSuspenseQuery).toBeDefined()\n    expect(atomWithSuspenseInfiniteQuery).toBeDefined()\n    expect(atomWithMutationState).toBeDefined()\n  })\n})\n"
  },
  {
    "path": "__tests__/atomWithInfiniteQuery.spec.tsx",
    "content": "import React, { Component, StrictMode, Suspense } from 'react'\nimport type { ReactNode } from 'react'\nimport { fireEvent, render } from '@testing-library/react'\nimport { useAtom } from 'jotai/react'\nimport { atom } from 'jotai/vanilla'\nimport { vi } from 'vitest'\nimport { atomWithInfiniteQuery } from '../src/index'\n\nlet originalConsoleError: typeof console.error\n\nbeforeEach(() => {\n  originalConsoleError = console.error\n  console.error = vi.fn()\n})\nafterEach(() => {\n  console.error = originalConsoleError\n})\n\nit('infinite query basic test', async () => {\n  let resolve = () => {}\n  type DataResponse = { response: { count: number } }\n  const countAtom = atomWithInfiniteQuery<DataResponse>(() => ({\n    initialPageParam: 1,\n    getNextPageParam: (lastPage) => lastPage.response.count + 1,\n    queryKey: ['countInfinite'],\n\n    queryFn: async ({ pageParam }) => {\n      await new Promise<void>((r) => (resolve = r))\n      return { response: { count: pageParam as number } }\n    },\n  }))\n\n  const Counter = () => {\n    const [countData] = useAtom(countAtom)\n\n    const { data, isPending, isError } = countData\n\n    if (isPending) return <>loading</>\n    if (isError) return <>error</>\n\n    return (\n      <>\n        <div>page count: {data.pages.length}</div>\n      </>\n    )\n  }\n\n  const { findByText } = render(\n    <StrictMode>\n      <Counter />\n    </StrictMode>\n  )\n\n  await findByText('loading')\n  resolve()\n  await findByText('page count: 1')\n})\n\nit('infinite query next page test', async () => {\n  const mockFetch = vi.fn((response: { count: number }) => ({ response }))\n  let resolve = () => {}\n  const countAtom = atomWithInfiniteQuery<{ response: { count: number } }>(\n    () => ({\n      initialPageParam: 1,\n      queryKey: ['nextPageAtom'],\n      queryFn: async ({ pageParam }) => {\n        await new Promise<void>((r) => (resolve = r))\n        return mockFetch({ count: pageParam as number })\n      },\n      getNextPageParam: (lastPage) => {\n        const {\n          response: { count },\n        } = lastPage\n        return (count + 1).toString()\n      },\n      getPreviousPageParam: (lastPage) => {\n        const {\n          response: { count },\n        } = lastPage\n        return (count - 1).toString()\n      },\n    })\n  )\n  const Counter = () => {\n    const [countData] = useAtom(countAtom)\n\n    const { isPending, isError, data, fetchNextPage, fetchPreviousPage } =\n      countData\n\n    if (isPending) return <>loading</>\n    if (isError) return <>error</>\n\n    return (\n      <>\n        <div>page count: {data.pages.length}</div>\n        <button onClick={() => fetchNextPage()}>next</button>\n        <button onClick={() => fetchPreviousPage()}>prev</button>\n      </>\n    )\n  }\n\n  const { findByText, getByText } = render(\n    <>\n      <Counter />\n    </>\n  )\n\n  await findByText('loading')\n  resolve()\n  await findByText('page count: 1')\n  expect(mockFetch).toBeCalledTimes(1)\n\n  fireEvent.click(getByText('next'))\n  resolve()\n  await findByText('page count: 2')\n  expect(mockFetch).toBeCalledTimes(2)\n\n  fireEvent.click(getByText('prev'))\n  resolve()\n  await findByText('page count: 3')\n  expect(mockFetch).toBeCalledTimes(3)\n})\n\nit('infinite query with enabled', async () => {\n  const slugAtom = atom<string | null>(null)\n\n  let resolve = () => {}\n  type DataResponse = {\n    response: {\n      slug: string\n      currentPage: number\n    }\n  }\n  const slugQueryAtom = atomWithInfiniteQuery<DataResponse>((get) => {\n    const slug = get(slugAtom)\n    return {\n      initialPageParam: 1,\n      getNextPageParam: (lastPage) => lastPage.response.currentPage + 1,\n      enabled: !!slug,\n      queryKey: ['disabled_until_value', slug],\n      queryFn: async ({ pageParam }) => {\n        await new Promise<void>((r) => (resolve = r))\n        return {\n          response: { slug: `hello-${slug}`, currentPage: pageParam as number },\n        }\n      },\n    }\n  })\n\n  const Slug = () => {\n    const [slugQueryData] = useAtom(slugQueryAtom)\n    const { data, isPending, isError, fetchStatus } = slugQueryData\n\n    if (isPending && fetchStatus === 'idle') return <div>not enabled</div>\n\n    if (isPending) return <>loading</>\n    if (isError) return <>error</>\n\n    return <div>slug: {data.pages[0]?.response?.slug}</div>\n  }\n\n  const Parent = () => {\n    const [, setSlug] = useAtom(slugAtom)\n    return (\n      <div>\n        <button\n          onClick={() => {\n            setSlug('world')\n          }}>\n          set slug\n        </button>\n        <Slug />\n      </div>\n    )\n  }\n\n  const { getByText, findByText } = render(\n    <StrictMode>\n      <Suspense fallback=\"loading\">\n        <Parent />\n      </Suspense>\n    </StrictMode>\n  )\n\n  await findByText('not enabled')\n\n  fireEvent.click(getByText('set slug'))\n  await findByText('loading')\n  resolve()\n  await findByText('slug: hello-world')\n})\n\nit('infinite query with enabled 2', async () => {\n  const enabledAtom = atom<boolean>(true)\n  const slugAtom = atom<string | null>('first')\n  type DataResponse = {\n    response: {\n      slug: string\n      currentPage: number\n    }\n  }\n  let resolve = () => {}\n  const slugQueryAtom = atomWithInfiniteQuery<DataResponse>((get) => {\n    const slug = get(slugAtom)\n    const isEnabled = get(enabledAtom)\n    return {\n      getNextPageParam: (lastPage) => lastPage.response.currentPage + 1,\n      initialPageParam: 1,\n      enabled: isEnabled,\n      queryKey: ['enabled_toggle'],\n      queryFn: async ({ pageParam }) => {\n        await new Promise<void>((r) => (resolve = r))\n        return {\n          response: { slug: `hello-${slug}`, currentPage: pageParam as number },\n        }\n      },\n    }\n  })\n\n  const Slug = () => {\n    const [slugQueryData] = useAtom(slugQueryAtom)\n    const { data, isPending, isError, fetchStatus } = slugQueryData\n\n    if (isPending && fetchStatus === 'idle') return <div>not enabled</div>\n\n    if (isPending) return <>loading</>\n    if (isError) return <>error</>\n\n    return <div>slug: {data.pages[0]?.response.slug}</div>\n  }\n\n  const Parent = () => {\n    const [, setSlug] = useAtom(slugAtom)\n    const [, setEnabled] = useAtom(enabledAtom)\n    return (\n      <div>\n        <button\n          onClick={() => {\n            setSlug('world')\n          }}>\n          set slug\n        </button>\n        <button\n          onClick={() => {\n            setEnabled(true)\n          }}>\n          set enabled\n        </button>\n        <button\n          onClick={() => {\n            setEnabled(false)\n          }}>\n          set disabled\n        </button>\n        <Slug />\n      </div>\n    )\n  }\n\n  const { getByText, findByText } = render(\n    <StrictMode>\n      <Suspense fallback=\"loading\">\n        <Parent />\n      </Suspense>\n    </StrictMode>\n  )\n\n  await findByText('loading')\n  resolve()\n  await findByText('slug: hello-first')\n  fireEvent.click(getByText('set disabled'))\n  fireEvent.click(getByText('set slug'))\n\n  await findByText('slug: hello-first')\n\n  fireEvent.click(getByText('set enabled'))\n  resolve()\n  await findByText('slug: hello-world')\n})\n\ndescribe('error handling', () => {\n  class ErrorBoundary extends Component<\n    { message?: string; retry?: () => void; children: ReactNode },\n    { hasError: boolean }\n  > {\n    constructor(props: { message?: string; children: ReactNode }) {\n      super(props)\n      this.state = { hasError: false }\n    }\n    static getDerivedStateFromError() {\n      return { hasError: true }\n    }\n    render() {\n      return this.state.hasError ? (\n        <div>\n          {this.props.message || 'errored'}\n          {this.props.retry && (\n            <button\n              onClick={() => {\n                this.props.retry?.()\n                this.setState({ hasError: false })\n              }}>\n              retry\n            </button>\n          )}\n        </div>\n      ) : (\n        this.props.children\n      )\n    }\n  }\n\n  it('can catch error in error boundary', async () => {\n    let resolve = () => {}\n    const countAtom = atomWithInfiniteQuery(() => ({\n      initialPageParam: 1,\n      getNextPageParam: (lastPage) => lastPage.response.count + 1,\n      queryKey: ['error test', 'count1Infinite'],\n      retry: false,\n      queryFn: async (): Promise<{ response: { count: number } }> => {\n        await new Promise<void>((r) => (resolve = r))\n        throw new Error('fetch error')\n      },\n      throwOnError: true,\n    }))\n    const Counter = () => {\n      const [{ data, isPending }] = useAtom(countAtom)\n\n      if (isPending) return <>loading</>\n\n      const pages = data?.pages\n\n      return (\n        <>\n          <div>count: {pages?.[0]?.response.count}</div>\n        </>\n      )\n    }\n\n    const { findByText } = render(\n      <StrictMode>\n        <ErrorBoundary>\n          <Suspense fallback=\"loading\">\n            <Counter />\n          </Suspense>\n        </ErrorBoundary>\n      </StrictMode>\n    )\n\n    await findByText('loading')\n    resolve()\n    await findByText('errored')\n  })\n})\n"
  },
  {
    "path": "__tests__/atomWithMutation.spec.tsx",
    "content": "import React, { useState } from 'react'\nimport { fireEvent, render } from '@testing-library/react'\nimport { useAtom } from 'jotai/react'\nimport { atomWithMutation } from '../src/index'\n\nit('atomWithMutation should be refreshed on unmount (#2060)', async () => {\n  let resolve: (() => void) | undefined\n  const mutateAtom = atomWithMutation<number, number>(() => ({\n    mutationKey: ['test-atom'],\n    mutationFn: async (a) => {\n      await new Promise<void>((r) => {\n        resolve = r\n      })\n      return a\n    },\n  }))\n\n  function App() {\n    const [mount, setMount] = useState<boolean>(true)\n    return (\n      <div>\n        <button onClick={() => setMount(false)}>unmount</button>\n        <button onClick={() => setMount(true)}>mount</button>\n        {mount && <TestView />}\n      </div>\n    )\n  }\n\n  function TestView() {\n    const [{ mutate, isPending, status }] = useAtom(mutateAtom)\n    return (\n      <div>\n        <p>status: {status}</p>\n        <button disabled={isPending} onClick={() => mutate(1)}>\n          mutate\n        </button>\n      </div>\n    )\n  }\n\n  const { findByText, getByText } = render(<App />)\n\n  await findByText('status: idle')\n\n  fireEvent.click(getByText('mutate'))\n  await findByText('status: pending')\n  resolve?.()\n  await findByText('status: success')\n\n  fireEvent.click(getByText('unmount'))\n  fireEvent.click(getByText('mount'))\n  await findByText('status: idle')\n  resolve?.()\n})\n"
  },
  {
    "path": "__tests__/atomWithMutationState.spec.tsx",
    "content": "import React from 'react'\nimport { QueryClient } from '@tanstack/query-core'\nimport { fireEvent, render } from '@testing-library/react'\nimport { Provider, useAtom } from 'jotai'\nimport { atomWithMutation, atomWithMutationState } from '../src'\n\nit('atomWithMutationState multiple', async () => {\n  const client = new QueryClient()\n  let resolve1: (() => void) | undefined\n  const mutateAtom1 = atomWithMutation<number, number>(\n    () => ({\n      mutationKey: ['test-atom'],\n      mutationFn: async (a) => {\n        await new Promise<void>((r) => {\n          resolve1 = r\n        })\n        return a\n      },\n    }),\n    () => client\n  )\n  let resolve2: (() => void) | undefined\n  const mutateAtom2 = atomWithMutation<number, number>(\n    () => ({\n      mutationKey: ['test-atom'],\n      mutationFn: async (a) => {\n        await new Promise<void>((r) => {\n          resolve2 = r\n        })\n        return a\n      },\n    }),\n    () => client\n  )\n\n  const pendingMutationStateAtom = atomWithMutationState(\n    () => ({ filters: { mutationKey: ['test-atom'], status: 'pending' } }),\n    () => client\n  )\n\n  const allMutationStatesAtom = atomWithMutationState(\n    () => ({ filters: { mutationKey: ['test-atom'] } }),\n    () => client\n  )\n\n  function App() {\n    const [{ mutate: mutate1 }] = useAtom(mutateAtom1)\n    const [{ mutate: mutate2 }] = useAtom(mutateAtom2)\n    const [pendingMutationState] = useAtom(pendingMutationStateAtom)\n    const [allMutationStates] = useAtom(allMutationStatesAtom)\n\n    return (\n      <div>\n        <p>all: {allMutationStates.length}</p>\n        <p>pending: {pendingMutationState.length}</p>\n        <button\n          onClick={() => {\n            mutate1(1)\n            mutate2(2)\n          }}>\n          mutate\n        </button>\n      </div>\n    )\n  }\n\n  const { findByText, getByText } = render(\n    <Provider>\n      <App />\n    </Provider>\n  )\n\n  await findByText('all: 0')\n  await findByText('pending: 0')\n  fireEvent.click(getByText('mutate'))\n  await findByText('all: 2')\n  await findByText('pending: 2')\n  resolve1?.()\n  await findByText('all: 2')\n  await findByText('pending: 1')\n  resolve2?.()\n  await findByText('all: 2')\n  await findByText('pending: 0')\n})\n"
  },
  {
    "path": "__tests__/atomWithQuery.spec.tsx",
    "content": "import React, { StrictMode, Suspense, useState } from 'react'\nimport { QueryClient } from '@tanstack/query-core'\nimport { fireEvent, render } from '@testing-library/react'\nimport { Getter, atom, useAtom, useSetAtom } from 'jotai'\nimport { unwrap } from 'jotai/utils'\nimport { ErrorBoundary } from 'react-error-boundary'\nimport { vi } from 'vitest'\nimport { atomWithQuery } from '../src'\n\nlet originalConsoleError: typeof console.error\n\nbeforeEach(() => {\n  originalConsoleError = console.error\n  console.error = vi.fn()\n})\nafterEach(() => {\n  console.error = originalConsoleError\n})\n\nit('query basic test', async () => {\n  let resolve = () => {}\n  const countAtom = atomWithQuery(() => ({\n    queryKey: ['test1'],\n    queryFn: async () => {\n      await new Promise<void>((r) => (resolve = r))\n      return { response: { count: 0 } }\n    },\n  }))\n  const Counter = () => {\n    const [countData] = useAtom(countAtom)\n    const { data, isPending, isError } = countData\n\n    if (isPending) {\n      return <>loading</>\n    }\n\n    if (isError) {\n      return <>errored</>\n    }\n\n    return (\n      <>\n        <div>count: {data.response.count}</div>\n      </>\n    )\n  }\n\n  const { findByText } = render(\n    <StrictMode>\n      <Counter />\n    </StrictMode>\n  )\n\n  await findByText('loading')\n  resolve()\n  await findByText('count: 0')\n})\n\nit('async query basic test', async () => {\n  const fn = vi.fn(() => Promise.resolve(2))\n  const queryFn = vi.fn((id) => {\n    return Promise.resolve({ response: { id } })\n  })\n\n  const userIdAtom = atom(async () => {\n    return await fn()\n  })\n\n  const userAtom = atomWithQuery((get) => {\n    const userId = get(unwrap(userIdAtom))\n\n    return {\n      queryKey: ['userId', userId],\n      queryFn: async ({ queryKey: [, id] }) => {\n        const res = await queryFn(id)\n        return res\n      },\n      enabled: !!userId,\n    }\n  })\n  const User = () => {\n    const [userData] = useAtom(userAtom)\n    const { data, isPending, isError } = userData\n\n    if (isPending) return <>loading</>\n    if (isError) return <>errored</>\n\n    return (\n      <>\n        <div>id: {data.response.id}</div>\n      </>\n    )\n  }\n  const { findByText } = render(\n    <StrictMode>\n      <Suspense fallback=\"loading\">\n        <User />\n      </Suspense>\n    </StrictMode>\n  )\n\n  await findByText('loading')\n  await findByText('id: 2')\n  expect(queryFn).toHaveBeenCalledTimes(1)\n})\n\nit('query refetch', async () => {\n  let count = 0\n  const mockFetch = vi.fn((response: { count: number }) => ({\n    response,\n  }))\n  let resolve = () => {}\n  const countAtom = atomWithQuery(() => ({\n    queryKey: ['test3'],\n    queryFn: async () => {\n      await new Promise<void>((r) => (resolve = r))\n      const response = mockFetch({ count })\n      ++count\n      return response\n    },\n  }))\n  const Counter = () => {\n    const [{ data, isPending, isError, refetch }] = useAtom(countAtom)\n\n    if (isPending) {\n      return <>loading</>\n    }\n\n    if (isError) {\n      return <>errored</>\n    }\n\n    return (\n      <>\n        <div>count: {data?.response.count}</div>\n        <button onClick={() => refetch()}>refetch</button>\n      </>\n    )\n  }\n\n  const { findByText, getByText } = render(\n    <StrictMode>\n      <Counter />\n    </StrictMode>\n  )\n\n  await findByText('loading')\n  resolve()\n  await findByText('count: 0')\n  expect(mockFetch).toBeCalledTimes(1)\n\n  fireEvent.click(getByText('refetch'))\n  await expect(() => findByText('loading')).rejects.toThrow() //refetch implementation in tanstack doesn't trigger loading state\n  resolve()\n  await findByText('count: 1')\n  expect(mockFetch).toBeCalledTimes(2)\n})\n\nit('query with enabled', async () => {\n  const slugAtom = atom<string | null>(null)\n  const mockFetch = vi.fn((response) => ({ response }))\n  let resolve = () => {}\n  const slugQueryAtom = atomWithQuery((get) => {\n    const slug = get(slugAtom)\n    return {\n      enabled: !!slug,\n      queryKey: ['disabled_until_value', slug],\n      queryFn: async () => {\n        await new Promise<void>((r) => (resolve = r))\n        return mockFetch({ slug: `hello-${slug}` })\n      },\n    }\n  })\n\n  const Slug = () => {\n    const [slugQueryData] = useAtom(slugQueryAtom)\n\n    const { data, isPending, isError, status, fetchStatus } = slugQueryData\n\n    //ref: https://tanstack.com/query/v4/docs/react/guides/dependent-queries\n    if (status === 'pending' && fetchStatus === 'idle') {\n      return <div>not enabled</div>\n    }\n\n    if (isPending) {\n      return <>loading</>\n    }\n\n    if (isError) {\n      return <>errored</>\n    }\n\n    return <div>slug: {data.response.slug}</div>\n  }\n\n  const Parent = () => {\n    const [, setSlug] = useAtom(slugAtom)\n    return (\n      <div>\n        <button\n          onClick={() => {\n            setSlug('world')\n          }}>\n          set slug\n        </button>\n        <Slug />\n      </div>\n    )\n  }\n\n  const { getByText, findByText } = render(\n    <StrictMode>\n      <Parent />\n    </StrictMode>\n  )\n\n  await findByText('not enabled')\n  expect(mockFetch).toHaveBeenCalledTimes(0)\n\n  fireEvent.click(getByText('set slug'))\n  await findByText('loading')\n  resolve()\n  await findByText('slug: hello-world')\n  expect(mockFetch).toHaveBeenCalledTimes(1)\n})\n\nit('query with enabled 2', async () => {\n  const mockFetch = vi.fn((response) => ({ response }))\n  const enabledAtom = atom<boolean>(true)\n  const slugAtom = atom<string | null>('first')\n  let resolve = () => {}\n\n  const slugQueryAtom = atomWithQuery((get: Getter) => {\n    const slug = get(slugAtom)\n    const enabled = get(enabledAtom)\n    return {\n      enabled: enabled,\n      queryKey: ['enabled_toggle'],\n      queryFn: async () => {\n        await new Promise<void>((r) => (resolve = r))\n\n        return mockFetch({ slug: `hello-${slug}` })\n      },\n    }\n  })\n\n  const Slug = () => {\n    const [slugQueryAtomData] = useAtom(slugQueryAtom)\n    const { data, isError, isPending, status, fetchStatus } = slugQueryAtomData\n\n    if (status === 'pending' && fetchStatus === 'idle') {\n      return <div>not enabled</div>\n    }\n\n    if (isPending) {\n      return <>loading</>\n    }\n\n    if (isError) {\n      return <>errored</>\n    }\n    return <div>slug: {data.response.slug}</div>\n  }\n\n  const Parent = () => {\n    const [, setSlug] = useAtom(slugAtom)\n    const [, setEnabled] = useAtom(enabledAtom)\n    return (\n      <div>\n        <button\n          onClick={() => {\n            setSlug('world')\n          }}>\n          set slug\n        </button>\n        <button\n          onClick={() => {\n            setEnabled(true)\n          }}>\n          set enabled\n        </button>\n        <button\n          onClick={() => {\n            setEnabled(false)\n          }}>\n          set disabled\n        </button>\n        <Slug />\n      </div>\n    )\n  }\n\n  const { getByText, findByText } = render(\n    <StrictMode>\n      <Parent />\n    </StrictMode>\n  )\n\n  await findByText('loading')\n  resolve()\n  await findByText('slug: hello-first')\n  expect(mockFetch).toHaveBeenCalledTimes(1)\n\n  fireEvent.click(getByText('set disabled'))\n  fireEvent.click(getByText('set slug'))\n\n  await findByText('slug: hello-first')\n  expect(mockFetch).toHaveBeenCalledTimes(1)\n\n  fireEvent.click(getByText('set enabled'))\n\n  await expect(() => findByText('loading')).rejects.toThrow() //refetch implementation in tanstack doesn't trigger loading state\n  resolve()\n  await findByText('slug: hello-world')\n  expect(mockFetch).toHaveBeenCalledTimes(2)\n})\n\nit('query with enabled (#500)', async () => {\n  const enabledAtom = atom(true)\n  let resolve = () => {}\n  const countAtom = atomWithQuery((get) => {\n    const enabled = get(enabledAtom)\n    return {\n      enabled,\n      queryKey: ['count_500_issue'],\n      queryFn: async () => {\n        await new Promise<void>((r) => (resolve = r))\n        return { response: { count: 1 } }\n      },\n    }\n  })\n\n  const Counter = () => {\n    const [countData] = useAtom(countAtom)\n\n    const { data, isPending, isError } = countData\n\n    if (isPending) {\n      return <>loading</>\n    }\n\n    if (isError) {\n      return <>errored</>\n    }\n\n    return <div>count: {data.response.count}</div>\n  }\n\n  const Parent = () => {\n    const [showChildren, setShowChildren] = useState(true)\n    const [, setEnabled] = useAtom(enabledAtom)\n    return (\n      <div>\n        <button\n          onClick={() => {\n            setShowChildren((x) => !x)\n            setEnabled((x) => !x)\n          }}>\n          toggle\n        </button>\n        {showChildren ? <Counter /> : <div>hidden</div>}\n      </div>\n    )\n  }\n\n  const { getByText, findByText } = render(\n    <StrictMode>\n      <Suspense fallback=\"loading\">\n        <Parent />\n      </Suspense>\n    </StrictMode>\n  )\n\n  await findByText('loading')\n  resolve()\n  await findByText('count: 1')\n\n  fireEvent.click(getByText('toggle'))\n  resolve()\n  await findByText('hidden')\n\n  fireEvent.click(getByText('toggle'))\n  resolve()\n  await findByText('count: 1')\n})\n\nit('query with initialData test', async () => {\n  const mockFetch = vi.fn((response: { count: number }) => ({\n    response,\n  }))\n\n  let resolve = () => {}\n\n  const countAtom = atomWithQuery(() => ({\n    queryKey: ['initialData_count1'],\n    queryFn: async () => {\n      await new Promise<void>((r) => (resolve = r))\n      return mockFetch({ count: 10 })\n    },\n    initialData: { response: { count: 0 } },\n  }))\n  const Counter = () => {\n    const [\n      {\n        data: {\n          response: { count },\n        },\n      },\n    ] = useAtom(countAtom)\n\n    return (\n      <>\n        <div>count: {count}</div>\n      </>\n    )\n  }\n\n  const { findByText } = render(\n    <StrictMode>\n      <Counter />\n    </StrictMode>\n  )\n\n  // NOTE: the atom is never loading\n  await expect(() => findByText('loading')).rejects.toThrow()\n  await findByText('count: 0')\n  resolve()\n  await findByText('count: 10')\n  expect(mockFetch).toHaveBeenCalledTimes(1)\n})\n\nit('query dependency test', async () => {\n  const baseCountAtom = atom(0)\n  const incrementAtom = atom(null, (_get, set) =>\n    set(baseCountAtom, (c) => c + 1)\n  )\n  let resolve = () => {}\n  const countAtom = atomWithQuery((get) => ({\n    queryKey: ['count_with_dependency', get(baseCountAtom)],\n    queryFn: async () => {\n      await new Promise<void>((r) => (resolve = r))\n      return { response: { count: get(baseCountAtom) } }\n    },\n  }))\n\n  const Counter = () => {\n    const [countData] = useAtom(countAtom)\n    const { data, isPending, isError } = countData\n\n    if (isPending) {\n      return <>loading</>\n    }\n\n    if (isError) {\n      return <>errored</>\n    }\n\n    return (\n      <>\n        <div>count: {data.response.count}</div>\n      </>\n    )\n  }\n\n  const Controls = () => {\n    const [, increment] = useAtom(incrementAtom)\n    return <button onClick={increment}>increment</button>\n  }\n\n  const { getByText, findByText } = render(\n    <StrictMode>\n      <Counter />\n      <Controls />\n    </StrictMode>\n  )\n\n  await findByText('loading')\n  resolve()\n  await findByText('count: 0')\n\n  fireEvent.click(getByText('increment'))\n  await findByText('loading')\n  resolve()\n  await findByText('count: 1')\n})\n\nit('query expected QueryCache test', async () => {\n  const queryClient = new QueryClient()\n  let resolve = () => {}\n  const countAtom = atomWithQuery(\n    () => ({\n      queryKey: ['count6'],\n      queryFn: async () => {\n        await new Promise<void>((r) => (resolve = r))\n        return { response: { count: 0 } }\n      },\n    }),\n    () => queryClient\n  )\n  const Counter = () => {\n    const [countData] = useAtom(countAtom)\n\n    const { data, isPending, isError } = countData\n\n    if (isPending) {\n      return <>loading</>\n    }\n\n    if (isError) {\n      return <>errored</>\n    }\n\n    return (\n      <>\n        <div>count: {data.response.count}</div>\n      </>\n    )\n  }\n\n  const { findByText } = render(\n    <StrictMode>\n      <Counter />\n    </StrictMode>\n  )\n\n  await findByText('loading')\n  resolve()\n  await findByText('count: 0')\n  expect(queryClient.getQueryCache().getAll().length).toBe(1)\n})\n\ndescribe('error handling', () => {\n  it('can catch error in error boundary', async () => {\n    let resolve = () => {}\n    const countAtom = atomWithQuery(() => ({\n      queryKey: ['catch'],\n      retry: false,\n      queryFn: async (): Promise<{ response: { count: number } }> => {\n        await new Promise<void>((r) => (resolve = r))\n        throw new Error('fetch error')\n      },\n      throwOnError: true,\n    }))\n    const Counter = () => {\n      const [countData] = useAtom(countAtom)\n\n      if ('data' in countData) {\n        if (countData.isPending) {\n          return <>loading</>\n        }\n\n        return <div>count: {countData.data?.response.count}</div>\n      }\n\n      return null\n    }\n\n    const { findByText } = render(\n      <StrictMode>\n        <ErrorBoundary fallback={<>errored</>}>\n          <Counter />\n        </ErrorBoundary>\n      </StrictMode>\n    )\n\n    await findByText('loading')\n    resolve()\n    await findByText('errored')\n  })\n\n  it('can recover from error', async () => {\n    let count = -1\n    let willThrowError = false\n    let resolve = () => {}\n    const countAtom = atomWithQuery(() => ({\n      queryKey: ['error test', 'count2'],\n      retry: false,\n      queryFn: async () => {\n        willThrowError = !willThrowError\n        ++count\n        await new Promise<void>((r) => (resolve = r))\n        if (willThrowError) {\n          throw new Error('fetch error')\n        }\n        return { response: { count } }\n      },\n      throwOnError: true,\n    }))\n    const Counter = () => {\n      const [countData] = useAtom(countAtom)\n      if (countData.isFetching) return <>loading</>\n      return (\n        <>\n          <div>count: {countData.data?.response.count}</div>\n          <button onClick={() => countData.refetch()}>refetch</button>\n        </>\n      )\n    }\n\n    const App = () => {\n      return (\n        <ErrorBoundary\n          FallbackComponent={({ resetErrorBoundary }) => {\n            return (\n              <>\n                <h1>errored</h1>\n                <button onClick={() => resetErrorBoundary()}>retry</button>\n              </>\n            )\n          }}>\n          <Counter />\n        </ErrorBoundary>\n      )\n    }\n\n    const { findByText, getByText } = render(\n      <>\n        <App />\n      </>\n    )\n\n    await findByText('loading')\n    resolve()\n    await findByText('errored')\n\n    fireEvent.click(getByText('retry'))\n    await findByText('loading')\n    resolve()\n    await findByText('count: 1')\n\n    fireEvent.click(getByText('refetch'))\n    await findByText('loading')\n    resolve()\n    await findByText('errored')\n\n    fireEvent.click(getByText('retry'))\n    await findByText('loading')\n    resolve()\n    await findByText('count: 3')\n  })\n})\n\n// Test for bug described here:\n// https://github.com/jotaijs/jotai-tanstack-query/issues/34\n// Note: If error handling tests run after this test, they are failing. Not sure why.\nit('renews the result when the query changes and a non stale cache is available', async () => {\n  const queryClient = new QueryClient({\n    defaultOptions: { queries: { staleTime: 5 * 60 * 1000 } },\n  })\n  queryClient.setQueryData(['currentCount', 2], 2)\n\n  const currentCountAtom = atom(1)\n\n  const countAtom = atomWithQuery(\n    (get) => {\n      const currentCount = get(currentCountAtom)\n      return {\n        queryKey: ['currentCount', currentCount],\n        queryFn: () => currentCount,\n      }\n    },\n    () => queryClient\n  )\n\n  const Counter = () => {\n    const setCurrentCount = useSetAtom(currentCountAtom)\n    const [countData] = useAtom(countAtom)\n\n    const { data, isPending, isError } = countData\n\n    if (isPending) {\n      return <>loading</>\n    }\n\n    if (isError) {\n      return <>errored</>\n    }\n\n    return (\n      <>\n        <button onClick={() => setCurrentCount(2)}>Set count to 2</button>\n        <div>count: {data}</div>\n      </>\n    )\n  }\n\n  const { findByText } = render(\n    <StrictMode>\n      <Counter />\n    </StrictMode>\n  )\n  await findByText('loading')\n  await findByText('count: 1')\n  fireEvent.click(await findByText('Set count to 2'))\n  await expect(() => findByText('loading')).rejects.toThrow()\n  await findByText('count: 2')\n})\n\n// https://github.com/jotaijs/jotai-tanstack-query/pull/40\nit(`ensure that setQueryData for an inactive query updates its atom state`, async () => {\n  const queryClient = new QueryClient({\n    defaultOptions: {\n      queries: {\n        refetchOnMount: false,\n      },\n    },\n  })\n\n  const extraKey = 'uniqueKey'\n  const pageAtom = atom(1)\n\n  const queryFn = vi.fn(() => {\n    return Promise.resolve('John Doe')\n  })\n\n  const userAtom = atomWithQuery(\n    () => {\n      return {\n        queryKey: [extraKey],\n        queryFn: async () => {\n          const name = await queryFn()\n          return { response: { name } }\n        },\n      }\n    },\n    () => queryClient\n  )\n\n  const User = () => {\n    const [{ data, isPending }] = useAtom(userAtom)\n\n    if (isPending) return <>loading</>\n\n    return <>Name: {data?.response.name}</>\n  }\n\n  const Controls = () => {\n    const [, setPage] = useAtom(pageAtom)\n    return (\n      <>\n        <button onClick={() => setPage(1)}>Set page 1</button>\n        <button onClick={() => setPage(2)}>Set page 2</button>\n      </>\n    )\n  }\n\n  const App = () => {\n    const [page] = useAtom(pageAtom)\n    return (\n      <>\n        {page === 1 && <User />}\n        <Controls />\n      </>\n    )\n  }\n\n  const { findByText } = render(\n    <StrictMode>\n      <App />\n    </StrictMode>\n  )\n\n  await findByText('loading')\n  await findByText('Name: John Doe')\n  fireEvent.click(await findByText('Set page 2'))\n  queryClient.setQueryData([extraKey], { response: { name: 'Alex Smith' } })\n  fireEvent.click(await findByText('Set page 1'))\n  await expect(() => findByText('loading')).rejects.toThrow()\n  await findByText('Name: Alex Smith')\n})\n"
  },
  {
    "path": "__tests__/atomWithSuspenseInfiniteQuery.spec.tsx",
    "content": "import React, { StrictMode, Suspense } from 'react'\nimport { fireEvent, render } from '@testing-library/react'\nimport { useAtom } from 'jotai'\nimport { atomWithSuspenseInfiniteQuery } from '../src'\n\nit('suspense basic, suspends', async () => {\n  let resolve = () => {}\n  type DataResponse = {\n    response: {\n      count: number\n    }\n  }\n  const countAtom = atomWithSuspenseInfiniteQuery<DataResponse>(() => ({\n    getNextPageParam: (lastPage) => {\n      const nextPageParam = lastPage.response.count + 1\n      return nextPageParam\n    },\n    initialPageParam: 1,\n    queryKey: ['test1'],\n    queryFn: async ({ pageParam }) => {\n      await new Promise<void>((r) => (resolve = r))\n      return { response: { count: pageParam as number } }\n    },\n  }))\n  const Counter = () => {\n    const [countData] = useAtom(countAtom)\n    const { data, fetchNextPage } = countData\n    return (\n      <>\n        <div>count: {data?.pages?.[data.pages.length - 1]?.response.count}</div>\n        <button\n          onClick={() => {\n            fetchNextPage()\n          }}>\n          fetchNextPage\n        </button>\n      </>\n    )\n  }\n\n  const { findByText, getByText } = render(\n    <StrictMode>\n      <Suspense fallback=\"loading\">\n        <Counter />\n      </Suspense>\n    </StrictMode>\n  )\n\n  await findByText('loading')\n  resolve()\n  await findByText('count: 1')\n\n  fireEvent.click(getByText('fetchNextPage'))\n  await expect(() => findByText('loading')).rejects.toThrow() //refetch implementation in tanstack doesn't trigger loading state\n  resolve()\n  await findByText('count: 2')\n})\n"
  },
  {
    "path": "__tests__/atomWithSuspenseQuery.spec.tsx",
    "content": "import React, { StrictMode, Suspense } from 'react'\nimport { QueryClient } from '@tanstack/query-core'\nimport { fireEvent, render } from '@testing-library/react'\nimport { Provider, atom, useAtom, useSetAtom } from 'jotai'\nimport { ErrorBoundary } from 'react-error-boundary'\nimport { vi } from 'vitest'\nimport { atomWithSuspenseQuery } from '../src'\n\nlet originalConsoleError: typeof console.error\n\nbeforeEach(() => {\n  originalConsoleError = console.error\n  console.error = vi.fn()\n})\nafterEach(() => {\n  console.error = originalConsoleError\n})\n\nit('suspense basic, suspends', async () => {\n  let resolve = () => {}\n  const countAtom = atomWithSuspenseQuery(() => ({\n    queryKey: ['test1', 'suspends'],\n    queryFn: async () => {\n      await new Promise<void>((r) => (resolve = r))\n      return { response: { count: 0 } }\n    },\n  }))\n  const Counter = () => {\n    const [{ data }] = useAtom(countAtom)\n    return (\n      <>\n        <div>count: {data.response.count}</div>\n      </>\n    )\n  }\n\n  const { findByText } = render(\n    <StrictMode>\n      <Suspense fallback=\"Loading...\">\n        <Counter />\n      </Suspense>\n    </StrictMode>\n  )\n\n  await findByText('Loading...')\n  resolve()\n  await findByText('count: 0')\n})\n\nit('query refetch', async () => {\n  const mockFetch = vi.fn((response: { message: string }) => ({\n    response,\n  }))\n  let resolve = () => {}\n  const greetingAtom = atomWithSuspenseQuery(() => ({\n    queryKey: ['test3'],\n    queryFn: async () => {\n      await new Promise<void>((r) => (resolve = r))\n      const response = mockFetch({ message: 'helloWorld' })\n      return response\n    },\n  }))\n  const Greeting = () => {\n    const [{ data, refetch }] = useAtom(greetingAtom)\n\n    return (\n      <>\n        <div>message: {data?.response.message}</div>\n        <button onClick={() => refetch?.()}>refetch</button>\n      </>\n    )\n  }\n\n  const { findByText, getByText } = render(\n    <StrictMode>\n      <Suspense fallback=\"loading\">\n        <Greeting />\n      </Suspense>\n    </StrictMode>\n  )\n\n  await findByText('loading')\n  resolve()\n  await findByText('message: helloWorld')\n  expect(mockFetch).toBeCalledTimes(1)\n\n  fireEvent.click(getByText('refetch'))\n  await expect(() => findByText('loading')).rejects.toThrow() //refetch implementation in tanstack doesn't trigger loading state\n  resolve()\n  await findByText('message: helloWorld')\n  expect(mockFetch).toBeCalledTimes(2) //this ensures we are actually running the query function again\n})\n\ndescribe('intialData test', () => {\n  it('query with initialData test', async () => {\n    const mockFetch = vi.fn((response) => ({ response }))\n    let resolve = () => {}\n\n    const countAtom = atomWithSuspenseQuery(() => ({\n      queryKey: ['initialData_count1'],\n      queryFn: async () => {\n        await new Promise<void>((r) => (resolve = r))\n        return mockFetch({ count: 10 })\n      },\n      initialData: { response: { count: 0 } },\n      staleTime: 0,\n    }))\n    const Counter = () => {\n      const [countData] = useAtom(countAtom)\n      const { data, isError } = countData\n\n      if (isError) {\n        return <>errored</>\n      }\n\n      const count = data?.response.count\n      return (\n        <>\n          <div>count: {count}</div>\n        </>\n      )\n    }\n\n    const { findByText } = render(\n      <StrictMode>\n        <Suspense fallback=\"loading\">\n          <Counter />\n        </Suspense>\n      </StrictMode>\n    )\n\n    // NOTE: the atom is never loading\n    await expect(() => findByText('loading')).rejects.toThrow()\n    await findByText('count: 0')\n    resolve()\n    await findByText('count: 10')\n    expect(mockFetch).toHaveBeenCalledTimes(1)\n  })\n\n  it('query with initialData test with dependency', async () => {\n    const mockFetch = vi.fn((response) => ({ response }))\n    let resolve = () => {}\n    const numberAtom = atom(10)\n    const countAtom = atomWithSuspenseQuery((get) => ({\n      queryKey: ['initialData_count1', get(numberAtom)],\n      queryFn: async ({ queryKey: [, myNumber] }) => {\n        await new Promise<void>((r) => (resolve = r))\n        return mockFetch({ count: myNumber })\n      },\n      initialData: { response: { count: 0 } },\n      staleTime: 0,\n    }))\n    const Counter = () => {\n      const [countData] = useAtom(countAtom)\n      const { data, isError } = countData\n      if (isError) {\n        return <>errored</>\n      }\n      const count = data?.response.count\n      return (\n        <>\n          <div>count: {count}</div>\n        </>\n      )\n    }\n\n    const Increment = () => {\n      const setNumber = useSetAtom(numberAtom)\n      return <button onClick={() => setNumber((n) => n + 1)}>increment</button>\n    }\n    const { findByText } = render(\n      <StrictMode>\n        <Suspense fallback=\"loading\">\n          <Counter />\n        </Suspense>\n        <Increment />\n      </StrictMode>\n    )\n    // NOTE: the atom is never loading\n    await expect(() => findByText('loading')).rejects.toThrow()\n    await findByText('count: 0')\n    resolve()\n    await findByText('count: 10')\n    expect(mockFetch).toHaveBeenCalledTimes(1)\n    await findByText('increment')\n    fireEvent.click(await findByText('increment'))\n    await findByText('count: 0')\n    resolve()\n    await findByText('count: 11')\n    expect(mockFetch).toHaveBeenCalledTimes(2)\n  })\n})\n\nit('query dependency test', async () => {\n  const baseCountAtom = atom(0)\n  const incrementAtom = atom(null, (_get, set) =>\n    set(baseCountAtom, (c) => c + 1)\n  )\n  let resolve = () => {}\n  const countAtom = atomWithSuspenseQuery((get) => ({\n    queryKey: ['count_with_dependency', get(baseCountAtom)],\n    queryFn: async () => {\n      await new Promise<void>((r) => (resolve = r))\n      return { response: { count: get(baseCountAtom) } }\n    },\n  }))\n\n  const Counter = () => {\n    const [{ data }] = useAtom(countAtom)\n\n    return (\n      <>\n        <div>count: {data.response.count}</div>\n      </>\n    )\n  }\n\n  const Controls = () => {\n    const [, increment] = useAtom(incrementAtom)\n    return <button onClick={increment}>increment</button>\n  }\n\n  const { getByText, findByText } = render(\n    <StrictMode>\n      <Suspense fallback={'loading'}>\n        <Counter />\n      </Suspense>\n      <Controls />\n    </StrictMode>\n  )\n\n  await findByText('loading')\n  resolve()\n  await findByText('count: 0')\n\n  fireEvent.click(getByText('increment'))\n  await findByText('loading')\n  resolve()\n  await findByText('count: 1')\n})\n\nit('query expected QueryCache test', async () => {\n  const queryClient = new QueryClient()\n  let resolve = () => {}\n  const countAtom = atomWithSuspenseQuery(\n    () => ({\n      queryKey: ['count6'],\n      queryFn: async () => {\n        await new Promise<void>((r) => (resolve = r))\n        return { response: { count: 0 } }\n      },\n    }),\n    () => queryClient\n  )\n  const Counter = () => {\n    const [{ data }] = useAtom(countAtom)\n\n    return (\n      <>\n        <div>count: {data.response.count}</div>\n      </>\n    )\n  }\n\n  const { findByText } = render(\n    <StrictMode>\n      <Suspense fallback=\"loading\">\n        <Counter />\n      </Suspense>\n    </StrictMode>\n  )\n\n  await findByText('loading')\n  resolve()\n  await findByText('count: 0')\n  expect(queryClient.getQueryCache().getAll().length).toBe(1)\n})\n\ndescribe('error handling', () => {\n  it('can catch error in error boundary', async () => {\n    let resolve = () => {}\n    const countAtom = atomWithSuspenseQuery(() => ({\n      queryKey: ['catch'],\n      retry: false,\n      queryFn: async (): Promise<{ response: { count: number } }> => {\n        await new Promise<void>((r) => (resolve = r))\n        throw new Error('fetch error')\n      },\n    }))\n    const Counter = () => {\n      const [{ data }] = useAtom(countAtom)\n      return <div>count: {data.response.count}</div>\n    }\n    const { findByText } = render(\n      <StrictMode>\n        <ErrorBoundary fallback={<>errored</>}>\n          <Suspense fallback={'loading'}>\n            <Counter />\n          </Suspense>\n        </ErrorBoundary>\n      </StrictMode>\n    )\n    await findByText('loading')\n    resolve()\n    await findByText('errored')\n  })\n  it('can recover from error', async () => {\n    let count = -1\n    let willThrowError = false\n    let resolve = () => {}\n    const countAtom = atomWithSuspenseQuery(() => ({\n      queryKey: ['error test', 'count2'],\n      retry: false,\n      queryFn: async () => {\n        willThrowError = !willThrowError\n        ++count\n        await new Promise<void>((r) => (resolve = r))\n        if (willThrowError) {\n          throw new Error('fetch error')\n        }\n        return { response: { count } }\n      },\n      throwOnError: true,\n    }))\n    const Counter = () => {\n      const [{ data, refetch, error, isFetching }] = useAtom(countAtom)\n\n      if (error && !isFetching) {\n        throw error\n      }\n\n      return (\n        <>\n          <div>count: {data?.response.count}</div>\n          <button onClick={() => refetch()}>refetch</button>\n        </>\n      )\n    }\n\n    const FallbackComponent: React.FC<{ resetErrorBoundary: () => void }> = ({\n      resetErrorBoundary,\n    }) => {\n      const refresh = useSetAtom(countAtom)\n      return (\n        <>\n          <h1>errored</h1>\n          <button\n            onClick={() => {\n              refresh()\n              resetErrorBoundary()\n            }}>\n            retry\n          </button>\n        </>\n      )\n    }\n    const App = () => {\n      return (\n        <Provider>\n          <ErrorBoundary FallbackComponent={FallbackComponent}>\n            <Suspense fallback=\"loading\">\n              <Counter />\n            </Suspense>\n          </ErrorBoundary>\n        </Provider>\n      )\n    }\n    const { findByText, getByText } = render(<App />)\n    await findByText('loading')\n    resolve()\n    await findByText('errored')\n    fireEvent.click(getByText('retry'))\n    await findByText('loading')\n    resolve()\n    await findByText('count: 1')\n    fireEvent.click(getByText('refetch'))\n    resolve()\n    await findByText('errored')\n    fireEvent.click(getByText('retry'))\n    resolve()\n    await findByText('count: 3')\n  })\n})\n\nit('renews the result when the query changes and a non stale cache is available', async () => {\n  const queryClient = new QueryClient({\n    defaultOptions: { queries: { staleTime: 5 * 60 * 1000 } },\n  })\n  queryClient.setQueryData([2], 2)\n\n  const currentCountAtom = atom(1)\n\n  const countAtom = atomWithSuspenseQuery(\n    (get) => {\n      const currentCount = get(currentCountAtom)\n      return {\n        queryKey: [currentCount],\n        queryFn: () => currentCount,\n      }\n    },\n    () => queryClient\n  )\n\n  const Counter = () => {\n    const setCurrentCount = useSetAtom(currentCountAtom)\n    const [{ data }] = useAtom(countAtom)\n\n    return (\n      <>\n        <button onClick={() => setCurrentCount(2)}>Set count to 2</button>\n        <div>count: {data}</div>\n      </>\n    )\n  }\n\n  const { findByText } = render(\n    <StrictMode>\n      <Suspense fallback={'loading'}>\n        <Counter />\n      </Suspense>\n    </StrictMode>\n  )\n  await findByText('loading')\n  await findByText('count: 1')\n  fireEvent.click(await findByText('Set count to 2'))\n  await expect(() => findByText('loading')).rejects.toThrow()\n  await findByText('count: 2')\n})\n\nit('on reset, throws suspense', async () => {\n  const queryClient = new QueryClient()\n  let count = 0\n  let resolve = () => {}\n  const countAtom = atomWithSuspenseQuery(\n    () => ({\n      queryKey: ['test1', count],\n      queryFn: async () => {\n        await new Promise<void>((r) => (resolve = r))\n        count++\n        return { response: { count } }\n      },\n    }),\n    () => queryClient\n  )\n  const Counter = () => {\n    const [{ data }] = useAtom(countAtom)\n    return (\n      <>\n        <div>count: {data.response.count}</div>\n        <button\n          onClick={() => queryClient.resetQueries({ queryKey: ['test1'] })}>\n          reset\n        </button>\n      </>\n    )\n  }\n\n  const { findByText, getByText } = render(\n    <StrictMode>\n      <Suspense fallback=\"loading\">\n        <Counter />\n      </Suspense>\n    </StrictMode>\n  )\n\n  await findByText('loading')\n  resolve()\n  await findByText('count: 1')\n  fireEvent.click(getByText('reset'))\n  await findByText('loading')\n  resolve()\n  await findByText('count: 2')\n})\n\n// https://github.com/jotaijs/jotai-tanstack-query/pull/40\nit(`ensure that setQueryData for an inactive query updates its atom state`, async () => {\n  const queryClient = new QueryClient({\n    defaultOptions: {\n      queries: {\n        refetchOnMount: false,\n      },\n    },\n  })\n\n  const extraKey = 'uniqueKey'\n  const pageAtom = atom(1)\n\n  const queryFn = vi.fn(() => {\n    return Promise.resolve('John Doe')\n  })\n\n  const userAtom = atomWithSuspenseQuery(\n    () => {\n      return {\n        queryKey: [extraKey],\n        queryFn: async () => {\n          const name = await queryFn()\n          return { response: { name } }\n        },\n      }\n    },\n    () => queryClient\n  )\n\n  const User = () => {\n    const [\n      {\n        data: {\n          response: { name },\n        },\n      },\n    ] = useAtom(userAtom)\n\n    return <>Name: {name}</>\n  }\n\n  const Controls = () => {\n    const [, setPage] = useAtom(pageAtom)\n    return (\n      <>\n        <button onClick={() => setPage(1)}>Set page 1</button>\n        <button onClick={() => setPage(2)}>Set page 2</button>\n      </>\n    )\n  }\n\n  const App = () => {\n    const [page] = useAtom(pageAtom)\n    return (\n      <>\n        <Suspense fallback=\"loading\">{page === 1 && <User />}</Suspense>\n        <Controls />\n      </>\n    )\n  }\n\n  const { findByText } = render(\n    <StrictMode>\n      <App />\n    </StrictMode>\n  )\n\n  await findByText('loading')\n  await findByText('Name: John Doe')\n  fireEvent.click(await findByText('Set page 2'))\n  queryClient.setQueryData([extraKey], { response: { name: 'Alex Smith' } })\n  fireEvent.click(await findByText('Set page 1'))\n  await expect(() => findByText('loading')).rejects.toThrow()\n  await findByText('Name: Alex Smith')\n})\n"
  },
  {
    "path": "eslint.config.js",
    "content": "import js from '@eslint/js'\nimport typescript from '@typescript-eslint/eslint-plugin'\nimport typescriptParser from '@typescript-eslint/parser'\nimport importPlugin from 'eslint-plugin-import'\nimport prettier from 'eslint-plugin-prettier'\nimport react from 'eslint-plugin-react'\nimport reactHooks from 'eslint-plugin-react-hooks'\n\nexport default [\n  js.configs.recommended,\n  {\n    files: ['**/*.{js,jsx,ts,tsx}'],\n    languageOptions: {\n      parser: typescriptParser,\n      parserOptions: {\n        ecmaVersion: 2018,\n        sourceType: 'module',\n        ecmaFeatures: {\n          jsx: true,\n        },\n      },\n      globals: {\n        // Browser globals\n        window: 'readonly',\n        document: 'readonly',\n        console: 'readonly',\n        fetch: 'readonly',\n        URL: 'readonly',\n        URLSearchParams: 'readonly',\n        // Node.js globals\n        process: 'readonly',\n        Buffer: 'readonly',\n        __dirname: 'readonly',\n        __filename: 'readonly',\n        global: 'readonly',\n        // ES6 globals\n        Promise: 'readonly',\n        Symbol: 'readonly',\n        Map: 'readonly',\n        Set: 'readonly',\n        WeakMap: 'readonly',\n        WeakSet: 'readonly',\n      },\n    },\n    plugins: {\n      '@typescript-eslint': typescript,\n      react,\n      'react-hooks': reactHooks,\n      import: importPlugin,\n      prettier,\n    },\n    rules: {\n      // ESLint recommended rules are included via js.configs.recommended\n\n      // Custom rules\n      eqeqeq: 'error',\n      'no-var': 'error',\n      'prefer-const': 'error',\n      curly: ['warn', 'multi-line', 'consistent'],\n      'no-console': 'off',\n\n      // Import rules\n      'import/no-unresolved': ['error', { commonjs: true, amd: true }],\n      'import/export': 'error',\n      'import/no-duplicates': ['error'],\n      'import/order': [\n        'error',\n        {\n          alphabetize: { order: 'asc', caseInsensitive: true },\n          groups: [\n            'builtin',\n            'external',\n            'internal',\n            'parent',\n            'sibling',\n            'index',\n            'object',\n          ],\n          'newlines-between': 'never',\n          pathGroups: [\n            {\n              pattern: 'react',\n              group: 'builtin',\n              position: 'before',\n            },\n          ],\n          pathGroupsExcludedImportTypes: ['builtin'],\n        },\n      ],\n\n      // TypeScript rules\n      '@typescript-eslint/explicit-module-boundary-types': 'off',\n      '@typescript-eslint/no-unused-vars': [\n        'warn',\n        { argsIgnorePattern: '^_', varsIgnorePattern: '^_' },\n      ],\n      '@typescript-eslint/no-use-before-define': 'off',\n      '@typescript-eslint/no-empty-function': 'off',\n      '@typescript-eslint/no-explicit-any': 'off',\n      '@typescript-eslint/no-redeclare': 'off',\n      'no-unused-vars': 'off', // Use TypeScript version instead\n      'no-redeclare': 'off', // Use TypeScript version instead\n\n      // React rules\n      'react/jsx-uses-react': 'off',\n      'react/react-in-jsx-scope': 'off',\n\n      // React hooks rules\n      'react-hooks/rules-of-hooks': 'error',\n      'react-hooks/exhaustive-deps': 'warn',\n\n      // Sort imports\n      'sort-imports': [\n        'error',\n        {\n          ignoreDeclarationSort: true,\n        },\n      ],\n    },\n    settings: {\n      react: {\n        version: 'detect',\n      },\n      'import/extensions': ['.js', '.jsx', '.ts', '.tsx'],\n      'import/parsers': {\n        '@typescript-eslint/parser': ['.js', '.jsx', '.ts', '.tsx'],\n      },\n      'import/resolver': {\n        node: {\n          extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],\n          paths: ['src'],\n        },\n        alias: {\n          extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],\n          map: [['jotai-tanstack-query', './src/index.ts']],\n        },\n      },\n    },\n  },\n  {\n    files: ['src/**/*.{ts,tsx}'],\n    languageOptions: {\n      parserOptions: {\n        project: './tsconfig.json',\n      },\n    },\n  },\n  {\n    files: ['tests/**/*.tsx', '__tests__/**/*'],\n    languageOptions: {\n      globals: {\n        describe: 'readonly',\n        it: 'readonly',\n        test: 'readonly',\n        expect: 'readonly',\n        beforeEach: 'readonly',\n        afterEach: 'readonly',\n        beforeAll: 'readonly',\n        afterAll: 'readonly',\n        vi: 'readonly',\n      },\n    },\n  },\n  {\n    files: ['./*.js'],\n    rules: {\n      '@typescript-eslint/no-var-requires': 'off',\n    },\n  },\n  {\n    files: ['examples/**/*.{ts,tsx}'],\n    rules: {\n      'import/no-unresolved': 'off',\n    },\n  },\n  {\n    ignores: ['dist/**', 'src/vendor/**', 'node_modules/**', '**/.next/**'],\n  },\n]\n"
  },
  {
    "path": "examples/01_query/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>jotai-tanstack-query example</title>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/index.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/01_query/package.json",
    "content": "{\n  \"name\": \"jotai-tanstack-query-example-query\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"dependencies\": {\n    \"@tanstack/query-core\": \"latest\",\n    \"jotai\": \"latest\",\n    \"jotai-tanstack-query\": \"*\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"latest\",\n    \"@types/react-dom\": \"latest\",\n    \"@vitejs/plugin-react\": \"latest\",\n    \"typescript\": \"latest\",\n    \"vite\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\"\n  }\n}\n"
  },
  {
    "path": "examples/01_query/src/App.tsx",
    "content": "import { Getter } from 'jotai'\nimport { useAtom } from 'jotai/react'\nimport { atom } from 'jotai/vanilla'\nimport { atomWithQuery } from 'jotai-tanstack-query'\n\nconst idAtom = atom(1)\n\nconst userAtom = atomWithQuery((get: Getter) => ({\n  queryKey: ['users', get(idAtom).toString()],\n  queryFn: async ({ queryKey: [, id] }) => {\n    const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)\n    return res.json()\n  },\n}))\n\nconst UserData = () => {\n  const [{ data, isPending, isError }] = useAtom(userAtom)\n\n  if (isPending) return <div>Loading...</div>\n  if (isError) return <div>Error</div>\n\n  return <div>{JSON.stringify(data)}</div>\n}\n\nconst Controls = () => {\n  const [id, setId] = useAtom(idAtom)\n  return (\n    <div>\n      ID: {id}{' '}\n      <button type=\"button\" onClick={() => setId((c) => c - 1)}>\n        Prev\n      </button>{' '}\n      <button type=\"button\" onClick={() => setId((c) => c + 1)}>\n        Next\n      </button>\n    </div>\n  )\n}\n\nconst App = () => (\n  <>\n    <Controls />\n    <UserData />\n  </>\n)\n\nexport default App\n"
  },
  {
    "path": "examples/01_query/src/index.tsx",
    "content": "import React from 'react'\nimport { createRoot } from 'react-dom/client'\nimport App from './App'\n\nconst ele = document.getElementById('app')\nif (ele) {\n  createRoot(ele).render(React.createElement(App))\n}\n"
  },
  {
    "path": "examples/01_query/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\"src\"],\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "examples/01_query/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"allowSyntheticDefaultImports\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "examples/01_query/vite.config.ts",
    "content": "import react from '@vitejs/plugin-react'\nimport { defineConfig } from 'vite'\n\nexport default defineConfig({\n  plugins: [react()],\n})\n"
  },
  {
    "path": "examples/02_suspense/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>jotai-tanstack-query example</title>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/index.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/02_suspense/package.json",
    "content": "{\n  \"name\": \"jotai-tanstack-query-example-suspense\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"dependencies\": {\n    \"@tanstack/query-core\": \"latest\",\n    \"jotai\": \"latest\",\n    \"jotai-tanstack-query\": \"*\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"latest\",\n    \"@types/react-dom\": \"latest\",\n    \"@vitejs/plugin-react\": \"latest\",\n    \"typescript\": \"latest\",\n    \"vite\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\"\n  }\n}\n"
  },
  {
    "path": "examples/02_suspense/src/App.tsx",
    "content": "import { Suspense } from 'react'\nimport { useAtom } from 'jotai/react'\nimport { atom } from 'jotai/vanilla'\nimport { atomWithSuspenseQuery } from 'jotai-tanstack-query'\n\nconst idAtom = atom(1)\n\nconst userAtom = atomWithSuspenseQuery<object>((get) => ({\n  queryKey: ['users', get(idAtom)],\n  queryFn: async ({ queryKey: [, id] }) => {\n    const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)\n    return res.json()\n  },\n}))\n\nconst UserData = () => {\n  const [{ data }] = useAtom(userAtom)\n\n  return <div>{JSON.stringify(data)}</div>\n}\n\nconst Controls = () => {\n  const [id, setId] = useAtom(idAtom)\n  return (\n    <div>\n      ID: {id}{' '}\n      <button type=\"button\" onClick={() => setId((c) => c - 1)}>\n        Prev\n      </button>{' '}\n      <button type=\"button\" onClick={() => setId((c) => c + 1)}>\n        Next\n      </button>\n    </div>\n  )\n}\n\nconst App = () => (\n  <>\n    <Controls />\n    <Suspense fallback=\"loading\">\n      <UserData />\n    </Suspense>\n  </>\n)\n\nexport default App\n"
  },
  {
    "path": "examples/02_suspense/src/index.tsx",
    "content": "import React from 'react'\nimport { createRoot } from 'react-dom/client'\nimport App from './App'\n\nconst ele = document.getElementById('app')\nif (ele) {\n  createRoot(ele).render(React.createElement(App))\n}\n"
  },
  {
    "path": "examples/02_suspense/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\"src\"],\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "examples/02_suspense/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"allowSyntheticDefaultImports\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "examples/02_suspense/vite.config.ts",
    "content": "import react from '@vitejs/plugin-react'\nimport { defineConfig } from 'vite'\n\nexport default defineConfig({\n  plugins: [react()],\n})\n"
  },
  {
    "path": "examples/03_infinite/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>jotai-tanstack-query example</title>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/index.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/03_infinite/package.json",
    "content": "{\n  \"name\": \"jotai-tanstack-query-example-infinite\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"dependencies\": {\n    \"@tanstack/query-core\": \"latest\",\n    \"jotai\": \"latest\",\n    \"jotai-tanstack-query\": \"*\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"latest\",\n    \"@types/react-dom\": \"latest\",\n    \"@vitejs/plugin-react\": \"latest\",\n    \"typescript\": \"latest\",\n    \"vite\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\"\n  }\n}\n"
  },
  {
    "path": "examples/03_infinite/src/App.tsx",
    "content": "import { useAtom } from 'jotai'\nimport { atomWithInfiniteQuery } from 'jotai-tanstack-query'\n\nconst postsAtom = atomWithInfiniteQuery(() => ({\n  queryKey: ['posts'],\n  queryFn: async ({ pageParam }) => {\n    const res = await fetch(\n      `https://jsonplaceholder.typicode.com/posts?_page=${pageParam}`\n    )\n    return res.json()\n  },\n  getNextPageParam: (...args) => {\n    return args[2] + 1\n  },\n  initialPageParam: 0,\n}))\n\nconst Posts = () => {\n  const [{ data, fetchNextPage, isPending, isError, isFetching }] =\n    useAtom(postsAtom)\n\n  if (isFetching || isPending) return <div>Loading...</div>\n  if (isError) return <div>Error</div>\n\n  return (\n    <>\n      {data?.pages.map((page, index) => (\n        <div key={index}>\n          {page.map((post: any) => (\n            <div key={post.id}>{post.title}</div>\n          ))}\n        </div>\n      ))}\n      <button onClick={() => fetchNextPage()}>Next</button>\n    </>\n  )\n}\n\nconst App = () => (\n  <>\n    <Posts />\n  </>\n)\n\nexport default App\n"
  },
  {
    "path": "examples/03_infinite/src/index.tsx",
    "content": "import React from 'react'\nimport { createRoot } from 'react-dom/client'\nimport App from './App'\n\nconst ele = document.getElementById('app')\nif (ele) {\n  createRoot(ele).render(React.createElement(App))\n}\n"
  },
  {
    "path": "examples/03_infinite/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\"src\"],\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "examples/03_infinite/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"allowSyntheticDefaultImports\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "examples/03_infinite/vite.config.ts",
    "content": "import react from '@vitejs/plugin-react'\nimport { defineConfig } from 'vite'\n\nexport default defineConfig({\n  plugins: [react()],\n})\n"
  },
  {
    "path": "examples/04_infinite_suspense/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>jotai-tanstack-query example</title>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/index.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/04_infinite_suspense/package.json",
    "content": "{\n  \"name\": \"jotai-tanstack-query-example-infinite-suspense\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"dependencies\": {\n    \"@tanstack/query-core\": \"latest\",\n    \"jotai\": \"latest\",\n    \"jotai-tanstack-query\": \"*\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"latest\",\n    \"@types/react-dom\": \"latest\",\n    \"@vitejs/plugin-react\": \"latest\",\n    \"typescript\": \"latest\",\n    \"vite\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\"\n  }\n}\n"
  },
  {
    "path": "examples/04_infinite_suspense/src/App.tsx",
    "content": "import { Suspense } from 'react'\nimport { useAtom } from 'jotai/react'\nimport { atomWithSuspenseInfiniteQuery } from 'jotai-tanstack-query'\n\nconst postsAtom = atomWithSuspenseInfiniteQuery(() => ({\n  initialPageParam: 1,\n  queryKey: ['posts'],\n  queryFn: async ({ pageParam = 1 }) => {\n    const res = await fetch(\n      `https://jsonplaceholder.typicode.com/posts/${pageParam}`\n    )\n    const data: { id: number; title: string } = await res.json()\n    return data\n  },\n  getNextPageParam: (lastPage) => lastPage.id + 1,\n}))\n\nconst Posts = () => {\n  const [{ data, fetchNextPage }] = useAtom(postsAtom)\n  return (\n    <div>\n      <button onClick={() => fetchNextPage()}>Next</button>\n      <ul>\n        {data.pages.map((item) => (\n          <li key={item.id}>{item.title}</li>\n        ))}\n      </ul>\n    </div>\n  )\n}\n\nconst App = () => (\n  <>\n    <Suspense fallback=\"Loading...\">\n      <Posts />\n    </Suspense>\n  </>\n)\n\nexport default App\n"
  },
  {
    "path": "examples/04_infinite_suspense/src/index.tsx",
    "content": "import React from 'react'\nimport { createRoot } from 'react-dom/client'\nimport App from './App'\n\nconst ele = document.getElementById('app')\nif (ele) {\n  createRoot(ele).render(React.createElement(App))\n}\n"
  },
  {
    "path": "examples/04_infinite_suspense/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\"src\"],\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "examples/04_infinite_suspense/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"allowSyntheticDefaultImports\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "examples/04_infinite_suspense/vite.config.ts",
    "content": "import react from '@vitejs/plugin-react'\nimport { defineConfig } from 'vite'\n\nexport default defineConfig({\n  plugins: [react()],\n})\n"
  },
  {
    "path": "examples/05_mutation/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>jotai-tanstack-query example</title>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/index.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/05_mutation/package.json",
    "content": "{\n  \"name\": \"jotai-tanstack-query-example-mutation\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"dependencies\": {\n    \"@tanstack/query-core\": \"latest\",\n    \"jotai\": \"latest\",\n    \"jotai-tanstack-query\": \"*\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"latest\",\n    \"@types/react-dom\": \"latest\",\n    \"@vitejs/plugin-react\": \"latest\",\n    \"typescript\": \"latest\",\n    \"vite\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\"\n  }\n}\n"
  },
  {
    "path": "examples/05_mutation/src/App.tsx",
    "content": "import React from 'react'\nimport { useAtom } from 'jotai/react'\nimport { atomWithMutation, atomWithQuery, queryClientAtom } from 'jotai-tanstack-query'\n\ninterface Post {\n  id: number\n  title: string\n  body: string\n  userId: number\n}\n\ninterface NewPost {\n  title: string\n}\n\ninterface OptimisticContext {\n  previousPosts: Post[] | undefined\n}\n\n// Query to fetch posts list\nconst postsQueryAtom = atomWithQuery(() => ({\n  queryKey: ['posts'],\n  queryFn: async () => {\n    const res = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=5')\n    return res.json() as Promise<Post[]>\n  },\n}))\n\n// Mutation with optimistic updates\nconst postAtom = atomWithMutation<Post, NewPost, Error, OptimisticContext>(\n  (get) => {\n    const queryClient = get(queryClientAtom)\n    return {\n      mutationKey: ['addPost'],\n      mutationFn: async ({ title }: NewPost) => {\n        // Randomly fail for testing error handling (30% failure rate)\n        if (Math.random() < 0.3) {\n          throw new Error('Randomly simulated API error')\n        }\n\n        const res = await fetch(`https://jsonplaceholder.typicode.com/posts`, {\n          method: 'POST',\n          body: JSON.stringify({\n            title,\n            body: 'body',\n            userId: 1,\n          }),\n          headers: {\n            'Content-type': 'application/json; charset=UTF-8',\n          },\n        })\n        const data = await res.json()\n        return data as Post\n      },\n      // When mutate is called:\n      onMutate: async (newPost: NewPost) => {\n        // Cancel any outgoing refetches\n        // (so they don't overwrite our optimistic update)\n        await queryClient.cancelQueries({ queryKey: ['posts'] })\n\n        // Snapshot the previous value\n        const previousPosts = queryClient.getQueryData<Post[]>(['posts'])\n\n        // Optimistically update to the new value\n        queryClient.setQueryData<Post[]>(['posts'], (old) => {\n          const optimisticPost: Post = {\n            id: Date.now(), // Temporary ID\n            title: newPost.title,\n            body: 'body',\n            userId: 1,\n          }\n          return old ? [...old, optimisticPost] : [optimisticPost]\n        })\n\n        // Return a result with the snapshotted value\n        return { previousPosts }\n      },\n      // If the mutation fails, use the result returned from onMutate to roll back\n      onError: (\n        _err: Error,\n        _newPost: NewPost,\n        onMutateResult: OptimisticContext | undefined\n      ) => {\n        console.debug('onError', onMutateResult)\n        if (onMutateResult?.previousPosts) {\n          queryClient.setQueryData(['posts'], onMutateResult.previousPosts)\n        }\n      },\n      // Always refetch after error or success:\n      onSettled: (\n        _data: Post | undefined,\n        _error: Error | null,\n        _variables: NewPost,\n        _onMutateResult: OptimisticContext | undefined\n      ) => {\n        queryClient.invalidateQueries({ queryKey: ['posts'] })\n      },\n    }\n  }\n)\n\nconst PostsList = () => {\n  const [{ data: posts, isPending }] = useAtom(postsQueryAtom)\n\n  if (isPending) return <div>Loading posts...</div>\n\n  return (\n    <div>\n      <h3>Posts:</h3>\n      <ul>\n        {posts?.map((post: Post) => (\n          <li key={post.id}>{post.title}</li>\n        ))}\n      </ul>\n    </div>\n  )\n}\n\nconst AddPost = () => {\n  const [{ mutate, isPending, status }] = useAtom(postAtom)\n  const [title, setTitle] = React.useState('')\n\n  return (\n    <div>\n      <div>\n        <input\n          value={title}\n          onChange={(e) => setTitle(e.target.value)}\n          placeholder=\"Enter post title\"\n        />\n        <button\n          onClick={() => {\n            if (title) {\n              mutate({ title })\n              setTitle('')\n            }\n          }}\n          disabled={isPending}\n        >\n          {isPending ? 'Adding...' : 'Add Post'}\n        </button>\n      </div>\n      <div>\n        <strong>Status:</strong> {status}\n      </div>\n    </div>\n  )\n}\n\nconst App = () => (\n  <>\n    <h2>atomWithMutation with optimistic updates</h2>\n    <PostsList />\n    <AddPost />\n  </>\n)\n\nexport default App\n"
  },
  {
    "path": "examples/05_mutation/src/index.tsx",
    "content": "import React from 'react'\nimport { createRoot } from 'react-dom/client'\nimport App from './App'\n\nconst ele = document.getElementById('app')\nif (ele) {\n  createRoot(ele).render(React.createElement(App))\n}\n"
  },
  {
    "path": "examples/05_mutation/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\"src\"],\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "examples/05_mutation/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"allowSyntheticDefaultImports\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "examples/05_mutation/vite.config.ts",
    "content": "import react from '@vitejs/plugin-react'\nimport { defineConfig } from 'vite'\n\nexport default defineConfig({\n  plugins: [react()],\n})\n"
  },
  {
    "path": "examples/06_refetch/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>jotai-tanstack-query example</title>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/index.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/06_refetch/package.json",
    "content": "{\n  \"name\": \"jotai-tanstack-query-example-refetch\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"dependencies\": {\n    \"@tanstack/query-core\": \"latest\",\n    \"jotai\": \"latest\",\n    \"jotai-tanstack-query\": \"*\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"latest\",\n    \"@types/react-dom\": \"latest\",\n    \"@vitejs/plugin-react\": \"latest\",\n    \"typescript\": \"latest\",\n    \"vite\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\"\n  }\n}\n"
  },
  {
    "path": "examples/06_refetch/src/App.tsx",
    "content": "import { useAtom } from 'jotai/react'\nimport { atom } from 'jotai/vanilla'\nimport { atomWithQuery } from 'jotai-tanstack-query'\nimport { ErrorBoundary } from 'react-error-boundary'\nimport type { FallbackProps } from 'react-error-boundary'\n\nconst idAtom = atom(1)\n\nconst userAtom = atomWithQuery((get) => ({\n  queryKey: ['users', get(idAtom)],\n  queryFn: async ({ queryKey: [, id] }) => {\n    const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)\n    return res.json()\n  },\n}))\n\nconst UserData = () => {\n  const [{ data, refetch, isPending }] = useAtom(userAtom)\n  if (isPending) return <div>Loading...</div>\n  return (\n    <div>\n      <ul>\n        <li>ID: {data.id}</li>\n        <li>Username: {data.username}</li>\n        <li>Email: {data.email}</li>\n      </ul>\n      <button onClick={() => refetch()}>refetch</button>\n    </div>\n  )\n}\n\nconst Controls = () => {\n  const [id, setId] = useAtom(idAtom)\n  return (\n    <div>\n      ID: {id}{' '}\n      <button type=\"button\" onClick={() => setId((c) => c - 1)}>\n        Prev\n      </button>{' '}\n      <button type=\"button\" onClick={() => setId((c) => c + 1)}>\n        Next\n      </button>\n    </div>\n  )\n}\n\nconst Fallback = ({ error, resetErrorBoundary }: FallbackProps) => {\n  return (\n    <div role=\"alert\">\n      <p>Something went wrong:</p>\n      <pre>{error.message}</pre>\n      <button type=\"button\" onClick={resetErrorBoundary}>\n        Try again\n      </button>\n    </div>\n  )\n}\n\nconst App = () => (\n  <ErrorBoundary FallbackComponent={Fallback}>\n    <Controls />\n    <UserData />\n  </ErrorBoundary>\n)\n\nexport default App\n"
  },
  {
    "path": "examples/06_refetch/src/index.tsx",
    "content": "import React from 'react'\nimport { createRoot } from 'react-dom/client'\nimport App from './App'\n\nconst ele = document.getElementById('app')\nif (ele) {\n  createRoot(ele).render(React.createElement(App))\n}\n"
  },
  {
    "path": "examples/06_refetch/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\"src\"],\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "examples/06_refetch/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"allowSyntheticDefaultImports\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "examples/06_refetch/vite.config.ts",
    "content": "import react from '@vitejs/plugin-react'\nimport { defineConfig } from 'vite'\n\nexport default defineConfig({\n  plugins: [react()],\n})\n"
  },
  {
    "path": "examples/07_queries/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>jotai-tanstack-query example</title>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/index.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/07_queries/package.json",
    "content": "{\n  \"name\": \"jotai-tanstack-query-example-queries\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"dependencies\": {\n    \"@tanstack/query-core\": \"latest\",\n    \"jotai\": \"latest\",\n    \"jotai-tanstack-query\": \"*\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"latest\",\n    \"@types/react-dom\": \"latest\",\n    \"@vitejs/plugin-react\": \"latest\",\n    \"typescript\": \"latest\",\n    \"vite\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\"\n  }\n}\n"
  },
  {
    "path": "examples/07_queries/src/App.tsx",
    "content": "import { Atom, atom } from 'jotai'\nimport { useAtom } from 'jotai/react'\nimport { type AtomWithQueryResult, atomWithQueries } from 'jotai-tanstack-query'\n\nconst userIdsAtom = atom([1, 2, 3])\n\ninterface User {\n  id: number\n  name: string\n  email: string\n}\n\nconst userQueryAtomsAtom = atom((get) => {\n  const userIds = get(userIdsAtom)\n  return atomWithQueries<User>({\n    queries: userIds.map((id) => () => ({\n      queryKey: ['user', id],\n      queryFn: async ({ queryKey: [, userId] }) => {\n        const res = await fetch(\n          `https://jsonplaceholder.typicode.com/users/${userId}`\n        )\n        return res.json()\n      },\n    })),\n  })\n})\n\nconst UsersData = () => {\n  const [userQueryAtoms] = useAtom(userQueryAtomsAtom)\n  return (\n    <div>\n      <h3>Users: </h3>\n      <div>\n        {userQueryAtoms.map((queryAtom, index) => (\n          <Data key={index} queryAtom={queryAtom} />\n        ))}\n      </div>\n    </div>\n  )\n}\n\nconst combinedUsersDataAtom = atom((get) => {\n  const userIds = get(userIdsAtom)\n  return atomWithQueries<{\n    data: User[]\n    isPending: boolean\n    isError: boolean\n  }>({\n    queries: userIds.map((id) => () => ({\n      queryKey: ['user', id],\n      queryFn: async ({ queryKey: [, userId] }) => {\n        const res = await fetch(\n          `https://jsonplaceholder.typicode.com/users/${userId}`\n        )\n        return res.json()\n      },\n    })),\n    combine: (results) => {\n      return {\n        data: results.map((result) => result.data as User),\n        isPending: results.some((result) => result.isPending),\n        isError: results.some((result) => result.isError),\n      }\n    },\n  })\n})\n\nconst CombinedUsersData = () => {\n  const [combinedUsersDataAtomValue] = useAtom(combinedUsersDataAtom)\n  const [{ data, isPending, isError }] = useAtom(combinedUsersDataAtomValue)\n\n  return (\n    <div>\n      <h3>Users: (combinedQueries)</h3>\n      {isPending && <div>Loading...</div>}\n      {isError && <div>Error</div>}\n      {!isPending && !isError && (\n        <div>\n          {data.map((user) => (\n            <UserDisplay key={user.id} user={user} />\n          ))}\n        </div>\n      )}\n    </div>\n  )\n}\n\nconst Data = ({\n  queryAtom,\n}: {\n  queryAtom: Atom<AtomWithQueryResult<User>>\n}) => {\n  const [{ data, isPending, isError }] = useAtom(queryAtom)\n\n  if (isPending) return <div>Loading...</div>\n  if (isError) return <div>Error</div>\n  if (!data) return null\n\n  return <UserDisplay user={data} />\n}\n\nconst UserDisplay = ({ user }: { user: User }) => {\n  return (\n    <div>\n      <div>ID: {user.id}</div>\n      <strong>{user.name}</strong> - {user.email}\n    </div>\n  )\n}\n\nconst Controls = () => {\n  const [userIds, setUserIds] = useAtom(userIdsAtom)\n\n  return (\n    <div>\n      <div>User IDs: {userIds.join(', ')} </div>\n      <button\n        onClick={() => {\n          const n = Math.floor(Math.random() * 8)\n          setUserIds([n + 1, n + 2, n + 3])\n        }}>\n        Random\n      </button>\n    </div>\n  )\n}\n\nconst App = () => {\n  return (\n    <div>\n      <Controls />\n      <UsersData />\n      <hr />\n      <CombinedUsersData />\n    </div>\n  )\n}\n\nexport default App\n"
  },
  {
    "path": "examples/07_queries/src/index.tsx",
    "content": "import React from 'react'\nimport { createRoot } from 'react-dom/client'\nimport App from './App'\n\nconst ele = document.getElementById('app')\nif (ele) {\n  createRoot(ele).render(React.createElement(App))\n}\n"
  },
  {
    "path": "examples/07_queries/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\"src\"],\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "examples/07_queries/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"allowSyntheticDefaultImports\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "examples/07_queries/vite.config.ts",
    "content": "import react from '@vitejs/plugin-react'\nimport { defineConfig } from 'vite'\n\nexport default defineConfig({\n  plugins: [react()],\n})\n"
  },
  {
    "path": "examples/08_query_client_atom_provider/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>jotai-tanstack-query example</title>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/index.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/08_query_client_atom_provider/package.json",
    "content": "{\n  \"name\": \"jotai-tanstack-query-example-query-client-atom-provider\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"dependencies\": {\n    \"@tanstack/query-core\": \"latest\",\n    \"@tanstack/react-query\": \"latest\",\n    \"jotai\": \"latest\",\n    \"jotai-tanstack-query\": \"*\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"latest\",\n    \"@types/react-dom\": \"latest\",\n    \"@vitejs/plugin-react\": \"latest\",\n    \"typescript\": \"latest\",\n    \"vite\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\"\n  }\n}\n"
  },
  {
    "path": "examples/08_query_client_atom_provider/src/App.tsx",
    "content": "import { QueryClient, useQuery } from '@tanstack/react-query'\nimport { useAtom } from 'jotai/react'\nimport { atom } from 'jotai/vanilla'\nimport { atomWithQuery } from 'jotai-tanstack-query'\nimport { QueryClientAtomProvider } from 'jotai-tanstack-query/react'\n\nconst queryClient = new QueryClient()\n\nconst idAtom = atom(1)\n\nconst userAtom = atomWithQuery(\n  (get) => ({\n    queryKey: ['users', get(idAtom)],\n  }),\n  () => queryClient\n)\n\nconst UserDataRawFetch = () => {\n  const [id] = useAtom(idAtom)\n  const { data, isPending, isError } = useQuery({\n    queryKey: ['users', id],\n    queryFn: async ({ queryKey: [, id] }) => {\n      const res = await fetch(\n        `https://jsonplaceholder.typicode.com/users/${id}`\n      )\n      return res.json()\n    },\n  })\n\n  if (isPending) return <div>Loading...</div>\n  if (isError) return <div>Error</div>\n\n  return (\n    <>\n      <h2>Tanstack Query</h2>\n      <div>{JSON.stringify(data)}</div>\n    </>\n  )\n}\n\nconst UserData = () => {\n  const [{ data, isPending, isError }] = useAtom(userAtom)\n\n  if (isPending) return <div>Loading...</div>\n  if (isError) return <div>Error</div>\n\n  return (\n    <>\n      <h2>Jotai-Tanstack-Query</h2>\n      <div>{JSON.stringify(data)}</div>\n    </>\n  )\n}\n\nconst Controls = () => {\n  const [id, setId] = useAtom(idAtom)\n  return (\n    <>\n      <div>\n        ID: {id}{' '}\n        <button type=\"button\" onClick={() => setId((c) => c - 1)}>\n          Prev\n        </button>{' '}\n        <button type=\"button\" onClick={() => setId((c) => c + 1)}>\n          Next\n        </button>\n      </div>\n      <div>\n        <button\n          type=\"button\"\n          onClick={() =>\n            queryClient.setQueryData(['users', id], (old: any) => {\n              return {\n                ...old,\n                name: old.name + '🔄',\n              }\n            })\n          }>\n          Update User Data\n        </button>\n      </div>\n    </>\n  )\n}\n\nconst App = () => (\n  <>\n    <QueryClientAtomProvider client={queryClient}>\n      <Controls />\n      <UserDataRawFetch />\n      <UserData />\n    </QueryClientAtomProvider>\n  </>\n)\n\nexport default App\n"
  },
  {
    "path": "examples/08_query_client_atom_provider/src/index.tsx",
    "content": "import React from 'react'\nimport { createRoot } from 'react-dom/client'\nimport App from './App'\n\nconst ele = document.getElementById('app')\nif (ele) {\n  createRoot(ele).render(React.createElement(App))\n}\n"
  },
  {
    "path": "examples/08_query_client_atom_provider/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\"src\"],\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "examples/08_query_client_atom_provider/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"allowSyntheticDefaultImports\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "examples/08_query_client_atom_provider/vite.config.ts",
    "content": "import react from '@vitejs/plugin-react'\nimport { defineConfig } from 'vite'\n\nexport default defineConfig({\n  plugins: [react()],\n})\n"
  },
  {
    "path": "examples/09_error_boundary/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>jotai-tanstack-query example</title>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/index.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/09_error_boundary/package.json",
    "content": "{\n  \"name\": \"jotai-tanstack-query-example-error-boundary\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"dependencies\": {\n    \"@tanstack/query-core\": \"latest\",\n    \"jotai\": \"latest\",\n    \"jotai-tanstack-query\": \"*\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\",\n    \"react-error-boundary\": \"latest\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"latest\",\n    \"@types/react-dom\": \"latest\",\n    \"@vitejs/plugin-react\": \"latest\",\n    \"typescript\": \"latest\",\n    \"vite\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\"\n  }\n}\n"
  },
  {
    "path": "examples/09_error_boundary/src/App.tsx",
    "content": "import { Suspense } from 'react'\nimport { atom, useAtom, useSetAtom } from 'jotai'\nimport { atomWithSuspenseQuery } from 'jotai-tanstack-query'\nimport { ErrorBoundary, type FallbackProps } from 'react-error-boundary'\n\nconst idAtom = atom(1)\nconst userAtom = atomWithSuspenseQuery<User>((get) => ({\n  queryKey: ['user', get(idAtom)],\n  queryFn: async ({ queryKey: [, id] }) => {\n    const randomNumber = Math.floor(Math.random() * 10)\n    if (randomNumber % 3 === 0) {\n      await fetch(`https://jsonplaceholder.typicode.com/users/error`)\n      return await Promise.reject('fetch failed')\n    }\n\n    const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)\n    return res.json()\n  },\n  retry: false,\n}))\n\nconst UserData = () => {\n  const [{ data }] = useAtom(userAtom)\n\n  return (\n    <>\n      <UserDisplay user={data} />\n    </>\n  )\n}\n\ninterface User {\n  id: number\n  name: string\n  email: string\n}\n\nconst UserDisplay = ({ user }: { user: User }) => {\n  return (\n    <div>\n      <div>ID: {user.id}</div>\n      <strong>{user.name}</strong> - {user.email}\n    </div>\n  )\n}\n\nconst Controls = () => {\n  const [id, setId] = useAtom(idAtom)\n  return (\n    <>\n      <div>\n        ID: {id}{' '}\n        <button type=\"button\" onClick={() => setId((c) => c - 1)}>\n          Prev\n        </button>{' '}\n        <button type=\"button\" onClick={() => setId((c) => c + 1)}>\n          Next\n        </button>\n      </div>\n    </>\n  )\n}\n\nconst Fallback = ({ error, resetErrorBoundary }: FallbackProps) => {\n  const reset = useSetAtom(userAtom)\n  const retry = () => {\n    reset()\n    resetErrorBoundary()\n  }\n  return (\n    <div role=\"alert\">\n      <p>Something went wrong:</p>\n      <pre>{error.message}</pre>\n      <button onClick={retry}>Try again</button>\n    </div>\n  )\n}\n\nconst App = () => {\n  return (\n    <ErrorBoundary FallbackComponent={Fallback}>\n      <Suspense fallback=\"Loading...\">\n        <Controls />\n        <UserData />\n      </Suspense>\n    </ErrorBoundary>\n  )\n}\n\nexport default App\n"
  },
  {
    "path": "examples/09_error_boundary/src/index.tsx",
    "content": "import React from 'react'\nimport { createRoot } from 'react-dom/client'\nimport App from './App'\n\nconst ele = document.getElementById('app')\nif (ele) {\n  createRoot(ele).render(React.createElement(App))\n}\n"
  },
  {
    "path": "examples/09_error_boundary/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\"src\"],\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "examples/09_error_boundary/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"allowSyntheticDefaultImports\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "examples/09_error_boundary/vite.config.ts",
    "content": "import react from '@vitejs/plugin-react'\nimport { defineConfig } from 'vite'\n\nexport default defineConfig({\n  plugins: [react()],\n})\n"
  },
  {
    "path": "examples/11_nextjs_app_router/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.*\n.yarn/*\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases\n!.yarn/versions\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.pnpm-debug.log*\n\n# env files (can opt-in for committing if needed)\n.env*\n\n# vercel\n.vercel\n\n# typescript\n*.tsbuildinfo\nnext-env.d.ts\n"
  },
  {
    "path": "examples/11_nextjs_app_router/README.md",
    "content": "This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).\n\n## Getting Started\n\nFirst, run the development server:\n\n```bash\nnpm run dev\n# or\nyarn dev\n# or\npnpm dev\n# or\nbun dev\n```\n\nOpen [http://localhost:3000](http://localhost:3000) with your browser to see the result.\n\nYou can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.\n\nThis project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.\n\n## Learn More\n\nTo learn more about Next.js, take a look at the following resources:\n\n- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.\n- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.\n\nYou can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!\n\n## Deploy on Vercel\n\nThe easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.\n\nCheck out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.\n"
  },
  {
    "path": "examples/11_nextjs_app_router/eslint.config.mjs",
    "content": "import { defineConfig, globalIgnores } from \"eslint/config\";\nimport nextVitals from \"eslint-config-next/core-web-vitals\";\nimport nextTs from \"eslint-config-next/typescript\";\n\nconst eslintConfig = defineConfig([\n  ...nextVitals,\n  ...nextTs,\n  // Override default ignores of eslint-config-next.\n  globalIgnores([\n    // Default ignores of eslint-config-next:\n    \".next/**\",\n    \"out/**\",\n    \"build/**\",\n    \"next-env.d.ts\",\n  ]),\n]);\n\nexport default eslintConfig;\n"
  },
  {
    "path": "examples/11_nextjs_app_router/next.config.ts",
    "content": "import type { NextConfig } from \"next\";\n\nconst nextConfig: NextConfig = {\n  /* config options here */\n};\n\nexport default nextConfig;\n"
  },
  {
    "path": "examples/11_nextjs_app_router/package.json",
    "content": "{\n  \"name\": \"jotai-tanstack-query-example-nextjs-app-router\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"build\": \"next build\",\n    \"start\": \"next start\",\n    \"lint\": \"eslint\"\n  },\n  \"dependencies\": {\n    \"@tanstack/react-query\": \"^5.90.10\",\n    \"jotai\": \"^2.15.1\",\n    \"jotai-tanstack-query\": \"*\",\n    \"next\": \"~15.4.7\",\n    \"react\": \"19.2.0\",\n    \"react-dom\": \"19.2.0\"\n  },\n  \"devDependencies\": {\n    \"@tailwindcss/postcss\": \"^4\",\n    \"@types/node\": \"^20\",\n    \"@types/react\": \"^19\",\n    \"@types/react-dom\": \"^19\",\n    \"eslint\": \"^9\",\n    \"eslint-config-next\": \"~15.4.7\",\n    \"tailwindcss\": \"^4\",\n    \"typescript\": \"^5\"\n  }\n}\n"
  },
  {
    "path": "examples/11_nextjs_app_router/postcss.config.mjs",
    "content": "const config = {\n  plugins: {\n    \"@tailwindcss/postcss\": {},\n  },\n};\n\nexport default config;\n"
  },
  {
    "path": "examples/11_nextjs_app_router/src/app/api.ts",
    "content": "export async function getPost(postId: string) {\n  console.debug('getPost called with postId:', postId)\n  const res = await fetch(\n    `https://jsonplaceholder.typicode.com/posts/${postId}`\n  )\n  return res.json()\n}\n"
  },
  {
    "path": "examples/11_nextjs_app_router/src/app/layout.tsx",
    "content": "import type { ReactNode } from 'react'\nimport Providers from './providers'\n\nexport default function RootLayout({\n  children,\n}: Readonly<{\n  children: ReactNode\n}>) {\n  return (\n    <html lang=\"en\">\n      <body>\n        <Providers>{children}</Providers>\n      </body>\n    </html>\n  )\n}\n"
  },
  {
    "path": "examples/11_nextjs_app_router/src/app/page.tsx",
    "content": "import { redirect } from 'next/navigation'\n\nexport default function Home() {\n  redirect('/posts/1')\n}\n"
  },
  {
    "path": "examples/11_nextjs_app_router/src/app/posts/[postId]/_components/post.tsx",
    "content": "'use client'\n\nimport { useEffect } from 'react'\nimport { useAtomValue, useSetAtom } from 'jotai'\nimport Link from 'next/link'\nimport { postIdAtom, postQueryAtom } from '../../../stores'\n\nexport const Post = ({ postId }: { postId: string }) => {\n  const setPostIdValue = useSetAtom(postIdAtom)\n  useEffect(() => {\n    setPostIdValue(postId)\n  }, [postId, setPostIdValue])\n\n  const { data, isPending, isError, refetch } = useAtomValue(postQueryAtom)\n  if (isPending) return <div>Loading...</div>\n  if (isError) return <div>Error</div>\n  return (\n    <div>\n      <Link href=\"/posts\">Back to posts</Link>\n      <div>ID: {data?.id}</div>\n      <h1>Title: {data?.title}</h1>\n      <div>Body: {data?.body}</div>\n\n      <div style={{ marginTop: '1rem' }}>\n        <button onClick={() => refetch()}>\n          Refetch post - only client-side\n        </button>\n      </div>\n\n      <div style={{ marginTop: '1rem' }}>\n        <Link href={`/posts/${Number(postId) + 1}`}>\n          Next post - only server-side\n        </Link>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "examples/11_nextjs_app_router/src/app/posts/[postId]/page.tsx",
    "content": "// app/posts/[postId]/page.tsx\nimport {\n  HydrationBoundary,\n  QueryClient,\n  dehydrate,\n} from '@tanstack/react-query'\nimport { Post } from './_components/post'\nimport { getPost } from '@/app/api'\n\nexport default async function PostPage({\n  params,\n}: {\n  params: Promise<{ postId: string }>\n}) {\n  const { postId } = await params\n  const queryClient = new QueryClient()\n\n  await queryClient.prefetchQuery({\n    queryKey: ['posts', postId],\n    queryFn: ({ queryKey: [, postId] }) => getPost(postId as string),\n  })\n\n  return (\n    // Neat! Serialization is now as easy as passing props.\n    // HydrationBoundary is a Client Component, so hydration will happen there.\n    <HydrationBoundary state={dehydrate(queryClient)}>\n      <Post postId={postId} />\n    </HydrationBoundary>\n  )\n}\n"
  },
  {
    "path": "examples/11_nextjs_app_router/src/app/posts/page.tsx",
    "content": "import Link from 'next/link'\n\nexport default function PostsPage() {\n  return (\n    <div>\n      Posts\n      <div>\n        <Link href=\"/posts/1\">Post 1</Link>\n        <br />\n        <Link href=\"/posts/2\">Post 2</Link>\n        <br />\n        <Link href=\"/posts/3\">Post 3</Link>\n        <br />\n        <Link href=\"/posts/4\">Post 4</Link>\n        <br />\n        <Link href=\"/posts/5\">Post 5</Link>\n        <br />\n        <Link href=\"/posts/6\">Post 6</Link>\n        <br />\n        <Link href=\"/posts/7\">Post 7</Link>\n        <br />\n        <Link href=\"/posts/8\">Post 8</Link>\n        <br />\n        <Link href=\"/posts/9\">Post 9</Link>\n        <br />\n        <Link href=\"/posts/10\">Post 10</Link>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "examples/11_nextjs_app_router/src/app/providers.tsx",
    "content": "// In Next.js, this file would be called: app/providers.tsx\n'use client'\n\n// Since QueryClientProvider relies on useContext under the hood, we have to put 'use client' on top\nimport type { ReactNode } from 'react'\nimport {\n  QueryClient,\n  QueryClientProvider,\n  QueryClientProviderProps,\n  isServer,\n} from '@tanstack/react-query'\nimport { Provider } from 'jotai/react'\nimport { useHydrateAtoms } from 'jotai/react/utils'\nimport { queryClientAtom } from 'jotai-tanstack-query'\n\nconst HydrateAtoms = ({ client, children }: QueryClientProviderProps) => {\n  useHydrateAtoms(new Map([[queryClientAtom, client]]))\n  return children\n}\nfunction makeQueryClient() {\n  return new QueryClient({\n    defaultOptions: {\n      queries: {\n        // With SSR, we usually want to set some default staleTime\n        // above 0 to avoid refetching immediately on the client\n        staleTime: 60 * 1000,\n      },\n    },\n  })\n}\n\nlet browserQueryClient: QueryClient | undefined = undefined\n\nfunction getQueryClient() {\n  if (isServer) {\n    // Server: always make a new query client\n    return makeQueryClient()\n  } else {\n    // Browser: make a new query client if we don't already have one\n    // This is very important, so we don't re-make a new client if React\n    // suspends during the initial render. This may not be needed if we\n    // have a suspense boundary BELOW the creation of the query client\n    if (!browserQueryClient) browserQueryClient = makeQueryClient()\n    return browserQueryClient\n  }\n}\n\nexport default function Providers({ children }: { children: ReactNode }) {\n  // NOTE: Avoid useState when initializing the query client if you don't\n  //       have a suspense boundary between this and the code that may\n  //       suspend because React will throw away the client on the initial\n  //       render if it suspends and there is no boundary\n  const queryClient = getQueryClient()\n\n  return (\n    <QueryClientProvider client={queryClient}>\n      <Provider>\n        <HydrateAtoms client={queryClient}>{children}</HydrateAtoms>\n      </Provider>\n    </QueryClientProvider>\n  )\n}\n"
  },
  {
    "path": "examples/11_nextjs_app_router/src/app/stores.ts",
    "content": "import { atom } from 'jotai'\nimport { atomWithQuery } from 'jotai-tanstack-query'\nimport { getPost } from './api'\n\nexport const postIdAtom = atom<string>('1')\nexport const postQueryAtom = atomWithQuery((get) => ({\n  queryKey: ['posts', get(postIdAtom)],\n  queryFn: ({ queryKey: [, postId] }) => getPost(postId as string),\n  staleTime: Infinity,\n  refetchOnMount: false,\n  refetchOnWindowFocus: false,\n}))\n"
  },
  {
    "path": "examples/11_nextjs_app_router/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2017\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"preserve\",\n    \"incremental\": true,\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ],\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  },\n  \"include\": [\n    \"next-env.d.ts\",\n    \"**/*.ts\",\n    \"**/*.tsx\",\n    \".next/types/**/*.ts\",\n    \".next/dev/types/**/*.ts\",\n    \"**/*.mts\"\n  ],\n  \"exclude\": [\"node_modules\", \".next\"]\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"jotai-tanstack-query\",\n  \"description\": \"👻🌺\",\n  \"version\": \"0.11.0\",\n  \"type\": \"module\",\n  \"author\": \"Daishi Kato\",\n  \"contributors\": [\n    \"Thaddeus Jiang\",\n    \"Mohammad Bagher Abiat\",\n    \"Kali Charan Reddy Jonna\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/jotaijs/jotai-tanstack-query.git\"\n  },\n  \"source\": \"./src/index.ts\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \"./package.json\": \"./package.json\",\n    \".\": {\n      \"types\": \"./dist/index.d.ts\",\n      \"module\": \"./dist/index.mjs\",\n      \"import\": \"./dist/index.mjs\",\n      \"default\": \"./dist/index.js\"\n    },\n    \"./react\": {\n      \"types\": \"./dist/react.d.ts\",\n      \"module\": \"./dist/react.js\",\n      \"import\": \"./dist/react.mjs\",\n      \"default\": \"./dist/react.js\"\n    }\n  },\n  \"sideEffects\": false,\n  \"files\": [\n    \"src\",\n    \"dist\"\n  ],\n  \"scripts\": {\n    \"compile\": \"bunchee --sourcemap\",\n    \"test\": \"run-s eslint tsc-test vitest\",\n    \"eslint\": \"eslint --ext .js,.ts,.tsx .\",\n    \"vitest\": \"vitest run\",\n    \"test:dev\": \"vitest\",\n    \"tsc-test\": \"tsc --project . --noEmit\",\n    \"examples:01_query\": \"pnpm --filter ./examples/01_query run dev\",\n    \"examples:02_suspense\": \"pnpm --filter ./examples/02_suspense run dev\",\n    \"examples:03_infinite\": \"pnpm --filter ./examples/03_infinite run dev\",\n    \"examples:04_infinite_suspense\": \"pnpm --filter ./examples/04_infinite_suspense run dev\",\n    \"examples:05_mutation\": \"pnpm --filter ./examples/05_mutation run dev\",\n    \"examples:06_refetch\": \"pnpm --filter ./examples/06_refetch run dev\",\n    \"examples:07_queries\": \"pnpm --filter ./examples/07_queries run dev\",\n    \"examples:08_query_client_atom_provider\": \"pnpm --filter ./examples/08_query_client_atom_provider run dev\",\n    \"examples:09_error_boundary\": \"pnpm --filter ./examples/09_error_boundary run dev\",\n    \"examples:11_nextjs_app_router\": \"pnpm --filter ./examples/11_nextjs_app_router run dev\"\n  },\n  \"keywords\": [\n    \"jotai\",\n    \"react\",\n    \"tanstack/query\",\n    \"state management\",\n    \"data fetching\"\n  ],\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@eslint/js\": \"^9.32.0\",\n    \"@tanstack/query-core\": \"^5.83.0\",\n    \"@tanstack/react-query\": \"^5.83.0\",\n    \"@testing-library/dom\": \"^10.4.0\",\n    \"@testing-library/react\": \"^16.3.0\",\n    \"@types/jsdom\": \"^21.1.7\",\n    \"@types/node\": \"^22.13.14\",\n    \"@types/react\": \"^19.1.8\",\n    \"@types/react-dom\": \"^19.1.6\",\n    \"@typescript-eslint/eslint-plugin\": \"^8.38.0\",\n    \"@typescript-eslint/parser\": \"^8.38.0\",\n    \"@vitest/ui\": \"^3.2.4\",\n    \"bunchee\": \"^6.5.4\",\n    \"eslint\": \"^9.32.0\",\n    \"eslint-config-prettier\": \"^9.1.0\",\n    \"eslint-import-resolver-alias\": \"^1.1.2\",\n    \"eslint-plugin-import\": \"^2.31.0\",\n    \"eslint-plugin-prettier\": \"^5.2.1\",\n    \"eslint-plugin-react\": \"^7.37.5\",\n    \"eslint-plugin-react-hooks\": \"^5.1.0\",\n    \"jotai\": \"^2.4.2\",\n    \"jsdom\": \"^25.0.1\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"prettier\": \"^3.0.3\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-error-boundary\": \"^4.0.11\",\n    \"typescript\": \"^5.8.3\",\n    \"vitest\": \"^3.2.4\"\n  },\n  \"peerDependencies\": {\n    \"@tanstack/query-core\": \"*\",\n    \"@tanstack/react-query\": \"*\",\n    \"jotai\": \">=2.0.0\",\n    \"react\": \"^18.0.0 || ^19.0.0\"\n  },\n  \"peerDependenciesMeta\": {\n    \"@tanstack/react-query\": {\n      \"optional\": true\n    },\n    \"react\": {\n      \"optional\": true\n    }\n  },\n  \"packageManager\": \"pnpm@10.13.1\"\n}\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "linkWorkspacePackages: true\npreferWorkspacePackages: true\npreferFrozenLockfile: true\n\npackages:\n  - examples/*\n\nonlyBuiltDependencies:\n  - '@swc/core'\n  - esbuild\n"
  },
  {
    "path": "src/_queryClientAtom.ts",
    "content": "import { QueryClient } from '@tanstack/query-core'\nimport { atom } from 'jotai/vanilla'\n\nexport const queryClientAtom = atom(new QueryClient())\n\nif (process.env.NODE_ENV !== 'production') {\n  queryClientAtom.debugPrivate = true\n}\n"
  },
  {
    "path": "src/atomWithInfiniteQuery.ts",
    "content": "import {\n  type DefaultError,\n  type InfiniteData,\n  InfiniteQueryObserver,\n  QueryClient,\n  type QueryKey,\n  QueryObserver,\n} from '@tanstack/query-core'\nimport { Getter, WritableAtom } from 'jotai'\nimport { queryClientAtom } from './_queryClientAtom'\nimport { baseAtomWithQuery } from './baseAtomWithQuery'\nimport {\n  AtomWithInfiniteQueryOptions,\n  AtomWithInfiniteQueryResult,\n  DefinedAtomWithInfiniteQueryResult,\n  DefinedInitialDataInfiniteOptions,\n  UndefinedInitialDataInfiniteOptions,\n} from './types'\n\nexport function atomWithInfiniteQuery<\n  TQueryFnData,\n  TError = DefaultError,\n  TData = InfiniteData<TQueryFnData>,\n  TQueryKey extends QueryKey = QueryKey,\n  TPageParam = unknown,\n>(\n  getOptions: (\n    get: Getter\n  ) => UndefinedInitialDataInfiniteOptions<\n    TQueryFnData,\n    TError,\n    TData,\n    TQueryKey,\n    TPageParam\n  >,\n  getQueryClient?: (get: Getter) => QueryClient\n): WritableAtom<AtomWithInfiniteQueryResult<TData, TError>, [], void>\nexport function atomWithInfiniteQuery<\n  TQueryFnData,\n  TError = DefaultError,\n  TData = InfiniteData<TQueryFnData>,\n  TQueryKey extends QueryKey = QueryKey,\n  TPageParam = unknown,\n>(\n  getOptions: (\n    get: Getter\n  ) => DefinedInitialDataInfiniteOptions<\n    TQueryFnData,\n    TError,\n    TData,\n    TQueryKey,\n    TPageParam\n  >,\n  getQueryClient?: (get: Getter) => QueryClient\n): WritableAtom<DefinedAtomWithInfiniteQueryResult<TData, TError>, [], void>\nexport function atomWithInfiniteQuery<\n  TQueryFnData,\n  TError = DefaultError,\n  TData = InfiniteData<TQueryFnData>,\n  TQueryKey extends QueryKey = QueryKey,\n  TPageParam = unknown,\n>(\n  getOptions: (\n    get: Getter\n  ) => AtomWithInfiniteQueryOptions<\n    TQueryFnData,\n    TError,\n    TData,\n    TQueryKey,\n    TPageParam\n  >,\n  getQueryClient?: (get: Getter) => QueryClient\n): WritableAtom<AtomWithInfiniteQueryResult<TData, TError>, [], void>\nexport function atomWithInfiniteQuery(\n  getOptions: (get: Getter) => AtomWithInfiniteQueryOptions,\n  getQueryClient: (get: Getter) => QueryClient = (get) => get(queryClientAtom)\n) {\n  return baseAtomWithQuery(\n    getOptions,\n    InfiniteQueryObserver as typeof QueryObserver,\n    getQueryClient\n  )\n}\n"
  },
  {
    "path": "src/atomWithMutation.ts",
    "content": "import {\n  MutationObserver,\n  type MutationOptions,\n  QueryClient,\n  notifyManager,\n} from '@tanstack/query-core'\nimport { Atom, Getter, atom } from 'jotai'\nimport { queryClientAtom } from './_queryClientAtom'\nimport { AtomWithMutationResult, MutateFunction } from './types'\nimport { shouldThrowError } from './utils'\n\nexport function atomWithMutation<\n  TData = unknown,\n  TVariables = void,\n  TError = unknown,\n  TContext = unknown,\n>(\n  getOptions: (\n    get: Getter\n  ) => MutationOptions<TData, TError, TVariables, TContext>,\n  getQueryClient: (get: Getter) => QueryClient = (get) => get(queryClientAtom)\n): Atom<AtomWithMutationResult<TData, TError, TVariables, TContext>> {\n  const IN_RENDER = Symbol()\n\n  const optionsAtom = atom((get) => {\n    const client = getQueryClient(get)\n    const options = getOptions(get)\n    return client.defaultMutationOptions(options)\n  })\n  if (process.env.NODE_ENV !== 'production') {\n    optionsAtom.debugPrivate = true\n  }\n\n  const observerCacheAtom = atom(\n    () =>\n      new WeakMap<\n        QueryClient,\n        MutationObserver<TData, TError, TVariables, TContext>\n      >()\n  )\n  if (process.env.NODE_ENV !== 'production') {\n    observerCacheAtom.debugPrivate = true\n  }\n\n  const observerAtom = atom((get) => {\n    const options = get(optionsAtom)\n    const client = getQueryClient(get)\n    const observerCache = get(observerCacheAtom)\n\n    const observer = observerCache.get(client)\n\n    if (observer) {\n      ;(observer as any)[IN_RENDER] = true\n      observer.setOptions(options)\n      delete (observer as any)[IN_RENDER]\n\n      return observer\n    }\n\n    const newObserver = new MutationObserver(client, options)\n    observerCache.set(client, newObserver)\n\n    return newObserver\n  })\n  if (process.env.NODE_ENV !== 'production') {\n    observerAtom.debugPrivate = true\n  }\n\n  const dataAtom = atom((get) => {\n    const observer = get(observerAtom)\n\n    const currentResult = observer.getCurrentResult()\n    const resultAtom = atom(currentResult)\n\n    resultAtom.onMount = (set) => {\n      observer.subscribe(notifyManager.batchCalls(set))\n      return () => {\n        observer.reset()\n      }\n    }\n    if (process.env.NODE_ENV !== 'production') {\n      resultAtom.debugPrivate = true\n    }\n\n    return resultAtom\n  })\n\n  const mutateAtom = atom((get) => {\n    const observer = get(observerAtom)\n    const mutate: MutateFunction<TData, TError, TVariables, TContext> = (\n      variables,\n      options\n    ) => {\n      observer.mutate(variables, options).catch(noop)\n    }\n\n    return mutate\n  })\n  if (process.env.NODE_ENV !== 'production') {\n    mutateAtom.debugPrivate = true\n  }\n\n  return atom((get) => {\n    const observer = get(observerAtom)\n    const resultAtom = get(dataAtom)\n\n    const result = get(resultAtom)\n    const mutate = get(mutateAtom)\n\n    if (\n      result.isError &&\n      shouldThrowError(observer.options.throwOnError, [result.error])\n    ) {\n      throw result.error\n    }\n\n    return { ...result, mutate, mutateAsync: result.mutate }\n  })\n}\n\nfunction noop() {}\n"
  },
  {
    "path": "src/atomWithMutationState.ts",
    "content": "import {\n  type DefaultError,\n  Mutation,\n  MutationCache,\n  type MutationFilters,\n  type MutationState,\n  QueryClient,\n} from '@tanstack/query-core'\nimport { Getter, atom } from 'jotai'\nimport { queryClientAtom } from './_queryClientAtom'\n\ntype MutationStateOptions<TResult = MutationState> = {\n  filters?: MutationFilters\n  select?: (\n    mutation: Mutation<unknown, DefaultError, unknown, unknown>\n  ) => TResult\n}\n\nfunction getResult<TResult = MutationState>(\n  mutationCache: MutationCache,\n  options: MutationStateOptions<TResult>\n): Array<TResult> {\n  return mutationCache\n    .findAll(options.filters)\n    .map(\n      (mutation): TResult =>\n        (options.select\n          ? options.select(\n              mutation as Mutation<unknown, DefaultError, unknown, unknown>\n            )\n          : mutation.state) as TResult\n    )\n}\n\nexport const atomWithMutationState = <TResult = MutationState>(\n  getOptions: (get: Getter) => MutationStateOptions<TResult>,\n  getQueryClient: (get: Getter) => QueryClient = (get) => get(queryClientAtom)\n) => {\n  const resultsAtom = atom<TResult[]>([])\n  if (process.env.NODE_ENV !== 'production') {\n    resultsAtom.debugPrivate = true\n  }\n\n  const observableAtom = atom((get) => {\n    const queryClient = getQueryClient(get)\n\n    const mutationCache = queryClient.getMutationCache()\n    resultsAtom.onMount = (set) => {\n      const unsubscribe = mutationCache.subscribe(() => {\n        set(getResult(getQueryClient(get).getMutationCache(), getOptions(get)))\n      })\n      return unsubscribe\n    }\n  })\n  if (process.env.NODE_ENV !== 'production') {\n    observableAtom.debugPrivate = true\n  }\n\n  return atom((get) => {\n    get(observableAtom)\n    return get(resultsAtom)\n  })\n}\n"
  },
  {
    "path": "src/atomWithQueries.ts",
    "content": "import {\n  type DefaultError,\n  QueryClient,\n  QueryObserver,\n} from '@tanstack/query-core'\nimport { Getter, WritableAtom, atom } from 'jotai'\nimport { queryClientAtom } from './_queryClientAtom'\nimport { baseAtomWithQuery } from './baseAtomWithQuery'\nimport { AtomWithQueryOptions, AtomWithQueryResult } from './types'\n\nexport function atomWithQueries<TCombinedResult>(\n  {\n    queries,\n    combine,\n  }: {\n    queries: Array<(get: Getter) => AtomWithQueryOptions>\n    combine: (results: AtomWithQueryResult[]) => TCombinedResult\n  },\n  getQueryClient?: (get: Getter) => QueryClient\n): WritableAtom<TCombinedResult, [], void>\nexport function atomWithQueries<\n  TQueryFnData = unknown,\n  TError = DefaultError,\n  TData = TQueryFnData,\n>(\n  {\n    queries,\n  }: {\n    queries: Array<(get: Getter) => AtomWithQueryOptions>\n  },\n  getQueryClient?: (get: Getter) => QueryClient\n): Array<WritableAtom<AtomWithQueryResult<TData, TError>, [], void>>\n\nexport function atomWithQueries(\n  {\n    queries,\n    combine,\n  }: {\n    queries: Array<(get: Getter) => AtomWithQueryOptions>\n    combine?: (results: AtomWithQueryResult[]) => any\n  },\n  getQueryClient: (get: Getter) => QueryClient = (get) => get(queryClientAtom)\n): any {\n  if (!combine) {\n    return queries.map((getOptions) =>\n      baseAtomWithQuery(getOptions, QueryObserver, getQueryClient)\n    )\n  }\n\n  const queryAtoms = queries.map((getOptions) =>\n    baseAtomWithQuery(getOptions, QueryObserver, getQueryClient)\n  )\n\n  return atom((get) => {\n    const results = queryAtoms.map((queryAtom) => {\n      const result = get(queryAtom)\n      return result as AtomWithQueryResult\n    })\n    return combine(results)\n  })\n}\n"
  },
  {
    "path": "src/atomWithQuery.ts",
    "content": "import {\n  type DefaultError,\n  QueryClient,\n  type QueryKey,\n  QueryObserver,\n} from '@tanstack/query-core'\nimport { Getter, WritableAtom } from 'jotai'\nimport { queryClientAtom } from './_queryClientAtom'\nimport { baseAtomWithQuery } from './baseAtomWithQuery'\nimport {\n  AtomWithQueryOptions,\n  AtomWithQueryResult,\n  DefinedAtomWithQueryResult,\n  DefinedInitialDataOptions,\n  UndefinedInitialDataOptions,\n} from './types'\n\nexport function atomWithQuery<\n  TQueryFnData = unknown,\n  TError = DefaultError,\n  TData = TQueryFnData,\n  TQueryKey extends QueryKey = QueryKey,\n>(\n  getOptions: (\n    get: Getter\n  ) => UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,\n  getQueryClient?: (get: Getter) => QueryClient\n): WritableAtom<AtomWithQueryResult<TData, TError>, [], void>\nexport function atomWithQuery<\n  TQueryFnData = unknown,\n  TError = DefaultError,\n  TData = TQueryFnData,\n  TQueryKey extends QueryKey = QueryKey,\n>(\n  getOptions: (\n    get: Getter\n  ) => DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,\n  getQueryClient?: (get: Getter) => QueryClient\n): WritableAtom<DefinedAtomWithQueryResult<TData, TError>, [], void>\nexport function atomWithQuery<\n  TQueryFnData = unknown,\n  TError = DefaultError,\n  TData = TQueryFnData,\n  TQueryKey extends QueryKey = QueryKey,\n>(\n  getOptions: (\n    get: Getter\n  ) => AtomWithQueryOptions<TQueryFnData, TError, TData, TQueryKey>,\n  getQueryClient?: (get: Getter) => QueryClient\n): WritableAtom<AtomWithQueryResult<TData, TError>, [], void>\nexport function atomWithQuery(\n  getOptions: (get: Getter) => AtomWithQueryOptions,\n  getQueryClient: (get: Getter) => QueryClient = (get) => get(queryClientAtom)\n) {\n  return baseAtomWithQuery(getOptions, QueryObserver, getQueryClient)\n}\n"
  },
  {
    "path": "src/atomWithSuspenseInfiniteQuery.ts",
    "content": "import {\n  type DefaultError,\n  type InfiniteData,\n  InfiniteQueryObserver,\n  QueryClient,\n  type QueryKey,\n  QueryObserver,\n} from '@tanstack/query-core'\nimport { Getter, WritableAtom, atom } from 'jotai'\nimport { queryClientAtom } from './_queryClientAtom'\nimport { baseAtomWithQuery } from './baseAtomWithQuery'\nimport {\n  AtomWithSuspenseInfiniteQueryOptions,\n  AtomWithSuspenseInfiniteQueryResult,\n} from './types'\nimport { defaultThrowOnError } from './utils'\n\nexport function atomWithSuspenseInfiniteQuery<\n  TQueryFnData,\n  TError = DefaultError,\n  TData = InfiniteData<TQueryFnData>,\n  TQueryKey extends QueryKey = QueryKey,\n  TPageParam = unknown,\n>(\n  getOptions: (\n    get: Getter\n  ) => AtomWithSuspenseInfiniteQueryOptions<\n    TQueryFnData,\n    TError,\n    TData,\n    TQueryKey,\n    TPageParam\n  >,\n  getQueryClient: (get: Getter) => QueryClient = (get) => get(queryClientAtom)\n): WritableAtom<AtomWithSuspenseInfiniteQueryResult<TData, TError>, [], void> {\n  const suspenseOptionsAtom = atom((get) => {\n    const options = getOptions(get)\n    return {\n      ...options,\n      enabled: true,\n      suspense: true,\n      throwOnError: defaultThrowOnError,\n    }\n  })\n\n  return baseAtomWithQuery(\n    (get: Getter) => get(suspenseOptionsAtom),\n    InfiniteQueryObserver as typeof QueryObserver,\n    getQueryClient\n  ) as unknown as WritableAtom<\n    AtomWithSuspenseInfiniteQueryResult<TData, TError>,\n    [],\n    void\n  >\n}\n"
  },
  {
    "path": "src/atomWithSuspenseQuery.ts",
    "content": "import {\n  type DefaultError,\n  QueryClient,\n  type QueryKey,\n  QueryObserver,\n} from '@tanstack/query-core'\nimport { Getter, WritableAtom, atom } from 'jotai'\nimport { queryClientAtom } from './_queryClientAtom'\nimport { baseAtomWithQuery } from './baseAtomWithQuery'\nimport {\n  AtomWithSuspenseQueryOptions,\n  AtomWithSuspenseQueryResult,\n} from './types'\nimport { defaultThrowOnError } from './utils'\n\nexport function atomWithSuspenseQuery<\n  TQueryFnData = unknown,\n  TError = DefaultError,\n  TData = TQueryFnData,\n  TQueryKey extends QueryKey = QueryKey,\n>(\n  getOptions: (\n    get: Getter\n  ) => AtomWithSuspenseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,\n  getQueryClient: (get: Getter) => QueryClient = (get) => get(queryClientAtom)\n): WritableAtom<AtomWithSuspenseQueryResult<TData, TError>, [], void> {\n  const suspenseOptions = atom((get) => {\n    const options = getOptions(get)\n    return {\n      ...options,\n      suspense: true,\n      enabled: true,\n      throwOnError: defaultThrowOnError,\n    }\n  })\n\n  return baseAtomWithQuery(\n    (get: Getter) => get(suspenseOptions),\n    QueryObserver,\n    getQueryClient\n  ) as WritableAtom<AtomWithSuspenseQueryResult<TData, TError>, [], void>\n}\n"
  },
  {
    "path": "src/baseAtomWithQuery.ts",
    "content": "import {\n  QueryClient,\n  type QueryKey,\n  QueryObserver,\n  type QueryObserverResult,\n  notifyManager,\n} from '@tanstack/query-core'\nimport { Getter, WritableAtom, atom } from 'jotai'\nimport { queryClientAtom } from './_queryClientAtom'\nimport { BaseAtomWithQueryOptions } from './types'\nimport { ensureStaleTime, getHasError, shouldSuspend } from './utils'\n\nexport function baseAtomWithQuery<\n  TQueryFnData,\n  TError,\n  TData,\n  TQueryData,\n  TQueryKey extends QueryKey,\n>(\n  getOptions: (\n    get: Getter\n  ) => BaseAtomWithQueryOptions<\n    TQueryFnData,\n    TError,\n    TData,\n    TQueryData,\n    TQueryKey\n  >,\n  Observer: typeof QueryObserver,\n  getQueryClient: (get: Getter) => QueryClient = (get) => get(queryClientAtom)\n): WritableAtom<\n  | QueryObserverResult<TData, TError>\n  | Promise<QueryObserverResult<TData, TError>>,\n  [],\n  void\n> {\n  const refreshAtom = atom(0)\n  const clientAtom = atom(getQueryClient)\n  if (process.env.NODE_ENV !== 'production') {\n    clientAtom.debugPrivate = true\n  }\n\n  const observerCacheAtom = atom(\n    () =>\n      new WeakMap<\n        QueryClient,\n        QueryObserver<TQueryFnData, TError, TData, TQueryData, TQueryKey>\n      >()\n  )\n  if (process.env.NODE_ENV !== 'production') {\n    observerCacheAtom.debugPrivate = true\n  }\n\n  const defaultedOptionsAtom = atom((get) => {\n    const client = get(clientAtom)\n    const options = getOptions(get)\n    const defaultedOptions = client.defaultQueryOptions(options)\n\n    const cache = get(observerCacheAtom)\n    const cachedObserver = cache.get(client)\n\n    defaultedOptions._optimisticResults = 'optimistic'\n\n    if (cachedObserver) {\n      cachedObserver.setOptions(defaultedOptions)\n    }\n\n    return ensureStaleTime(defaultedOptions)\n  })\n  if (process.env.NODE_ENV !== 'production') {\n    defaultedOptionsAtom.debugPrivate = true\n  }\n\n  const observerAtom = atom((get) => {\n    const client = get(clientAtom)\n    const defaultedOptions = get(defaultedOptionsAtom)\n\n    const observerCache = get(observerCacheAtom)\n\n    const cachedObserver = observerCache.get(client)\n\n    if (cachedObserver) return cachedObserver\n\n    const newObserver = new Observer(client, defaultedOptions)\n    observerCache.set(client, newObserver)\n\n    return newObserver\n  })\n  if (process.env.NODE_ENV !== 'production') {\n    observerAtom.debugPrivate = true\n  }\n\n  const dataAtom = atom((get) => {\n    const observer = get(observerAtom)\n    const defaultedOptions = get(defaultedOptionsAtom)\n    const result = observer.getOptimisticResult(defaultedOptions)\n\n    const resultAtom = atom(result)\n    if (process.env.NODE_ENV !== 'production') {\n      resultAtom.debugPrivate = true\n    }\n\n    resultAtom.onMount = (set) => {\n      const unsubscribe = observer.subscribe(notifyManager.batchCalls(set))\n      return () => {\n        if (observer.getCurrentResult().isError) {\n          observer.getCurrentQuery().reset()\n        }\n        unsubscribe()\n      }\n    }\n\n    return resultAtom\n  })\n  if (process.env.NODE_ENV !== 'production') {\n    dataAtom.debugPrivate = true\n  }\n\n  return atom(\n    (get) => {\n      get(refreshAtom)\n      const observer = get(observerAtom)\n      const defaultedOptions = get(defaultedOptionsAtom)\n\n      const result = get(get(dataAtom))\n\n      if (shouldSuspend(defaultedOptions, result, false)) {\n        return observer.fetchOptimistic(defaultedOptions)\n      }\n\n      if (\n        getHasError({\n          result,\n          query: observer.getCurrentQuery(),\n          throwOnError: defaultedOptions.throwOnError,\n        })\n      ) {\n        throw result.error\n      }\n\n      return result\n    },\n    (_get, set) => {\n      set(refreshAtom, (c) => c + 1)\n    }\n  )\n}\n"
  },
  {
    "path": "src/index.ts",
    "content": "export { queryClientAtom } from './_queryClientAtom'\nexport { atomWithQuery } from './atomWithQuery'\nexport { atomWithQueries } from './atomWithQueries'\nexport { atomWithSuspenseQuery } from './atomWithSuspenseQuery'\nexport { atomWithInfiniteQuery } from './atomWithInfiniteQuery'\nexport { atomWithMutation } from './atomWithMutation'\nexport { atomWithSuspenseInfiniteQuery } from './atomWithSuspenseInfiniteQuery'\nexport { atomWithMutationState } from './atomWithMutationState'\nexport * from './types'\n"
  },
  {
    "path": "src/react.ts",
    "content": "import { createElement } from 'react'\nimport {\n  QueryClientProvider,\n  type QueryClientProviderProps,\n} from '@tanstack/react-query'\nimport { Provider } from 'jotai'\nimport { useHydrateAtoms } from 'jotai/utils'\nimport { queryClientAtom } from './_queryClientAtom'\n\nconst HydrateAtoms = ({ client, children }: QueryClientProviderProps) => {\n  useHydrateAtoms([[queryClientAtom, client]])\n  return children\n}\n\nexport function QueryClientAtomProvider({\n  client,\n  children,\n}: QueryClientProviderProps) {\n  return createElement(\n    QueryClientProvider,\n    { client },\n    createElement(\n      Provider,\n      null,\n      createElement(HydrateAtoms, { client }, children)\n    )\n  )\n}\n"
  },
  {
    "path": "src/types.ts",
    "content": "import {\n  type DefaultError,\n  type DefinedInfiniteQueryObserverResult,\n  type DefinedQueryObserverResult,\n  type InfiniteData,\n  type InfiniteQueryObserverOptions,\n  type InfiniteQueryObserverResult,\n  type MutationObserverOptions,\n  type MutationObserverResult,\n  type QueryKey,\n  type MutateFunction as QueryMutateFunction,\n  type QueryObserverOptions,\n  type QueryObserverResult,\n  type WithRequired,\n} from '@tanstack/query-core'\n\ntype Override<A, B> = { [K in keyof A]: K extends keyof B ? B[K] : A[K] }\n\nexport type MutateFunction<\n  TData = unknown,\n  TError = DefaultError,\n  TVariables = void,\n  TContext = unknown,\n> = (\n  ...args: Parameters<QueryMutateFunction<TData, TError, TVariables, TContext>>\n) => void\n\nexport type MutateAsyncFunction<\n  TData = unknown,\n  TError = DefaultError,\n  TVariables = void,\n  TContext = unknown,\n> = QueryMutateFunction<TData, TError, TVariables, TContext>\n\nexport type AtomWithMutationResult<TData, TError, TVariables, TContext> =\n  Override<\n    MutationObserverResult<TData, TError, TVariables, TContext>,\n    { mutate: MutateFunction<TData, TError, TVariables, TContext> }\n  > & {\n    mutateAsync: MutateAsyncFunction<TData, TError, TVariables, TContext>\n  }\n\nexport type MutationOptions<\n  TData = unknown,\n  TError = DefaultError,\n  TVariables = void,\n  TContext = unknown,\n> = Omit<\n  MutationObserverOptions<TData, TError, TVariables, TContext>,\n  '_defaulted' | 'variables'\n>\n\nexport type BaseAtomWithQueryOptions<\n  TQueryFnData = unknown,\n  TError = DefaultError,\n  TData = TQueryFnData,\n  TQueryData = TQueryFnData,\n  TQueryKey extends QueryKey = QueryKey,\n> = WithRequired<\n  QueryObserverOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>,\n  'queryKey'\n>\n\nexport type AtomWithQueryOptions<\n  TQueryFnData = unknown,\n  TError = DefaultError,\n  TData = TQueryFnData,\n  TQueryKey extends QueryKey = QueryKey,\n> = Omit<\n  WithRequired<\n    BaseAtomWithQueryOptions<\n      TQueryFnData,\n      TError,\n      TData,\n      TQueryFnData,\n      TQueryKey\n    >,\n    'queryKey'\n  >,\n  'suspense'\n>\n\nexport type AtomWithSuspenseQueryOptions<\n  TQueryFnData = unknown,\n  TError = DefaultError,\n  TData = TQueryFnData,\n  TQueryKey extends QueryKey = QueryKey,\n> = Omit<\n  AtomWithQueryOptions<TQueryFnData, TError, TData, TQueryKey>,\n  'enabled' | 'throwOnError' | 'placeholderData'\n>\n\nexport type AtomWithInfiniteQueryOptions<\n  TQueryFnData = unknown,\n  TError = DefaultError,\n  TData = TQueryFnData,\n  TQueryKey extends QueryKey = QueryKey,\n  TPageParam = unknown,\n> = WithRequired<\n  Omit<\n    InfiniteQueryObserverOptions<\n      TQueryFnData,\n      TError,\n      TData,\n      TQueryKey,\n      TPageParam\n    >,\n    'suspense'\n  >,\n  'queryKey'\n>\n\nexport type AtomWithSuspenseInfiniteQueryOptions<\n  TQueryFnData = unknown,\n  TError = DefaultError,\n  TData = TQueryFnData,\n  TQueryKey extends QueryKey = QueryKey,\n  TPageParam = unknown,\n> = Omit<\n  AtomWithInfiniteQueryOptions<\n    TQueryFnData,\n    TError,\n    TData,\n    TQueryKey,\n    TPageParam\n  >,\n  'enabled' | 'throwOnError' | 'placeholderData'\n>\n\nexport type AtomWithQueryResult<\n  TData = unknown,\n  TError = DefaultError,\n> = QueryObserverResult<TData, TError>\n\nexport type DefinedAtomWithQueryResult<\n  TData = unknown,\n  TError = DefaultError,\n> = DefinedQueryObserverResult<TData, TError>\n\nexport type AtomWithSuspenseQueryResult<\n  TData = unknown,\n  TError = DefaultError,\n> =\n  | Omit<DefinedQueryObserverResult<TData, TError>, 'isPlaceholderData'>\n  | Promise<\n      Omit<DefinedQueryObserverResult<TData, TError>, 'isPlaceholderData'>\n    >\n\nexport type AtomWithInfiniteQueryResult<\n  TData = unknown,\n  TError = DefaultError,\n> = InfiniteQueryObserverResult<TData, TError>\n\nexport type DefinedAtomWithInfiniteQueryResult<\n  TData = unknown,\n  TError = DefaultError,\n> = DefinedInfiniteQueryObserverResult<TData, TError>\n\nexport type AtomWithSuspenseInfiniteQueryResult<\n  TData = unknown,\n  TError = DefaultError,\n> = Promise<\n  Omit<DefinedInfiniteQueryObserverResult<TData, TError>, 'isPlaceholderData'>\n>\n\nexport type UndefinedInitialDataOptions<\n  TQueryFnData = unknown,\n  TError = DefaultError,\n  TData = TQueryFnData,\n  TQueryKey extends QueryKey = QueryKey,\n> = AtomWithQueryOptions<TQueryFnData, TError, TData, TQueryKey> & {\n  initialData?: undefined\n}\n\ntype NonUndefinedGuard<T> = T extends undefined ? never : T\n\nexport type DefinedInitialDataOptions<\n  TQueryFnData = unknown,\n  TError = DefaultError,\n  TData = TQueryFnData,\n  TQueryKey extends QueryKey = QueryKey,\n> = AtomWithQueryOptions<TQueryFnData, TError, TData, TQueryKey> & {\n  initialData:\n    | NonUndefinedGuard<TQueryFnData>\n    | (() => NonUndefinedGuard<TQueryFnData>)\n}\n\nexport type UndefinedInitialDataInfiniteOptions<\n  TQueryFnData,\n  TError = DefaultError,\n  TData = InfiniteData<TQueryFnData>,\n  TQueryKey extends QueryKey = QueryKey,\n  TPageParam = unknown,\n> = AtomWithInfiniteQueryOptions<\n  TQueryFnData,\n  TError,\n  TData,\n  TQueryKey,\n  TPageParam\n> & {\n  initialData?: undefined\n}\n\nexport type DefinedInitialDataInfiniteOptions<\n  TQueryFnData,\n  TError = DefaultError,\n  TData = InfiniteData<TQueryFnData>,\n  TQueryKey extends QueryKey = QueryKey,\n  TPageParam = unknown,\n> = AtomWithInfiniteQueryOptions<\n  TQueryFnData,\n  TError,\n  TData,\n  TQueryKey,\n  TPageParam\n> & {\n  initialData:\n    | NonUndefinedGuard<InfiniteData<TQueryFnData, TPageParam>>\n    | (() => NonUndefinedGuard<InfiniteData<TQueryFnData, TPageParam>>)\n}\n"
  },
  {
    "path": "src/utils.ts",
    "content": "import type {\n  DefaultError,\n  DefaultedQueryObserverOptions,\n  Query,\n  QueryKey,\n  QueryObserverResult,\n  ThrowOnError,\n} from '@tanstack/query-core'\n\nexport const shouldSuspend = (\n  defaultedOptions:\n    | DefaultedQueryObserverOptions<any, any, any, any, any>\n    | undefined,\n  result: QueryObserverResult<any, any>,\n  isRestoring: boolean\n) => defaultedOptions?.suspense && willFetch(result, isRestoring)\n\nexport const willFetch = (\n  result: QueryObserverResult<any, any>,\n  isRestoring: boolean\n) => result.isPending && !isRestoring\n\nexport const getHasError = <\n  TData,\n  TError,\n  TQueryFnData,\n  TQueryData,\n  TQueryKey extends QueryKey,\n>({\n  result,\n  throwOnError,\n  query,\n}: {\n  result: QueryObserverResult<TData, TError>\n  throwOnError:\n    | ThrowOnError<TQueryFnData, TError, TQueryData, TQueryKey>\n    | undefined\n  query: Query<TQueryFnData, TError, TQueryData, TQueryKey>\n}) => {\n  return (\n    result.isError &&\n    !result.isFetching &&\n    shouldThrowError(throwOnError, [result.error, query])\n  )\n}\n\nexport function shouldThrowError<T extends (...args: any[]) => boolean>(\n  throwOnError: boolean | T | undefined,\n  params: Parameters<T>\n): boolean {\n  // Allow useErrorBoundary function to override throwing behavior on a per-error basis\n  if (typeof throwOnError === 'function') {\n    return throwOnError(...params)\n  }\n\n  return !!throwOnError\n}\n\nexport const defaultThrowOnError = <\n  TQueryFnData = unknown,\n  TError = DefaultError,\n  TData = TQueryFnData,\n  TQueryKey extends QueryKey = QueryKey,\n>(\n  _error: TError,\n  query: Query<TQueryFnData, TError, TData, TQueryKey>\n) => typeof query.state.data === 'undefined'\n\nexport const ensureStaleTime = (\n  defaultedOptions: DefaultedQueryObserverOptions<any, any, any, any, any>\n) => {\n  if (defaultedOptions.suspense) {\n    if (typeof defaultedOptions.staleTime !== 'number') {\n      return {\n        ...defaultedOptions,\n        staleTime: 1000,\n      }\n    }\n  }\n\n  return defaultedOptions\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"target\": \"esnext\",\n    \"downlevelIteration\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"es2015\",\n    \"moduleResolution\": \"bundler\",\n    \"allowJs\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noUncheckedIndexedAccess\": true,\n    \"exactOptionalPropertyTypes\": true,\n    \"jsx\": \"react\",\n    \"baseUrl\": \".\",\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"jotai-tanstack-query\": [\"./src\"]\n    },\n    \"types\": [\"vitest/globals\", \"jsdom\"]\n  },\n  \"include\": [\"__tests__\", \"src\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "vitest.config.ts",
    "content": "/// <reference types=\"vitest\" />\nimport { defineConfig } from 'vitest/config'\n\nexport default defineConfig({\n  test: {\n    environment: 'jsdom',\n    globals: true,\n    setupFiles: [],\n    projects: ['vitest.config.ts'],\n    include: ['__tests__/**/*.spec.tsx'],\n    testTimeout: 10000, // Increase timeout for tests with fake timers\n  },\n  resolve: {\n    alias: {\n      'jotai-tanstack-query': new URL('./src/index.ts', import.meta.url)\n        .pathname,\n    },\n  },\n})\n"
  }
]