defmodule Vela.Distributed.RPC do @moduledoc """ Thin wrapper around `:erpc` for remote procedure calls. Provides a consistent interface with timeout handling and error normalization for Vela's distributed topologies. """ @default_timeout 5_000 @doc """ Synchronous call to a function on a remote node. Returns `{:ok, result}` or `{:error, reason}`. """ @spec call(node(), module(), atom(), [term()], timeout()) :: {:ok, term()} | {:error, term()} def call(node, mod, fun, args, timeout \\ @default_timeout) do {:ok, :erpc.call(node, mod, fun, args, timeout)} rescue e in ErlangError -> {:error, {:rpc_failed, node, e}} catch :exit, reason -> {:error, {:rpc_exit, node, reason}} end @doc """ Asynchronous cast to a function on a remote node. Fire-and-forget — does not wait for a result. """ @spec cast(node(), module(), atom(), [term()]) :: :ok def cast(node, mod, fun, args) do :erpc.cast(node, mod, fun, args) :ok end @doc """ Calls a function on multiple nodes in parallel. Returns a map of `%{node => {:ok, result} | {:error, reason}}`. """ @spec multi_call([node()], module(), atom(), [term()], timeout()) :: %{node() => {:ok, term()} | {:error, term()}} def multi_call(nodes, mod, fun, args, timeout \\ @default_timeout) do nodes |> Enum.map(fn target -> {target, Task.async(fn -> call(target, mod, fun, args, timeout) end)} end) |> Enum.map(fn {target, task} -> {target, Task.await(task, timeout + 1_000)} end) |> Map.new() end end