Distribution: workspace or server — and why explicit is better than implicit
pyrpc introduces a single new field to pyrpc.jsonthat changes how the server thinks about type distribution: distribution.
{
"version": 1,
"framework": "fastapi",
"entrypoint": "app.main",
"distribution": "workspace",
"client_root": "../frontend"
}Two values: "workspace" and "server". That’s it. But this distinction was hiding in the architecture from the beginning, silently implied by whether client_root was present. Making it explicit was the right thing to do — and it forced us to think clearly about what pyrpc actually does.
What was wrong before?
Previously, pyrpc had an implicit model: if you set client_root inpyrpc.json, types got written to that path. If you didn’t, types weren’t written anywhere. The behavior was inferred from which fields happened to exist in the config.
This worked for the simple case (monorepo with a frontend folder), but it broke down when users asked: “How do I run pyrpc without a frontend project?” or “How does this work with separate repositories?” The answer was different for every user, because there was no explicit contract.
GPT said it best during a design review:
Heuristics that infer intent from whether a field happens to exist are not obvious, not explicit, and don’t scale. The field should be required.
So we made distribution required. If you upgrade from an older config that lacks it, pyrpc dev detects the missing field and runs the setup wizard. One question, asked once, then it’s stored forever.
Workspace mode
{
"distribution": "workspace",
"client_root": "../frontend"
}Workspace mode is the tRPC-like experience. The server writes TypeScript types directly to a client project on the same filesystem. The flow:
pyrpc devreads config, resolvesclient_root, validates it exists- Watcher reloads the router on file changes
- After each reload, regenerates types and writes to
client_root/node_modules/@pyrpc/types - The client imports types without any network request
This is the mode most users will use — monorepo or same-repo setups where the server and client share a filesystem. It’s fast, simple, and requires zero client-side configuration.
Server mode
{
"distribution": "server"
}Server mode is for separate-repository setups. The server never touches the client’s filesystem. Instead, it exposes the schema at GET /rpc (which the ASGI transport already did for introspection). The watcher reloads the router and updates the in-memory registry, but does not write types anywhere.
The client fetches types on demand via npx pyrpc sync, which reads a pyrpc-client.json file (created during npm install), fetches the schema from the server URL, and regenerates @pyrpc/types.
Why required and not inferred?
Three reasons:
- Explicit config is self-documenting. Reading
distribution: "server"tells you more about the deployment than the absence ofclient_root. The config file becomes a readable description of the architecture. - No silent defaults. If someone accidentally deletes
client_rootfrom their config, the behavior changes silently. With a requireddistributionfield, pyrpc refuses to guess. - The wizard is the migration path. Old configs without
distributiontrigger the setup wizard. The user sees the distribution question exactly once, makes a choice, and never thinks about it again. No heuristics, no surprises.
The wizard flow
pyRPC Setup Let's configure pyRPC for your project. Which web framework are you using? fastapi (default) flask asgi Python module to scan for @rpc procedures (e.g. main, app.main) app.main How are types distributed to the client? workspace (default) server (If workspace) Where is your TypeScript client project? ../frontend
The distribution question is always asked during first-time setup. If you chooseworkspace, the wizard additionally prompts for client_root. If you choose server, the client_root prompt is skipped entirely — it would be meaningless.
CLI flags
Like other config fields, --distribution is available as a flag:
pyrpc dev --distribution server pyrpc dev --distribution workspace --client-root ../frontend-v2 pyrpc dev --reconfigure # includes distribution question
The bottom line
A single required field, two possible values, one question in the wizard, and the entire type distribution model becomes explicit. No heuristics, no inference, no “it depends on whether this other field exists.” This is the kind of boring, explicit config that scales from a monorepo side project to an organization with separate frontend and backend repositories.

pyRPC