Macro-based DSL for defining RPC procedures in a router module.
Usage
defmodule MyApp.Router do
use RpcElixir.Router
procedure "users.get", &Hello.Users.get/2
procedure "users.update", &Hello.Users.update/2, middleware: [MyApp.Middleware.RequireUser]
endEach procedure takes a wire name and a remote function capture of arity 2.
Captures give editors "go to definition" support and enforce arity at the
call site. Local captures (&local_fn/2), captures of other arities, and
non-capture values raise CompileError.
Scoping
scope groups procedures under a shared prefix and/or shared middleware so a
cross-cutting concern (e.g. authentication) is declared once for the whole
group instead of repeated on every procedure. See scope/2 and scope/3.
scope "users", middleware: [MyApp.Middleware.RequireUser] do
procedure "list", &Hello.Users.list/2 # → "users.list"
procedure "get", &Hello.Users.get/2 # → "users.get"
endexpose registers every public, @spec'd, arity-2 function of a handler module
as a procedure named after the function, composing with the enclosing scope. See
expose/2.
scope "users", middleware: [MyApp.Middleware.RequireUser] do
expose Hello.Users # → "users.list", "users.get", ...
endWire aliases
The wire_aliases option maps a source type's .t() to a RpcElixir.CustomType
module so it crosses the wire as that custom type project-wide, without per-field
annotation. For example, {DateTime, RpcElixir.UnixMillis} makes every DateTime
serialize as the branded EpochMillis number. Aliases are applied at router compile
time so codegen and runtime agree. The target must implement the
RpcElixir.CustomType behaviour, and a source cannot alias to itself.
use RpcElixir.Router, wire_aliases: [{DateTime, RpcElixir.UnixMillis}]Generated functions
__procedures__/0— returns a list of maps with keys:name,handler_mod,handler_fun,input,output,error,middleware,doc,schema_base.input,output, anderrorare internal IR maps produced byRpcElixir.Types.FromSpec.__manifest__/0— same list with the:middlewarekey removed. Intended for serialisation and codegen; middleware is server-internal.__procedures_index__/0— a%{name => procedure}map for O(1) dispatch lookup. Same procedure maps as__procedures__/0, keyed by:name.
Compile-time guarantees
Every procedure call is validated at compile time:
- The capture must be a remote function capture of arity 2.
- The handler module must be compilable via
Code.ensure_compiled!/1. - The function must carry a
@specin the RPC convention(input, ctx) :: {:ok, output} | {:error, error}. - Procedure names must be unique within a router.
Violations raise CompileError pointing at the offending procedure call site.
Summary
Functions
Registers every public, @spec'd, arity-2 function of a handler module as a
procedure, named after the function.
Groups the procedure calls in the block under a shared prefix and/or shared
middleware.
Returns the absolute paths of the router's own source file and every handler module's source file, deduplicated.
Functions
Registers every public, @spec'd, arity-2 function of a handler module as a
procedure, named after the function.
The module must use RpcElixir.Handler. The wire name is the function name,
combined with any enclosing scope prefix; enclosing scope middleware (and any
:middleware passed here) apply as with procedure. Every exposed function
must follow the RPC contract (input, ctx) :: {:ok, _} | {:error, _}; a
function that doesn't raises a CompileError, the same as a hand-written
procedure.
scope "counter", middleware: [RequireUser] do
expose Hello.Counter # → "counter.get", "counter.adjust", ...
endUse this when the module is the API surface; adding a spec'd arity-2 function
to it then publishes a procedure. Prefer explicit procedure calls when the
exposed surface should be an auditable subset of the module.
Groups the procedure calls in the block under a shared prefix and/or shared
middleware.
A scope prepends its :middleware to every procedure defined inside it (before
any procedure-specific middleware, so outer-most auth runs first) and, given a
string prefix, prepends a dotted segment to each inner procedure's wire name.
Scopes nest; prefixes concatenate and middleware accumulates outer-to-inner.
scope "users", middleware: [RequireUser] do
procedure "list", &Users.list/2 # → "users.list", middleware: [RequireUser]
procedure "get", &Users.get/2 # → "users.get", middleware: [RequireUser]
endThe prefix is optional — scope middleware: [RequireUser] do ... end shares
middleware without renaming. Procedures outside any scope are unaffected.
Returns the absolute paths of the router's own source file and every handler module's source file, deduplicated.