The runtime map procedureKinds tells the running adapter which hooks to expose. But your editor needs the same information before anything runs, it has to know that update_user has no useQuery. That is the job of a type-level brand.
The brand, planted by codegen
Every generated procedure type is intersected with a branded object literal:
greet: ((name: string) => Promise<string>) & {
readonly _pyrpcKind: "query";
};The _pyrpcKind property carries the procedure's kind as a literal type. The readonly modifier stops accidental reassignment, and the whole intersection means the brand travels alongside the callable signature. This is structural branding: the kind is a real property of the type, not a side table.
Reading the brand back out
The adapter maps over TProcedures and, for each key, inspects the brand:
export type InferProcedureKinds<TProcedures extends ProceduresRecord> = {
[K in keyof TProcedures]: TProcedures[K] extends {
readonly _pyrpcKind: infer Kind;
}
? Kind extends ProcedureKind
? Kind
: undefined
: undefined;
};For each procedure key it does a conditional-type match: does the procedure type have a _pyrpcKind? If so, is the inferred Kind a valid "query" | "mutation"? The result is a map like { greet: "query"; update_user: "mutation" }, the compile-time twin of the runtime const.
The undefined branch is the safety net
The fallback branches yield undefined. Why not "query"? Because a procedure whose kind cannot be proven should not be silently assumed safe, undefined flows into ProcedureHooksForKind and resolves to "both hooks" rather than the wrong one. It mirrors the runtime philosophy: when in doubt, expose more rather than guess.
Where inference plugs in
createReactClient<TProcedures> returns ReactClient<TProcedures, InferProcedureKinds<TProcedures>>. The inferred kinds map selects, per procedure, one of three hook bundles:
"mutation"→ onlyuseMutation"query"→ onlyuseQueryundefined→ both
So api.greet.useMutation is a compile error while api.update_user.useMutation typechecks, the type system has already read the server's decorators.
Why branding beats a separate kind union
A parallel ProcedureKinds map already exists as a type. But deriving the hooks from the branded Types interface means there is exactly one source of truth: the procedure type itself. If codegen ever changes a kind, the hooks signature changes with it automatically, and no second structure can drift out of sync.

pyRPC