{"_id":"@barandurakk/query-api-router","_rev":"3-4683d05a5426c9ecbd64a4efd3c8a8f1","name":"@barandurakk/query-api-router","dist-tags":{"latest":"0.3.0"},"versions":{"0.1.0":{"name":"@barandurakk/query-api-router","version":"0.1.0","keywords":["tanstack-query","react-query","typescript","query-keys","data-fetching"],"author":{"url":"https://github.com/barandurakk","name":"Baran Durak"},"license":"MIT","_id":"@barandurakk/query-api-router@0.1.0","maintainers":[{"name":"barandurakk","email":"barandurak07@gmail.com"}],"homepage":"https://github.com/barandurakk/query-api-router#readme","bugs":{"url":"https://github.com/barandurakk/query-api-router/issues"},"dist":{"shasum":"2876a0df92d070c6fb49096576c02eaf5482b157","tarball":"https://registry.npmjs.org/@barandurakk/query-api-router/-/query-api-router-0.1.0.tgz","fileCount":23,"integrity":"sha512-Dum7SupqZpg0H0BGBPyKTxkZm/mPwvyODGGKct1Pz8eQ74SdqoCpSHNuWRQRPkDcIYYPpEAdJtoPqVHvtFtQTQ==","signatures":[{"sig":"MEUCIFQdrJrrmAO6qGzhjgztPK8MIMataZrMDcjx1QOhbkQEAiEAwSho7ijYZYWBfZ/BGm7sQdKUiAg8C9ZZpM1oRwde/jU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":76838},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"},"./package.json":"./package.json"},"gitHead":"bff26d7b20c1f278095c6ec97f9724993afa4a67","scripts":{"test":"pnpm run build && node --test tests/*.test.mjs","build":"tsc -p tsconfig.build.json","verify":"pnpm run type-check && pnpm test","prepack":"pnpm run verify","type-check":"tsc -p tsconfig.json --noEmit && tsc -p tsconfig.type-tests.json --noEmit","test:consumer":"node scripts/test-packed-consumer.mjs"},"_npmUser":{"name":"barandurakk","email":"barandurak07@gmail.com"},"repository":{"url":"git+https://github.com/barandurakk/query-api-router.git","type":"git"},"_npmVersion":"11.18.0","description":"Type-safe endpoint router helpers for TanStack Query.","directories":{},"sideEffects":false,"_nodeVersion":"20.19.5","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.18.2","devDependencies":{"react":"^19.2.3","typescript":"^5.9.3","@types/react":"^19.2.8","react-test-renderer":"^19.2.7","@tanstack/react-query":"^5.90.18","@types/react-test-renderer":"^19.1.0"},"peerDependencies":{"react":">=18.0.0 || >=19.0.0","@tanstack/react-query":"^5.90.18"},"_npmOperationalInternal":{"tmp":"tmp/query-api-router_0.1.0_1783968085554_0.15891659923679824","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@barandurakk/query-api-router","version":"0.2.0","keywords":["tanstack-query","react-query","typescript","query-keys","data-fetching"],"author":{"url":"https://github.com/barandurakk","name":"Baran Durak"},"license":"MIT","_id":"@barandurakk/query-api-router@0.2.0","maintainers":[{"name":"barandurakk","email":"barandurak07@gmail.com"}],"homepage":"https://github.com/barandurakk/query-api-router#readme","bugs":{"url":"https://github.com/barandurakk/query-api-router/issues"},"dist":{"shasum":"80631ee8419dd9364a68c9b7c4adebe4cb468c4c","tarball":"https://registry.npmjs.org/@barandurakk/query-api-router/-/query-api-router-0.2.0.tgz","fileCount":23,"integrity":"sha512-qQRyXjbOlPvrRpnmGs1j180CUH648Lqm8sePo8jcz7Oc0ka913No13Sm9S0uGn2XBj80ukja4TZNKE3GY+B3Ow==","signatures":[{"sig":"MEUCIQCm3BAA/X5GG5qU8WtfXzuFLHVVME4QTLy0dfhaAh/6DQIgO5eNMGW0+23UgU9SOdQdNwMhpyOYmx39PwXyiMvieAo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":84515},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"},"./package.json":"./package.json"},"gitHead":"70e94e8d68c0531e4584728ef026fae98fc622a0","scripts":{"test":"pnpm run build && node --test tests/*.test.mjs","build":"tsc -p tsconfig.build.json","verify":"pnpm run type-check && pnpm test","prepack":"pnpm run verify","type-check":"tsc -p tsconfig.json --noEmit && tsc -p tsconfig.type-tests.json --noEmit","test:consumer":"node scripts/test-packed-consumer.mjs"},"_npmUser":{"name":"barandurakk","email":"barandurak07@gmail.com"},"repository":{"url":"git+https://github.com/barandurakk/query-api-router.git","type":"git"},"_npmVersion":"10.8.2","description":"Type-safe endpoint router helpers for TanStack Query.","directories":{},"sideEffects":false,"_nodeVersion":"20.19.5","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.18.2","devDependencies":{"react":"^19.2.3","typescript":"^5.9.3","@types/react":"^19.2.8","react-test-renderer":"^19.2.7","@tanstack/react-query":"^5.90.18","@types/react-test-renderer":"^19.1.0"},"peerDependencies":{"react":">=18.0.0 || >=19.0.0","@tanstack/react-query":"^5.90.18"},"_npmOperationalInternal":{"tmp":"tmp/query-api-router_0.2.0_1784076238316_0.24185362503140784","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@barandurakk/query-api-router","version":"0.3.0","description":"Type-safe endpoint router helpers for TanStack Query.","keywords":["tanstack-query","react-query","typescript","query-keys","data-fetching"],"homepage":"https://github.com/barandurakk/query-api-router#readme","bugs":{"url":"https://github.com/barandurakk/query-api-router/issues"},"repository":{"type":"git","url":"git+https://github.com/barandurakk/query-api-router.git"},"license":"MIT","author":{"name":"Baran Durak","url":"https://github.com/barandurakk"},"type":"module","sideEffects":false,"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"},"./package.json":"./package.json"},"main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","packageManager":"pnpm@10.18.2","scripts":{"build":"tsc -p tsconfig.build.json","test":"pnpm run build && node --test tests/*.test.mjs","test:consumer":"node scripts/test-packed-consumer.mjs","type-check":"tsc -p tsconfig.json --noEmit && tsc -p tsconfig.type-tests.json --noEmit","verify":"pnpm run type-check && pnpm test","prepack":"pnpm run verify"},"peerDependencies":{"@tanstack/react-query":"^5.90.18","react":">=18.0.0 || >=19.0.0"},"devDependencies":{"@tanstack/react-query":"^5.90.18","@types/react":"^19.2.8","@types/react-test-renderer":"^19.1.0","react":"^19.2.3","react-test-renderer":"^19.2.7","typescript":"^5.9.3"},"publishConfig":{"access":"public"},"_id":"@barandurakk/query-api-router@0.3.0","gitHead":"ad0a27c4ea9b9840c40cb4caf7c95dfb12e96a65","_nodeVersion":"20.19.5","_npmVersion":"10.8.2","dist":{"integrity":"sha512-m+Un9YlFLVSGxy6ctSyG/UWYSkogMIB9MuaSGkFqxB/qI6UcrYxGulpCSKeqRBqOa1PXbTpENozTAvoUy6paQA==","shasum":"2051f69cd3edf18c206d2f17e430f7e48f768947","tarball":"https://registry.npmjs.org/@barandurakk/query-api-router/-/query-api-router-0.3.0.tgz","fileCount":23,"unpackedSize":87332,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCvYdJZM7ol1p5zhbAh8olioczPbG/WZwfXlkTjfTkoBgIgK3/VNi4f3WYwYWC6U0yHNwZzfH0Tyw+tuLQ3675Eo7U="}]},"_npmUser":{"name":"barandurakk","email":"barandurak07@gmail.com"},"directories":{},"maintainers":[{"name":"barandurakk","email":"barandurak07@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/query-api-router_0.3.0_1784124555415_0.06732991771887509"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-13T18:41:25.404Z","modified":"2026-07-15T14:09:15.637Z","0.1.0":"2026-07-13T18:41:25.691Z","0.2.0":"2026-07-15T00:43:58.451Z","0.3.0":"2026-07-15T14:09:15.549Z"},"bugs":{"url":"https://github.com/barandurakk/query-api-router/issues"},"author":{"name":"Baran Durak","url":"https://github.com/barandurakk"},"license":"MIT","homepage":"https://github.com/barandurakk/query-api-router#readme","keywords":["tanstack-query","react-query","typescript","query-keys","data-fetching"],"repository":{"type":"git","url":"git+https://github.com/barandurakk/query-api-router.git"},"description":"Type-safe endpoint router helpers for TanStack Query.","maintainers":[{"name":"barandurakk","email":"barandurak07@gmail.com"}],"readme":"# Query API Router\n\nType-safe endpoint definitions on top of TanStack Query for clean, reusable, low-boilerplate data hooks.\n\nBuild API “routers” once, then use consistent `useQuery`, `useMutation`, `useSuspenseQuery`, `useInfiniteQuery`, `getKey`, `getPrefixKey`, and `getOptions` everywhere.\n\n## Why This Exists\n\nTanStack Query is powerful, but large apps often drift into:\n\n- Repeated query key strings\n- Repeated `queryFn` wiring\n- Inconsistent invalidation logic\n- Loader/prefetch code that duplicates component query logic\n- Hard-to-find endpoint behavior and defaults\n\n`Query API Router` solves this by making endpoint definitions the single source of truth.\n\nYou define each endpoint once, and get:\n\n- Reusable hooks\n- Centralized keys\n- Reusable query options for loaders/prefetch/fetchQuery\n- Built-in mutation-driven invalidation\n- Strong TypeScript inference\n\n## Key Features\n\n- Endpoint router pattern: organize by domain (`users`, `orders`, `attachments`, etc.)\n- Typed query/mutation/infinite-query definitions\n- Generated hook API per endpoint\n- `getKey()` for stable key access and manual invalidation\n- `getPrefixKey()` for invalidating every state variant of a query endpoint\n- `getOptions()` for `ensureQueryData`, `fetchQuery`, `useQueries`, and non-React usage\n- TanStack `QueryFunctionContext` access for cancellation, metadata, keys, and the active client\n- Inferred, router-level mutation invalidation with support for keys from any router\n- Layered defaults:\n  - API-level defaults\n  - Endpoint-level defaults\n  - Call-site overrides\n- Transport-agnostic (Axios, fetch, GraphQL client, anything async)\n\n## Installation\n\n```bash\npnpm add @barandurakk/query-api-router @tanstack/react-query react\n```\n\n## Compatibility\n\n- TanStack React Query: `>=5.90.18 <6`\n- React: `>=18`\n- Package format: ESM\n\nCI verifies the library on maintained Node.js releases and tests the packed artifact against both the exact minimum peer versions and the latest compatible TanStack Query v5/React release. The compatibility matrix also runs weekly so newly published compatible peer versions are exercised even when this repository has no new commits.\n\n## Quick Start\n\n### 1) Create QueryClient\n\n```ts\nimport { QueryClient } from '@tanstack/react-query';\n\nexport const queryClient = new QueryClient({\n  defaultOptions: {\n    queries: {\n      retry: 2,\n      refetchOnWindowFocus: false,\n    },\n  },\n});\n```\n\nPass this client to `QueryClientProvider` as usual. Generated mutations use that active client for invalidation unless the router explicitly supplies a different client.\n\n### 2) Define an API router\n\n```ts\nimport api from '@barandurakk/query-api-router';\nimport { apiClient } from './http'; // axios/fetch wrapper\n\ntype ApiError = unknown;\ntype UserProfile = { id: string; email: string; language: string };\ntype Response<T> = { data: T; success: boolean; message: string };\n\nexport const usersApi = api(\n  'users',\n  {\n    getProfile: api.query<Response<UserProfile>, void, ApiError>({\n      queryFn: async () => (await apiClient.get('/users/profile')).data,\n    }),\n\n    updateLanguage: api.mutation<Response<{ language: string }>, { language: string }, ApiError>({\n      mutationFn: async (body) => (await apiClient.put('/users/profile/language', body)).data,\n    }),\n  },\n  {\n    invalidations: {\n      updateLanguage: (router) => [router.getProfile.getKey()],\n    },\n    queryOptions: {\n      staleTime: 60_000,\n    },\n  }\n);\n```\n\n### 3) Use in components\n\n```tsx\nfunction ProfileSection() {\n  const { data, isLoading } = usersApi.getProfile.useQuery();\n  const { mutateAsync: updateLanguage, isPending } = usersApi.updateLanguage.useMutation();\n\n  if (isLoading) return <div>Loading...</div>;\n\n  return (\n    <button\n      disabled={isPending}\n      onClick={() => updateLanguage({ language: 'ru' })}\n    >\n      Current language: {data?.data.language}\n    </button>\n  );\n}\n```\n\n## Core Concepts\n\n### 1) API Router\n\n`api(baseKey, endpoints, options?)`\n\n- `baseKey` becomes the root query key namespace (example: `'orders'`)\n- `endpoints` is an object of `query`, `mutation`, `infiniteQuery`\n- `options` can provide:\n  - inferred mutation `invalidations`\n  - `queryClient` (optional explicit invalidation-client override)\n  - shared `queryOptions`\n  - shared `mutationOptions`\n\nMutation invalidation uses the `QueryClient` from the active mutation context by default. Supply `queryClient` only when invalidation intentionally needs to target a different client.\n\nShared defaults use TanStack Query's default-option contracts, so misspelled or unsupported option names fail TypeScript checks.\n\nCanonical placement for mutation behavior:\n\n- Put the request function and reusable TanStack mutation options inside `api.mutation(...)`.\n- Put cache effects inside the outer router's `invalidations` map, keyed by mutation name.\n- Build invalidation targets with router-owned key helpers instead of repeating key arrays.\n\n### 2) Endpoint Definition Types\n\n- `api.query({ queryFn, options? })`\n- `api.mutation({ mutationFn, options? })`\n- `api.infiniteQuery({ queryFn, options })`\n\n### 3) Generated Endpoint API\n\nFor a `query` endpoint:\n\n- `useQuery(state?, options?)`\n- `useSuspenseQuery(state?, options?)`\n- `getKey(state?)`\n- `getPrefixKey()`\n- `getOptions(state?)`\n\nFor a `mutation` endpoint:\n\n- `useMutation(options?)`\n- `getKey()`\n\nFor an `infiniteQuery` endpoint:\n\n- `useInfiniteQuery(state?, options?)`\n- `getKey(state?)`\n- `getPrefixKey()`\n- `getOptions(state?)`\n\n### 4) Automatic Invalidation\n\nDeclare invalidation next to the router, after all endpoints are known. The callback receives the fully inferred current router, so endpoint names and state arguments remain type-safe without a handwritten interface.\n\n```ts\nimport api from '@barandurakk/query-api-router';\n\n// This router may live in another module.\nexport const customersApi = api('customers', {\n  getById: api.query<Response<Customer>, { customerId: string }, ApiError>({\n    queryFn: async ({ customerId }) =>\n      (await apiClient.get(`/customers/${customerId}`)).data,\n  }),\n});\n\nexport const ordersApi = api(\n  'orders',\n  {\n    list: api.query<Response<Order[]>, { page: number; status?: string }, ApiError>({\n      queryFn: async (filters) =>\n        (await apiClient.get('/orders', { params: filters })).data,\n    }),\n    myOrders: api.query<Response<Order[]>, void, ApiError>({\n      queryFn: async () => (await apiClient.get('/orders/mine')).data,\n    }),\n    getById: api.query<Response<Order>, { orderId: string }, ApiError>({\n      queryFn: async ({ orderId }) =>\n        (await apiClient.get(`/orders/${orderId}`)).data,\n    }),\n    createOrder: api.mutation<Response<Order>, { body: CreateOrder }, ApiError>({\n      mutationFn: async ({ body }) =>\n        (await apiClient.post('/orders', body)).data,\n    }),\n  },\n  {\n    invalidations: {\n      createOrder: (orders, _variables, result, _onMutateResult, _context) => [\n        // Keys owned by the current router:\n        // Every filtered or paginated list variant:\n        orders.list.getPrefixKey(),\n        orders.myOrders.getKey(),\n        // One state-specific query variant:\n        orders.getById.getKey({ orderId: result.data.id }),\n\n        // Keys owned by another router work too:\n        customersApi.getById.getKey({\n          customerId: result.data.customerId,\n        }),\n      ],\n    },\n  },\n);\n```\n\nEach invalidation callback receives:\n\n1. The fully inferred current router\n2. Mutation variables\n3. Mutation result data\n4. The value returned by `onMutate`\n5. TanStack Query's `MutationFunctionContext`\n\nReturn either raw `QueryKey` values or complete `InvalidateQueryFilters` objects. Every target is invalidated on the mutation's active `QueryClient`, and all invalidations finish before API-default, endpoint, and call-site success callbacks run.\n\nCross-router keys therefore work when the routers share a cache. Referencing another router does not switch to that router's configured client; use an explicit client only when intentionally targeting a different cache.\n\nThe older endpoint-local `invalidateQueries` callback remains supported for compatibility. Prefer the router-level `invalidations` map for new code because it provides complete inference without a separate router interface. If both forms are supplied for one mutation, the router-level callback takes precedence.\n\n### 5) Option Layering (important)\n\nFinal options are merged in this order:\n\n1. API defaults\n2. Endpoint options\n3. Call-site options\n\nThis gives global consistency with local flexibility.\n\nCall sites may intentionally override a generated query key:\n\n```ts\nconst query = usersApi.getProfile.useQuery(undefined, {\n  queryKey: ['users', 'profile', 'embedded-view'],\n});\n```\n\n`getKey()` still returns the canonical endpoint key. When a call site uses a different key, that call site owns invalidation of the alternate key.\n\n## Real Usage Patterns\n\n### Pattern: Router Loaders / Prefetch\n\nUse the exact same endpoint definition in route loaders:\n\n```ts\nawait queryClient.ensureQueryData(\n  ordersApi.getById.getOptions({ orderId })\n);\n```\n\nNo duplicate keys. No duplicate fetch logic.\n\n### Pattern: Suspense\n\n```tsx\nconst { data } = ordersApi.getById.useSuspenseQuery({ orderId });\n```\n\n### Pattern: Query cancellation and context\n\nStandard query functions receive state first and TanStack's `QueryFunctionContext` second:\n\n```ts\ndownload: api.query<Blob, { documentId: string }, ApiError>({\n  queryFn: async ({ documentId }, { signal }) => {\n    const response = await fetch(`/documents/${documentId}`, { signal });\n    return response.blob();\n  },\n});\n```\n\nExisting query functions that only need state may omit the second parameter.\n\n### Pattern: `useQueries` composition\n\n```ts\nconst results = useQueries({\n  queries: [\n    ordersApi.myOrders.getOptions({ page: 1, limit: 100 }),\n    ticketApi.myTickets.getOptions({ page: 1, limit: 100 }),\n    usersApi.getProfile.getOptions(),\n  ],\n});\n```\n\n### Pattern: Dynamic endpoint selection\n\nWhen context decides which endpoint to call, compose with `getOptions()`:\n\n```ts\nconst options =\n  context === 'invoice'\n    ? invoicesApi.getDownloadUrl.getOptions({ invoiceId: id })\n    : attachmentsApi.getDownloadUrl.getOptions({ attachmentId: id });\n\nconst result = await queryClient.fetchQuery({ ...options, staleTime: 0 });\n```\n\n### Pattern: Infinite query endpoint definition\n\n```ts\nconst addressesApi = api(\n  'addresses',\n  {\n    infiniteList: api.infiniteQuery<Paginated<Address[]>, void, number, ApiError>({\n      queryFn: async (_state, { pageParam }) =>\n        (await apiClient.get(`/account/addresses?page=${pageParam}&limit=10`)).data,\n      options: {\n        initialPageParam: 1,\n        getNextPageParam: (lastPage) =>\n          lastPage.pagination.totalPages > lastPage.pagination.page\n            ? lastPage.pagination.page + 1\n            : undefined,\n      },\n    }),\n  },\n);\n```\n\nUsage:\n\n```tsx\nconst listQuery = addressesApi.infiniteList.useInfiniteQuery();\n```\n\nRequired pagination behavior lives with the endpoint so `getOptions()` is also complete. Components can still provide partial overrides as the second argument.\n\n### Pattern: Manual invalidation when needed\n\nYou still have full `QueryClient` control. Choose the narrowest router-owned key for the cache scope you want:\n\n```ts\n// One canonical state-specific query variant: ['orders', 'getById', { orderId }]\nawait queryClient.invalidateQueries({\n  queryKey: ordersApi.getById.getKey({ orderId }),\n  exact: true,\n});\n\n// Every state variant of one query endpoint: ['orders', 'list']\nawait queryClient.invalidateQueries({\n  queryKey: ordersApi.list.getPrefixKey(),\n});\n\n// Every query endpoint in the router: ['orders']\nawait queryClient.invalidateQueries({\n  queryKey: ordersApi.getKey(),\n});\n```\n\n`getKey(state)` remains state-safe: endpoints with required state still require it. `getPrefixKey()` takes no state and is available only on query and infinite-query endpoints. Mutation endpoints keep their existing mutation `getKey()` helper.\n\n## API Reference\n\n### `api(baseKey, endpoints, options?)`\n\n- `baseKey: string`\n- `endpoints: Record<string, EndpointDefinition>`\n- `options?: { invalidations?: ApiInvalidations<TBaseKey, TEndpoints>; queryClient?: QueryClient; queryOptions?: ApiQueryOptions; mutationOptions?: ApiMutationOptions }`\n\n`invalidations` is keyed by mutation endpoint name. Each callback is inferred from the complete router and the selected mutation definition:\n\n```ts\n{\n  invalidations: {\n    mutationName: (router, variables, data, onMutateResult, context) => [\n      router.someQuery.getKey(),\n    ],\n  },\n}\n```\n\nQuery endpoint names are rejected as `invalidations` entries. Callback targets may also use imported routers or full TanStack `InvalidateQueryFilters` objects.\n\nReturns an API instance with:\n\n- `getKey(): QueryKey` for base namespace\n- query and infinite-query endpoints with `getKey(state?)`, `getPrefixKey()`, and their generated option/hook helpers\n- mutation endpoints with their existing `getKey()` and `useMutation()` helpers\n\n### `api.query<TQueryFnData, TState, TError, TData = TQueryFnData>(definition)`\n\n`definition`:\n\n- `queryFn: (state: TState, context: QueryFunctionContext) => Promise<TQueryFnData>`\n- `options?: UseQueryOptions without queryKey/queryFn`\n\n### `api.mutation<TData, TVariables, TError, TApi = InvalidationApi, TOnMutateResult = unknown>(definition)`\n\n`definition`:\n\n- `mutationFn: (variables: TVariables) => Promise<TData>`\n- `options?: UseMutationOptions without mutationFn`\n- `invalidateQueries?`: legacy endpoint-local invalidation callback\n\nFor new code, put invalidation in the outer router's `invalidations` map and normally specify only `TData`, `TVariables`, and `TError`. The `TApi` parameter remains available for backward-compatible endpoint-local callbacks, where its contract must still be supplied explicitly.\n\n### `api.infiniteQuery<TQueryFnData, TState, TPageParam, TError, TData = InfiniteData<TQueryFnData, TPageParam>>(definition)`\n\n`definition`:\n\n- `queryFn: (state: TState, ctx: QueryFunctionContext) => Promise<TQueryFnData>`\n- `options: UseInfiniteQueryOptions without queryKey/queryFn`\n\n`options` must include `initialPageParam` and `getNextPageParam`. This guarantees that both the generated hook and `getOptions()` are valid TanStack infinite-query configurations.\n\n## Why Teams Like It\n\n- Less boilerplate than raw hooks per endpoint\n- Better consistency across large codebases\n- Easy discoverability: “all order endpoints are in `ordersApi`”\n- Shared patterns for components, loaders, and utilities\n- Safer refactors with central key ownership\n- Easy onboarding for new team members\n\n## Before vs After\n\nRaw React Query style:\n\n```ts\nuseQuery({\n  queryKey: ['orders', 'getById', { orderId }],\n  queryFn: () => apiClient.get(`/orders/${orderId}`).then((r) => r.data),\n});\n```\n\nWith Query API Router:\n\n```ts\nordersApi.getById.useQuery({ orderId });\n```\n\nAnd for loaders:\n\n```ts\nawait queryClient.ensureQueryData(ordersApi.getById.getOptions({ orderId }));\n```\n\nOne endpoint definition, reused everywhere.\n\n## Best Practices\n\n- Keep one router per domain (`usersApi`, `ordersApi`, `ticketApi`)\n- Use descriptive endpoint names (`getById`, `list`, `update`, `activate`)\n- Keep `state` objects serializable and stable\n- Put cache policy (`staleTime`, `gcTime`, polling) at endpoint level\n- Prefer the router-level `invalidations` map over scattered component invalidation\n- Use `getOptions()` for non-hook contexts (loaders, utility flows, background tasks)\n- Use API-level defaults for cross-cutting behavior (retry/error handling)\n\n## FAQ\n\n### Does this replace TanStack Query?\n\nNo. It is a structured layer on top of TanStack Query.\n\n### Can I still use QueryClient directly?\n\nYes. Fully compatible. `getKey()`, `getPrefixKey()`, and `getOptions()` are designed for that.\n\n### Can I use it without Axios?\n\nYes. Axios is not a dependency or peer dependency. Any async client works, and applications define their own error types.\n\n### Does it support Suspense and Infinite Query?\n\nYes. `useSuspenseQuery` and `useInfiniteQuery` are first-class.\n\n### Is this good for large apps?\n\nYes, that is the primary use case.\n\n## Package and License\n\nThe package publishes ESM JavaScript, TypeScript declarations, declaration maps, runtime source maps, and their TypeScript sources. Runtime maps embed their source content, while declaration maps resolve to the shipped `src/` tree. This keeps debugging and editor navigation working from the installed package; the export map still prevents unsupported source-level imports.\n\nQuery API Router is available under the [MIT License](./LICENSE).\n","readmeFilename":"README.md"}