# `Aerospike`
[🔗](https://github.com/luisgabrielroldan/aerospike_driver/blob/v0.3.1/lib/aerospike.ex#L1)

Public entry point for the Aerospike Elixir driver.

`Aerospike` exposes the low-level API for starting a supervised cluster,
constructing keys, and running commands against that cluster. Most
applications should define an `Aerospike.Repo` module to bind this API to
one configured cluster; lower-level code can call this module directly when
it needs explicit control over the cluster name.

The main command families are:

  * `get/3` reads all bins for a key
  * `get_header/2` reads only record metadata for a key
  * `put/4` writes a bin map
  * `put_payload/4` sends a caller-built single-record write/delete frame
  * `apply_udf/6` executes one record UDF against one key
  * `register_udf/3`, `register_udf/4`, `remove_udf/2`,
    `remove_udf/3`, and `list_udfs/1` / `list_udfs/2` manage
    server-side UDF packages and return explicit metadata/task
    handles
  * `truncate/2`, `truncate/3`, and `truncate/4` expose one-node
    operator truncation helpers with explicit namespace and set forms
  * `create_user/5`, `create_pki_user/4`, `drop_user/3`,
    `change_password/4`, `grant_roles/4`, `revoke_roles/4`, `query_user/3`,
    `query_users/2`, `create_role/4`, `drop_role/3`,
    `set_whitelist/4`, `set_quotas/5`, `grant_privileges/4`,
    `revoke_privileges/4`, `query_role/3`, and `query_roles/2` expose the
    enterprise security-admin seam
  * `add/4`, `append/4`, and `prepend/4` expose thin unary write
    helpers for common counter and string mutations
  * `metrics_enabled?/1`, `enable_metrics/2`, `disable_metrics/1`,
    `stats/1`, and `warm_up/2` expose opt-in runtime metrics and an
    explicit operator pool probe over the already-started workers
  * `touch/2` updates record metadata
  * `delete/2` removes a record
  * `exists/2` performs a header-only existence probe
  * `operate/4` runs simple, CDT-style, and expression operation lists built
    with `Aerospike.Op`, `Aerospike.Op.List`, `Aerospike.Op.Map`,
    `Aerospike.Op.Exp`, and `Aerospike.Ctx`
  * unary commands (`get/3`, `get_header/2`, `put/4`, `exists/2`,
    `touch/2`, `delete/2`, `operate/4`, `apply_udf/6`, `add/4`,
    `append/4`, and `prepend/4`) accept `%Aerospike.Exp{}` via `:filter`
    for server-side execution filtering
  * `Aerospike.Exp` builds server-side expression values, including values
    usable as expression-backed secondary-index sources
  * `create_expression_index/5` creates expression-backed secondary indexes
    on servers that support them and returns a pollable index task
  * `batch_get/4`, `batch_get_header/3`, `batch_exists/3`,
    `batch_get_operate/4`, `batch_delete/3`, and `batch_udf/6` operate on
    multiple keys and return per-key results in caller order
  * `Aerospike.Batch` and `batch_operate/3` expose a curated heterogeneous
    batch surface for mixing reads, writes, deletes, operations, and record
    UDF calls while returning one `%Aerospike.BatchResult{}` per input
  * `child_spec/1`, `close/2`, `key/3`, and `key_digest/3` round out
    the root lifecycle and key-construction boundary
  * `info/3`, `info_node/4`, `nodes/1`, and `node_names/1` expose
    one-node operator reads over the published cluster view; `info_node/4`
    targets one named active node discovered from `node_names/1` or `nodes/1`
  * `set_xdr_filter/4` sets or clears Enterprise XDR expression filters
    through a one-node info command
  * `query_stream!/3`, `query_all/3`, `query_count/3`,
    `query_aggregate/6`, and `query_aggregate_result/6` run
    secondary-index queries through the same
    node-preparation pipeline, with lazy outer streams but
    node-buffered record delivery
    and optional `%Aerospike.Exp{}` filters through `Query.filter/2`
    when a secondary-index predicate from `Query.where/2` is also used.
  * `query_execute/4` and `query_udf/6` run background query jobs on
    that same setup path and return pollable task handles
  * `scan_stream/3`, `scan_stream!/3`, `scan_all/3`, `scan_all!/3`,
    `scan_count/3`, `scan_count!/3`, `scan_page/3`, and `scan_page!/3`
    run scan fan-out across the same scan/runtime setup, again with lazy
    outer streams, node-buffered record delivery, and resumable pages
  * scan/query helpers that already support node targeting accept
    `node: node_name` in `opts`; discover names with `node_names/1` or
    `nodes/1`

Quick-start shape:

    {:ok, _sup} =
      Aerospike.start_link(
        name: :aerospike,
        transport: Aerospike.Transport.Tcp,
        hosts: ["127.0.0.1:3000"],
        namespaces: ["test"],
        pool_size: 2
      )

    Aerospike.Cluster.ready?(:aerospike)

Scan and query stream helpers are lazy at the `Enumerable` boundary. The
runtime buffers each node's response before yielding that node's records
downstream.

Expression builders cover scalar, metadata, arithmetic, conditional,
variable, CDT, bit, and HyperLogLog helper families. Public command and
startup options are keyword-based and validated by the facade before command
execution.

# `admin_opt`

```elixir
@type admin_opt() :: {:pool_checkout_timeout, timeout_ms()}
```

Option accepted by one-node info/admin helpers.

`:pool_checkout_timeout` bounds checkout from the selected node pool before
the one-node info/admin command is sent.

# `admin_opts`

```elixir
@type admin_opts() :: [admin_opt()]
```

Keyword options accepted by one-node info/admin helpers.

# `aggregate_result_opt`

```elixir
@type aggregate_result_opt() :: {:source, String.t()} | {:source_path, Path.t()}
```

Additional local-source option for finalized aggregate queries.

Exactly one of these is required by `query_aggregate_result/6`: inline Lua
source via `:source`, or a readable local file path via `:source_path`.

# `aggregate_result_opts`

```elixir
@type aggregate_result_opts() :: [scan_query_runtime_opt() | aggregate_result_opt()]
```

Keyword options accepted by `query_aggregate_result/6` and `query_aggregate_result!/6`.

# `batch_parent_opt`

```elixir
@type batch_parent_opt() ::
  {:timeout, timeout_ms()}
  | {:socket_timeout, timeout_ms()}
  | {:max_concurrent_nodes, non_neg_integer()}
  | {:allow_partial_results, boolean()}
  | {:respond_all_keys, boolean()}
  | {:allow_inline, boolean()}
  | {:allow_inline_ssd, boolean()}
```

Parent batch dispatch option.

Parent options describe the batch request as a whole: deadline, node
concurrency, partial-result handling, response shape, and inline execution
hints. Per-record read/write policy belongs on `batch_record_read_opt/0` and
`batch_record_write_opt/0`.

# `batch_parent_opts`

```elixir
@type batch_parent_opts() :: [batch_parent_opt()]
```

Parent batch options accepted by `batch_operate/3`.

# `batch_read_opt`

```elixir
@type batch_read_opt() :: batch_parent_opt() | batch_record_read_opt()
```

Batch read option accepted by homogeneous batch-read helpers.

# `batch_read_opts`

```elixir
@type batch_read_opts() :: [batch_read_opt()]
```

Keyword options accepted by batch read helpers.

# `batch_record_read_opt`

```elixir
@type batch_record_read_opt() ::
  {:filter, Aerospike.Exp.t() | nil}
  | {:read_mode_ap, read_mode_ap()}
  | {:read_mode_sc, read_mode_sc()}
  | {:read_touch_ttl_percent, -1 | 0..100}
```

Batch read option accepted by per-entry batch reads.

These options are encoded with one batch read entry. Parent dispatch options
are only accepted by homogeneous batch read helpers and `batch_operate/3`.

# `batch_record_read_opts`

```elixir
@type batch_record_read_opts() :: [batch_record_read_opt()]
```

Keyword options accepted by per-entry batch reads.

# `batch_record_write_opt`

```elixir
@type batch_record_write_opt() ::
  {:ttl, ttl()}
  | {:generation, non_neg_integer()}
  | {:generation_policy, generation_policy()}
  | {:exists, record_exists_action()}
  | {:commit_level, commit_level()}
  | {:durable_delete, boolean()}
  | {:respond_per_op, boolean()}
  | {:send_key, boolean()}
  | {:filter, Aerospike.Exp.t() | nil}
  | {:read_mode_ap, read_mode_ap()}
  | {:read_mode_sc, read_mode_sc()}
  | {:read_touch_ttl_percent, -1 | 0..100}
```

Batch write option accepted by per-entry batch writes.

These options are encoded with one batch write/delete/UDF entry. Parent
dispatch options are only accepted by homogeneous batch write helpers and
`batch_operate/3`.

# `batch_record_write_opts`

```elixir
@type batch_record_write_opts() :: [batch_record_write_opt()]
```

Keyword options accepted by per-entry batch writes.

# `batch_write_opt`

```elixir
@type batch_write_opt() :: batch_parent_opt() | batch_record_write_opt()
```

Batch write option accepted by homogeneous batch-write helpers.

# `batch_write_opts`

```elixir
@type batch_write_opts() :: [batch_write_opt()]
```

Keyword options accepted by batch write helpers.

# `cluster`

```elixir
@type cluster() :: named_cluster() | pid()
```

Identifier for a running cluster facade.

Read-side helpers accept the registered cluster name or a pid
registered under that name. Arbitrary `GenServer.server()` forms are
not supported.

# `commit_level`

```elixir
@type commit_level() :: :all | :master
```

Commit acknowledgement level for write commands.

`:all` waits for the server's configured replica commit level. `:master`
accepts acknowledgement from the master partition owner.

# `create_expression_index_opt`

```elixir
@type create_expression_index_opt() ::
  {:name, String.t()}
  | {:type, index_type()}
  | {:collection, index_collection()}
  | admin_opt()
```

Option accepted by `create_expression_index/5`.

`:name` and `:type` are required. Expression indexes use the provided
`%Aerospike.Exp{}` as their source and therefore do not accept `:bin` or
nested CDT `:ctx`.

# `create_expression_index_opts`

```elixir
@type create_expression_index_opts() :: [create_expression_index_opt()]
```

Keyword options accepted by `create_expression_index/5`.

# `create_index_opt`

```elixir
@type create_index_opt() ::
  {:bin, String.t()}
  | {:name, String.t()}
  | {:type, index_type()}
  | {:collection, index_collection()}
  | {:ctx, [Aerospike.Ctx.step()]}
  | admin_opt()
```

Option accepted by `create_index/4`.

`:bin`, `:name`, and `:type` are required. `:collection` creates a collection
index over list values, map keys, or map values. `:ctx` targets a nested CDT
path built with `Aerospike.Ctx`.

# `create_index_opts`

```elixir
@type create_index_opts() :: [create_index_opt()]
```

Keyword options accepted by `create_index/4`.

# `create_role_opt`

```elixir
@type create_role_opt() ::
  {:whitelist, [String.t()]}
  | {:read_quota, non_neg_integer()}
  | {:write_quota, non_neg_integer()}
  | security_admin_opt()
```

Role-creation option accepted by `create_role/4`.

# `create_role_opts`

```elixir
@type create_role_opts() :: [create_role_opt()]
```

Keyword options accepted by `create_role/4`.

# `generation_policy`

```elixir
@type generation_policy() :: :none | :expect_equal | :expect_gt
```

Generation check behavior for write commands.

`:none` disables generation checks. `:expect_equal` requires the server
record generation to equal `:generation`. `:expect_gt` requires it to be
greater than `:generation`. When `:generation` is positive and
`:generation_policy` is omitted, the driver defaults to `:expect_equal`.

# `index_collection`

```elixir
@type index_collection() :: :list | :mapkeys | :mapvalues
```

Secondary-index collection type.

# `index_type`

```elixir
@type index_type() :: :numeric | :string | :geo2dsphere
```

Secondary-index particle/source type.

# `metrics_opt`

```elixir
@type metrics_opt() :: {:reset, boolean()}
```

Option accepted by `enable_metrics/2`.

# `metrics_opts`

```elixir
@type metrics_opts() :: [metrics_opt()]
```

Keyword options accepted by `enable_metrics/2`.

# `named_cluster`

```elixir
@type named_cluster() :: atom()
```

Registered atom name for a running cluster.

Lifecycle and transaction helpers resolve supervisor and ETS resources from
this name, so they do not currently accept arbitrary `GenServer.server()`
identities.

# `node_info`

```elixir
@type node_info() :: %{name: String.t(), host: String.t(), port: :inet.port_number()}
```

One active cluster node as returned by `nodes/1`.

# `node_opt`

```elixir
@type node_opt() :: {:node, String.t()}
```

Node-targeting option accepted by scan/query helpers that support single-node execution.

# `operate_operation`

```elixir
@type operate_operation() ::
  {:write, String.t() | atom(), term()}
  | {:read, String.t() | atom()}
  | {:add, String.t() | atom(), integer() | float()}
  | {:append, String.t() | atom(), String.t()}
  | {:prepend, String.t() | atom(), String.t()}
  | :touch
  | :delete
  | Aerospike.Op.t()
```

Operation input accepted by `operate/4`.

# `privilege`

```elixir
@type privilege() :: Aerospike.Privilege.t()
```

Security privilege metadata used by role administration APIs.

# `read_mode_ap`

```elixir
@type read_mode_ap() :: :one | :all
```

AP namespace read consistency.

`:one` reads from one available replica. `:all` asks the server to consult
all relevant replicas for the read.

# `read_mode_sc`

```elixir
@type read_mode_sc() :: :session | :linearize | :allow_replica | :allow_unavailable
```

Strong-consistency namespace read consistency.

The default is `:session`. `:linearize` requests linearized reads.
`:allow_replica` and `:allow_unavailable` relax consistency for availability
when the server namespace configuration allows it.

# `read_opt`

```elixir
@type read_opt() ::
  {:timeout, timeout_ms()}
  | {:socket_timeout, timeout_ms()}
  | retry_opt()
  | {:read_mode_ap, read_mode_ap()}
  | {:read_mode_sc, read_mode_sc()}
  | {:read_touch_ttl_percent, -1 | 0..100}
  | {:send_key, boolean()}
  | {:use_compression, boolean() | nil}
  | {:filter, Aerospike.Exp.t() | nil}
```

Read option accepted by `get/4`, `get_header/3`, and `exists/3`.

Read helpers accept timeout/retry opts, consistency opts, `:send_key`,
`:use_compression`, and a server-side expression `:filter`.

# `read_opts`

```elixir
@type read_opts() :: [read_opt()]
```

Keyword options accepted by read helpers.

# `record_exists_action`

```elixir
@type record_exists_action() ::
  :update | :update_only | :create_or_replace | :replace_only | :create_only
```

Record existence behavior for write commands.

  * `:update` - create or update bins (default)
  * `:update_only` - update an existing record only
  * `:create_or_replace` - create a new record or replace the existing record
  * `:replace_only` - replace an existing record only
  * `:create_only` - create only when the key is absent

# `retry_opt`

```elixir
@type retry_opt() ::
  {:max_retries, non_neg_integer()}
  | {:sleep_between_retries_ms, non_neg_integer()}
  | {:replica_policy, Aerospike.RetryPolicy.replica_policy()}
```

Retry option accepted by single-record, scan, and query command families.

  * `:max_retries` - retries after the first attempt
  * `:sleep_between_retries_ms` - fixed delay between retry attempts
  * `:replica_policy` - read retry routing policy, `:master` or `:sequence`

Retry defaults are configured at cluster start and can be overridden per
command where this type appears.

# `role_info`

```elixir
@type role_info() :: Aerospike.Role.t()
```

Security role metadata returned by `query_role/3` and `query_roles/2`.

# `scan_query_opt`

```elixir
@type scan_query_opt() :: scan_query_runtime_opt() | node_opt()
```

Runtime option accepted by scan/query helpers that may target one node.

`:node` must be an active node name returned by `node_names/1` or `nodes/1`.
Final aggregate reduction does not accept `:node` because it must consume all
server partials required to produce one local result.

# `scan_query_opts`

```elixir
@type scan_query_opts() :: [scan_query_opt()]
```

Keyword options accepted by scan and query helpers.

# `scan_query_runtime_opt`

```elixir
@type scan_query_runtime_opt() ::
  {:timeout, timeout_ms()}
  | {:socket_timeout, timeout_ms()}
  | retry_opt()
  | {:task_timeout, timeout_ms() | :infinity}
  | {:pool_checkout_timeout, timeout_ms()}
  | {:max_concurrent_nodes, non_neg_integer()}
  | {:records_per_second, non_neg_integer()}
  | {:include_bin_data, boolean()}
  | {:expected_duration, :long | :short | :long_relax_ap}
  | {:task_id, pos_integer()}
  | {:cursor, Aerospike.Cursor.t()}
```

Runtime option accepted by all scan and query helpers.

These options tune client runtime behavior and server scan/query fields:
command deadlines, retries, task timeout, pool checkout timeout, fan-out
concurrency, throttling, bin-data inclusion, expected duration, task id, and
cursor resume state.

# `security_admin_opt`

```elixir
@type security_admin_opt() ::
  {:timeout, timeout_ms()} | {:pool_checkout_timeout, timeout_ms()}
```

Option accepted by security-admin helpers.

`:timeout` bounds the admin protocol operation. `:pool_checkout_timeout`
bounds checkout from the selected node pool before the command is sent.

# `security_admin_opts`

```elixir
@type security_admin_opts() :: [security_admin_opt()]
```

Keyword options accepted by security-admin helpers.

# `timeout_ms`

```elixir
@type timeout_ms() :: non_neg_integer()
```

Non-negative timeout value in milliseconds.

`:timeout` is the total client-side budget for a command. `:socket_timeout`
is the per-attempt idle socket deadline; `0` asks the command path to use the
remaining total budget.

# `transaction_opts`

```elixir
@type transaction_opts() :: [{:timeout, timeout_ms()}]
```

Transaction options accepted by `transaction/3` when a handle is not supplied.

# `truncate_opt`

```elixir
@type truncate_opt() :: {:before, DateTime.t()} | admin_opt()
```

Option accepted by `truncate/3` and `truncate/4`.

`:before` limits truncation to records last updated before the given
timestamp. `:pool_checkout_timeout` bounds checkout for the admin request.

# `truncate_opts`

```elixir
@type truncate_opts() :: [truncate_opt()]
```

Keyword options accepted by truncate helpers.

# `ttl`

```elixir
@type ttl() :: non_neg_integer() | :default | :never_expire | :dont_update
```

Record TTL accepted by write commands.

  * non-negative integer - explicit TTL in seconds
  * `:default` - use the namespace default TTL
  * `:never_expire` - ask the server to keep the record indefinitely
  * `:dont_update` - preserve the existing record TTL

# `user_info`

```elixir
@type user_info() :: Aerospike.User.t()
```

Security user metadata returned by `query_user/3` and `query_users/2`.

# `warm_up_opt`

```elixir
@type warm_up_opt() ::
  {:count, non_neg_integer()} | {:pool_checkout_timeout, timeout_ms()}
```

Option accepted by `warm_up/2`.

`:count` is the number of worker checkout probes per active node. `0` means
up to the configured pool size. `:pool_checkout_timeout` bounds each probe.

# `warm_up_opts`

```elixir
@type warm_up_opts() :: [warm_up_opt()]
```

Keyword options accepted by `warm_up/2`.

# `write_opt`

```elixir
@type write_opt() ::
  {:timeout, timeout_ms()}
  | {:socket_timeout, timeout_ms()}
  | retry_opt()
  | {:ttl, ttl()}
  | {:generation, non_neg_integer()}
  | {:generation_policy, generation_policy()}
  | {:exists, record_exists_action()}
  | {:commit_level, commit_level()}
  | {:durable_delete, boolean()}
  | {:respond_per_op, boolean()}
  | {:send_key, boolean()}
  | {:read_mode_ap, read_mode_ap()}
  | {:read_mode_sc, read_mode_sc()}
  | {:read_touch_ttl_percent, -1 | 0..100}
  | {:use_compression, boolean() | nil}
  | {:filter, Aerospike.Exp.t() | nil}
  | {:txn, Aerospike.Txn.t()}
```

Write option accepted by single-record write, delete, UDF, and operate helpers.

Write helpers accept timeout/retry opts, record TTL and generation policy,
existence policy, commit level, durable delete, `:respond_per_op`, `:send_key`,
read-touch metadata opts used by mixed operations, per-command compression,
a server-side expression `:filter`, and an optional transaction handle.

# `write_opts`

```elixir
@type write_opts() :: [write_opt()]
```

Keyword options accepted by single-record write, delete, UDF, and operate helpers.

# `abort`

```elixir
@spec abort(named_cluster(), Aerospike.Txn.t()) ::
  {:ok, :aborted | :already_aborted} | {:error, Aerospike.Error.t()}
```

Aborts a transaction on the named cluster `cluster`.

Like `commit/2`, this requires a handle with initialized runtime tracking on
`cluster`. It is for an already-open transaction; it does not create one,
and it currently requires the registered cluster atom.

# `add`

```elixir
@spec add(
  cluster(),
  Aerospike.Key.key_input(),
  Aerospike.Record.bins_input(),
  write_opts()
) ::
  {:ok, Aerospike.Record.metadata()}
  | {:error, Aerospike.Error.t()}
  | {:error, :cluster_not_ready | :no_master | :unknown_node}
```

Atomically adds numeric deltas in `bins` for `key`.

This is a thin unary write helper over the same routed write path as `put/4`.
The return shape stays aligned with the write family and does not expose
`operate/4` record results.

Supported write opts include:

  * `:timeout`
  * `:socket_timeout`
  * `:max_retries`
  * `:sleep_between_retries_ms`
  * `:ttl`
  * `:generation`
  * `:generation_policy` — `:none`, `:expect_equal`, or `:expect_gt`
  * `:exists` — one of `:update`, `:update_only`,
    `:create_or_replace`, `:replace_only`, or `:create_only`
  * `:commit_level` — `:all` or `:master`
  * `:durable_delete` — when `true`, write/delete commands that remove a
    record ask the server to leave a tombstone
  * `:respond_per_op`
  * `:send_key`
  * `:read_mode_ap`
  * `:read_mode_sc`
  * `:read_touch_ttl_percent`
  * `:use_compression`
  * `:filter` — non-empty `%Aerospike.Exp{}` server-side filter
    expression, or `nil` for no filter

Accepts `%Aerospike.Key{}` or `{namespace, set, user_key}`.

# `all`

> This function is deprecated. Use scan_all/3 instead..

```elixir
@spec all(cluster(), Aerospike.Scan.t(), scan_query_opts()) ::
  {:ok, [Aerospike.Record.t()]} | {:error, Aerospike.Error.t()}
```

Deprecated alias for `scan_all/3`.

# `all!`

> This function is deprecated. Use scan_all!/3 instead..

```elixir
@spec all!(cluster(), Aerospike.Scan.t(), scan_query_opts()) :: [Aerospike.Record.t()]
```

Deprecated alias for `scan_all!/3`.

# `append`

```elixir
@spec append(
  cluster(),
  Aerospike.Key.key_input(),
  Aerospike.Record.bins_input(),
  write_opts()
) ::
  {:ok, Aerospike.Record.metadata()}
  | {:error, Aerospike.Error.t()}
  | {:error, :cluster_not_ready | :no_master | :unknown_node}
```

Atomically appends string suffixes in `bins` for `key`.

This stays on the unary write path and returns write metadata, not an
`operate/4` record payload.

Supported write opts include:

  * `:timeout`
  * `:socket_timeout`
  * `:max_retries`
  * `:sleep_between_retries_ms`
  * `:ttl`
  * `:generation`
  * `:generation_policy` — `:none`, `:expect_equal`, or `:expect_gt`
  * `:exists` — one of `:update`, `:update_only`,
    `:create_or_replace`, `:replace_only`, or `:create_only`
  * `:commit_level` — `:all` or `:master`
  * `:durable_delete` — when `true`, write/delete commands that remove a
    record ask the server to leave a tombstone
  * `:respond_per_op`
  * `:send_key`
  * `:read_mode_ap`
  * `:read_mode_sc`
  * `:read_touch_ttl_percent`
  * `:use_compression`
  * `:filter` — non-empty `%Aerospike.Exp{}` server-side filter
    expression, or `nil` for no filter

Accepts `%Aerospike.Key{}` or `{namespace, set, user_key}`.

# `apply_udf`

```elixir
@spec apply_udf(
  cluster(),
  Aerospike.Key.key_input(),
  String.t(),
  String.t(),
  list(),
  write_opts()
) ::
  {:ok, term()}
  | {:error, Aerospike.Error.t()}
  | {:error, :cluster_not_ready | :no_master | :unknown_node}
```

Executes one record UDF against `key`.

This is a single-record command, not a background query job. It accepts the
same write opts as the unary write family:

  * `:timeout`
  * `:socket_timeout`
  * `:max_retries`
  * `:sleep_between_retries_ms`
  * `:ttl`
  * `:generation`
  * `:generation_policy`
  * `:exists`
  * `:commit_level`
  * `:durable_delete`
  * `:respond_per_op`
  * `:send_key`
  * `:read_mode_ap`
  * `:read_mode_sc`
  * `:read_touch_ttl_percent`
  * `:use_compression`
  * `:filter`
  * `:txn`

Accepts `%Aerospike.Key{}` or `{namespace, set, user_key}`.

Transport failures are not retried automatically once the request is on the
wire, because record UDFs may already have produced server-side effects.
Package lifecycle lives on `register_udf/*`, `remove_udf/*`, and `list_udfs/2`.

# `batch_delete`

```elixir
@spec batch_delete(cluster(), [Aerospike.Key.key_input()], batch_write_opts()) ::
  {:ok, [Aerospike.BatchResult.t()]}
  | {:error, Aerospike.Error.t()}
  | {:error, :cluster_not_ready}
```

Deletes multiple `keys` from `cluster`.

The result list stays in the same order as `keys`. Each list item is a
`%Aerospike.BatchResult{}` with `status: :ok` for a deleted record or
`status: :error` for a per-key failure such as a missing key, routing
failure, or node transport error. Missing keys remain explicit error
results with the server result code; this helper does not collapse them to
booleans.

This helper accepts parent batch opts and encodable write fields such as
`:generation_policy`, `:generation`, `:commit_level`, `:durable_delete`,
`:send_key`, and `:filter`.

Accepts `%Aerospike.Key{}` values or `{namespace, set, user_key}` tuples.

# `batch_exists`

```elixir
@spec batch_exists(cluster(), [Aerospike.Key.key_input()], batch_read_opts()) ::
  {:ok,
   ok: boolean(), error: Aerospike.Error.t(), error: :no_master | :unknown_node}
  | {:error, Aerospike.Error.t()}
  | {:error, :cluster_not_ready}
```

Checks existence for multiple `keys` from `cluster`.

The result list stays in the same order as `keys`. Each list item is
either `{:ok, true}` / `{:ok, false}` for the targeted key or an
indexed error for that key (`{:error, %Aerospike.Error{}}`,
`{:error, :no_master}`, or `{:error, :unknown_node}`).

This helper accepts the same batch read opts as `batch_get/4`.

Accepts `%Aerospike.Key{}` values or `{namespace, set, user_key}` tuples.

# `batch_get`

```elixir
@spec batch_get(
  cluster(),
  [Aerospike.Key.key_input()],
  :all | [String.t() | atom()],
  batch_read_opts()
) ::
  {:ok,
   ok: Aerospike.Record.t(),
   error: Aerospike.Error.t(),
   error: :no_master | :unknown_node}
  | {:error, Aerospike.Error.t()}
  | {:error, :cluster_not_ready}
```

Reads multiple `keys` from `cluster` in one batch request per target node.

The result list stays in the same order as `keys`. Each list item is
either `{:ok, %Aerospike.Record{}}` for a hit or an indexed error for
that key (`{:error, %Aerospike.Error{}}`, `{:error, :no_master}`, or
`{:error, :unknown_node}`).

Pass `:all` to read every bin, or pass a non-empty list of string or atom bin
names to project only those bins. Batch opts include parent dispatch fields
such as `:timeout`, `:socket_timeout`, `:max_concurrent_nodes`,
`:allow_partial_results`, `:respond_all_keys`, `:allow_inline`, and
`:allow_inline_ssd`, plus encodable read fields such as `:filter`,
`:read_mode_ap`, `:read_mode_sc`, and `:read_touch_ttl_percent`.

Accepts `%Aerospike.Key{}` values or `{namespace, set, user_key}` tuples.

# `batch_get_header`

```elixir
@spec batch_get_header(cluster(), [Aerospike.Key.key_input()], batch_read_opts()) ::
  {:ok,
   ok: Aerospike.Record.t(),
   error: Aerospike.Error.t(),
   error: :no_master | :unknown_node}
  | {:error, Aerospike.Error.t()}
  | {:error, :cluster_not_ready}
```

Reads record headers for multiple `keys` from `cluster`.

The result list stays in the same order as `keys`. Each list item is
either `{:ok, %Aerospike.Record{bins: %{}}}` for a hit or an indexed
error for that key (`{:error, %Aerospike.Error{}}`,
`{:error, :no_master}`, or `{:error, :unknown_node}`).

This helper accepts the same batch read opts as `batch_get/4`.

Accepts `%Aerospike.Key{}` values or `{namespace, set, user_key}` tuples.

# `batch_get_operate`

```elixir
@spec batch_get_operate(
  cluster(),
  [Aerospike.Key.key_input()],
  [Aerospike.Op.t()],
  batch_read_opts()
) ::
  {:ok,
   ok: Aerospike.Record.t(),
   error: Aerospike.Error.t(),
   error: :no_master | :unknown_node}
  | {:error, Aerospike.Error.t()}
  | {:error, :cluster_not_ready}
```

Runs one read-only operation list for multiple `keys` from `cluster`.

The result list stays in the same order as `keys`. Each list item is
either `{:ok, %Aerospike.Record{}}` for a hit or an indexed error for
that key (`{:error, %Aerospike.Error{}}`, `{:error, :no_master}`, or
`{:error, :unknown_node}`). Missing keys remain explicit per-key
errors; this helper does not collapse misses to `nil`.

This helper accepts the same batch read opts as `batch_get/4`.

Accepts `%Aerospike.Key{}` values or `{namespace, set, user_key}` tuples.

# `batch_operate`

```elixir
@spec batch_operate(cluster(), [Aerospike.Batch.t()], batch_parent_opts()) ::
  {:ok, [Aerospike.BatchResult.t()]}
  | {:error, Aerospike.Error.t()}
  | {:error, :cluster_not_ready}
```

Executes heterogeneous batch entries built with `Aerospike.Batch`.

The result list stays in the same order as the input entries. Each list item
is a `%Aerospike.BatchResult{}` with `status: :ok` for a successful row or
`status: :error` for a per-key failure such as a missing key, routing
failure, server error, or node transport error.

Parent batch policy opts are accepted in `opts`. Per-entry read/write policy
opts can be attached with the `Aerospike.Batch` builders.

# `batch_udf`

```elixir
@spec batch_udf(
  cluster(),
  [Aerospike.Key.key_input()],
  String.t(),
  String.t(),
  list(),
  batch_write_opts()
) ::
  {:ok, [Aerospike.BatchResult.t()]}
  | {:error, Aerospike.Error.t()}
  | {:error, :cluster_not_ready}
```

Executes one record UDF for multiple `keys` from `cluster`.

The result list stays in the same order as `keys`. Each list item is a
`%Aerospike.BatchResult{}` with `status: :ok` for a successful UDF row or
`status: :error` for a per-key failure such as a missing key, missing UDF,
routing failure, or node transport error. Successful UDF rows may include a
returned `%Aerospike.Record{}` when the server sends return bins.

This helper accepts the same parent batch and write opts as
`batch_delete/3`.

Accepts `%Aerospike.Key{}` values or `{namespace, set, user_key}` tuples.

# `change_password`

```elixir
@spec change_password(cluster(), String.t(), String.t(), security_admin_opts()) ::
  :ok | {:error, Aerospike.Error.t()}
```

Changes a security user's password.

When `user_name` matches the credentials configured on `cluster`, the driver
uses the self-service password-change command and rotates the running
cluster's in-memory credentials for future reconnects. For other users it
uses the user-admin password-set command.

This requires Aerospike Enterprise with security enabled.

# `child_spec`

```elixir
@spec child_spec([Aerospike.Cluster.Supervisor.option()]) :: Supervisor.child_spec()
```

Returns a child specification for one supervised cluster.

This delegates to the same cluster-supervisor implementation as
`start_link/1`, so the accepted options and validation boundary match.

# `close`

```elixir
@spec close(named_cluster(), timeout :: non_neg_integer()) :: :ok
```

Stops the supervised cluster registered under the atom `cluster`.

This targets the registered cluster supervisor name derived from `cluster`
and returns `:ok` when the supervisor exits or is already absent.

# `commit`

```elixir
@spec commit(named_cluster(), Aerospike.Txn.t()) ::
  {:ok, :committed | :already_committed} | {:error, Aerospike.Error.t()}
```

Commits a transaction on the named cluster `cluster`.

This only works for a transaction handle whose tracking row is already
initialized on `cluster`. A fresh `%Aerospike.Txn{}` is not enough by itself.
In the current driver, public code initializes that runtime state only when
`transaction/2` or `transaction/3` enters its callback. Transaction
tracking is keyed off the started cluster name, so this helper currently
requires that registered atom.

# `count`

> This function is deprecated. Use scan_count/3 instead..

```elixir
@spec count(cluster(), Aerospike.Scan.t(), scan_query_opts()) ::
  {:ok, non_neg_integer()} | {:error, Aerospike.Error.t()}
```

Deprecated alias for `scan_count/3`.

# `count!`

> This function is deprecated. Use scan_count!/3 instead..

```elixir
@spec count!(cluster(), Aerospike.Scan.t(), scan_query_opts()) :: non_neg_integer()
```

Deprecated alias for `scan_count!/3`.

# `create_expression_index`

```elixir
@spec create_expression_index(
  cluster(),
  String.t(),
  String.t(),
  Aerospike.Exp.t(),
  create_expression_index_opts()
) :: {:ok, Aerospike.IndexTask.t()} | {:error, Aerospike.Error.t()}
```

Creates an expression-backed secondary index and returns a pollable task
handle.

Required options:

  * `:name` — non-empty index name.
  * `:type` — one of `:numeric`, `:string`, or `:geo2dsphere`.

Optional options:

  * `:collection` — one of `:list`, `:mapkeys`, or `:mapvalues`.
  * `:pool_checkout_timeout` — non-negative pool checkout timeout in
    milliseconds.

The source must be a `%Aerospike.Exp{}` with non-empty wire bytes. Expression
indexes use the expression as the source and therefore do not accept `:bin`.
Servers older than Aerospike 8.1 reject expression-backed index creation
before the create command is sent.

    {:ok, task} =
      Aerospike.create_expression_index(cluster, "test", "users", Exp.int_bin("age"),
        name: "users_age_expr_idx",
        type: :numeric
      )

    :ok = Aerospike.IndexTask.wait(task)

# `create_index`

```elixir
@spec create_index(cluster(), String.t(), String.t(), create_index_opts()) ::
  {:ok, Aerospike.IndexTask.t()} | {:error, Aerospike.Error.t()}
```

Creates a secondary index and returns a pollable task handle.

Required options:

  * `:bin` — non-empty bin name.
  * `:name` — non-empty index name.
  * `:type` — one of `:numeric`, `:string`, or `:geo2dsphere`.

Optional options:

  * `:collection` — one of `:list`, `:mapkeys`, or `:mapvalues`.
  * `:ctx` — non-empty nested CDT path built with `Aerospike.Ctx`.
  * `:pool_checkout_timeout` — non-negative pool checkout timeout in
    milliseconds.

Geo indexes use `type: :geo2dsphere` and can be queried with
`Aerospike.Filter.geo_within/2` or `Aerospike.Filter.geo_contains/2`.

    {:ok, task} =
      Aerospike.create_index(cluster, "test", "places",
        bin: "loc",
        name: "places_loc_geo_idx",
        type: :geo2dsphere
      )

    :ok = Aerospike.IndexTask.wait(task)

# `create_pki_user`

```elixir
@spec create_pki_user(cluster(), String.t(), [String.t()], security_admin_opts()) ::
  :ok | {:error, Aerospike.Error.t()}
```

Creates a PKI-authenticated security user.

The user is created with a no-password credential and is intended for TLS
certificate authentication. This requires Aerospike Enterprise with security
enabled, server support for PKI users, and a cluster connection
authenticated as a user that holds the `user-admin` privilege.

Supported opts are:

  * `:timeout`
  * `:pool_checkout_timeout`

# `create_role`

```elixir
@spec create_role(cluster(), String.t(), [privilege()], create_role_opts()) ::
  :ok | {:error, Aerospike.Error.t()}
```

Creates a security role.

This requires Aerospike Enterprise with security enabled.

Supported opts are:

  * `:whitelist` — list of client address strings
  * `:read_quota` — non-negative integer operations-per-second limit
  * `:write_quota` — non-negative integer operations-per-second limit
  * `:timeout`
  * `:pool_checkout_timeout`

# `create_user`

```elixir
@spec create_user(
  cluster(),
  String.t(),
  String.t(),
  [String.t()],
  security_admin_opts()
) ::
  :ok | {:error, Aerospike.Error.t()}
```

Creates a password-authenticated security user.

This requires Aerospike Enterprise with security enabled and a cluster
connection authenticated as a user that holds the `user-admin` privilege.

Supported opts are:

  * `:timeout`
  * `:pool_checkout_timeout`

# `delete`

```elixir
@spec delete(cluster(), Aerospike.Key.key_input(), write_opts()) ::
  {:ok, boolean()}
  | {:error, Aerospike.Error.t()}
  | {:error, :cluster_not_ready | :no_master | :unknown_node}
```

Deletes `key` from `cluster`.

Returns `{:ok, true}` when a record was deleted and `{:ok, false}`
when the key was already absent. Supported write opts are `:timeout`,
`:socket_timeout`, `:max_retries`, `:sleep_between_retries_ms`, `:ttl`,
`:generation`, `:generation_policy`, `:exists`, `:commit_level`,
`:durable_delete`, `:respond_per_op`, `:send_key`, `:read_mode_ap`,
`:read_mode_sc`, `:read_touch_ttl_percent`, `:use_compression`, and
`:filter`.

Accepts `%Aerospike.Key{}` or `{namespace, set, user_key}`.

# `disable_metrics`

```elixir
@spec disable_metrics(cluster()) :: :ok | {:error, Aerospike.Error.t()}
```

Disables internal runtime metrics for `cluster`.

Counter rows remain in place so `stats/1` still reports the last collected
values until metrics are re-enabled or reset.

# `drop_index`

```elixir
@spec drop_index(cluster(), String.t(), String.t(), admin_opts()) ::
  :ok | {:error, Aerospike.Error.t()}
```

Drops a secondary index.

# `drop_role`

```elixir
@spec drop_role(cluster(), String.t(), security_admin_opts()) ::
  :ok | {:error, Aerospike.Error.t()}
```

Drops a security role.

This requires Aerospike Enterprise with security enabled.

# `drop_user`

```elixir
@spec drop_user(cluster(), String.t(), security_admin_opts()) ::
  :ok | {:error, Aerospike.Error.t()}
```

Drops a security user.

This requires Aerospike Enterprise with security enabled.

# `enable_metrics`

```elixir
@spec enable_metrics(cluster(), metrics_opts()) :: :ok | {:error, Aerospike.Error.t()}
```

Enables internal runtime metrics for `cluster`.

Supported options:

  * `:reset` — boolean. When `true`, clears the existing runtime counters
    before enabling collection.

# `exists`

```elixir
@spec exists(cluster(), Aerospike.Key.key_input(), read_opts()) ::
  {:ok, boolean()}
  | {:error, Aerospike.Error.t()}
  | {:error, :cluster_not_ready | :no_master | :unknown_node}
```

Returns whether `key` exists in `cluster` without reading bins.

Supported read opts are `:timeout`, `:socket_timeout`, `:max_retries`,
`:sleep_between_retries_ms`, `:replica_policy`, `:read_mode_ap`,
`:read_mode_sc`, `:read_touch_ttl_percent`, `:send_key`,
`:use_compression`, and `:filter`.

Accepts `%Aerospike.Key{}` or `{namespace, set, user_key}`.

# `get`

```elixir
@spec get(
  cluster(),
  Aerospike.Key.key_input(),
  :all | [String.t() | atom()],
  read_opts()
) ::
  {:ok, Aerospike.Record.t()}
  | {:error, Aerospike.Error.t()}
  | {:error, :cluster_not_ready | :no_master | :unknown_node}
```

Reads `key` from `cluster`.

Pass `:all` to read every bin, or pass a non-empty list of string or atom bin
names to project only those bins.

Options:

  * `:timeout` — total op-budget milliseconds for the call, shared
    across the initial send and every retry. Default `5_000`.
  * `:socket_timeout` — per-attempt socket idle deadline in milliseconds,
    capped by the remaining total budget. `0` uses the remaining total
    budget.
  * `:max_retries` — overrides the cluster-default retry cap for this
    call. `0` disables retry entirely. See `Aerospike.RetryPolicy`.
  * `:sleep_between_retries_ms` — fixed delay between retry attempts.
  * `:replica_policy` — `:master` (all attempts against the master)
    or `:sequence` (walk the replica list by attempt index).
  * `:read_mode_ap` — `:one` or `:all`.
  * `:read_mode_sc` — `:session`, `:linearize`, `:allow_replica`, or
    `:allow_unavailable`.
  * `:read_touch_ttl_percent` — `-1`, `0`, or an integer from `1` through
    `100`.
  * `:send_key` — include the user key in the request when `true`.
  * `:use_compression` — per-command compression override; `nil` keeps the
    cluster/node default.
  * `:filter` — non-empty `%Aerospike.Exp{}` server-side filter
    expression, or `nil` for no filter.

Returns `{:ok, %Aerospike.Record{}}` on hit,
`{:error, %Aerospike.Error{code: :key_not_found}}` on miss, or a
routing atom (`:cluster_not_ready`, `:no_master`, `:unknown_node`)
when the cluster view cannot serve the request.

Accepts `%Aerospike.Key{}` or `{namespace, set, user_key}`.

# `get_header`

```elixir
@spec get_header(cluster(), Aerospike.Key.key_input(), read_opts()) ::
  {:ok, Aerospike.Record.t()}
  | {:error, Aerospike.Error.t()}
  | {:error, :cluster_not_ready | :no_master | :unknown_node}
```

Reads only record metadata for `key` from `cluster`.

This is the explicit single-record header helper. It reuses the same unary
read path as `get/3`, but requests only generation/TTL metadata and returns
a `%Aerospike.Record{}` with `bins: %{}` on hit.

Options:

  * `:timeout` — total op-budget milliseconds for the call, shared
    across the initial send and every retry. Default `5_000`.
  * `:socket_timeout` — per-attempt socket idle deadline in milliseconds,
    capped by the remaining total budget. `0` uses the remaining total
    budget.
  * `:max_retries` — overrides the cluster-default retry cap for this
    call. `0` disables retry entirely. See `Aerospike.RetryPolicy`.
  * `:sleep_between_retries_ms` — fixed delay between retry attempts.
  * `:replica_policy` — `:master` (all attempts against the master)
    or `:sequence` (walk the replica list by attempt index).
  * `:read_mode_ap` — `:one` or `:all`.
  * `:read_mode_sc` — `:session`, `:linearize`, `:allow_replica`, or
    `:allow_unavailable`.
  * `:read_touch_ttl_percent` — `-1`, `0`, or an integer from `1` through
    `100`.
  * `:send_key` — include the user key in the request when `true`.
  * `:use_compression` — per-command compression override; `nil` keeps the
    cluster/node default.
  * `:filter` — non-empty `%Aerospike.Exp{}` server-side filter
    expression, or `nil` for no filter.

Returns `{:ok, %Aerospike.Record{bins: %{}}}` on hit,
`{:error, %Aerospike.Error{code: :key_not_found}}` on miss, or a
routing atom (`:cluster_not_ready`, `:no_master`, `:unknown_node`)
when the cluster view cannot serve the request.

Accepts `%Aerospike.Key{}` or `{namespace, set, user_key}`.

# `grant_privileges`

```elixir
@spec grant_privileges(cluster(), String.t(), [privilege()], security_admin_opts()) ::
  :ok | {:error, Aerospike.Error.t()}
```

Grants privileges to a security role.

This requires Aerospike Enterprise with security enabled.

# `grant_roles`

```elixir
@spec grant_roles(cluster(), String.t(), [String.t()], security_admin_opts()) ::
  :ok | {:error, Aerospike.Error.t()}
```

Grants roles to a security user.

This requires Aerospike Enterprise with security enabled.

# `info`

```elixir
@spec info(cluster(), String.t(), admin_opts()) ::
  {:ok, String.t()} | {:error, Aerospike.Error.t()}
```

Sends one info command to one active cluster node and returns that node's reply.

# `info_node`

```elixir
@spec info_node(cluster(), String.t(), String.t(), admin_opts()) ::
  {:ok, String.t()} | {:error, Aerospike.Error.t()}
```

Sends one info command to the named active cluster node and returns that
node's reply.

`node_name` must be one of the names returned by `node_names/1` or
`nodes/1`. Stale or unknown names return an
`%Aerospike.Error{code: :invalid_node}` instead of falling back to a
different node.

    {:ok, [node_name | _]} = Aerospike.node_names(:aerospike)
    {:ok, response} = Aerospike.info_node(:aerospike, node_name, "statistics")

Supported options:

  * `:pool_checkout_timeout` — non-negative pool checkout timeout in
    milliseconds.

# `key`

```elixir
@spec key(String.t(), String.t(), String.t() | integer()) :: Aerospike.Key.t()
```

Builds a key from namespace, set, and a user key.

This is a thin wrapper over `Aerospike.Key.new/3`.

# `key_digest`

```elixir
@spec key_digest(String.t(), String.t(), &lt;&lt;_::160&gt;&gt;) :: Aerospike.Key.t()
```

Builds a key from namespace, set, and an existing 20-byte digest.

This is a thin wrapper over `Aerospike.Key.from_digest/3`.

# `list_udfs`

```elixir
@spec list_udfs(cluster(), admin_opts()) ::
  {:ok, [Aerospike.UDF.t()]} | {:error, Aerospike.Error.t()}
```

Lists the registered server-side UDF packages visible from one active node.

This is package lifecycle state, not record execution. Use `apply_udf/6`
to invoke one function against one key.

# `metrics_enabled?`

```elixir
@spec metrics_enabled?(cluster()) :: boolean()
```

Returns whether internal runtime metrics are enabled for `cluster`.

Metrics are opt-in. The collector is initialized at cluster start so the
config rows exist early, but command, pool, and tender counters remain idle
until `enable_metrics/2` is called.

# `node_names`

```elixir
@spec node_names(cluster()) :: {:ok, [String.t()]}
```

Returns the published active cluster node-name snapshot.

# `nodes`

```elixir
@spec nodes(cluster()) :: {:ok, [node_info()]}
```

Returns the published active cluster nodes with their direct-connect host and port.

# `operate`

```elixir
@spec operate(
  cluster(),
  Aerospike.Key.key_input(),
  [operate_operation()],
  write_opts()
) ::
  {:ok, Aerospike.Record.t()}
  | {:error, Aerospike.Error.t()}
  | {:error, :cluster_not_ready | :no_master | :unknown_node}
```

Runs a constrained unary operate list for `key`.

Supported operations:

  * `{:write, bin, value}` — simple bin write
  * `{:read, bin}` — simple bin read
  * `{:add, bin, delta}` — numeric increment
  * `{:append, bin, suffix}` — string suffix mutation
  * `{:prepend, bin, prefix}` — string prefix mutation
  * `:touch` — refresh record metadata
  * `:delete` — remove the record

The command routes per input batch: read-only lists use read routing;
any list that includes a write uses write routing.

Supported opts are `:timeout`, `:socket_timeout`, `:max_retries`,
`:sleep_between_retries_ms`, `:ttl`, `:generation`, `:generation_policy`,
`:exists`, `:commit_level`, `:durable_delete`, `:respond_per_op`,
`:send_key`, `:read_mode_ap`, `:read_mode_sc`,
`:read_touch_ttl_percent`, `:use_compression`, and `:filter`.

Accepted operations include the simple tuple form plus the public
`Aerospike.Op` helpers for primitive, CDT, bit, HyperLogLog, and expression
operations. Returned operation values are accumulated into
`%Aerospike.Record{bins: map}` by bin name.

    Aerospike.operate(cluster, key, [
      Aerospike.Op.Exp.read("projected", Aerospike.Exp.int_bin("count")),
      Aerospike.Op.Exp.write("computed", Aerospike.Exp.int(99))
    ])

    Aerospike.operate(cluster, key, [
      Aerospike.Op.List.append("profile", "signed-in",
        ctx: [Aerospike.Ctx.map_key("events")]
      )
    ])

    Aerospike.operate(cluster, key, [
      Aerospike.Op.Bit.count("flags", 0, 8),
      Aerospike.Op.HLL.get_count("visitors")
    ])

Accepts `%Aerospike.Key{}` or `{namespace, set, user_key}`.

# `prepend`

```elixir
@spec prepend(
  cluster(),
  Aerospike.Key.key_input(),
  Aerospike.Record.bins_input(),
  write_opts()
) ::
  {:ok, Aerospike.Record.metadata()}
  | {:error, Aerospike.Error.t()}
  | {:error, :cluster_not_ready | :no_master | :unknown_node}
```

Atomically prepends string prefixes in `bins` for `key`.

This stays on the unary write path and returns write metadata, not an
`operate/4` record payload.

Supported write opts include:

  * `:timeout`
  * `:socket_timeout`
  * `:max_retries`
  * `:sleep_between_retries_ms`
  * `:ttl`
  * `:generation`
  * `:generation_policy` — `:none`, `:expect_equal`, or `:expect_gt`
  * `:exists` — one of `:update`, `:update_only`,
    `:create_or_replace`, `:replace_only`, or `:create_only`
  * `:commit_level` — `:all` or `:master`
  * `:durable_delete` — when `true`, write/delete commands that remove a
    record ask the server to leave a tombstone
  * `:respond_per_op`
  * `:send_key`
  * `:read_mode_ap`
  * `:read_mode_sc`
  * `:read_touch_ttl_percent`
  * `:use_compression`
  * `:filter` — non-empty `%Aerospike.Exp{}` server-side filter
    expression, or `nil` for no filter

Accepts `%Aerospike.Key{}` or `{namespace, set, user_key}`.

# `put`

```elixir
@spec put(
  cluster(),
  Aerospike.Key.key_input(),
  Aerospike.Record.bins_input(),
  write_opts()
) ::
  {:ok, Aerospike.Record.metadata()}
  | {:error, Aerospike.Error.t()}
  | {:error, :cluster_not_ready | :no_master | :unknown_node}
```

Writes `bins` for `key` to `cluster`.

The driver accepts only a non-empty bin map and only scalar, list, map,
bytes, geo, and HyperLogLog values supported by the command encoder.
Supported write opts include:

  * `:timeout`
  * `:socket_timeout`
  * `:max_retries`
  * `:sleep_between_retries_ms`
  * `:ttl`
  * `:generation`
  * `:generation_policy` — `:none`, `:expect_equal`, or `:expect_gt`
  * `:exists` — one of `:update`, `:update_only`,
    `:create_or_replace`, `:replace_only`, or `:create_only`
  * `:commit_level` — `:all` or `:master`
  * `:durable_delete` — when `true`, write/delete commands that remove a
    record ask the server to leave a tombstone
  * `:respond_per_op`
  * `:send_key`
  * `:read_mode_ap`
  * `:read_mode_sc`
  * `:read_touch_ttl_percent`
  * `:use_compression`
  * `:filter` — non-empty `%Aerospike.Exp{}` server-side filter
    expression, or `nil` for no filter

Accepts `%Aerospike.Key{}` or `{namespace, set, user_key}`.

# `put_payload`

```elixir
@spec put_payload(cluster(), Aerospike.Key.key_input(), binary(), write_opts()) ::
  :ok
  | {:error, Aerospike.Error.t()}
  | {:error, :cluster_not_ready | :no_master | :unknown_node}
```

Sends a caller-built single-record write/delete frame for `key`.

This helper is intended for tooling, proxy, and replay scenarios.
`payload` must be a complete Aerospike wire frame for one write-shaped
command. The client uses `key` only to choose the write partition owner,
forwards `payload` unchanged, and parses only the standard write response.

The payload must already contain every server-visible write attribute, such
as generation, TTL, send-key, delete flags, filters, and any transaction
fields. Passing `:txn` validates the transaction option shape but does not
register the key with the transaction monitor or add transaction fields.

Supported write opts are validated for routing and I/O budgets:

  * `:timeout`
  * `:socket_timeout`
  * `:max_retries`
  * `:sleep_between_retries_ms`
  * `:ttl`
  * `:generation`
  * `:generation_policy`
  * `:exists`
  * `:commit_level`
  * `:durable_delete`
  * `:respond_per_op`
  * `:send_key`
  * `:use_compression`
  * `:filter`
  * `:txn`

Accepts `%Aerospike.Key{}` or `{namespace, set, user_key}`.

# `put_payload!`

```elixir
@spec put_payload!(cluster(), Aerospike.Key.key_input(), binary(), write_opts()) ::
  :ok
```

Same as `put_payload/4` but returns `:ok` or raises `Aerospike.Error`.

# `query_aggregate`

```elixir
@spec query_aggregate(
  cluster(),
  Aerospike.Query.t(),
  String.t(),
  String.t(),
  list(),
  scan_query_opts()
) ::
  {:ok, Enumerable.t()} | {:error, Aerospike.Error.t()}
```

Streams aggregate query values over the same node-buffered query
runtime used by `query_stream/3`.

The returned stream yields the aggregate values emitted by the server. It
does not run local Lua finalization. Use `query_aggregate_result/6` when
the caller wants one locally finalized aggregate result.

# `query_aggregate_result`

```elixir
@spec query_aggregate_result(
  cluster(),
  Aerospike.Query.t(),
  String.t(),
  String.t(),
  list(),
  aggregate_result_opts()
) :: {:ok, term() | nil} | {:error, Aerospike.Error.t()}
```

Returns one finalized aggregate query result.

This runs the same server aggregate query as `query_aggregate/6`, then
locally executes the package's Lua stream finalization over the
server-emitted aggregate values. Use `query_aggregate/6` when callers need
the partial server values themselves.

The local Lua package source must be supplied with exactly one of:

  * `:source` - inline Lua source as a binary
  * `:source_path` - readable local Lua source path

Source loading is local-only and separate from server UDF registration. The
client does not derive a local path from `package` and does not fetch source
from the server. Source option errors, unsupported local argument values, and
`node: node_name` fail with `{:error, %Aerospike.Error{code:
:invalid_argument}}` before opening the server query stream.

The local reducer runs in a fresh bounded Lua state. The existing `:timeout`
option bounds local execution when it is a positive integer; otherwise a
finite default is used. Supported local stream helpers are `map`, `filter`,
`aggregate`, and `reduce`. Logging helpers are no-ops. Filesystem, OS,
package loading, dynamic loading, debug access, `require`, `groupby`, `list`,
`bytes`, and record/database mutation or lookup helpers fail explicitly with
`%Aerospike.Error{code: :query_generic}`.

Values crossing the local Lua boundary are limited to `nil`, booleans,
integers, floats, binaries, lists, and maps with scalar keys. Blob, geo, raw,
HLL, and other unsupported values fail instead of being coerced.

Example:

    query =
      Aerospike.Query.new("test", "users")
      |> Aerospike.Query.where(Aerospike.Filter.range("age", 18, 65))

    Aerospike.query_aggregate_result(
      :aerospike,
      query,
      "user_stats",
      "sum_age",
      ["age"],
      source_path: "priv/udf/user_stats.lua",
      timeout: 10_000
    )

Returns `{:ok, nil}` when local finalization produces no value. Returns an
error when finalization produces multiple values or local Lua execution fails.

# `query_aggregate_result!`

```elixir
@spec query_aggregate_result!(
  cluster(),
  Aerospike.Query.t(),
  String.t(),
  String.t(),
  list(),
  aggregate_result_opts()
) :: term() | nil
```

Same as `query_aggregate_result/6` but returns the value or raises
`Aerospike.Error`.

# `query_all`

```elixir
@spec query_all(cluster(), Aerospike.Query.t(), scan_query_opts()) ::
  {:ok, [Aerospike.Record.t()]} | {:error, Aerospike.Error.t()}
```

Eagerly collects query records into a list.

`query.max_records` must be set because this helper advances through
the query in repeated page-sized steps until the cursor is exhausted.
Pass `node: node_name` in `opts` to query one active node, using a name
returned by `node_names/1` or `nodes/1`.

# `query_all!`

```elixir
@spec query_all!(cluster(), Aerospike.Query.t(), scan_query_opts()) :: [
  Aerospike.Record.t()
]
```

Same as `query_all/3` but returns the list or raises `Aerospike.Error`.

# `query_count`

```elixir
@spec query_count(cluster(), Aerospike.Query.t(), scan_query_opts()) ::
  {:ok, non_neg_integer()} | {:error, Aerospike.Error.t()}
```

Counts query matches without materializing the records.

This still walks the query stream and counts client-side. It is not a
separate server-side count primitive. Pass `node: node_name` in `opts` to
count records from one active node, using a name returned by `node_names/1`
or `nodes/1`.

# `query_count!`

```elixir
@spec query_count!(cluster(), Aerospike.Query.t(), scan_query_opts()) ::
  non_neg_integer()
```

Same as `query_count/3` but returns the count or raises `Aerospike.Error`.

# `query_execute`

```elixir
@spec query_execute(cluster(), Aerospike.Query.t(), list(), scan_query_opts()) ::
  {:ok, Aerospike.ExecuteTask.t()} | {:error, Aerospike.Error.t()}
```

Starts a background query write job that applies the given operations.

This returns a pollable task handle, not a resumable record stream.
Pass `node: node_name` in `opts` to start the job on one active node,
using a name returned by `node_names/1` or `nodes/1`.

# `query_page`

```elixir
@spec query_page(cluster(), Aerospike.Query.t(), scan_query_opts()) ::
  {:ok, Aerospike.Page.t()} | {:error, Aerospike.Error.t()}
```

Returns one collected query page and a resumable cursor when more
records remain.

`query.max_records` is required because it seeds the partition-tracker
budget for the page walk. On multi-node queries that budget is
distributed across active nodes, so a page is resumable but not
guaranteed to contain exactly `query.max_records` records. The cursor
resumes partition progress from the prior page; it is not a stable
snapshot token. Pass `node: node_name` in `opts` to collect a page from one
active node, using a name returned by `node_names/1` or `nodes/1`.

# `query_page!`

```elixir
@spec query_page!(cluster(), Aerospike.Query.t(), scan_query_opts()) ::
  Aerospike.Page.t()
```

Same as `query_page/3` but returns the page or raises `Aerospike.Error`.

# `query_role`

```elixir
@spec query_role(cluster(), String.t(), security_admin_opts()) ::
  {:ok, role_info() | nil} | {:error, Aerospike.Error.t()}
```

Queries one security role.

Returns `{:ok, nil}` when the named role does not exist.

This requires Aerospike Enterprise with security enabled.

# `query_roles`

```elixir
@spec query_roles(cluster(), security_admin_opts()) ::
  {:ok, [role_info()]} | {:error, Aerospike.Error.t()}
```

Queries all security roles visible to the authenticated cluster user.

This requires Aerospike Enterprise with security enabled.

# `query_stream`

```elixir
@spec query_stream(cluster(), Aerospike.Query.t(), scan_query_opts()) ::
  {:ok, Enumerable.t()} | {:error, Aerospike.Error.t()}
```

Returns a lazy `Stream` of records from a secondary-index query.

Like `scan_stream/3`, this is lazy only at the outer Enumerable boundary.
The current runtime buffers each node's query results before yielding
them to the caller. Pass `node: node_name` in `opts` to query one active
node, using a name returned by `node_names/1` or `nodes/1`.

# `query_stream!`

```elixir
@spec query_stream!(cluster(), Aerospike.Query.t(), scan_query_opts()) ::
  Enumerable.t()
```

Same as `query_stream/3` but raises on error.

# `query_udf`

```elixir
@spec query_udf(
  cluster(),
  Aerospike.Query.t(),
  String.t(),
  String.t(),
  list(),
  scan_query_opts()
) ::
  {:ok, Aerospike.ExecuteTask.t()} | {:error, Aerospike.Error.t()}
```

Starts a background query UDF job.

This returns a pollable task handle, not a resumable record stream.
Pass `node: node_name` in `opts` to start the job on one active node,
using a name returned by `node_names/1` or `nodes/1`.

# `query_user`

```elixir
@spec query_user(cluster(), String.t(), security_admin_opts()) ::
  {:ok, user_info() | nil} | {:error, Aerospike.Error.t()}
```

Queries one security user.

Returns `{:ok, nil}` when the named user does not exist.

This requires Aerospike Enterprise with security enabled.

# `query_users`

```elixir
@spec query_users(cluster(), security_admin_opts()) ::
  {:ok, [user_info()]} | {:error, Aerospike.Error.t()}
```

Queries all security users visible to the authenticated cluster user.

This requires Aerospike Enterprise with security enabled.

# `register_udf`

```elixir
@spec register_udf(cluster(), String.t(), String.t(), admin_opts()) ::
  {:ok, Aerospike.RegisterTask.t()} | {:error, Aerospike.Error.t()}
```

Uploads a UDF package from inline source or a readable local `.lua` path.

Returns a pollable `Aerospike.RegisterTask` once the server accepts the
upload. The package may still be propagating, so call `RegisterTask.wait/2`
before relying on it from `apply_udf/6` or background UDF jobs.

# `remove_udf`

```elixir
@spec remove_udf(cluster(), String.t(), admin_opts()) ::
  :ok | {:error, Aerospike.Error.t()}
```

Removes a registered UDF package by server filename.

This is idempotent: removing an already-absent package still returns `:ok`.

# `revoke_privileges`

```elixir
@spec revoke_privileges(cluster(), String.t(), [privilege()], security_admin_opts()) ::
  :ok | {:error, Aerospike.Error.t()}
```

Revokes privileges from a security role.

This requires Aerospike Enterprise with security enabled.

# `revoke_roles`

```elixir
@spec revoke_roles(cluster(), String.t(), [String.t()], security_admin_opts()) ::
  :ok | {:error, Aerospike.Error.t()}
```

Revokes roles from a security user.

This requires Aerospike Enterprise with security enabled.

# `scan_all`

```elixir
@spec scan_all(cluster(), Aerospike.Scan.t(), scan_query_opts()) ::
  {:ok, [Aerospike.Record.t()]} | {:error, Aerospike.Error.t()}
```

Eagerly collects scan records into a list.

Pass `node: node_name` in `opts` to scan one active node, using a name
returned by `node_names/1` or `nodes/1`.

# `scan_all!`

```elixir
@spec scan_all!(cluster(), Aerospike.Scan.t(), scan_query_opts()) :: [
  Aerospike.Record.t()
]
```

Same as `scan_all/3` but returns the list or raises `Aerospike.Error`.

# `scan_count`

```elixir
@spec scan_count(cluster(), Aerospike.Scan.t(), scan_query_opts()) ::
  {:ok, non_neg_integer()} | {:error, Aerospike.Error.t()}
```

Counts scan matches without materializing the records.

Pass `node: node_name` in `opts` to count records from one active node,
using a name returned by `node_names/1` or `nodes/1`.

# `scan_count!`

```elixir
@spec scan_count!(cluster(), Aerospike.Scan.t(), scan_query_opts()) ::
  non_neg_integer()
```

Same as `scan_count/3` but returns the count or raises `Aerospike.Error`.

# `scan_page`

```elixir
@spec scan_page(cluster(), Aerospike.Scan.t(), scan_query_opts()) ::
  {:ok, Aerospike.Page.t()} | {:error, Aerospike.Error.t()}
```

Returns one collected scan page and a resumable cursor when more records
remain.

`scan.max_records` is required because it seeds the partition-tracker
budget for the page walk. On multi-node scans that budget is distributed
across active nodes, so a page is resumable but not guaranteed to contain
exactly `scan.max_records` records. The cursor resumes partition progress
from the prior page; it is not a stable snapshot token. Pass
`node: node_name` in `opts` to collect a page from one active node, using
a name returned by `node_names/1` or `nodes/1`.

# `scan_page!`

```elixir
@spec scan_page!(cluster(), Aerospike.Scan.t(), scan_query_opts()) ::
  Aerospike.Page.t()
```

Same as `scan_page/3` but returns the page or raises `Aerospike.Error`.

# `scan_stream`

```elixir
@spec scan_stream(cluster(), Aerospike.Scan.t(), scan_query_opts()) ::
  {:ok, Enumerable.t()} | {:error, Aerospike.Error.t()}
```

Returns a lazy `Stream` of records from a scan.

The returned stream is lazy at the Enumerable boundary, but the current
runtime drains each node response fully before yielding that node's
records downstream. It does not promise frame-by-frame backpressure or
an explicit cancellation API. Pass `node: node_name` in `opts` to scan one
active node, using a name returned by `node_names/1` or `nodes/1`.

# `scan_stream!`

```elixir
@spec scan_stream!(cluster(), Aerospike.Scan.t(), scan_query_opts()) :: Enumerable.t()
```

Same as `scan_stream/3` but raises on error.

# `set_quotas`

```elixir
@spec set_quotas(
  cluster(),
  String.t(),
  non_neg_integer(),
  non_neg_integer(),
  security_admin_opts()
) :: :ok | {:error, Aerospike.Error.t()}
```

Sets read and write quota limits for a security role.

Pass `0` for either quota to clear that limit. Quotas require server security
configuration with quotas enabled.

# `set_whitelist`

```elixir
@spec set_whitelist(cluster(), String.t(), [String.t()], security_admin_opts()) ::
  :ok | {:error, Aerospike.Error.t()}
```

Sets or clears a security role's client-address whitelist.

Pass an empty list to clear the role's whitelist. This requires Aerospike
Enterprise with security enabled.

# `set_xdr_filter`

```elixir
@spec set_xdr_filter(cluster(), String.t(), String.t(), Aerospike.Exp.t() | nil) ::
  :ok | {:error, Aerospike.Error.t()}
```

Sets or clears the Enterprise XDR filter for one datacenter and namespace.

Pass a non-empty `%Aerospike.Exp{}` to set the filter, or `nil` to clear the
current filter. `datacenter` and `namespace` must be non-empty info-command
identifiers and cannot contain command delimiters.

Live application requires an Enterprise server with XDR configured. Community
Edition or unconfigured clusters may reject the command after local
validation.

    filter = Exp.eq(Exp.int_bin("active"), Exp.int(1))
    :ok = Aerospike.set_xdr_filter(cluster, "dc-west", "test", filter)
    :ok = Aerospike.set_xdr_filter(cluster, "dc-west", "test", nil)

# `start_link`

```elixir
@spec start_link([Aerospike.Cluster.Supervisor.option()]) :: Supervisor.on_start()
```

Starts a supervised cluster.

Internally this delegates to `Aerospike.Cluster.Supervisor.start_link/1`,
which boots the cluster-state owner, per-node pool supervisor, partition-map
writer, and tend-cycle process under one `rest_for_one` tree. The `:name`
option is the public cluster identity later passed to `get/3`,
`Aerospike.Cluster.ready?/1`, and the other facade/read-side helpers.

Required options: `:name`, `:transport`, `:hosts`, `:namespaces`.

Startup validation happens synchronously at this boundary. Shape
errors for required opts, retry/breaker knobs, TLS/connect opts, and
auth pairs fail `start_link/1` immediately instead of surfacing later
from the first tend cycle or pool worker. The `:name` remains the
public cluster identity for facade calls such as `get/3`, `info/3`,
and `Aerospike.Cluster.ready?/1`.

Cluster lifecycle knobs:

  * `:tend_trigger` — `:timer` (default) or `:manual`.
  * `:tend_interval_ms` — automatic tend period in milliseconds.
    Positive integer.
  * `:failure_threshold` — consecutive tend failures before the
    tend cycle demotes a node. Non-negative integer.

Pool-level knobs (forwarded internally on each node-pool start):

  * `:pool_size` — workers per node. Positive integer.
  * `:min_connections_per_node` — minimum warm connection target per
    node. Non-negative integer. `0` allows lazy worker creation.
  * `:idle_timeout_ms` — milliseconds a worker may sit idle before the
    pool verification step evicts it. Positive integer. Defaults stay under
    Aerospike's `proto-fd-idle-ms`.
  * `:max_idle_pings` — bound on how many idle workers NimblePool
    may drop per verification cycle. Positive integer.

Breaker and retry knobs:

  * `:circuit_open_threshold` — consecutive node failures tolerated
    before new commands are refused. Non-negative integer.
  * `:max_concurrent_ops_per_node` — in-flight plus queued command
    cap enforced per node. Positive integer.
  * `:max_retries` — retries after the initial attempt. Non-negative
    integer.
  * `:sleep_between_retries_ms` — fixed delay between retries.
    Non-negative integer.
  * `:replica_policy` — `:master` or `:sequence`.

Cluster feature toggles:

  * `:use_compression` — boolean cluster-wide request-compression
    opt-in, gated per node by advertised capabilities.
  * `:use_services_alternate` — boolean toggle for
    `peers-clear-alt` discovery.
  * `:seed_only_cluster` — boolean. When true, discovery is limited to
    configured seed addresses.
  * `:cluster_name` — expected server cluster name. Nodes reporting a
    different name are rejected.
  * `:application_id` — application identity sent to capable servers for
    server-side client correlation.

Auth opts:

  * `:auth_mode` — `:internal` (default), `:external`, or `:pki`.
    External and PKI modes require `Aerospike.Transport.Tls`.
  * `:user` / `:password` — cluster-wide credentials. Must be passed
    together or omitted together. PKI auth uses the TLS client certificate
    and must omit both.
  * `:login_timeout_ms` — startup/login read deadline in milliseconds.

TCP-level tuning knobs (passed verbatim to the TCP transport via the
`:connect_opts` keyword):

  * `:connect_timeout_ms` — handshake + write-buffer drain deadline.
  * `:info_timeout` — read deadline applied to every `info/2` call.
    Defaults to `:connect_timeout_ms`.
  * `:tcp_nodelay` — boolean, default `true`.
  * `:tcp_keepalive` — boolean, default `true`.
  * `:tcp_sndbuf` / `:tcp_rcvbuf` — positive integer kernel buffer
    sizes. Unset lets the kernel pick.

`Aerospike.Cluster.Supervisor` documents the underlying validation and child
ownership details.

# `stats`

```elixir
@spec stats(cluster()) :: map()
```

Returns the current internal runtime metrics snapshot for `cluster`.

The returned map is intentionally limited to the counters and cluster
metadata the runtime collector actually records today. It is not a
generalized exporter surface.

# `stream!`

> This function is deprecated. Use scan_stream!/3 instead..

```elixir
@spec stream!(cluster(), Aerospike.Scan.t(), scan_query_opts()) :: Enumerable.t()
```

Deprecated alias for `scan_stream!/3`.

# `touch`

```elixir
@spec touch(cluster(), Aerospike.Key.key_input(), write_opts()) ::
  {:ok, Aerospike.Record.metadata()}
  | {:error, Aerospike.Error.t()}
  | {:error, :cluster_not_ready | :no_master | :unknown_node}
```

Updates `key`'s header metadata in `cluster`.

Supported write opts are `:timeout`, `:socket_timeout`, `:max_retries`,
`:sleep_between_retries_ms`, `:ttl`, `:generation`, `:generation_policy`,
`:exists`, `:commit_level`, `:durable_delete`, `:respond_per_op`,
`:send_key`, `:read_mode_ap`, `:read_mode_sc`,
`:read_touch_ttl_percent`, `:use_compression`, and `:filter`.

Accepts `%Aerospike.Key{}` or `{namespace, set, user_key}`.

# `transaction`

```elixir
@spec transaction(named_cluster(), (Aerospike.Txn.t() -&gt; term())) ::
  {:ok, term()} | {:error, Aerospike.Error.t()}
```

Runs a function within a new transaction on the named cluster `cluster`.

The callback owns the public transaction lifecycle. The driver initializes the
runtime tracking row before invoking `fun`, then commits on success or aborts
on any failure path. Do not call `commit/2` or `abort/2` from inside the
callback.

The `%Aerospike.Txn{}` passed to `fun` is safe only for sequential use within
that transaction. Do not share it across concurrent processes, and do not use
scans or queries with it; the current transaction proof covers only
transaction-aware single-record commands. This helper currently requires the
registered cluster atom.

# `transaction`

```elixir
@spec transaction(
  named_cluster(),
  Aerospike.Txn.t() | transaction_opts(),
  (Aerospike.Txn.t() -&gt; term())
) ::
  {:ok, term()} | {:error, Aerospike.Error.t()}
```

Runs a function within a transaction on the named cluster `cluster` using a
provided handle or options.

When `txn_or_opts` is a `%Aerospike.Txn{}`, the driver initializes fresh
runtime tracking for that handle on `cluster` at callback entry. Reusing the
same handle concurrently or against another cluster is unsupported. This
helper currently requires the registered cluster atom.

# `truncate`

```elixir
@spec truncate(cluster(), String.t(), truncate_opts()) ::
  :ok | {:error, Aerospike.Error.t()}
```

Truncates all records in `namespace`.

This sends one truncate info command through the shared admin seam. The
server then distributes the truncate across the cluster.

Options:

  * `:before` — `%DateTime{}` — truncate only records whose last-update
    time is older than the provided timestamp
  * `:pool_checkout_timeout` — non-negative integer checkout timeout in
    milliseconds for the one-node admin request

# `truncate`

```elixir
@spec truncate(cluster(), String.t(), String.t(), truncate_opts()) ::
  :ok | {:error, Aerospike.Error.t()}
```

Truncates all records in `namespace` and `set`.

Like `truncate/3`, this uses the shared one-node admin info seam. The optional
`:before` filter is forwarded to the server as a last-update cutoff.

Options:

  * `:before` — `%DateTime{}` — truncate only records whose last-update
    time is older than the provided timestamp
  * `:pool_checkout_timeout` — non-negative integer checkout timeout in
    milliseconds for the one-node admin request

# `txn_status`

```elixir
@spec txn_status(named_cluster(), Aerospike.Txn.t()) ::
  {:ok, :open | :verified | :committed | :aborted}
  | {:error, Aerospike.Error.t()}
```

Returns the current state of a transaction on the named cluster `cluster`.

This reflects only the in-flight states backed by the runtime tracking row.
After commit or abort, the driver cleans that row up, so `txn_status/2`
returns an error instead of a terminal `:committed` or `:aborted` state.
Like the other transaction lifecycle helpers, this currently requires the
registered cluster atom.

# `warm_up`

```elixir
@spec warm_up(cluster(), warm_up_opts()) ::
  {:ok, map()} | {:error, Aerospike.Error.t()}
```

Verifies that the active node pools can serve checkouts through the normal path.

This is an explicit operator action; it does not toggle metrics and it does
not change the pool startup mode. The current driver pools are already eager at
cluster start. `warm_up/2` simply proves that the active pools can hand out
the requested number of workers right now.

Supported options:

  * `:count` — non-negative integer. `0` (default) means "up to the configured
    pool size per active node".
  * `:pool_checkout_timeout` — non-negative integer timeout in milliseconds
    for each checkout probe.

---

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