.agents/skills/frontend-query-mutation/references/contract-patterns.md
web/contract/*.useQuery(consoleQuery|marketplaceQuery.xxx.queryOptions(...)) when endpoint behavior maps 1:1 to the contract.web/contract/
├── base.ts
├── router.ts
├── marketplace.ts
└── console/
├── billing.ts
└── ...other domains
web/service/client.ts
web/contract/console/{domain}.ts or web/contract/marketplace.ts.
base.route({...}).output(type<...>()) as the baseline..input(type<...>()) only when the request has params, query, or body.GET without input, omit .input(...); do not use .input(type<unknown>()).web/contract/router.ts.
import { useQuery } from '@tanstack/react-query'
import { consoleQuery } from '@/service/client'
const invoiceQuery = useQuery(consoleQuery.billing.invoices.queryOptions({
staleTime: 5 * 60 * 1000,
throwOnError: true,
select: invoice => invoice.url,
}))
*.queryOptions(...) usage at the call site.use-* passthrough hook.web/service/use-{domain}.ts only for orchestration.
const invoicesBaseQueryOptions = () =>
consoleQuery.billing.invoices.queryOptions({ retry: false })
const invoiceQuery = useQuery({
...invoicesBaseQueryOptions(),
throwOnError: true,
})
consoleQuery or marketplaceQuery, for example useMutation(consoleQuery.billing.bindPartnerStack.mutationOptions(...)).mutationFn, for example consoleClient.xxx or marketplaceClient.xxx, instead of handwritten non-oRPC mutation logic.useQuery with options?: Partial<UseQueryOptions>.queryKey and queryFn when oRPC queryOptions already exists and fits the use case.use-* passthrough hooks for a single endpoint.throwOnError and select, and add unnecessary indirection.{ params, query?, body? }.GET: omit .input(...); do not use .input(type<unknown>()).{paramName} in the path and match it in the params object./billing/* becomes billing: {}.@/types/ and use the type<T>() helper.mutationOptions; use explicit mutationKey mainly for defaults, filtering, and devtools.export type ConsoleInputs = InferContractRouterInputs<typeof consoleRouterContract>