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

In-memory key-value namespaces backed by SuperCache ETS partitions.

Works transparently in both **local** and **distributed** modes — the
mode is determined by the `:cluster` option passed to `SuperCache.start!/1`.

Multiple independent namespaces coexist using different `kv_name` values.

## ETS table type support

KeyValue adapts its behavior based on the configured `:table_type`:

| Operation       | `:set` / `:ordered_set`              | `:bag` / `:duplicate_bag`          |
|-----------------|--------------------------------------|-------------------------------------|
| `add/3`         | Atomic upsert (update or insert)     | Insert (duplicates allowed)        |
| `get/3`         | Single value or default             | Most recent value or default       |
| `get_all/3`     | List with at most one element        | All values for the key             |
| `update/3`      | Atomic via `update_element`          | Delete-all + insert (not atomic)   |
| `update/4`      | Best-effort read-modify-write        | Best-effort read-modify-write       |
| `increment/4`  | Atomic via `update_counter`          | Not supported (raises)             |
| `replace/3`     | Same as `update/3`                   | Delete-all + insert                |

## Atomic operations

- `update/3` — atomically set a value using `:ets.update_element/3,4`
  (`:set`/`:ordered_set` only). If the key does not exist, a new record
  is inserted (upsert semantics).
- `increment/4` — atomically increment a counter using
  `:ets.update_counter/3,4` (`:set`/`:ordered_set` only).
- `add/3` on `:set`/`:ordered_set` tables uses `update_element` with a
  default for true atomic upsert semantics in `:async`/`:sync` replication
  mode.

## Read modes (distributed)

Pass `read_mode: :primary` or `read_mode: :quorum` when you need to read
your own writes from any node.

## Example

    alias SuperCache.KeyValue

    # Works identically in local and distributed mode
    KeyValue.add("session", :user_id, 42)
    KeyValue.get("session", :user_id)       # => 42
    KeyValue.keys("session")                # => [:user_id]
    KeyValue.remove("session", :user_id)
    KeyValue.remove_all("session")

    # Atomic operations (:set / :ordered_set tables)
    KeyValue.update("counters", :hits, 1)           # => :ok
    KeyValue.increment("counters", :hits, 0, 1)     # => 2

    # Bag table support
    KeyValue.get_all("tags", :elixir)               # => [1, 2]  (all values)
    KeyValue.replace("tags", :elixir, 3)            # => :ok     (replace all)

# `add`

```elixir
@spec add(any(), any(), any()) :: true
```

Add or update a key-value pair.

For `:set`/`:ordered_set` tables, this is an atomic upsert — if the key
exists, the value is updated in-place via `:ets.update_element`; if not,
a new record is inserted.

For `:bag`/`:duplicate_bag` tables, this inserts a new record. Duplicate
keys are allowed. Use `replace/3` to atomically replace all values for a key.

Returns `true`.

# `add_batch`

```elixir
@spec add_batch(any(), [{any(), any()}]) :: :ok
```

Add multiple key-value pairs in a single batch operation.

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

## Example

    KeyValue.add_batch("session", [
      {:user_1, %{name: "Alice"}},
      {:user_2, %{name: "Bob"}}
    ])

# `count`

```elixir
@spec count(
  any(),
  keyword()
) :: non_neg_integer()
```

# `get`

```elixir
@spec get(any(), any(), any(), keyword()) :: any()
```

Get the value for `key`, returning `default` if not found.

For `:set`/`:ordered_set` tables, returns the single value or `default`.

For `:bag`/`:duplicate_bag` tables, returns the **most recently inserted**
value. Use `get_all/3` to retrieve all values for a key.

# `get_all`

```elixir
@spec get_all(any(), any(), keyword()) :: [any()]
```

Get all values for `key` as a list.

Useful for `:bag`/`:duplicate_bag` tables where multiple records can share
the same key. For `:set`/`:ordered_set` tables, returns a list with at most
one element.

# `increment`

```elixir
@spec increment(any(), any(), number(), number()) :: number()
```

Atomically increment a counter field.

The value at `key` must be a number. If the key doesn't exist, `default`
is used as the initial value before incrementing by `step`.

Only supported for `:set`/`:ordered_set` tables. Raises `ArgumentError`
for `:bag`/`:duplicate_bag` tables — use `:set` or `:ordered_set` for
atomic counter operations.

Returns the new counter value.

# `keys`

```elixir
@spec keys(
  any(),
  keyword()
) :: [any()]
```

# `remove`

```elixir
@spec remove(any(), any()) :: :ok
```

# `remove_all`

```elixir
@spec remove_all(any()) :: :ok
```

# `remove_batch`

```elixir
@spec remove_batch(any(), [any()]) :: :ok
```

Remove multiple keys in a single batch operation.

Groups entries by partition and sends each group in a single `:erpc` call
in distributed mode.

## Example

    KeyValue.remove_batch("session", [:user_1, :user_2])

# `replace`

```elixir
@spec replace(any(), any(), any()) :: :ok
```

Replace all values for `key` with a single value.

For `:bag`/`:duplicate_bag` tables, this deletes all existing records for
the key and inserts a single new record. For `:set`/`:ordered_set` tables,
this is equivalent to `update/3` (atomic upsert).

**Note**: For `:bag`/`:duplicate_bag` tables, this operation is not atomic —
a concurrent reader may observe the key as missing between the delete and
the insert.

Returns `:ok`.

# `to_list`

```elixir
@spec to_list(
  any(),
  keyword()
) :: [{any(), any()}]
```

# `update`

```elixir
@spec update(any(), any(), any()) :: :ok
```

Atomically set the value for `key` (upsert semantics).

For `:set`/`:ordered_set` tables, uses `:ets.update_element/3,4` which is
guaranteed atomic at the ETS level. If the key doesn't exist, a new record
is inserted.

For `:bag`/`:duplicate_bag` tables, deletes all existing records for the key
and inserts a new one. This is **not atomic** — a concurrent reader may
observe the key as missing between the delete and the insert. Prefer
`:set` or `:ordered_set` tables when atomic updates are required.

Returns `:ok`.

# `update`

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

Update the value for `key` using a function.

`fun` receives the current value (or `default` if the key doesn't exist)
and must return the new value.

**Warning**: This is a read-modify-write operation and is **not atomic**.
A concurrent writer may modify the value between the read and the write,
causing a lost update. For atomic value updates, use `update/3`. For
atomic counter increments, use `increment/4`.

In distributed mode, `fun` is serialized and sent to the primary node via
`:erpc`. The function must not capture node-specific resources (PIDs, etc.).

Returns the new value.

# `values`

```elixir
@spec values(
  any(),
  keyword()
) :: [any()]
```

---

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