Current section
Files
Jump to
Current section
Files
lib/hermes/server/session.ex
defmodule Hermes.Server.Session do
@moduledoc false
use Agent, restart: :transient
@type t :: %__MODULE__{
protocol_version: String.t() | nil,
initialized: boolean(),
name: GenServer.name() | nil,
client_info: map() | nil,
client_capabilities: map() | nil,
log_level: String.t(),
id: String.t() | nil,
pending_requests: %{
String.t() => %{started_at: integer(), method: String.t()}
}
}
defstruct [
:id,
:protocol_version,
:log_level,
:name,
initialized: false,
client_info: nil,
client_capabilities: nil,
pending_requests: %{}
]
@doc """
Starts a new session agent with initial state.
"""
@spec start_link(keyword()) :: Agent.on_start()
def start_link(opts \\ []) do
session_id = Keyword.fetch!(opts, :session_id)
name = Keyword.fetch!(opts, :name)
Agent.start_link(fn -> new(id: session_id, name: name) end, name: name)
end
@doc """
Creates a new server state with the given options.
"""
@spec new(Enumerable.t()) :: t()
def new(opts), do: struct(__MODULE__, opts)
@doc """
Guard to check if a session has been initialized.
"""
defguard is_initialized(session) when session.initialized
@doc """
Retrieves the current state of a session.
"""
@spec get(GenServer.name()) :: t
def get(session) do
Agent.get(session, & &1)
end
@doc """
Updates state after successful initialization handshake.
This function:
1. Sets the negotiated protocol version
2. Stores client information and capabilities
3. Marks the server as initialized
"""
@spec update_from_initialization(GenServer.name(), String.t(), map, map) :: :ok
def update_from_initialization(session, negotiated_version, client_info, capabilities) do
Agent.update(session, fn state ->
%{
state
| protocol_version: negotiated_version,
client_info: client_info,
client_capabilities: capabilities
}
end)
end
@doc """
Marks the session as initialized.
"""
@spec mark_initialized(GenServer.name()) :: :ok
def mark_initialized(session) do
Agent.update(session, fn state -> %{state | initialized: true} end)
end
@doc """
Updates the log level.
"""
@spec set_log_level(GenServer.name(), String.t()) :: :ok
def set_log_level(session, level) do
Agent.update(session, fn state -> %{state | log_level: level} end)
end
@doc """
Tracks a new pending request in the session.
"""
@spec track_request(GenServer.name(), String.t(), String.t()) :: :ok
def track_request(session, request_id, method) do
Agent.update(session, fn state ->
request_info = %{
started_at: System.system_time(:millisecond),
method: method
}
%{
state
| pending_requests: Map.put(state.pending_requests, request_id, request_info)
}
end)
end
@doc """
Removes a completed request from tracking.
"""
@spec complete_request(GenServer.name(), String.t()) :: map() | nil
def complete_request(session, request_id) do
Agent.get_and_update(session, fn state ->
{request_info, pending_requests} = Map.pop(state.pending_requests, request_id)
{request_info, %{state | pending_requests: pending_requests}}
end)
end
@doc """
Checks if a request is currently pending.
"""
@spec has_pending_request?(GenServer.name(), String.t()) :: boolean()
def has_pending_request?(session, request_id) do
Agent.get(session, fn state ->
Map.has_key?(state.pending_requests, request_id)
end)
end
@doc """
Gets all pending requests for a session.
"""
@spec get_pending_requests(GenServer.name()) :: map()
def get_pending_requests(session) do
Agent.get(session, & &1.pending_requests)
end
end