# `SuperCache`
[🔗](https://github.com/ohhi-vn/super_cache/blob/main/lib/api/super_cache.ex#L7)

High-throughput, in-memory cache backed by ETS.

Works in both **local** (single-node) and **distributed** (cluster) modes.
The mode is selected at startup via the `:cluster` option and is
transparent to callers — the same API covers both.

## Setup

    # Local (single-node, default)
    SuperCache.start!(key_pos: 0, partition_pos: 0)

    # Distributed cluster
    SuperCache.start!(
      key_pos:            0,
      partition_pos:      0,
      cluster:            :distributed,
      replication_factor: 2,
      replication_mode:   :async,
      num_partition:      16
    )

    # Enable debug logging
    # in config.exs:
    # config :super_cache, debug_log: true

## Options

| Option           | Type    | Default         | Description                          |
|------------------|---------|-----------------|--------------------------------------|
| `:key_pos`       | integer | `0`             | Tuple index used as the ETS key      |
| `:partition_pos` | integer | `0`             | Tuple index used to select partition |
| `:cluster`       | atom    | `:local`        | `:local` or `:distributed`           |
| `:num_partition` | integer | scheduler count | Number of ETS partitions             |
| `:table_type`    | atom    | `:set`          | ETS table type                       |

## Read modes (meaningful in distributed mode)

| Mode       | Consistency        | Latency              |
|------------|--------------------|----------------------|
| `:local`   | Eventual (default) | Zero extra latency   |
| `:primary` | Strong per key     | +1 RTT if non-primary|
| `:quorum`  | Majority vote      | +1 RTT (parallel)    |

## Replication modes (distributed mode only)

| Mode      | Guarantee          | Extra latency     |
|-----------|--------------------|-------------------|
| `:async`  | Eventual (default) | None              |
| `:sync`   | At-least-once      | +1 RTT per write  |
| `:strong` | Three-phase commit | +3 RTTs per write |

## Basic usage

    SuperCache.put!({:user, 1, "Alice"})
    SuperCache.get!({:user, 1, nil})
    # => [{:user, 1, "Alice"}]

    # Read modes (distributed)
    SuperCache.get!({:user, 1, nil}, read_mode: :primary)
    SuperCache.get!({:user, 1, nil}, read_mode: :quorum)

    SuperCache.delete!({:user, 1, nil})
    SuperCache.delete_all()

## Error handling

`!`-suffix functions raise on error.  Non-bang variants return
`{:error, exception}` instead and never raise.

# `cluster_stats`

```elixir
@spec cluster_stats() :: map()
```

Return an aggregated cluster-wide statistics map.

In local mode wraps `stats/0` in the same map format for a uniform interface.
In distributed mode gathers per-node counts via `:erpc`.

# `delete`

```elixir
@spec delete(tuple()) :: :ok | {:error, Exception.t()}
```

Non-raising variant of `delete!/1`.

# `delete!`

```elixir
@spec delete!(tuple()) :: :ok
```

Delete the record matching the key in `data`.

Routes to the primary in distributed mode.

# `delete_all`

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

Delete all records from every partition.

In distributed mode, issues one routed delete per partition to its primary.

# `delete_by_key_partition`

```elixir
@spec delete_by_key_partition(any(), any()) :: :ok | {:error, Exception.t()}
```

Non-raising variant of `delete_by_key_partition!/2`.

# `delete_by_key_partition!`

```elixir
@spec delete_by_key_partition!(any(), any()) :: :ok
```

Delete by explicit `key` and `partition_data`.

# `delete_by_match`

```elixir
@spec delete_by_match(any(), tuple()) :: :ok | {:error, Exception.t()}
```

Non-raising variant of `delete_by_match!/2`.

# `delete_by_match!`

```elixir
@spec delete_by_match!(tuple()) :: :ok
```

Delete from all partitions. Equivalent to `delete_by_match!(:_, pattern)`.

# `delete_by_match!`

```elixir
@spec delete_by_match!(any(), tuple()) :: :ok
```

Delete records matching `pattern` in `partition_data` (or all partitions for `:_`).

# `delete_partition!`

```elixir
@spec delete_partition!(any(), atom() | :ets.tid()) :: :ok
```

Delete a record directly from a specific partition table.

Bypasses all routing and partition resolution.

## Examples

    partition = SuperCache.Partition.get_partition(:my_key)
    SuperCache.delete_partition!(:user, partition)
    # => :ok

# `delete_partition_by_idx!`

```elixir
@spec delete_partition_by_idx!(any(), non_neg_integer()) :: :ok
```

Delete a record directly from a partition by its integer index.

This is the fastest delete path — bypasses routing and partition resolution.

## Examples

    idx = SuperCache.Partition.get_partition_order(:my_key)
    SuperCache.delete_partition_by_idx!(:user, idx)
    # => :ok

# `delete_same_key_partition`

```elixir
@spec delete_same_key_partition(any()) :: :ok | {:error, Exception.t()}
```

Non-raising variant of `delete_same_key_partition!/1`.

# `delete_same_key_partition!`

```elixir
@spec delete_same_key_partition!(any()) :: :ok
```

Equivalent to `delete_by_key_partition!(key, key)` — for tables where key_pos == partition_pos.

# `distributed?`

```elixir
@spec distributed?() :: boolean()
```

Returns `true` when SuperCache is running in distributed mode.

Delegates to `Config.distributed?/0` for zero-cost persistent_term reads.

# `get`

```elixir
@spec get(
  tuple(),
  keyword()
) :: [tuple()] | {:error, Exception.t()}
```

Retrieve records. Returns `[tuple] | {:error, exception}` instead of raising.

# `get!`

```elixir
@spec get!(
  tuple(),
  keyword()
) :: [tuple()]
```

Retrieve all records whose key matches the key element of `data`.

## Options

- `:read_mode` — `:local` (default), `:primary`, or `:quorum`.

## Examples

    SuperCache.get!({:user, 1, nil})
    SuperCache.get!({:user, 1, nil}, read_mode: :primary)
    SuperCache.get!({:user, 1, nil}, read_mode: :quorum)

# `get_by_key_partition`

```elixir
@spec get_by_key_partition(any(), any(), keyword()) ::
  [tuple()] | {:error, Exception.t()}
```

Non-raising variant of `get_by_key_partition!/3`.

# `get_by_key_partition!`

```elixir
@spec get_by_key_partition!(any(), any(), keyword()) :: [tuple()]
```

Retrieve records by explicit `key` and `partition_data`.

## Options

- `:read_mode` — `:local` (default), `:primary`, or `:quorum`.

# `get_by_match`

```elixir
@spec get_by_match(any(), tuple(), keyword()) :: [[any()]] | {:error, Exception.t()}
```

Non-raising variant of `get_by_match!/2`.

# `get_by_match!`

```elixir
@spec get_by_match!(tuple()) :: [[any()]]
```

Scan all partitions. Equivalent to `get_by_match!(:_, pattern, [])`.

# `get_by_match!`

```elixir
@spec get_by_match!(any(), tuple(), keyword()) :: [[any()]]
```

Retrieve records matching an ETS match pattern (binding lists).

Pass `:_` as `partition_data` to scan all partitions.

## Options

- `:read_mode` — `:local` (default), `:primary`, or `:quorum`.

# `get_by_match_object`

```elixir
@spec get_by_match_object(any(), tuple(), keyword()) ::
  [tuple()] | {:error, Exception.t()}
```

Non-raising variant of `get_by_match_object!/2`.

# `get_by_match_object!`

```elixir
@spec get_by_match_object!(tuple()) :: [tuple()]
```

Scan all partitions. Equivalent to `get_by_match_object!(:_, pattern, [])`.

# `get_by_match_object!`

```elixir
@spec get_by_match_object!(any(), tuple(), keyword()) :: [tuple()]
```

Retrieve full records matching an ETS match-object pattern.

Pass `:_` as `partition_data` to scan all partitions.

## Options

- `:read_mode` — `:local` (default), `:primary`, or `:quorum`.

# `get_partition!`

```elixir
@spec get_partition!(any(), atom() | :ets.tid()) :: [tuple()]
```

Retrieve records directly from a specific partition table.

Bypasses all routing and partition resolution. The `key` is looked up
directly in the given `partition` table.

## Examples

    partition = SuperCache.Partition.get_partition(:my_key)
    SuperCache.get_partition!(:user, partition)
    # => [{:user, 1, "Alice"}]

# `get_partition_by_idx!`

```elixir
@spec get_partition_by_idx!(any(), non_neg_integer()) :: [tuple()]
```

Retrieve records directly from a partition by its integer index.

This is the fastest read path — bypasses routing, partition resolution,
and partition table name lookup.

## Examples

    idx = SuperCache.Partition.get_partition_order(:my_key)
    SuperCache.get_partition_by_idx!(:user, idx)
    # => [{:user, 1, "Alice"}]

# `get_same_key_partition`

```elixir
@spec get_same_key_partition(
  any(),
  keyword()
) :: [tuple()] | {:error, Exception.t()}
```

Non-raising variant of `get_same_key_partition!/2`.

# `get_same_key_partition!`

```elixir
@spec get_same_key_partition!(
  any(),
  keyword()
) :: [tuple()]
```

Equivalent to `get_by_key_partition!(key, key, opts)` — for tables where key_pos == partition_pos.

# `lazy_put`

```elixir
@spec lazy_put(tuple()) :: :ok
```

Enqueue a tuple for a buffered (lazy) write.

Falls back to `put!/1` with a warning when `:strong` replication is active.

# `put`

```elixir
@spec put(tuple()) :: true | {:error, Exception.t()}
```

Store a tuple. Returns `true | {:error, exception}` instead of raising.

# `put!`

```elixir
@spec put!(tuple()) :: true
```

Store a tuple in the cache.

In distributed mode, routes the write to the partition's primary node and
replicates according to `:replication_mode`.  Raises on error.

# `put_batch!`

```elixir
@spec put_batch!([tuple()]) :: :ok
```

Store multiple tuples in a single batch operation.

In distributed mode, groups data by partition and sends each group
in a single `:erpc` call, dramatically reducing network overhead.

## Example

    SuperCache.put_batch!([
      {:user, 1, "Alice"},
      {:user, 2, "Bob"},
      {:session, "tok1", :active}
    ])

# `put_partition!`

```elixir
@spec put_partition!(tuple(), atom() | :ets.tid()) :: true
```

Store a tuple directly into a specific partition table.

Bypasses all routing and partition resolution. Use when you already know
the target partition table atom (e.g., from a previous `get_partition/1`
call or from `Partition.get_all_partition/0`).

## Examples

    partition = SuperCache.Partition.get_partition(:my_key)
    SuperCache.put_partition!({:user, 1, "Alice"}, partition)

# `put_partition_by_idx!`

```elixir
@spec put_partition_by_idx!(tuple(), non_neg_integer()) :: true
```

Store a tuple directly into a partition by its integer index.

This is the fastest write path — bypasses routing, partition resolution,
and even the partition table name lookup. The index is used directly to
fetch the cached partition table atom from `persistent_term`.

## Examples

    # Get partition index once
    idx = SuperCache.Partition.get_partition_order(:my_key)

    # Use it for fast repeated access
    SuperCache.put_partition_by_idx!({:user, 1, "Alice"}, idx)

# `scan`

```elixir
@spec scan(any(), (any(), any() -&gt; any()), any()) :: any() | {:error, Exception.t()}
```

Non-raising variant of `scan!/3`.

# `scan!`

```elixir
@spec scan!((any(), any() -&gt; any()), any()) :: any()
```

Equivalent to `scan!(:_, fun, acc)`.

# `scan!`

```elixir
@spec scan!(any(), (any(), any() -&gt; any()), any()) :: any()
```

Fold over every record in a partition (or all when `:_`).

Always reads from the local ETS table regardless of replication mode.

# `start`

```elixir
@spec start() :: :ok | {:error, Exception.t()}
```

Start SuperCache. Returns `:ok | {:error, exception}` instead of raising.

# `start`

```elixir
@spec start(keyword()) :: :ok | {:error, Exception.t()}
```

Start SuperCache with opts. Returns `:ok | {:error, exception}` instead of raising.

# `start!`

```elixir
@spec start!() :: :ok
```

Start SuperCache with default options (local, key_pos: 0, partition_pos: 0).

# `start!`

```elixir
@spec start!(keyword()) :: :ok
```

Start SuperCache with the given keyword options. Raises on invalid config.

# `started?`

```elixir
@spec started?() :: boolean()
```

Returns `true` when SuperCache is running and ready.

# `stats`

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

Return local ETS record counts per partition plus `:total`.

# `stop`

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

Stop SuperCache and free all ETS memory.

---

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