Until pyrpc dev runs for the first time, there is no generated module. Yet @pyrpc/types must still exist on disk so imports resolve. The tension between "nothing is generated yet" and "imports must not crash" is solved by a placeholder, and since v0.12.0, that placeholder is a Proxy that throws.
The placeholder, complete
export type Types = Record<string, never>;
export type ProcedureKinds = Record<string, never>;
export const procedureKinds: ProcedureKinds = new Proxy(
{} as ProcedureKinds,
{
get() {
throw new Error(
"pyRPC: '@pyrpc/types' is still the placeholder, the generated " +
"__pyrpc.ts is not being resolved. Run `pyrpc dev` and make sure " +
'"@pyrpc/types" resolves to your generated "./__pyrpc.ts" ...',
);
},
},
);Three exports, three deliberate choices.
Types = Record<string, never>
Record<string, never> is an empty map: any key is allowed at the type level but has no usable value. api.get_user typechecks as existing, but its type is effectively unusable, a deliberate mid-state between "the API does not exist" and "the API exists but is wrong". It keeps the import graph typeable before codegen while refusing to pretend the procedures are real.
The Proxy and its get trap
The interesting part is the const. procedureKinds is not an empty object (it is a Proxy whose get handler unconditionally throws. Any property access) kinds.get_user, kinds["greet"] (detonates with the error message.
Why a Proxy instead of a plain object? Because a plain empty object would look innocent. The whole point is that reading kinds from a placeholder is always a bug, and the Proxy converts that bug from a silent undefined into a loud, actionable failure.
The error message is a diagnosis
The thrown error is not "procedureKinds is unavailable". It states three things: the @pyrpc/types package is still the placeholder; the generated __pyrpc.ts is not being resolved; and the fix is to run pyrpc dev and make sure the alias points at ./__pyrpc.ts. The message names the mechanism (resolution) and the two resolution routes (tsconfig paths or a bundler alias), turning a stack trace into a checklist.
When it fires
The trap fires whenever a bundler resolved @pyrpc/types to the real npm package instead of the alias target. In practice: a Vite or Turbopack project before the bundler alias was injected, or any project where the tsconfig alias never made it. That is exactly the failure mode the placeholder exists to surface.
The philosophy
A placeholder is a promise that has not been fulfilled. The v0.12.0 placeholder refuses to fake fulfillment: if you touch the kinds before codegen has run, you find out the instant the module loads, with a message that tells you how to finish the setup. That is fail-closed engineering for an otherwise silent category of bug.

pyRPC