For the first ten releases, codegen wrote __pyrpc.d.ts into your client folder. A declaration file. In v0.12.0 that file became __pyrpc.ts, a real runtime module. The rename is one character, but it is the entire point of the release. This post explains why a declaration file could never carry what v0.12.0 needs.
The one thing a .d.ts cannot do
TypeScript has two erasable things: type aliases and interfaces. A .d.ts file is pure description, the compiler reads it, the runtime never sees it. That is fine when all you ship is shapes. It is useless when the file is supposed to hand the running program a value.
The framework adapters need to know, at runtime, whether a procedure is a query or a mutation so they can expose useQuery or useMutation. That knowledge is a value. A const procedureKinds = {...} must exist in the JS bundle. It cannot live in a declaration file.
// declaration-only (impossible for values): export const procedureKinds = ??? // .d.ts cannot hold a value
Two channels, one file
The pivot means the generated file now carries two things that used to be split across layers:
- The
Typesinterface, the compile-time channel the compiler uses for autocomplete and type errors. - The
procedureKindsconst, the runtime channel the adapters read in their Proxy handlers.
A .ts file is the only artifact that can be both: the compiler consumes it as types, and the bundler consumes it as code. That is why the extension matters.
The rename ripples outward
Changing the emitted filename is a breaking change, and the ripples were intentional:
- The tsconfig
pathsalias now points at"./__pyrpc.ts"instead of"./__pyrpc.d.ts". - Bundlers that ignore tsconfig paths for node_modules imports now need an explicit alias to the same file.
- The generated file's header comment documents the new resolution contract, because the file has to actually compile and run now.
Every one of those ripples is downstream of a single insight: if adapters must branch on kinds at runtime, the codegen output must be runnable.
What did not change
The consumer-facing shape stayed identical. You still write import type { Types } from "@pyrpc/types" and pass it to createReactClient<Types>. The alias indirection means your imports never reference __pyrpc.ts by path, they reference @pyrpc/types, which resolves to the generated file. The runtime module is a replacement artifact behind a stable import surface.
The takeaway
v0.12.0 is not a cosmetic refactor. It is the moment the generated artifact stopped being documentation for the compiler and became a first-class citizen of your bundle. From this release on, codegen output has a runtime responsibility, and everything downstream (bundler aliases, the throwing placeholder, externalized type packages) exists because of that single requirement.

pyRPC