The runtime Proxy reads kinds to decide what exists. But TypeScript must agree before your code even compiles, and it does, through a conditional type that mirrors the runtime selection one-for-one.
The three hook shapes
The adapter defines three building blocks, each a mapped object type wrapping a TanStack Query hook:
export type ProcedureQueryHooks<TProc> = {
useQuery: <TData = ProcResult<TProc>>(
input: QueryInput<TProc>,
options?: Omit<UseQueryOptions<ProcResult<TProc>, Error, TData>, 'queryKey' | 'queryFn'>,
) => UseQueryResult<TData, Error>;
};
export type ProcedureMutationHooks<TProc> = {
useMutation: <TContext = unknown>(
options?: Omit<UseMutationOptions<ProcResult<TProc>, Error, QueryInput<TProc>, TContext>, 'mutationFn'>,
) => UseMutationResult<ProcResult<TProc>, Error, QueryInput<TProc>, TContext>;
};
export type ProcedureHooksBoth<TProc> = ProcedureQueryHooks<TProc> &
ProcedureMutationHooks<TProc>;Note how the query and mutation inputs differ. useQuery takes the input as its first argument and returns a result that may be wider than the procedure's (<TData>). useMutation takes only options and returns a result plus the imperative mutate. The two contracts are genuinely different, which is why exposing both on a mutation-only procedure is a real API leak, not a cosmetic one.
The selector
export type ProcedureHooksForKind<
TProc extends AnyProc,
TKind extends ProcedureKind | undefined,
> = TKind extends 'mutation'
? ProcedureMutationHooks<TProc>
: TKind extends 'query'
? ProcedureQueryHooks<TProc>
: ProcedureHooksBoth<TProc>;This is the type-level twin of the runtime guard. Where the Proxy asks "is the kind mutation?", this conditional type asks the same question of a type parameter. The order matters: 'mutation' first, then 'query', then the undefined fallback of both.
Wired into the client shape
export type ReactClient<TProcedures, TKinds = {}> = {
[K in keyof TProcedures]: ProcedureHooksForKind<TProcedures[K], TKinds[K]>;
} & {
Provider: ComponentType<ReactClientProviderProps>;
useUtils: () => ReactClientUtils<TProcedures>;
client: TProcedures;
};A mapped type walks every procedure key and applies ProcedureHooksForKind with that procedure's inferred kind. The intersection adds the Provider/useUtils/client members, matching exactly which prop in target checks the Proxy short-circuits at runtime.
Mirror discipline
The striking thing is how precisely the runtime and type systems parallel each other:
- Runtime:
kind !== 'mutation'→ includeuseQuery - Type:
TKind extends 'mutation'→ excludeuseQuery
Same decision, two languages. Keeping the two mirrors in step is a discipline the codegen contract makes possible: because kinds come from a single generated artifact, the runtime branch and the type branch can never disagree about which hooks a procedure supports.

pyRPC