RpcElixir.Router (elixir_ts_rpc v0.0.1)

Copy Markdown View Source

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]
end

Each 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"
end

expose 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", ...
end

Wire 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, and error are internal IR maps produced by RpcElixir.Types.FromSpec.

  • __manifest__/0 — same list with the :middleware key 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 @spec in 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

expose(handler_ast, opts \\ [])

(macro)

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", ...
end

Use 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.

procedure(name, capture_ast, opts \\ [])

(macro)

scope(prefix_or_opts, list)

(macro)

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]
end

The prefix is optional — scope middleware: [RequireUser] do ... end shares middleware without renaming. Procedures outside any scope are unaffected.

scope(prefix, opts, list)

(macro)

source_files(router)

@spec source_files(module()) :: [String.t()]

Returns the absolute paths of the router's own source file and every handler module's source file, deduplicated.