# `AshOnetime.Cache.Ets`
[🔗](https://github.com/baselabs/ash_onetime/blob/v1.5.0/lib/ash_onetime/cache/ets.ex#L1)

A reference ETS adapter for the optional completed-response cache.

Bounded and TTL-aware. `:ets` has no native TTL, so each entry carries an expiry
monotonic-time deadline and `get/1` rejects expired entries (and lazily deletes them). A
max-entries cap bounds steady-state memory; when the cap is reached `put/3` evicts the
single oldest-by-deadline entry before inserting, which keeps the hot set resident under
churn without a background sweeper.

The adapter is NOT admission authority — `AshOnetime` validates every field of a returned
entry against the authoritative PostgreSQL claim before using the payload (see
`AshOnetime.Cache`). Treat every entry as untrusted.

## Supervision

The ETS table is owned by a GenServer started via `start_link/1`. Add it to your supervision
tree (it owns the table; if it terminates the table is destroyed, which is safe — the cache
is optional and a miss falls through to PostgreSQL):

    children = [
      # ...your repo...
      {AshOnetime.Cache.Ets, max_entries: 10_000}
    ]

Then configure it as the cache module:

    config :ash_onetime, cache: AshOnetime.Cache.Ets

The table is named `__MODULE__` (one ETS cache per VM). A multi-instance deployment (e.g.
one cache per tenant prefix) wraps this module behind a per-instance module so each gets its
own `__MODULE__`-named table; the reference adapter is single-instance by design.

## Options

  * `:max_entries` — the bounded cap; `put/3` evicts the oldest-by-deadline entry when the
    cap is reached before inserting (default 10_000). The cap bounds entry COUNT only — the
    per-entry byte ceiling is enforced upstream by `AshOnetime.Cache` (`max_entry_bytes`).

## Limitations

This is a reference adapter tuned for simplicity and correctness, not throughput: the
eviction is a single oldest-by-deadline scan, and expired entries are reaped lazily on
`get/1` rather than by a background sweeper. A high-throughput deployment should reach for
Redis or a dedicated cache with sampled-LRU eviction and active expiry; this adapter exists
so the cache-degradation path in `AshOnetime.Cache` is reachable without a third-party
dependency.

# `child_spec`

```elixir
@spec child_spec(keyword()) :: Supervisor.child_spec()
```

A supervisor child specification for the owning GenServer.

## Options

  * `:max_entries` — the bounded entry cap (default 10_000).

Add it to your supervision tree:

    children = [
      {AshOnetime.Cache.Ets, max_entries: 10_000}
    ]

# `clear`

```elixir
@spec clear() :: :ok
```

Drops every entry from the cache (the table and config are retained).

Safe to call while the cache is serving; `get/1` calls during a wipe may race with the
delete and return `:miss`, which is always safe (a miss falls through to PostgreSQL).

# `max_entries`

```elixir
@spec max_entries() :: non_neg_integer()
```

Returns the configured max-entries cap.

# `start_link`

```elixir
@spec start_link(keyword()) :: GenServer.on_start()
```

Starts the owning GenServer for the ETS table.

## Options

  * `:max_entries` — the bounded entry cap (default 10_000).

Returns `{:ok, pid}` or `{:error, {:already_started, pid}}`.

---

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