Guide for implementing oRPC contract-first API patterns in Dify frontend. Trigger when creating or updating contracts in web/contract, wiring router composition, integrating TanStack Query with typed contracts, migrating legacy service calls to oRPC, or deciding whether to call queryOptions directly vs extracting a helper or use-* hook in web/service.
94
89%
Does it follow best practices?
Impact
96%
2.66xAverage score across 6 eval scenarios
Passed
No known issues
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.tsweb/contract/console/{domain}.ts or web/contract/marketplace.ts
base.route({...}).output(type<...>()) as baseline..input(type<...>()) only when request has params/query/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(...).retry: false), extract a small queryOptions helper, not a 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 / marketplaceQuery, for example useMutation(consoleQuery.billing.bindPartnerStack.mutationOptions(...)).mutationFn (for example consoleClient.xxx / marketplaceClient.xxx), instead of generic handwritten non-oRPC mutation logic..key vs .queryKey vs .mutationKey).key(...):
queryClient.invalidateQueries({ queryKey: consoleQuery.billing.key() }).queryKey(...):
.mutationKey(...):
useIsMutating, queryClient.isMutating), or explicit devtools grouping.useQuery with options?: Partial<UseQueryOptions>.queryKey/queryFn when oRPC queryOptions already exists and fits the use case.use-* passthrough hooks for a single endpoint.data may become unknown, especially around throwOnError/select) and add unnecessary indirection.{ params, query?, body? } format.input(...); do not use .input(type<unknown>()){paramName} in path, match in params object/billing/* -> billing: {})@/types/, use type<T>() helpermutationOptions; use explicit mutationKey mainly for defaults/filtering/devtoolsexport type ConsoleInputs = InferContractRouterInputs<typeof consoleRouterContract>4717168
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.