# `Cache.RefreshAhead`
[🔗](https://github.com/mikaak/elixir_cache/blob/main/lib/cache/refresh_ahead.ex#L1)

Refresh-ahead caching strategy that proactively refreshes values before they expire.

Values are stored with a timestamp. On `get`, if the value is within the
refresh window (i.e. `now - inserted_at >= ttl - refresh_before`), the current
value is returned immediately and an async `Task` is spawned to refresh it.

Only keys that are actively read get refreshed — unread keys naturally expire
from the underlying adapter.

## Usage

```elixir
defmodule MyApp.Cache do
  use Cache,
    adapter: {Cache.RefreshAhead, Cache.Redis},
    name: :my_cache,
    opts: [
      uri: "redis://localhost:6379",
      refresh_before: :timer.seconds(30)
    ]

  def refresh(key) do
    {:ok, fetch_fresh_value(key)}
  end
end
```

## Options

* `:refresh_before` (`t:pos_integer/0`) - Required. Milliseconds before TTL expiry at which to trigger a background refresh.

* `:on_refresh` - Optional refresh callback. If not provided, the cache module must define `refresh/1`.

* `:lock_node_whitelist` - Optional node whitelist for distributed refresh locks. Defaults to all connected nodes.

## How It Works

1. `put/5` wraps the value as `{value, inserted_at_ms, ttl_ms}` before delegating.
2. `get/4` unwraps the tuple. If `now - inserted_at >= ttl - refresh_before`,
   a background `Task` is spawned to call the refresh callback with the key.
3. A per-cache ETS deduplication table (`:<name>_refresh_tracker`) prevents
   multiple concurrent refresh tasks for the same key.
4. On successful refresh, `put/5` is called with the new value and the same TTL,
   resetting the inserted_at timestamp.

> **Note**: When `sandbox?: true`, values are stored unwrapped. Refresh logic
> is bypassed entirely.

---

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