Packages

Multi-surface application runtime for Elixir. One TEA module renders to terminal, browser (LiveView), SSH, and MCP (agents). 30+ widgets, flexbox + CSS grid, AI agent runtime, distributed swarm with CRDTs, time-travel debugging, session recording, sandboxed REPL, and agentic commerce.

Current section

Files

Jump to
raxol lib raxol core runtime events events_handler.ex
Raw

lib/raxol/core/runtime/events/events_handler.ex

defmodule Raxol.Core.Runtime.Events.Handler do
@moduledoc """
Manages event handlers registration and execution in the Raxol system.
This module is responsible for:
* Registering event handlers for specific event types
* Executing handlers when events occur
* Managing the priority and order of handlers
"""
require Raxol.Core.Runtime.Log
alias Raxol.Core.Runtime.ProcessStore
@doc """
Registers a new event handler for the specified event types.
"""
def register_handler(handler_id, event_types, handler_fun, options \\ []) do
priority = Keyword.get(options, :priority, 100)
filter = Keyword.get(options, :filter, fn _event -> true end)
handler = %{
id: handler_id,
event_types: List.wrap(event_types),
handler_fun: handler_fun,
priority: priority,
filter: filter
}
_ = put_handler(handler_id, handler)
{:ok, handler_id}
end
@doc """
Unregisters an event handler.
## Parameters
- `handler_id`: ID of the handler to remove
"""
def unregister_handler(handler_id) do
case get_handler(handler_id) do
nil ->
{:error, :not_found}
_handler ->
_ = remove_handler(handler_id)
:ok
end
end
@doc """
Executes all registered handlers for the given event.
Handlers are executed in priority order (lowest to highest).
Each handler can transform the event for the next handler.
"""
def execute_handlers(event, state) do
handlers = get_relevant_handlers(event)
execute_handlers_in_order(handlers, event, state)
end
defp get_relevant_handlers(event) do
get_all_handlers()
|> Enum.filter(fn handler ->
event.type in handler.event_types and handler.filter.(event)
end)
|> Enum.sort_by(fn handler -> handler.priority end)
end
defp execute_handlers_in_order(handlers, event, state) do
case Raxol.Core.ErrorHandling.safe_call(fn ->
reduce_handlers_safely(handlers, event, state)
end) do
{:ok, result} ->
result
{:error, error} ->
log_handler_error(error, event, state, nil)
{:error, {:handler_error, error}, state}
end
end
defp execute_single_handler(handler, {current_event, current_state}) do
case handler.handler_fun.(current_event, current_state) do
{:ok, new_event, new_state} ->
{:cont, {new_event, new_state}}
{:stop, new_event, new_state} ->
{:halt, {new_event, new_state}}
{:error, reason} ->
Raxol.Core.Runtime.Log.error(
"Handler error",
%{
module: __MODULE__,
event: current_event,
state: current_state,
reason: reason
}
)
{:halt, {:error, reason, current_state}}
end
end
defp log_handler_error(error, event, state, stacktrace) do
case Raxol.Core.ErrorHandling.safe_call(fn ->
Raxol.Core.Runtime.Log.error_with_stacktrace(
"Error executing handlers",
error,
stacktrace,
%{module: __MODULE__, event: event, state: state}
)
end) do
{:ok, _} ->
:ok
{:error, e} ->
Raxol.Core.Runtime.Log.error(
"Failed to log handler error: #{inspect(e)}",
%{module: __MODULE__, event: event, state: state}
)
end
end
defp put_handler(id, handler) do
# Create a simple module and function name for EventManager registration
# Since EventManager expects (event_type, module, function), we'll register
# a generic handler that can invoke the function-based handlers
Enum.each(handler.event_types, fn event_type ->
Raxol.Core.Events.EventManager.register_handler(
event_type,
__MODULE__,
:execute_dynamic_handler
)
end)
# Store the handler config for retrieval
_ = ProcessStore.put({:handler_config, id}, handler)
{:ok, id}
end
@doc """
Executes a dynamically registered handler.
This is called by EventManager and looks up the actual handler function.
"""
def execute_dynamic_handler(event) do
# Get all stored handler configs and find ones that match this event type
handlers =
get_all_stored_handlers()
|> Enum.filter(fn {_id, handler} ->
Enum.member?(handler.event_types, event.type)
end)
|> Enum.sort_by(fn {_id, handler} -> handler.priority end)
# Execute handlers in priority order
initial_state = %{count: 0}
Enum.reduce_while(handlers, {event, initial_state}, fn {_id, handler},
{current_event,
current_state} ->
run_single_handler(handler, current_event, current_state)
end)
end
defp run_single_handler(handler, current_event, current_state) do
if handler.filter.(current_event) do
invoke_handler_fun(handler.handler_fun, current_event, current_state)
else
{:cont, {current_event, current_state}}
end
end
defp invoke_handler_fun(handler_fun, current_event, current_state) do
case handler_fun.(current_event, current_state) do
{:ok, updated_event, updated_state} ->
{:cont, {updated_event, updated_state}}
{:error, reason, updated_state} ->
{:halt, {:error, reason, updated_state}}
{:stop, updated_event, updated_state} ->
{:halt, {updated_event, updated_state}}
end
rescue
error ->
{:halt, {:error, {:handler_error, error}, current_state}}
end
defp get_all_stored_handlers do
# Get all handler configs from store
ProcessStore.get_all()
|> Enum.filter(fn
{{key, _id}, _handler} when key == :handler_config -> true
_ -> false
end)
|> Enum.map(fn {{_key, id}, handler} -> {id, handler} end)
end
defp get_handler(id) do
case ProcessStore.get({:handler_config, id}) do
nil -> nil
handler -> {id, handler}
end
end
defp remove_handler(id) do
case ProcessStore.get({:handler_config, id}) do
nil ->
{:error, :not_found}
handler ->
# Remove from store
_ = ProcessStore.delete({:handler_config, id})
# Try to unregister from EventManager (though this is complex with the current design)
Enum.each(handler.event_types, fn event_type ->
try do
Raxol.Core.Events.EventManager.unregister_handler(
event_type,
__MODULE__,
:execute_dynamic_handler
)
rescue
# Ignore if already unregistered
_ -> :ok
end
end)
:ok
end
end
defp get_all_handlers do
get_all_stored_handlers()
|> Enum.map(fn {_id, handler} -> handler end)
end
defp reduce_handlers_safely(handlers, event, state) do
Enum.reduce_while(
handlers,
{event, state},
&execute_single_handler/2
)
|> case do
{updated_event, updated_state} ->
{:ok, updated_event, updated_state}
{:error, reason, error_state} ->
{:error, reason, error_state}
end
end
end