# `Hermolaos.Client.RequestTracker`
[🔗](https://github.com/nyo16/hermolaos/blob/v0.5.0/lib/hermolaos/client/request_tracker.ex#L1)

Tracks pending JSON-RPC requests and correlates them with responses.

The RequestTracker maintains a mapping of request IDs to caller information,
enabling the Connection to route responses back to the correct caller.

## Features

- Monotonically increasing integer IDs for efficiency
- ETS-backed storage for O(1) lookups and concurrent access
- Automatic timeout handling with configurable defaults
- Statistics tracking for monitoring

## Design Notes

This module uses ETS for storage because:

1. **Performance**: O(1) lookups regardless of pending request count
2. **Concurrency**: ETS tables support concurrent reads without locking
3. **Isolation**: Each tracker has its own table, crashes don't affect others

## Example

    {:ok, tracker} = Hermolaos.Client.RequestTracker.start_link(timeout: 30_000)

    # Track a request
    id = Hermolaos.Client.RequestTracker.next_id(tracker)
    :ok = Hermolaos.Client.RequestTracker.track(tracker, id, "tools/list", from)

    # When response arrives, complete the request
    {:ok, from, method} = Hermolaos.Client.RequestTracker.complete(tracker, id)
    GenServer.reply(from, result)

# `from`

```elixir
@type from() :: GenServer.from()
```

# `id`

```elixir
@type id() :: integer()
```

# `method`

```elixir
@type method() :: String.t()
```

# `pending_request`

```elixir
@type pending_request() :: %{
  method: method(),
  from: from(),
  timeout_ref: reference() | nil,
  started_at: integer()
}
```

# `stats`

```elixir
@type stats() :: %{
  requests_tracked: non_neg_integer(),
  requests_completed: non_neg_integer(),
  requests_failed: non_neg_integer(),
  requests_timed_out: non_neg_integer(),
  requests_cancelled: non_neg_integer()
}
```

# `t`

```elixir
@type t() :: GenServer.server()
```

# `cancel`

```elixir
@spec cancel(t(), id()) :: :ok
```

Cancels a pending request.

## Examples

    :ok = Hermolaos.Client.RequestTracker.cancel(tracker, 1)

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `complete`

```elixir
@spec complete(t(), id()) :: {:ok, from() | nil, method()} | {:error, :not_found}
```

Completes a pending request successfully.

Returns the original caller's `from` and method so the caller can be notified.

## Examples

    case Hermolaos.Client.RequestTracker.complete(tracker, 1) do
      {:ok, from, "tools/list"} ->
        GenServer.reply(from, {:ok, result})

      {:error, :not_found} ->
        # Request already completed, timed out, or never existed
        :ok
    end

# `fail`

```elixir
@spec fail(t(), id(), term()) :: {:ok, from() | nil, method()} | {:error, :not_found}
```

Fails a pending request with an error.

## Examples

    case Hermolaos.Client.RequestTracker.fail(tracker, 1, error) do
      {:ok, from, method} ->
        GenServer.reply(from, {:error, error})

      {:error, :not_found} ->
        :ok
    end

# `fail_all`

```elixir
@spec fail_all(t(), term()) :: [{from(), method()}]
```

Fails all pending requests (e.g., when connection closes).

Returns the list of failed requests with their callers.

## Examples

    failed = Hermolaos.Client.RequestTracker.fail_all(tracker, {:error, :connection_closed})
    for {from, method} <- failed do
      GenServer.reply(from, {:error, :connection_closed})
    end

# `next_id`

```elixir
@spec next_id(t()) :: id()
```

Gets the next request ID (monotonically increasing integer).

## Examples

    id = Hermolaos.Client.RequestTracker.next_id(tracker)
    # => 1

    id = Hermolaos.Client.RequestTracker.next_id(tracker)
    # => 2

# `pending?`

```elixir
@spec pending?(t(), id()) :: boolean()
```

Checks if a request ID is currently pending.

# `pending_count`

```elixir
@spec pending_count(t()) :: non_neg_integer()
```

Returns the number of pending requests.

## Examples

    count = Hermolaos.Client.RequestTracker.pending_count(tracker)
    # => 5

# `start_link`

```elixir
@spec start_link(keyword()) :: {:ok, pid()} | {:error, term()}
```

Starts a new request tracker.

## Options

- `:timeout` - Default timeout for requests in ms (default: 30000)
- `:name` - GenServer name (optional)

## Examples

    {:ok, tracker} = Hermolaos.Client.RequestTracker.start_link()
    {:ok, tracker} = Hermolaos.Client.RequestTracker.start_link(timeout: 60_000)

# `stats`

```elixir
@spec stats(t()) :: stats()
```

Returns tracker statistics.

## Examples

    stats = Hermolaos.Client.RequestTracker.stats(tracker)
    # => %{requests_tracked: 100, requests_completed: 95, ...}

# `track`

```elixir
@spec track(t(), id(), method(), from() | nil, timeout() | nil) :: :ok
```

Tracks a pending request.

## Parameters

- `tracker` - The tracker process
- `id` - Request ID (from `next_id/1`)
- `method` - JSON-RPC method name
- `from` - GenServer from tuple for reply
- `timeout` - Optional timeout override in ms

## Examples

    :ok = Hermolaos.Client.RequestTracker.track(tracker, 1, "tools/list", from)
    :ok = Hermolaos.Client.RequestTracker.track(tracker, 2, "tools/call", from, 60_000)

---

*Consult [api-reference.md](api-reference.md) for complete listing*
