# `SuperCache.Cluster.Router`
[🔗](https://github.com/ohhi-vn/super_cache/blob/main/lib/cluster/router.ex#L1)

Routes SuperCache operations to the correct primary node and applies
replication after each write.

## Routing contract

1. Determine the **partition order** (integer index) for the operation from
   the data tuple or explicit partition argument via
   `Partition.get_partition_order/1`.
2. Look up `{primary, replicas}` from `Manager.get_replicas/1`
   (zero-cost `:persistent_term` read).
3. If `node() == primary` → apply locally, then call `Replicator.replicate/3`.
4. Otherwise → forward the entire operation to the primary via `:erpc`,
   which applies and replicates it.  Forwarded calls never forward again
   (detected via a `:forwarded` flag in opts) to prevent cycles.

## Anti-cycle guard

Every outbound `:erpc` call appends `forwarded: true` to its opts list.
A function that receives `forwarded: true` always executes locally and
skips the primary check, preventing infinite forwarding chains when the
partition map is momentarily inconsistent.

## No anonymous functions across node boundaries

All `:erpc` calls pass only plain, serializable Erlang terms — integers,
atoms, and tuples.  Anonymous functions (closures) are never passed via
`:erpc` because Erlang fun serialization is fragile: the remote node must
have the identical module version, otherwise the call raises `badfun`.
Instead, every remote read goes through the explicit public dispatcher
`local_read/3`, which takes an operation atom (`:get | :match | :match_object`)
and a plain argument.

## 3PC writes

When `Manager.replication_mode/0` returns `:strong`, writes are handed
to `ThreePhaseCommit.commit/2` on the primary instead of the normal
local-write + async/sync replicate path.

## Read-your-writes consistency

When a process writes a key, the Router records the partition order in a
per-process ETS table.  Subsequent reads of the same partition (within a
configurable TTL) are automatically routed to the primary node, ensuring
the reader sees its own writes even in `:local` read mode.

The tracking table is cleaned up lazily — entries older than the TTL are
pruned on each write.  This adds negligible overhead (~100ns per write)
while providing strong read-your-writes guarantees without requiring
`read_mode: :primary` on every call.

# `route_delete!`

```elixir
@spec route_delete!(
  tuple(),
  keyword()
) :: :ok
```

Route a key-based delete to the correct primary.

# `route_delete_all`

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

Delete all records — one routed call per partition.

# `route_delete_by_key_partition!`

```elixir
@spec route_delete_by_key_partition!(any(), any(), keyword()) :: :ok
```

Route a delete by explicit key + partition value to the correct primary.

# `route_delete_match!`

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

Route a match-based delete, one partition order at a time.

# `route_get!`

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

Route a key-based get.

# `route_get_by_key_partition!`

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

Route a get by explicit key + partition value.

# `route_get_by_match!`

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

Route a match-pattern scan across one or all partitions.

# `route_get_by_match_object!`

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

Route a match-object scan across one or all partitions.

# `route_put!`

```elixir
@spec route_put!(
  tuple(),
  keyword()
) :: true
```

Route a put to the correct primary, then replicate.

# `route_put_batch!`

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

Route a batch of puts to the correct primary, then replicate.

Groups data by partition order and sends each group in a single `:erpc` call,
dramatically reducing network overhead for bulk writes.

## Example

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

# `route_scan!`

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

Fold over local ETS — always local, never forwarded.

---

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