Current section
Files
Jump to
Current section
Files
lib/ex_claw/plugin/registry.ex
defmodule ExClaw.Plugin.Registry do
@moduledoc """
ETS-backed registry for ExClaw plugins.
Manages plugin lifecycle and dispatches events through the plugin chain.
"""
use GenServer
@table __MODULE__
# Client API
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
@doc "Register and initialize a plugin."
@spec register(module(), map()) :: :ok | {:error, term()}
def register(module, config \\ %{}) do
GenServer.call(__MODULE__, {:register, module, config})
end
@doc "Unregister a plugin."
@spec unregister(module()) :: :ok
def unregister(module) do
GenServer.call(__MODULE__, {:unregister, module})
end
@doc "List all registered plugins as `{module, state}` tuples."
@spec list() :: [{module(), term()}]
def list do
:ets.tab2list(@table)
end
@doc """
Dispatch an event through all registered plugins sequentially.
Returns `{:ok, payload}` or `{:halt, reason}`.
"""
@spec dispatch(ExClaw.Plugin.event(), map()) :: {:ok, map()} | {:halt, term()}
def dispatch(event, payload) do
plugins = :ets.tab2list(@table)
Enum.reduce_while(plugins, {:ok, payload}, fn {module, state}, {:ok, acc_payload} ->
case module.handle_event(event, acc_payload, state) do
{:ok, new_state} ->
:ets.insert(@table, {module, new_state})
{:cont, {:ok, acc_payload}}
{:ok, new_payload, new_state} ->
:ets.insert(@table, {module, new_state})
{:cont, {:ok, new_payload}}
{:halt, reason, new_state} ->
:ets.insert(@table, {module, new_state})
{:halt, {:halt, reason}}
end
end)
end
# Server callbacks
@impl true
def init(_opts) do
table = :ets.new(@table, [:named_table, :set, :public, read_concurrency: true])
{:ok, %{table: table}}
end
@impl true
def handle_call({:register, module, config}, _from, state) do
case module.init(config) do
{:ok, plugin_state} ->
:ets.insert(@table, {module, plugin_state})
{:reply, :ok, state}
{:error, reason} ->
{:reply, {:error, reason}, state}
end
end
def handle_call({:unregister, module}, _from, state) do
:ets.delete(@table, module)
{:reply, :ok, state}
end
end