No schema, anywhere
No GraphQL SDL, no OpenAPI document, no Zod mirror. There is a function, and the compiler already knows its shape.
Your Elixir functions callable from TypeScript. The function is the contract.
A guided tour of the playground. Edit a @spec, watch the client regenerate, follow a method back to the handler that produced it, then rename a field and watch the TypeScript break. That is the real codegen running in the browser, compiled to WebAssembly. Try it yourself โ
A procedure is an ordinary function. Its @spec is how you tell the compiler its shape today:
defmodule MyApp.Users do
use RpcElixir.Handler
@spec get(%{id: String.t()}, RpcElixir.Context.t()) ::
{:ok, %{id: String.t(), email: String.t()}} | {:error, :not_found}
def get(%{id: id}, _ctx), do: ...
endExpose the module on a router:
scope "users" do
expose MyApp.Users # โ "users.get", "users.list", โฆ
endRun mix rpc.gen.ts, then call it from the browser:
const user = await client.users.get({ id: "u_1" });
const email = user.email;