{"_id":"@asouei/safe-fetch-react-query","name":"@asouei/safe-fetch-react-query","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@asouei/safe-fetch-react-query","version":"0.1.0","description":"React Query adapter for @asouei/safe-fetch","type":"module","main":"./dist/index.cjs","module":"./dist/index.mjs","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.cjs"}},"sideEffects":false,"scripts":{"build":"vite build","dev":"vite build --watch","test":"vitest run","test:watch":"vitest","clean":"rimraf dist","prepublishOnly":"npm run clean && npm run build && npm test","lint":"echo lint:ok"},"keywords":["react-query","tanstack","safe-fetch","http","typesafe","fetch","adapter","no-throw"],"author":{"name":"Aleksandr Mikhailishin"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/asouei/safe-fetch.git","directory":"packages/react-query"},"bugs":{"url":"https://github.com/asouei/safe-fetch/issues"},"homepage":"https://asouei.dev","publishConfig":{"access":"public"},"engines":{"node":">=18"},"peerDependencies":{"@tanstack/react-query":"^5.0.0","@asouei/safe-fetch":"workspace:*"},"devDependencies":{"@types/node":"^22.0.0","typescript":"^5.9.0","vite":"^6.0.0","vite-plugin-dts":"^4.5.4","vitest":"^3.2.4","rimraf":"^6.0.1"},"_id":"@asouei/safe-fetch-react-query@0.1.0","gitHead":"13d4431a67fa5db4cc00356a37f5d37f3c10fce6","_nodeVersion":"22.17.0","_npmVersion":"11.5.2","dist":{"integrity":"sha512-PYs5M3AG7VcOFLNp0zKd9/fTtHspSXtDESKvwuI5dqEDvR3AR4bLDBzHW74j+da4O6Gp7Xqbn7iSfNcW1G6Mqw==","shasum":"8830f4c5673fe2434511f6c0897bef096384cac5","tarball":"https://registry.npmjs.org/@asouei/safe-fetch-react-query/-/safe-fetch-react-query-0.1.0.tgz","fileCount":10,"unpackedSize":29160,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCICmV5iBQFAvpFVX3bHs8s3BVkIoDDpnHnGoX88WWYFmrAiBCyfBD+DVDIXcH+gK74VrNBorPasn49lxEtGIxYhw5+A=="}]},"_npmUser":{"name":"asouei","email":"brain3run@gmail.com"},"directories":{},"maintainers":[{"name":"asouei","email":"brain3run@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/safe-fetch-react-query_0.1.0_1757060116355_0.16088824182342187"},"_hasShrinkwrap":false}},"time":{"created":"2025-09-05T08:15:16.239Z","0.1.0":"2025-09-05T08:15:16.541Z","modified":"2025-09-05T08:15:16.891Z"},"maintainers":[{"name":"asouei","email":"brain3run@gmail.com"}],"description":"React Query adapter for @asouei/safe-fetch","homepage":"https://asouei.dev","keywords":["react-query","tanstack","safe-fetch","http","typesafe","fetch","adapter","no-throw"],"repository":{"type":"git","url":"git+https://github.com/asouei/safe-fetch.git","directory":"packages/react-query"},"author":{"name":"Aleksandr Mikhailishin"},"bugs":{"url":"https://github.com/asouei/safe-fetch/issues"},"license":"MIT","readme":"# @asouei/safe-fetch-react-query\r\n\r\n[![npm version](https://img.shields.io/npm/v/@asouei/safe-fetch-react-query.svg)](https://www.npmjs.com/package/@asouei/safe-fetch-react-query)\r\n[![npm downloads](https://img.shields.io/npm/dw/@asouei/safe-fetch-react-query.svg)](https://www.npmjs.com/package/@asouei/safe-fetch-react-query)\r\n[![bundle size](https://img.shields.io/bundlephobia/minzip/@asouei/safe-fetch-react-query)](https://bundlephobia.com/package/@asouei/safe-fetch-react-query)\r\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\r\n\r\n*[English version](README.md) | Русская версия*\r\n\r\n**Экспериментальная (0.x)** - адаптер TanStack React Query для [@asouei/safe-fetch](../core)\r\n\r\n> Преобразует результаты safe-fetch в исключения и предоставляет разумные значения по умолчанию для интеграции с React Query.\r\n\r\n## Что это делает\r\n\r\nЭтот адаптер соединяет API безопасных результатов `safe-fetch` (`{ ok: true | false }`) с ожиданием React Query получать брошенные ошибки для неудачных запросов. Он предоставляет:\r\n\r\n- **Преобразование результатов**: `{ ok: false, error }` → `throw error`\r\n- **Фабричные функции**: Готовые создатели `queryFn` и `mutationFn`\r\n- **Разумные значения по умолчанию**: Рекомендует `retry: false`, чтобы safe-fetch обрабатывал повторы\r\n\r\n## Установка\r\n\r\n```bash\r\nnpm install @asouei/safe-fetch @asouei/safe-fetch-react-query @tanstack/react-query\r\n# или\r\npnpm add @asouei/safe-fetch @asouei/safe-fetch-react-query @tanstack/react-query\r\n```\r\n\r\n## Быстрый пример\r\n\r\n```typescript\r\nimport { createSafeFetch } from '@asouei/safe-fetch';\r\nimport { createQueryFn, createMutationFn, rqDefaults } from '@asouei/safe-fetch-react-query';\r\nimport { useQuery, useMutation } from '@tanstack/react-query';\r\n\r\nconst api = createSafeFetch({ \r\n  baseURL: '/api', \r\n  retries: { retries: 2 } // Пусть safe-fetch обрабатывает повторы\r\n});\r\n\r\nconst queryFn = createQueryFn(api);\r\nconst mutationFn = createMutationFn(api);\r\n\r\nexport function useUsers() {\r\n  return useQuery({\r\n    queryKey: ['users'],\r\n    queryFn: queryFn<User[]>('/users'),\r\n    ...rqDefaults(), // Важно: { retry: false }\r\n  });\r\n}\r\n\r\nexport function useCreateUser() {\r\n  return useMutation({\r\n    mutationFn: mutationFn<User>('/users', { method: 'POST' }),\r\n  });\r\n}\r\n\r\n// Использование в компоненте\r\nfunction UserList() {\r\n  const { data: users, error, isLoading } = useUsers();\r\n  const createUser = useCreateUser();\r\n\r\n  if (isLoading) return <div>Загрузка...</div>;\r\n  if (error) return <div>Ошибка: {error.name}</div>; // Типизированная ошибка от safe-fetch\r\n  \r\n  return (\r\n    <div>\r\n      {users?.map(user => <div key={user.id}>{user.name}</div>)}\r\n      <button onClick={() => createUser.mutate({ name: 'Новый пользователь' })}>\r\n        Добавить пользователя\r\n      </button>\r\n    </div>\r\n  );\r\n}\r\n```\r\n\r\n## Справочник API\r\n\r\n### `createQueryFn(api)`\r\nСоздает фабрику функций запросов для React Query.\r\n\r\n```typescript\r\nconst queryFn = createQueryFn(api);\r\nconst getUsersFn = queryFn<User[]>('/users', { \r\n  headers: { Authorization: 'Bearer token' } \r\n});\r\n\r\nuseQuery({\r\n  queryKey: ['users'],\r\n  queryFn: getUsersFn,\r\n  ...rqDefaults()\r\n});\r\n```\r\n\r\n### `createMutationFn(api)`\r\nСоздает фабрику функций мутаций. По умолчанию использует метод `POST`.\r\n\r\n```typescript\r\nconst mutationFn = createMutationFn(api);\r\nconst createUserFn = mutationFn<User>('/users'); // POST по умолчанию\r\nconst updateUserFn = mutationFn<User>('/users', { method: 'PUT' });\r\n\r\nuseMutation({\r\n  mutationFn: createUserFn // (body) => Promise<User>\r\n});\r\n```\r\n\r\n### `rqDefaults()`\r\nВозвращает рекомендуемые значения по умолчанию для React Query.\r\n\r\n```typescript\r\nrqDefaults(); // { retry: false }\r\n```\r\n\r\n**Почему `retry: false`?** Пусть safe-fetch обрабатывает повторы с правильным экспоненциальным отступом, джиттером и поддержкой `Retry-After` вместо более простой логики повторов React Query.\r\n\r\n### `unwrap(promise)`\r\nУтилита для преобразования безопасных результатов в исключения (реэкспортирована из core для удобства).\r\n\r\n```typescript\r\nconst result = await unwrap(safeFetch.get('/users'));\r\n// Бросает исключение при ошибке, возвращает данные при успехе\r\n```\r\n\r\n## Расширенное использование\r\n\r\n### Кастомный хук запроса с валидацией\r\n\r\n```typescript\r\nimport { z } from 'zod';\r\n\r\nconst UserSchema = z.array(z.object({\r\n  id: z.number(),\r\n  name: z.string(),\r\n  email: z.string().email()\r\n}));\r\n\r\nexport function useUsers() {\r\n  const queryFn = createQueryFn(api);\r\n  \r\n  return useQuery({\r\n    queryKey: ['users'],\r\n    queryFn: queryFn<z.infer<typeof UserSchema>>('/users', {\r\n      validate: (raw) => {\r\n        const result = UserSchema.safeParse(raw);\r\n        return result.success \r\n          ? { success: true, data: result.data }\r\n          : { success: false, error: result.error };\r\n      }\r\n    }),\r\n    ...rqDefaults()\r\n  });\r\n}\r\n```\r\n\r\n### Обработка ошибок с Type Guards\r\n\r\n```typescript\r\nimport type { HttpError, NetworkError } from '@asouei/safe-fetch';\r\n\r\nconst isHttpError = (error: any): error is HttpError => \r\n  error?.name === 'HttpError';\r\n\r\nconst isNetworkError = (error: any): error is NetworkError => \r\n  error?.name === 'NetworkError';\r\n\r\nfunction UserList() {\r\n  const { data, error } = useUsers();\r\n  \r\n  if (error) {\r\n    if (isHttpError(error)) {\r\n      return <div>Ошибка сервера: {error.status} {error.statusText}</div>;\r\n    }\r\n    if (isNetworkError(error)) {\r\n      return <div>Сетевая ошибка: Проверьте подключение</div>;\r\n    }\r\n    return <div>Неизвестная ошибка: {error.message}</div>;\r\n  }\r\n  \r\n  return <div>{/* отрисовка пользователей */}</div>;\r\n}\r\n```\r\n\r\n### Бесконечные запросы\r\n\r\n```typescript\r\nexport function useInfiniteUsers() {\r\n  const queryFn = createQueryFn(api);\r\n  \r\n  return useInfiniteQuery({\r\n    queryKey: ['users', 'infinite'],\r\n    queryFn: ({ pageParam = 1 }) => \r\n      queryFn<{ users: User[]; nextPage?: number }>('/users', {\r\n        query: { page: pageParam, limit: 10 }\r\n      })(),\r\n    getNextPageParam: (lastPage) => lastPage.nextPage,\r\n    ...rqDefaults()\r\n  });\r\n}\r\n```\r\n\r\n## Лучшие практики\r\n\r\n### 1. Всегда используйте `rqDefaults()`\r\n```typescript\r\n// ✅ Хорошо\r\nuseQuery({\r\n  queryKey: ['users'],\r\n  queryFn: queryFn('/users'),\r\n  ...rqDefaults()\r\n});\r\n\r\n// ❌ Избегайте - React Query будет повторять со своей логикой\r\nuseQuery({\r\n  queryKey: ['users'],\r\n  queryFn: queryFn('/users')\r\n  // отсутствует rqDefaults()\r\n});\r\n```\r\n\r\n### 2. Настраивайте повторы в safe-fetch, а не в React Query\r\n```typescript\r\n// ✅ Хорошо\r\nconst api = createSafeFetch({\r\n  retries: { \r\n    retries: 2,\r\n    baseDelayMs: 300 \r\n  }\r\n});\r\n\r\n// ❌ Избегайте - двойные повторы\r\nuseQuery({\r\n  queryFn: queryFn('/users'),\r\n  retry: 3 // Не делайте так с safe-fetch\r\n});\r\n```\r\n\r\n### 3. Правильно обрабатывайте состояния загрузки\r\n```typescript\r\nfunction UserProfile({ id }: { id: string }) {\r\n  const { data: user, isLoading, error } = useQuery({\r\n    queryKey: ['users', id],\r\n    queryFn: queryFn<User>(`/users/${id}`),\r\n    ...rqDefaults()\r\n  });\r\n\r\n  // Явно обрабатывайте все состояния\r\n  if (isLoading) return <UserSkeleton />;\r\n  if (error) return <ErrorBoundary error={error} />;\r\n  if (!user) return <NotFound />; // Не должно произойти, но будьте осторожны\r\n  \r\n  return <div>{user.name}</div>;\r\n}\r\n```\r\n\r\n## Совместимость\r\n\r\n- **React Query**: v5.x\r\n- **SSR/Next.js**: Совместимо (чистые функции, без runtime зависимости от React)\r\n- **Размер бандла**: Минимальный - только тонкие обертки\r\n\r\n## Почему такой подход?\r\n\r\nВместо предоставления кастомных хуков типа `useSafeQuery`, этот адаптер фокусируется на:\r\n\r\n1. **Минимальная поверхность API**: Только фабричные функции\r\n2. **Без peer зависимости от React**: Работает в любой настройке React Query\r\n3. **Композабельность**: Используйте с существующими паттернами React Query\r\n4. **Типобезопасность**: Сохраняет типизацию ошибок safe-fetch\r\n\r\n## Устранение неполадок\r\n\r\n### \"Query function threw an error\"\r\nЭто ожидаемо! Адаптер преобразует результаты `{ ok: false }` в брошенные ошибки, которые может обрабатывать React Query.\r\n\r\n### Ошибки типов с функциями запросов\r\nУбедитесь, что указали ожидаемый тип возврата:\r\n```typescript\r\n// ✅ Хорошо\r\nconst queryFn = createQueryFn(api);\r\nconst getUserFn = queryFn<User>('/user/123');\r\n\r\n// ❌ Проблемы с типами\r\nconst getUserFn = queryFn('/user/123'); // неизвестный тип возврата\r\n```\r\n\r\n### Повторы работают не как ожидалось\r\nНе забывайте использовать `rqDefaults()` для отключения повторов React Query:\r\n```typescript\r\nuseQuery({\r\n  queryKey: ['data'],\r\n  queryFn: queryFn('/data'),\r\n  ...rqDefaults() // Это устанавливает retry: false\r\n});\r\n```\r\n\r\n## Миграция с прямого safe-fetch\r\n\r\n**До:**\r\n```typescript\r\nfunction useUsers() {\r\n  return useQuery({\r\n    queryKey: ['users'],\r\n    queryFn: async () => {\r\n      const result = await safeFetch.get<User[]>('/users');\r\n      if (!result.ok) throw result.error;\r\n      return result.data;\r\n    },\r\n    retry: false\r\n  });\r\n}\r\n```\r\n\r\n**После:**\r\n```typescript\r\nconst queryFn = createQueryFn(api);\r\n\r\nfunction useUsers() {\r\n  return useQuery({\r\n    queryKey: ['users'],\r\n    queryFn: queryFn<User[]>('/users'),\r\n    ...rqDefaults()\r\n  });\r\n}\r\n```\r\n\r\n## Дорожная карта\r\n\r\n- **v0.1**: Основные функции адаптера ✅ **Опубликовано**\r\n- **v0.2**: Опциональные кастомные хуки (`useSafeQuery`, `useSafeMutation`)\r\n- **v1.0**: Стабильный продакшн релиз после отзывов сообщества\r\n\r\n## Лицензия\r\n\r\nMIT © [Aleksandr Mikhailishin](https://github.com/asouei)","readmeFilename":"README.ru.md","_rev":"1-885ae5dc7aba174c3703b77e10fdc871"}