Install @developerehsan/api-client-query and the TanStack adapter for your
framework, then build an integration from your client + a descriptor map (the
generated generatedModules, or a hand-written one).
pnpm add @developerehsan/api-client-query @tanstack/react-query # or vue-/solid-query// query.ts
import { createQueryIntegration } from '@developerehsan/api-client-query/react' // or /vue, /solid
import { generatedModules } from './generated/api.modules'
import { api } from './api'
export const q = createQueryIntegration(api, { modules: generatedModules })Per module you get queryOptions, mutationOptions, and (for paginated GETs)
infiniteQueryOptions factories that plug straight into the hooks.
import { useQuery, useMutation, useInfiniteQuery, useQueryClient } from '@tanstack/react-query'
import { q } from './query'
function UsersPage() {
const queryClient = useQueryClient()
const { data, isLoading } = useQuery(q.users.queryOptions.list({ page: 1 }))
const create = useMutation(
q.users.mutationOptions.create({
onSuccess: () => q.users.invalidateQueries(queryClient),
}),
)
const infinite = useInfiniteQuery(q.users.infiniteQueryOptions.list({ limit: 20 }))
return null
}See it live: the React example drives an infinite product list + a create
mutation (which invalidates and refetches) through the same typed client —
examples/react-vite/src/features/TanstackDemo.tsx,
integration in query.ts.
import { createQueryIntegration } from '@developerehsan/api-client-query/vue'<script setup lang="ts">
import { useQuery, useMutation, useQueryClient } from '@tanstack/vue-query'
import { q } from './query'
const queryClient = useQueryClient()
const { data, isLoading } = useQuery(q.users.queryOptions.list({ page: 1 }))
const create = useMutation(q.users.mutationOptions.create({
onSuccess: () => q.users.invalidateQueries(queryClient),
}))
</script>With Vue Query you typically pass reactive params inside a
computed:computed(() => q.users.queryOptions.list({ page: page.value })).
import { createQueryIntegration } from '@developerehsan/api-client-query/solid'import { useQuery, useMutation } from '@tanstack/solid-query'
import { q } from './query'
function Users() {
const query = useQuery(() => q.users.queryOptions.list({ page: 1 }))
const create = useMutation(() => q.users.mutationOptions.create())
return null
}Solid Query expects a function returning the options object, so wrap the factory call in an arrow.
- Keys are stable and hierarchical:
['developerEhsan', module, method, params]. - Passing
null/undefinedparams to a query that needs them setsenabled: falseautomatically (dependent queries). - The
AbortSignalfrom TanStack is forwarded into the pipeline, so unmounting cancels the request.
Any GET the codegen marks paginated gets an infiniteQueryOptions.<method>
factory. Configure how pages advance with pageParamName + getNextPageParam —
e.g. for an offset/skip API like DummyJSON:
export const q = createQueryIntegration(api, {
modules: generatedModules,
pageParamName: 'skip',
getNextPageParam: (last: { skip: number; limit: number; total: number }) => {
const next = last.skip + last.limit
return next < last.total ? next : undefined
},
})This exact config is used in query.ts.
Point the integration at the browser RPC client plus the paths-stripped
rpcModules descriptor (api.rpc.ts) to get the same hooks client-side without
leaking backend paths:
import { createQueryIntegration } from '@developerehsan/api-client-query/react'
import { api } from './rpc-client'
import { rpcModules } from './types/generated/api.rpc' // no backend paths
export const q = createQueryIntegration(api, { modules: rpcModules })See SSR RPC bridge and the
examples/nextjs app.