Advanced RPC type sharing in Base
The server interface is the client contract, shared as a type, so no codegen and no drift.
Advanced RPC type sharing in Base
The RPC posts showed you how to call one worker from another and how to call a worker from the web, with the calls feeling local because the types come along. This post is about the machinery that makes that true: how a server's RPC interface becomes a typed client with no code generation, no schema file, and no second definition of the same shape. It is the deepest expression of the idea that runs through the whole framework, that a TypeScript type can be the contract across a boundary.
The interface is the contract
On the server, an RPC service is a class with methods. That class has a TypeScript type, the set of methods it exposes and their signatures, and that type is the contract. The client does not get a copy of the contract, a generated stub, or a schema describing it. The client gets the type itself, shared as a TypeScript type, and is built against it directly.
So the same interface that describes the server describes the client. When you change a method's signature on the server, the client that is typed against that interface sees the change immediately, as a type error if the call no longer matches. There is no regeneration step to forget and no generated file to fall out of date, because nothing was generated. The type that was true on the server is the type the client uses.
How the client makes it feel local
The client is given the server's interface as a type, and it presents an object of that exact shape to call against. Under the hood it is not a real instance of your service, it is a stand-in that intercepts each method call and turns it into a remote call over the wire, but to your code and to the type checker it looks and types exactly like the service. You write what reads as a normal method call on a normal object, fully autocompleted and type-checked, and the client quietly does the network work.
That is the whole trick. The interface is real, the call site is typed against it, and the thing you call is a typed proxy that does the remote work. You get the developer experience of calling a local object and the reality of a network call, with the type system guaranteeing the two stay in agreement.
Why sharing the type beats generating one
Code generation is the usual way to get typed clients, and it works, but it adds a step and a moment of drift. You change the server, you regenerate, and in between the generated client is wrong. Anyone who forgets to regenerate ships against a stale contract. Sharing the actual TypeScript type removes that gap entirely. There is no "in between," because there is no generation: the client references the same type the server defines, so the only way for them to disagree is for the code not to compile.
This is why it lives in the advanced post. It is not a feature you configure; it is a consequence of how the framework is built, that the server and client share a language and Base lets them share the type rather than a generated description of it. Once you see it, the typed-call experience in the earlier RPC posts stops feeling like magic and starts feeling like the obvious result of not throwing the type away at the boundary.
The mental model to keep
Advanced RPC type sharing means the server's RPC interface is the client's contract, shared as a TypeScript type rather than generated. The client presents a typed proxy shaped exactly like the service, so calling it reads like a local method call and type-checks against the real interface, while it does the network work underneath. Because nothing is generated, the server and client cannot drift, the only way to disagree is to fail to compile. It is the framework's core idea at its sharpest: keep the type across the boundary instead of describing it twice.