# `Nebulex.Cache`
[🔗](https://github.com/elixir-nebulex/nebulex/blob/v3.1.0/lib/nebulex/cache.ex#L1)

Cache abstraction layer inspired by
[Ecto](https://github.com/elixir-ecto/ecto).

A cache maps to an underlying in-memory storage controlled by the adapter.
For example, the `nebulex_local` package provides an adapter that
implements a local generational cache.

The cache expects the `:otp_app` and `:adapter` as options when used.
The `:otp_app` should point to an OTP application with the cache
configuration. See the compile time options for more information:

* `:otp_app` (`t:atom/0`) - Required. The OTP application the cache configuration is under.

* `:adapter` (`t:module/0`) - Required. The cache adapter module. It must implement `Nebulex.Adapter`,
  `Nebulex.Adapter.KV`, and `Nebulex.Adapter.CompositeKV`.

* `:default_dynamic_cache` (`t:atom/0`) - Default dynamic cache for executing cache commands. Set to the
  defined cache module by default. For example, when you call
  `MyApp.Cache.start_link/1`, it will start a cache with the name
  `MyApp.Cache`.

  See the ["Dynamic caches"](#module-dynamic-caches) section
  for more information.

* `:adapter_opts` (`t:keyword/0`) - Specifies a list of options passed to the adapter in compilation time. The default value is `[]`.

For example, the cache:

    defmodule MyApp.Cache do
      use Nebulex.Cache,
        otp_app: :my_app,
        adapter: Nebulex.Adapters.Local
    end

Could be configured with:

    config :my_app, MyApp.Cache,
      gc_interval: :timer.hours(12),
      max_size: 1_000_000,
      allocated_memory: 2_000_000_000,
      gc_memory_check_interval: :timer.seconds(10)

Most of the configuration that goes into the `config` is specific
to the adapter. For this particular example, you can check
[`Nebulex.Adapters.Local`][local_adapter] for more information.
Despite this, the following configuration values are shared
across all adapters:

* `:name` (`t:atom/0` | `{:via, reg_mod :: module(), via_name :: any()}`) - The name of the supervisor process the cache is started under.
  Set to the defined cache module by default. For example, when
  you call `MyApp.Cache.start_link/1`, a cache named `MyApp.Cache`
  is started.

* `:telemetry` (`t:boolean/0`) - A flag to determine whether to emit the Telemetry cache command events.

  This sets the default behavior for all cache commands. It can be overridden
  on a per-command basis using the `:telemetry` option when calling a cache
  function. See the ["Shared options"](#module-shared-options) section for
  more information.

  The default value is `true`.

* `:telemetry_prefix` (list of `t:atom/0`) - Nebulex emits cache events using the [Telemetry](`:telemetry`) library.
  By default, the telemetry prefix is based on the module name, so if your
  module is called `MyApp.Cache`, the prefix will be `[:my_app, :cache]`.
  See the ["Telemetry events"](#module-telemetry-events) section to see
  which events Nebulex emits out of the box.

  If you have multiple caches, keep the `:telemetry_prefix` consistent for
  each cache and use the `:cache` property within the `:adapter_meta` coming
  in the event metadata to distinguish between caches. For dynamic caches,
  you should additionally use the `:name` property. Alternatively, you can
  use different `:telemetry_prefix` values.

* `:bypass_mode` (`t:boolean/0`) - If `true`, the cache calls are skipped by overwriting the configured
  adapter with `Nebulex.Adapters.Nil` when the cache starts. This option
  is handy for tests if you want to disable or bypass the cache while
  running the tests. The default value is `false`.

[local_adapter]: https://hexdocs.pm/nebulex_local/Nebulex.Adapters.Local.html

## Shared options

All of the cache functions outlined in this module accept the following
options:

* `:timeout` (`t:timeout/0`) - The time in **milliseconds** to wait for a command to finish
  (`:infinity` to wait indefinitely). The default value is `5000`.
  However, it may change depending on the adapter.

  > #### Timeout option {: .warning}
  >
  > Despite being a shared option accepted by almost all cache functions,
  > it is up to the adapter to support it.

* `:telemetry` (`t:boolean/0`) - Overrides the cache-level `:telemetry` setting for this command, so you
  can enable or disable Telemetry per command without starting separate
  cache instances. For example, you might disable it for hot read paths
  and keep it on for writes.

* `:telemetry_event` (list of `t:atom/0`) - The telemetry event name to dispatch the event under. Defaults to the
  `:telemetry_prefix` followed by `:command` (for example,
  `[:my_app, :cache, :command]`). See the
  ["Telemetry events"](#module-telemetry-events) section
  for more information.

* `:telemetry_metadata` (map of `t:term/0` keys and `t:term/0` values) - Extra metadata to add to the Telemetry cache command events.
  These end up in the `:extra_metadata` metadata key of these events.

  See the ["Telemetry events"](#module-telemetry-events) section
  for more information.

> #### Adapter-specific options {: .info}
>
> In addition to the shared options, each adapter can define its
> specific options. Therefore, Nebulex recommends reviewing the
> adapter's documentation.

## Telemetry events

There are two types of telemetry events: the ones emitted by Nebulex and
the adapter-specific ones. The ones emitted by Nebulex fall into two
categories: cache lifecycle events and cache command events.

### Cache lifecycle events

All Nebulex caches emit the following events:

  * `[:nebulex, :cache, :init]` - It is dispatched whenever a cache starts.
    The only measurement is the current system time in native units from
    calling: `System.system_time()`. The `:opts` key in the metadata
    contains all initialization options.

    * Measurement: `%{system_time: integer()}`
    * Metadata: `%{cache: module(), name: atom(), opts: keyword()}`

### Cache command events

When the option `:telemetry` is set to `true` (the default), Nebulex will
emit Telemetry span events for each cache command. Those events will use
the `:telemetry_prefix` outlined in the options above which defaults to
`[:my_app, :cache]`.

For instance, to receive all events published for the cache `MyApp.Cache`,
one could define a module:

    defmodule MyApp.Telemetry do
      def handle_event(
            [:my_app, :cache, :command, event],
            measurements,
            metadata,
            _config
          ) do
        case event do
          :start ->
            # Handle start event ...
            IO.puts("Cache command started: #{inspect(metadata.command)}")
            IO.puts("Arguments: #{inspect(metadata.args)}")

          :stop ->
            # Handle stop event ...
            duration = measurements.duration
            IO.puts("Cache command completed: #{inspect(metadata.command)}")
            IO.puts("Duration: #{duration} native units")
            IO.puts("Result: #{inspect(metadata.result)}")

          :exception ->
            # Handle exception event ...
            IO.puts("Cache command failed: #{inspect(metadata.command)}")
            IO.puts("Error: #{inspect(metadata.reason)}")
            IO.puts("Stacktrace: #{inspect(metadata.stacktrace)}")
        end
      end
    end

Then, in the `Application.start/2` callback, attach the handler to the events
using a unique handler id:

    :telemetry.attach_many(
      "my-app-handler-id",
      [
        [:my_app, :cache, :command, :start],
        [:my_app, :cache, :command, :stop],
        [:my_app, :cache, :command, :exception]
      ],
      &MyApp.Telemetry.handle_event/4,
      :no_config
    )

The following are the events you should expect from Nebulex. All examples
below consider a cache named `MyApp.Cache`:

#### `[:my_app, :cache, :command, :start]`

This event is emitted before a cache command is executed.

The `:measurements` map will include the following:

  * `:system_time` - The current system time in native units from calling:
    `System.system_time()`.

A Telemetry `:metadata` map including the following fields:

  * `:adapter_meta` - The adapter metadata.
  * `:command` - The name of the invoked adapter's command.
  * `:args` - The arguments passed to the invoked adapter, except for the
    first one, since the adapter's metadata is available in the event's
    metadata. These are the adapter-level arguments, not the ones given
    to the cache function. For example, `put/3` pops `:ttl` and
    `:keep_ttl` from the options and passes them as positional arguments.
  * `:extra_metadata` - Additional metadata through the runtime option
    `:telemetry_metadata`.

Example event data:

    %{
      measurements: %{system_time: 1_678_123_456_789},
      metadata: %{
        adapter_meta: %{...},
        command: :put,
        args: [
          "key",
          "value",
          :put,
          10_000,
          false,
          [telemetry_metadata: %{user_id: 123}]
        ],
        extra_metadata: %{user_id: 123}
      }
    }

#### `[:my_app, :cache, :command, :stop]`

This event is emitted after a cache command is executed.

The `:measurements` map will include the following:

  * `:duration` - The time spent executing the cache command. The measurement
    is given in the `:native` time unit. You can read more about it in the
    docs for `System.convert_time_unit/3`.

A Telemetry `:metadata` map including the following fields:

  * `:adapter_meta` - The adapter metadata.
  * `:command` - The name of the invoked adapter's command.
  * `:args` - The arguments passed to the invoked adapter, except for the
    first one, since the adapter's metadata is available in the event's
    metadata. These are the adapter-level arguments, not the ones given
    to the cache function. For example, `put/3` pops `:ttl` and
    `:keep_ttl` from the options and passes them as positional arguments.
  * `:extra_metadata` - Additional metadata through the runtime option
    `:telemetry_metadata`.
  * `:result` - The adapter's result for the command, before the cache
    function turns it into its public return value. For example, `put/3`
    returns `:ok` but the adapter result is `{:ok, true}`. A command that
    fails with `{:error, reason}` still emits this event; the error tuple
    is the `:result`.

Example event data:

    %{
      measurements: %{duration: 1_234_567},
      metadata: %{
        adapter_meta: %{...},
        command: :put,
        args: [
          "key",
          "value",
          :put,
          10_000,
          false,
          [telemetry_metadata: %{user_id: 123}]
        ],
        extra_metadata: %{user_id: 123},
        result: {:ok, true}
      }
    }

#### `[:my_app, :cache, :command, :exception]`

This event is emitted only when the adapter raises, exits, or throws
while running the command. A command that returns `{:error, reason}` does
not emit it; the error arrives in the `:stop` event's `:result`.

The `:measurements` map will include the following:

  * `:duration` - The time spent executing the cache command. The measurement
    is given in the `:native` time unit. You can read more about it in the
    docs for `System.convert_time_unit/3`.

A Telemetry `:metadata` map including the following fields:

  * `:adapter_meta` - The adapter metadata.
  * `:command` - The name of the invoked adapter's command.
  * `:args` - The arguments passed to the invoked adapter, except for the
    first one, since the adapter's metadata is available in the event's
    metadata. These are the adapter-level arguments, not the ones given
    to the cache function. For example, `put/3` pops `:ttl` and
    `:keep_ttl` from the options and passes them as positional arguments.
  * `:extra_metadata` - Additional metadata through the runtime option
    `:telemetry_metadata`.
  * `:kind` - The type of the error: `:error`, `:exit`, or `:throw`.
  * `:reason` - The reason of the error.
  * `:stacktrace` - Exception's stack trace.

Example event data:

    %{
      measurements: %{duration: 1_234_567},
      metadata: %{
        adapter_meta: %{...},
        command: :put,
        args: [
          "key",
          "value",
          :put,
          10_000,
          false,
          [telemetry_metadata: %{user_id: 123}]
        ],
        extra_metadata: %{user_id: 123},
        kind: :error,
        reason: %RuntimeError{message: "connection refused"},
        stacktrace: [...]
      }
    }

### Adapter-specific events

Adapters are free to emit their own events in addition to the ones above,
but those are out of Nebulex's scope. Check the adapter's documentation if
you want to use them.

## Dynamic caches

Nebulex allows you to start multiple processes from the same cache module.
This feature is typically useful when you want to have different cache
instances but access them through the same cache module.

When you list a cache in your supervision tree, such as `MyApp.Cache`, it will
start a supervision tree with a process named `MyApp.Cache` under the hood.
By default, the process has the same name as the cache module. Hence, whenever
you invoke a function in `MyApp.Cache`, such as `MyApp.Cache.put/3`, Nebulex
will execute the command in the cache process named `MyApp.Cache`.

However, with Nebulex, you can start multiple processes from the same cache.
The only requirement is that they must have different process names, like
this:

    children = [
      MyApp.Cache,
      {MyApp.Cache, name: MyApp.UsersCache}
    ]

Now you have two cache instances running: one is named `MyApp.Cache`, and the
other one is named `MyApp.UsersCache`. You can tell Nebulex which process you
want to use in your cache operations by calling:

    MyApp.Cache.put_dynamic_cache(MyApp.Cache)
    MyApp.Cache.put_dynamic_cache(MyApp.UsersCache)

Once you call `MyApp.Cache.put_dynamic_cache(name)`, all invocations made on
`MyApp.Cache` will use the cache instance denoted by `name`.

Nebulex also provides a handy function for invoking commands using dynamic
caches: `c:with_dynamic_cache/2`.

    MyApp.Cache.with_dynamic_cache(MyApp.UsersCache, fn ->
      # all commands here will use MyApp.UsersCache
      MyApp.Cache.put("u1", "joe")
      ...
    end)

You may also want to pass the dynamic cache directly to the command instead
of going through `c:put_dynamic_cache/1` or `c:with_dynamic_cache/2`. Since
v3.0, every Cache API command has a variant that takes the dynamic cache as
its first argument:

    MyApp.Cache.put(MyApp.UsersCache, "u1", "joe", ttl: :timer.hours(1))
    MyApp.Cache.get(MyApp.UsersCache, "u1", nil, [])
    MyApp.Cache.delete(MyApp.UsersCache, "u1", [])

## Declarative caching with decorators

Besides the cache API, Nebulex provides caching decorators so you can
annotate functions instead of calling the cache directly. See
`Nebulex.Caching.Decorators` for the `cacheable`, `cache_put`, and
`cache_evict` decorators.

## Distributed topologies

One of the goals of Nebulex is also to provide the ability to set up
distributed cache topologies, but this feature will depend on the adapters.

See ["Nebulex Distributed"](https://github.com/elixir-nebulex/nebulex_distributed).

# `init`
*optional* 

```elixir
@callback init(config) :: {:ok, config} | :ignore when config: keyword()
```

A callback executed when the cache starts or when configuration is read.

# `__adapter__`

```elixir
@callback __adapter__() :: Nebulex.Adapter.t()
```

Returns the adapter tied to the cache.

# `config`

```elixir
@callback config() :: keyword()
```

Returns the adapter configuration stored in the `:otp_app` environment.

If the `c:init/1` callback is implemented in the cache, it will be invoked.

# `get_dynamic_cache`

```elixir
@callback get_dynamic_cache() :: dynamic_cache()
```

Returns the atom name or pid of the current cache
(based on Ecto dynamic repo).

See also `c:put_dynamic_cache/1`.

# `put_dynamic_cache`

```elixir
@callback put_dynamic_cache(dynamic_cache()) :: dynamic_cache()
```

Sets the dynamic cache to be used in further commands
(based on Ecto dynamic repo).

There are cases where you may want to have different cache instances but
access them through the same cache module. By default, when you call
`MyApp.Cache.start_link/1`, it will start a cache with the name
`MyApp.Cache`. But it is also possible to start multiple caches by using
a different name for each of them:

    MyApp.Cache.start_link(name: :cache1)
    MyApp.Cache.start_link(name: :cache2)

You can also start caches without names by explicitly setting the name
to `nil`:

    MyApp.Cache.start_link(name: nil)

> #### Adapter requirements {: .info}
>
> Some adapters require the `:name` option anyway; check the documentation
> of the adapter you want to use.

All operations through `MyApp.Cache` are sent by default to the cache named
`MyApp.Cache`. But you can change the default cache at compile-time:

    use Nebulex.Cache, default_dynamic_cache: :cache_name

Or anytime at runtime by calling `put_dynamic_cache/1`:

    MyApp.Cache.put_dynamic_cache(:another_cache_name)

From this moment on, all future commands performed by the current process
will run on `:another_cache_name`.

All cache commands also accept the dynamic cache (name or PID) as the
first argument, so you can address a cache instance directly. See the
["Dynamic caches"](#module-dynamic-caches) section in the module
documentation for more information.

# `start_link`

```elixir
@callback start_link(opts()) ::
  {:ok, pid()} | :ignore | {:error, {:already_started, pid()}} | {:error, any()}
```

Starts a supervision tree and returns `{:ok, pid}`, or `:ignore` when the
`c:init/1` callback returns `:ignore`.

Returns `{:error, {:already_started, pid}}` if the cache is already
started or `{:error, term}` in case anything else goes wrong.

## Options

See the configuration in the moduledoc for options shared between adapters,
for adapter-specific configuration see the adapter's documentation.

# `stop`

```elixir
@callback stop(opts()) :: :ok
```

Shuts down the cache.

## Options

  `:timeout` - It is an integer that specifies how many milliseconds to wait
  for the cache supervisor process to terminate, or the atom `:infinity` to
  wait indefinitely. Defaults to `5000`. See `Supervisor.stop/3`.

This is the only option; the shared runtime options do not apply.

# `stop`

```elixir
@callback stop(dynamic_cache(), opts()) :: :ok
```

Same as `c:stop/1` but stops the cache instance given in the first argument
`dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

# `with_dynamic_cache`

```elixir
@callback with_dynamic_cache(dynamic_cache(), fun()) :: any()
```

Invokes the function `fun` using the given dynamic cache.

## Example

    MyCache.with_dynamic_cache(:my_cache, fn ->
      MyCache.put("foo", "var")
    end)

See `c:get_dynamic_cache/0` and `c:put_dynamic_cache/1`.

# `decr`

```elixir
@callback decr(key(), amount :: integer(), opts()) :: ok_error_tuple(integer())
```

Decrements the counter stored at `key` by the given `amount` and returns
the current count as `{:ok, count}`.

If `amount < 0`, the value is incremented by that `amount` instead
(opposite to `incr/3`).

If there's an error with executing the command, `{:error, reason}`
is returned, where `reason` is the cause of the error.

If the key doesn't exist, the TTL is set. Otherwise, only the counter value
is updated, keeping the TTL set for the first time.

## Options

* `:ttl` (`t:timeout/0`) - The key's time-to-live (or expiry time) in **milliseconds**
  (`:infinity` to store indefinitely). The default value is `:infinity`.

* `:default` (`t:integer/0`) - If the key is not present in the cache, the default value is inserted as
  the key's initial value before it is incremented. The default value is `0`.

See the ["Shared options"](#module-shared-options) section in the module
documentation for more options.

## Examples

    iex> MyCache.decr(:a)
    {:ok, -1}
    iex> MyCache.decr(:a, 2)
    {:ok, -3}
    iex> MyCache.decr(:a, -1)
    {:ok, -2}

    iex> MyCache.decr(:missing_key, 2, default: 10)
    {:ok, 8}

# `decr`

```elixir
@callback decr(dynamic_cache(), key(), amount :: integer(), opts()) ::
  ok_error_tuple(integer())
```

Same as `c:decr/3`, but the command is executed on the cache instance
given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    iex> MyCache.decr(MyCache1, :a, 1, [])
    {:ok, -1}

# `decr!`

```elixir
@callback decr!(key(), amount :: integer(), opts()) :: integer()
```

Same as `c:decr/3` but raises an exception if an error occurs.

## Examples

    iex> MyCache.decr!(:a)
    -1

# `decr!`

```elixir
@callback decr!(dynamic_cache(), key(), amount :: integer(), opts()) :: integer()
```

Same as `c:decr/4` but raises an exception if an error occurs.

# `delete`

```elixir
@callback delete(key(), opts()) :: :ok | error_tuple()
```

Deletes the entry in the cache for a specific `key`.

If there's an error with executing the command, `{:error, reason}`
is returned, where `reason` is the cause of the error.

## Options

See the ["Shared options"](#module-shared-options) section in the module
documentation for more options.

## Examples

    iex> MyCache.put(:a, 1)
    :ok
    iex> MyCache.delete(:a)
    :ok
    iex> MyCache.get!(:a)
    nil
    iex> MyCache.delete(:nonexistent)
    :ok

# `delete`

```elixir
@callback delete(dynamic_cache(), key(), opts()) :: :ok | error_tuple()
```

Same as `c:delete/2`, but the command is executed on the cache instance
given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    iex> MyCache.delete(MyCache1, :a, [])
    :ok

# `delete!`

```elixir
@callback delete!(key(), opts()) :: :ok
```

Same as `c:delete/2` but raises an exception if an error occurs.

# `delete!`

```elixir
@callback delete!(dynamic_cache(), key(), opts()) :: :ok
```

Same as `c:delete/3` but raises an exception if an error occurs.

# `expire`

```elixir
@callback expire(key(), ttl(), opts()) :: ok_error_tuple(boolean())
```

Returns `{:ok, true}` if the given `key` exists and the new `ttl` is
successfully updated; otherwise, `{:ok, false}` is returned.

If there's an error with executing the command, `{:error, reason}`
is returned; where `reason` is the cause of the error.

## Options

See the ["Shared options"](#module-shared-options) section in the module
documentation for more options.

## Examples

    iex> MyCache.put(:a, 1)
    :ok
    iex> MyCache.expire(:a, :timer.hours(1))
    {:ok, true}
    iex> MyCache.expire(:a, :infinity)
    {:ok, true}
    iex> MyCache.expire(:b, 5)
    {:ok, false}

# `expire`

```elixir
@callback expire(dynamic_cache(), key(), ttl(), opts()) :: ok_error_tuple(boolean())
```

Same as `c:expire/3`, but the command is executed on the cache instance
given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    iex> MyCache.expire(MyCache1, :a, :timer.hours(1), [])
    {:ok, false}

# `expire!`

```elixir
@callback expire!(key(), ttl(), opts()) :: boolean()
```

Same as `c:expire/3` but raises an exception if an error occurs.

## Examples

    iex> MyCache.put(:a, 1)
    :ok
    iex> MyCache.expire!(:a, :timer.hours(1))
    true

# `expire!`

```elixir
@callback expire!(dynamic_cache(), key(), ttl(), opts()) :: boolean()
```

Same as `c:expire/4` but raises an exception if an error occurs.

# `fetch`

```elixir
@callback fetch(key(), opts()) :: ok_error_tuple(value(), fetch_error_reason())
```

Fetches the value for a specific `key` in the cache.

If the cache contains the given `key`, then its value is returned
in the shape of `{:ok, value}`.

If there's an error with executing the command, `{:error, reason}`
is returned. `reason` is the cause of the error and can be
`Nebulex.KeyError` if the cache does not contain `key`,
`Nebulex.Error` otherwise.

## Options

See the ["Shared options"](#module-shared-options) section in the module
documentation for more options.

## Examples

    iex> MyCache.put("foo", "bar")
    :ok
    iex> MyCache.fetch("foo")
    {:ok, "bar"}

    # Key not found error
    iex> {:error, %Nebulex.KeyError{key: "bar"} = e} = MyCache.fetch("bar")
    iex> e.reason
    :not_found

# `fetch`

```elixir
@callback fetch(dynamic_cache(), key(), opts()) ::
  ok_error_tuple(value(), fetch_error_reason())
```

Same as `c:fetch/2`, but the command is executed on the cache instance
given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    iex> MyCache.put(MyCache1, "foo", "bar", [])
    :ok
    iex> MyCache.fetch(MyCache1, "foo", [])
    {:ok, "bar"}

# `fetch!`

```elixir
@callback fetch!(key(), opts()) :: value()
```

Same as `c:fetch/2` but raises `Nebulex.KeyError` if the cache doesn't contain
`key` or `Nebulex.Error` if another error occurs while executing the command.

## Examples

    iex> MyCache.put("foo", "bar")
    :ok
    iex>  MyCache.fetch!("foo")
    "bar"

# `fetch!`

```elixir
@callback fetch!(dynamic_cache(), key(), opts()) :: value()
```

Same as `c:fetch/3` but raises `Nebulex.KeyError` if the cache doesn't contain
`key` or `Nebulex.Error` if another error occurs while executing the command.

# `get`

```elixir
@callback get(key(), default :: value(), opts()) :: ok_error_tuple(value())
```

Gets a value from the cache where the key matches the given `key`.

If the cache contains the given `key` its value is returned as
`{:ok, value}`.

If the cache does not contain `key`, `{:ok, default}` is returned.

If there's an error with executing the command, `{:error, reason}`
is returned, where `reason` is the cause of the error.

## Options

See the ["Shared options"](#module-shared-options) section in the module
documentation for more options.

## Examples

    iex> MyCache.put("foo", "bar")
    :ok
    iex> MyCache.get("foo")
    {:ok, "bar"}
    iex> MyCache.get(:nonexistent)
    {:ok, nil}
    iex> MyCache.get(:nonexistent, :default)
    {:ok, :default}

# `get`

```elixir
@callback get(dynamic_cache(), key(), default :: value(), opts()) ::
  ok_error_tuple(value())
```

Same as `c:get/3`, but the command is executed on the cache instance
given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    iex> MyCache.get(MyCache1, "key", nil, [])
    {:ok, nil}

# `get!`

```elixir
@callback get!(key(), default :: value(), opts()) :: value()
```

Same as `c:get/3` but raises an exception if an error occurs.

# `get!`

```elixir
@callback get!(dynamic_cache(), key(), default :: value(), opts()) :: value()
```

Same as `c:get/4` but raises an exception if an error occurs.

# `has_key?`

```elixir
@callback has_key?(key(), opts()) :: ok_error_tuple(boolean())
```

Determines if the cache contains an entry for the specified `key`.

More formally, it returns `{:ok, true}` if the cache contains the given `key`.
If the cache doesn't contain `key`, `{:ok, false}` is returned.

If there's an error with executing the command, `{:error, reason}`
is returned, where `reason` is the cause of the error.

## Options

See the ["Shared options"](#module-shared-options) section in the module
documentation for more options.

## Examples

    iex> MyCache.put(:a, 1)
    :ok
    iex> MyCache.has_key?(:a)
    {:ok, true}
    iex> MyCache.has_key?(:b)
    {:ok, false}

# `has_key?`

```elixir
@callback has_key?(dynamic_cache(), key(), opts()) :: ok_error_tuple(boolean())
```

Same as `c:has_key?/2`, but the command is executed on the cache instance
given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    iex> MyCache.has_key?(MyCache1, :a, [])
    {:ok, false}

# `incr`

```elixir
@callback incr(key(), amount :: integer(), opts()) :: ok_error_tuple(integer())
```

Increments the counter stored at `key` by the given `amount` and returns
the current count as `{:ok, count}`.

If `amount < 0`, the value is decremented by that `amount` instead.

If there's an error with executing the command, `{:error, reason}`
is returned, where `reason` is the cause of the error.

If the key doesn't exist, the TTL is set. Otherwise, only the counter value
is updated, keeping the TTL set for the first time.

## Options

* `:ttl` (`t:timeout/0`) - The key's time-to-live (or expiry time) in **milliseconds**
  (`:infinity` to store indefinitely). The default value is `:infinity`.

* `:default` (`t:integer/0`) - If the key is not present in the cache, the default value is inserted as
  the key's initial value before it is incremented. The default value is `0`.

See the ["Shared options"](#module-shared-options) section in the module
documentation for more options.

## Examples

    iex> MyCache.incr(:a)
    {:ok, 1}
    iex> MyCache.incr(:a, 2)
    {:ok, 3}
    iex> MyCache.incr(:a, -1)
    {:ok, 2}
    iex> MyCache.incr(:missing_key, 2, default: 10)
    {:ok, 12}

    # Initialize the counter with a TTL
    iex> MyCache.incr(:new_counter, 10, ttl: :timer.seconds(1))
    {:ok, 10}

# `incr`

```elixir
@callback incr(dynamic_cache(), key(), amount :: integer(), opts()) ::
  ok_error_tuple(integer())
```

Same as `c:incr/3`, but the command is executed on the cache instance
given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    iex> MyCache.incr(MyCache1, :a, 1, [])
    {:ok, 1}

# `incr!`

```elixir
@callback incr!(key(), amount :: integer(), opts()) :: integer()
```

Same as `c:incr/3` but raises an exception if an error occurs.

## Examples

    iex> MyCache.incr!(:a)
    1
    iex> MyCache.incr!(:a, 2)
    3

# `incr!`

```elixir
@callback incr!(dynamic_cache(), key(), amount :: integer(), opts()) :: integer()
```

Same as `c:incr/4` but raises an exception if an error occurs.

# `put`

```elixir
@callback put(key(), value(), opts()) :: :ok | error_tuple()
```

Puts the given `value` under `key` into the cache.

If `key` already holds an entry, it is overwritten. Any previous TTL
(time to live) associated with the key is discarded on a successful
`put` operation.

Returns `:ok` if successful; `{:error, reason}` otherwise.

## Options

* `:ttl` (`t:timeout/0`) - The key's time-to-live (or expiry time) in **milliseconds**
  (`:infinity` to store indefinitely). The default value is `:infinity`.

* `:keep_ttl` (`t:boolean/0`) - Indicates whether to retain the time to live associated with the key.
  Otherwise, the value in the `:ttl` option overwrites the existing one. The default value is `false`.

See the ["Shared options"](#module-shared-options) section in the module
documentation for more options.

## Examples

    iex> MyCache.put("foo", "bar")
    :ok

Putting entries with specific time-to-live:

    iex> MyCache.put("foo", "bar", ttl: :timer.seconds(10))
    :ok
    iex> MyCache.put("foo", "bar", ttl: :timer.hours(1))
    :ok
    iex> MyCache.put("foo", "bar", ttl: :timer.minutes(1))
    :ok
    iex> MyCache.put("foo", "bar", ttl: :timer.seconds(30))
    :ok

To keep the current TTL in case the key already exists:

    iex> MyCache.put("foo", "bar", keep_ttl: true)
    :ok

# `put`

```elixir
@callback put(dynamic_cache(), key(), value(), opts()) :: :ok | error_tuple()
```

Same as `c:put/3`, but the command is executed on the cache instance
given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    iex> MyCache.put(MyCache1, "foo", "bar", [])
    :ok
    iex> MyCache.put(MyCache2, "foo", "bar", ttl: :timer.hours(1))
    :ok

# `put!`

```elixir
@callback put!(key(), value(), opts()) :: :ok
```

Same as `c:put/3` but raises an exception if an error occurs.

# `put!`

```elixir
@callback put!(dynamic_cache(), key(), value(), opts()) :: :ok
```

Same as `c:put/4` but raises an exception if an error occurs.

# `put_all`

```elixir
@callback put_all(entries(), opts()) :: :ok | error_tuple()
```

Puts the given `entries` (key/value pairs) into the cache. It replaces
existing values with new values (just as regular `put`).

Returns `:ok` if successful; `{:error, reason}` otherwise.

## Options

* `:ttl` (`t:timeout/0`) - The key's time-to-live (or expiry time) in **milliseconds**
  (`:infinity` to store indefinitely). The default value is `:infinity`.

See the ["Shared options"](#module-shared-options) section in the module
documentation for more options.

## Examples

    iex> MyCache.put_all(apples: 3, bananas: 1)
    :ok
    iex> MyCache.put_all(%{apples: 2, oranges: 1}, ttl: :timer.hours(1))
    :ok

> #### Atomic operation {: .warning}
>
> Ideally, this operation should be atomic, so all given keys are put at once.
> But it depends purely on the adapter's implementation and the backend used
> internally by the adapter. Hence, reviewing the adapter's documentation is
> highly recommended.

# `put_all`

```elixir
@callback put_all(dynamic_cache(), entries(), opts()) :: :ok | error_tuple()
```

Same as `c:put_all/2`, but the command is executed on the cache instance
given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    iex> MyCache.put_all(MyCache1, [apples: 3, bananas: 1], [])
    :ok
    iex> MyCache.put_all(MyCache1, %{oranges: 1}, ttl: :timer.hours(1))
    :ok

# `put_all!`

```elixir
@callback put_all!(entries(), opts()) :: :ok
```

Same as `c:put_all/2` but raises an exception if an error occurs.

# `put_all!`

```elixir
@callback put_all!(dynamic_cache(), entries(), opts()) :: :ok
```

Same as `c:put_all/3` but raises an exception if an error occurs.

# `put_new`

```elixir
@callback put_new(key(), value(), opts()) :: ok_error_tuple(boolean())
```

Puts the given `value` under `key` into the cache only if it does not
already exist.

Returns `{:ok, true}` if the value is stored; otherwise, `{:ok, false}`
is returned.

If there's an error with executing the command, `{:error, reason}`
is returned, where `reason` is the cause of the error.

## Options

* `:ttl` (`t:timeout/0`) - The key's time-to-live (or expiry time) in **milliseconds**
  (`:infinity` to store indefinitely). The default value is `:infinity`.

See the ["Shared options"](#module-shared-options) section in the module
documentation for more options.

## Examples

    iex> MyCache.put_new("foo", "bar")
    {:ok, true}
    iex> MyCache.put_new("foo", "bar", ttl: :timer.hours(1))
    {:ok, false}

# `put_new`

```elixir
@callback put_new(dynamic_cache(), key(), value(), opts()) :: ok_error_tuple(boolean())
```

Same as `c:put_new/3`, but the command is executed on the cache instance
given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    iex> MyCache.put_new(MyCache1, "foo", "bar", [])
    {:ok, true}
    iex> MyCache.put_new(MyCache1, "foo", "bar", ttl: :timer.hours(1))
    {:ok, false}

# `put_new!`

```elixir
@callback put_new!(key(), value(), opts()) :: boolean()
```

Same as `c:put_new/3` but raises an exception if an error occurs.

## Examples

    iex> MyCache.put_new!("foo", "bar")
    true
    iex> MyCache.put_new!("foo", "bar", ttl: :timer.hours(1))
    false

# `put_new!`

```elixir
@callback put_new!(dynamic_cache(), key(), value(), opts()) :: boolean()
```

Same as `c:put_new/4` but raises an exception if an error occurs.

# `put_new_all`

```elixir
@callback put_new_all(entries(), opts()) :: ok_error_tuple(boolean())
```

Puts the given `entries` (key/value pairs) into the `cache`. It will not
perform any operation, even if a single key exists.

Returns `{:ok, true}` if all entries are successfully stored, or
`{:ok, false}` if no key was set (at least one key already existed).

If there's an error with executing the command, `{:error, reason}`
is returned, where `reason` is the cause of the error.

## Options

* `:ttl` (`t:timeout/0`) - The key's time-to-live (or expiry time) in **milliseconds**
  (`:infinity` to store indefinitely). The default value is `:infinity`.

See the ["Shared options"](#module-shared-options) section in the module
documentation for more options.

## Examples

    iex> MyCache.put_new_all(apples: 3, bananas: 1)
    {:ok, true}
    iex> MyCache.put_new_all(%{apples: 3, oranges: 1}, ttl: :timer.hours(1))
    {:ok, false}

> #### Atomic operation {: .warning}
>
> Ideally, this operation should be atomic, so all given keys are put at once.
> But it depends purely on the adapter's implementation and the backend used
> internally by the adapter. Hence, reviewing the adapter's documentation is
> highly recommended.

# `put_new_all`

```elixir
@callback put_new_all(dynamic_cache(), entries(), opts()) :: ok_error_tuple(boolean())
```

Same as `c:put_new_all/2`, but the command is executed on the cache instance
given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    iex> MyCache.put_new_all(MyCache1, [apples: 3, bananas: 1], [])
    {:ok, true}
    iex> MyCache.put_new_all(MyCache1, %{apples: 3, oranges: 1}, ttl: :timer.seconds(10))
    {:ok, false}

# `put_new_all!`

```elixir
@callback put_new_all!(entries(), opts()) :: boolean()
```

Same as `c:put_new_all/2` but raises an exception if an error occurs.

## Examples

    iex> MyCache.put_new_all!(apples: 3, bananas: 1)
    true
    iex> MyCache.put_new_all!(%{apples: 3, oranges: 1}, ttl: :timer.hours(1))
    false

# `put_new_all!`

```elixir
@callback put_new_all!(dynamic_cache(), entries(), opts()) :: boolean()
```

Same as `c:put_new_all/3` but raises an exception if an error occurs.

# `replace`

```elixir
@callback replace(key(), value(), opts()) :: ok_error_tuple(boolean())
```

Alters the entry stored under `key`, but only if the entry already exists
in the cache.

Returns `{:ok, true}` if the value is replaced. Otherwise, `{:ok, false}`
is returned.

If there's an error with executing the command, `{:error, reason}`
is returned, where `reason` is the cause of the error.

## Options

* `:ttl` (`t:timeout/0`) - The key's time-to-live (or expiry time) in **milliseconds**
  (`:infinity` to store indefinitely). The default value is `:infinity`.

* `:keep_ttl` (`t:boolean/0`) - Indicates whether to retain the time to live associated with the key.
  Otherwise, the value in the `:ttl` option overwrites the existing one. The default value is `true`.

See the ["Shared options"](#module-shared-options) section in the module
documentation for more options.

## Examples

    iex> MyCache.replace("foo", "bar")
    {:ok, false}
    iex> MyCache.put_new("foo", "bar")
    {:ok, true}
    iex> MyCache.replace("foo", "bar2")
    {:ok, true}

Update current value and TTL:

    iex> MyCache.replace("foo", "bar3", ttl: :timer.seconds(10))
    {:ok, true}

To keep the current TTL:

    iex> MyCache.replace("foo", "bar4", keep_ttl: true)
    {:ok, true}

# `replace`

```elixir
@callback replace(dynamic_cache(), key(), value(), opts()) :: ok_error_tuple(boolean())
```

Same as `c:replace/3`, but the command is executed on the cache instance
given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    iex> MyCache.replace(MyCache1, "foo", "bar", [])
    {:ok, false}
    iex> MyCache.put_new(MyCache1, "foo", "bar", [])
    {:ok, true}
    iex> MyCache.replace(MyCache1, "foo", "bar", ttl: :timer.hours(1))
    {:ok, true}

# `replace!`

```elixir
@callback replace!(key(), value(), opts()) :: boolean()
```

Same as `c:replace/3` but raises an exception if an error occurs.

## Examples

    iex> MyCache.replace!("foo", "bar")
    false
    iex> MyCache.put_new!("foo", "bar")
    true
    iex> MyCache.replace!("foo", "bar2")
    true

# `replace!`

```elixir
@callback replace!(dynamic_cache(), key(), value(), opts()) :: boolean()
```

Same as `c:replace/4` but raises an exception if an error occurs.

# `take`

```elixir
@callback take(key(), opts()) :: ok_error_tuple(value(), fetch_error_reason())
```

Removes and returns the value associated with `key` in the cache.

If `key` is present in the cache, its value is removed and returned as
`{:ok, value}`.

If there's an error with executing the command, `{:error, reason}`
is returned. `reason` is the cause of the error and can be
`Nebulex.KeyError` if the cache does not contain `key` or
`Nebulex.Error` otherwise.

## Options

See the ["Shared options"](#module-shared-options) section in the module
documentation for more options.

## Examples

    iex> MyCache.put(:a, 1)
    :ok
    iex> MyCache.take(:a)
    {:ok, 1}

    iex> {:error, %Nebulex.KeyError{key: :a} = e} = MyCache.take(:a)
    iex> e.reason
    :not_found

# `take`

```elixir
@callback take(dynamic_cache(), key(), opts()) ::
  ok_error_tuple(value(), fetch_error_reason())
```

Same as `c:take/2`, but the command is executed on the cache instance
given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    iex> MyCache.put(MyCache1, :a, 1, [])
    :ok
    iex> MyCache.take(MyCache1, :a, [])
    {:ok, 1}

# `take!`

```elixir
@callback take!(key(), opts()) :: value()
```

Same as `c:take/2` but raises an exception if an error occurs.

## Examples

    iex> MyCache.put(:a, 1)
    :ok
    iex> MyCache.take!(:a)
    1

# `take!`

```elixir
@callback take!(dynamic_cache(), key(), opts()) :: value()
```

Same as `c:take/3` but raises an exception if an error occurs.

# `touch`

```elixir
@callback touch(key(), opts()) :: ok_error_tuple(boolean())
```

Returns `{:ok, true}` if the given `key` exists and the last access time is
successfully updated; otherwise, `{:ok, false}` is returned.

If there's an error with executing the command, `{:error, reason}`
is returned, where `reason` is the cause of the error.

## Options

See the ["Shared options"](#module-shared-options) section in the module
documentation for more options.

## Examples

    iex> MyCache.put(:a, 1)
    :ok
    iex> MyCache.touch(:a)
    {:ok, true}
    iex> MyCache.touch(:b)
    {:ok, false}

# `touch`

```elixir
@callback touch(dynamic_cache(), key(), opts()) :: ok_error_tuple(boolean())
```

Same as `c:touch/2`, but the command is executed on the cache instance
given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    iex> MyCache.touch(MyCache1, :a, [])
    {:ok, false}

# `touch!`

```elixir
@callback touch!(key(), opts()) :: boolean()
```

Same as `c:touch/2` but raises an exception if an error occurs.

## Examples

    iex> MyCache.put(:a, 1)
    :ok
    iex> MyCache.touch!(:a)
    true

# `touch!`

```elixir
@callback touch!(dynamic_cache(), key(), opts()) :: boolean()
```

Same as `c:touch/3` but raises an exception if an error occurs.

# `ttl`

```elixir
@callback ttl(key(), opts()) :: ok_error_tuple(timeout(), fetch_error_reason())
```

Returns the remaining time-to-live for the given `key`.

If `key` is present in the cache, its remaining TTL is returned as
`{:ok, ttl}`.

If there's an error with executing the command, `{:error, reason}`
is returned. `reason` is the cause of the error and can be
`Nebulex.KeyError` if the cache does not contain `key`,
`Nebulex.Error` otherwise.

## Options

See the ["Shared options"](#module-shared-options) section in the module
documentation for more options.

## Examples

    iex> MyCache.put(:a, 1, ttl: :timer.seconds(5))
    :ok
    iex> MyCache.put(:b, 2)
    :ok
    iex> MyCache.ttl(:a)
    {:ok, _remaining_ttl}
    iex> MyCache.ttl(:b)
    {:ok, :infinity}

    iex> {:error, %Nebulex.KeyError{key: :c} = e} = MyCache.ttl(:c)
    iex> e.reason
    :not_found

# `ttl`

```elixir
@callback ttl(dynamic_cache(), key(), opts()) ::
  ok_error_tuple(timeout(), fetch_error_reason())
```

Same as `c:ttl/2`, but the command is executed on the cache instance
given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    iex> MyCache.put(MyCache1, :a, 1, ttl: :timer.seconds(5))
    :ok
    iex> MyCache.ttl(MyCache1, :a, [])
    {:ok, _remaining_ttl}

# `ttl!`

```elixir
@callback ttl!(key(), opts()) :: timeout()
```

Same as `c:ttl/2` but raises an exception if an error occurs.

## Examples

    iex> MyCache.put(:a, 1, ttl: :timer.seconds(5))
    :ok
    iex> MyCache.ttl!(:a)
    _remaining_ttl

# `ttl!`

```elixir
@callback ttl!(dynamic_cache(), key(), opts()) :: timeout()
```

Same as `c:ttl/3` but raises an exception if an error occurs.

# `fetch_or_store`

```elixir
@callback fetch_or_store(key(), fetch_or_store_fun(), opts()) :: ok_error_tuple(value())
```

Fetches the value for the given `key` from the cache. If the key is not
present, the provided anonymous function is executed.

If the function returns `{:ok, value}`, the value is cached under the given
`key` and returned as the result. If it returns `{:error, reason}`, the value
is **not** cached, and `reason` is returned wrapped in a `Nebulex.Error`.

If the function returns any other value, a `RuntimeError` is raised.

Use it when you only want to cache successful results, such as API calls
or database queries that may fail. Since failures are not cached, the next
call retries the operation.

## Options

Supports the following write options:

* `:ttl` (`t:timeout/0`) - The key's time-to-live (or expiry time) in **milliseconds**
  (`:infinity` to store indefinitely). The default value is `:infinity`.

* `:keep_ttl` (`t:boolean/0`) - Indicates whether to retain the time to live associated with the key.
  Otherwise, the value in the `:ttl` option overwrites the existing one. The default value is `false`.

See the ["Shared options"](#module-shared-options) section in the module
documentation for a list of supported options.

> #### Composite operation {: .warning}
>
> By default, this operation reads the value with `fetch` and, on a cache
> miss, runs the given function in the calling process and writes the
> result with `put`. Those steps are not atomic: concurrent misses on the
> same key can run the function more than once, and the last write wins.
>
> Adapters may override this implementation, including where the function
> runs and the atomicity it provides. See `Nebulex.Adapter.CompositeKV`.

## Examples

### Basic usage

    iex> MyCache.fetch_or_store("foo", fn -> {:ok, "bar"} end)
    {:ok, "bar"}

    iex> MyCache.fetch_or_store("foo", fn -> {:ok, "new value"} end)
    {:ok, "bar"}  # Returns cached value, function not executed

### Error handling

When the function returns an error, the value is not cached:

    iex> MyCache.fetch_or_store("user", fn -> {:error, "not found"} end)
    {:error, %Nebulex.Error{reason: "not found"}}

    iex> MyCache.fetch("user")
    {:error, %Nebulex.KeyError{key: "user"}}  # Key was not cached

### Invalid return values

The function must return either `{:ok, value}` or `{:error, reason}`:

    iex> MyCache.fetch_or_store("foo", fn -> :invalid end)
    ** (RuntimeError) the supplied lambda function must return ..

### With TTL

    iex> MyCache.fetch_or_store(
    ...>   "session",
    ...>   fn -> {:ok, "data"} end,
    ...>   ttl: :timer.minutes(5)
    ...> )
    {:ok, "data"}

### API calls

    def get_user_from_api(user_id) do
      MyCache.fetch_or_store(
        "user:#{user_id}",
        fn ->
          case HTTPClient.get("/users/#{user_id}") do
            {:ok, %{status: 200, body: data}} -> {:ok, data}
            {:ok, %{status: 404}} -> {:error, :not_found}
            {:error, reason} -> {:error, reason}
          end
        end,
        ttl: :timer.minutes(10)
      )
    end

### Database queries

    def fetch_product(id) do
      MyCache.fetch_or_store(
        "product:#{id}",
        fn ->
          case Repo.get(Product, id) do
            %Product{} = product -> {:ok, product}
            nil -> {:error, :not_found}
          end
        end,
        ttl: :timer.minutes(5)
      )
    end

## Choosing between `fetch_or_store` and `get_or_store`

The difference is whether the function's error results are cached.
`fetch_or_store/3` caches only `{:ok, value}` results, so a failed
computation runs again on the next call. `get_or_store/3` caches whatever
the function returns. See `c:get_or_store/3` for the comparison.

# `fetch_or_store`

```elixir
@callback fetch_or_store(dynamic_cache(), key(), fetch_or_store_fun(), opts()) ::
  ok_error_tuple(value())
```

Same as `c:fetch_or_store/3`, but the command is executed on the cache
instance given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    iex> MyCache.fetch_or_store(MyCache1, "key", fn -> {:ok, "value"} end, [])
    {:ok, "value"}

# `fetch_or_store!`

```elixir
@callback fetch_or_store!(key(), fetch_or_store_fun(), opts()) :: value()
```

Same as `c:fetch_or_store/3` but raises an exception if an error occurs.

Returns the unwrapped value on success, or raises `Nebulex.Error` if the
function returns `{:error, reason}`.

## Examples

    iex> MyCache.fetch_or_store!("key", fn -> {:ok, "value"} end)
    "value"

    iex> MyCache.fetch_or_store!("key", fn -> {:error, :error} end)
    ** (Nebulex.Error) fetch_or_store command failed with reason: :error

    iex> MyCache.fetch_or_store!("key", fn -> :invalid end)
    ** (RuntimeError) the supplied lambda function must return ...

# `fetch_or_store!`

```elixir
@callback fetch_or_store!(dynamic_cache(), key(), fetch_or_store_fun(), opts()) :: value()
```

Same as `c:fetch_or_store!/3` but the command is executed on the cache
instance given at the first argument `dynamic_cache`.

# `get_and_update`

```elixir
@callback get_and_update(key(), get_and_update_fun(), opts()) ::
  ok_error_tuple({current_value, new_value})
when current_value: value(), new_value: value()
```

Gets the value for `key` and updates it using the given function.

`fun` is called with the current cached value under `key` (or `nil` if `key`
hasn't been cached) and must return a two-element tuple: the value to return
as the current value, which may differ from the cached one, and the new
value to store under `key`. `fun` may also return `:pop` to remove the entry
and return the current value.

Since `nil` is a valid cache value, returning `{current_value, nil}` stores
`nil` under `key` like any other value. Use `:pop` to remove the entry.

This function returns:

  * `{:ok, {current_value, new_value}}` - The `current_value` is the current
    cached value and `new_value` the updated one returned by `fun`.

  * `{:error, reason}` - An error occurred executing the command.
    `reason` is the cause of the error.

## Options

* `:ttl` (`t:timeout/0`) - The key's time-to-live (or expiry time) in **milliseconds**
  (`:infinity` to store indefinitely). The default value is `:infinity`.

* `:keep_ttl` (`t:boolean/0`) - Indicates whether to retain the time to live associated with the key.
  Otherwise, the value in the `:ttl` option overwrites the existing one. The default value is `false`.

See the ["Shared options"](#module-shared-options) section in the module
documentation for more options.

> #### Composite operation {: .warning}
>
> By default, this operation reads the value with `fetch`, runs the given
> function in the calling process, and writes the result with `put` (or
> removes the entry with `delete` for `:pop`). Those steps are not atomic:
> concurrent calls on the same key can overwrite each other's changes.
>
> Adapters may override this implementation, including where the function
> runs and the atomicity it provides. See `Nebulex.Adapter.CompositeKV`.

## Examples

Update nonexistent key:

    iex> MyCache.get_and_update(:a, fn current_value ->
    ...>   {current_value, "value!"}
    ...> end)
    {:ok, {nil, "value!"}}

Update existing key:

    iex> MyCache.get_and_update(:a, fn current_value ->
    ...>   {current_value, "new value!"}
    ...> end)
    {:ok, {"value!", "new value!"}}

Cache a `nil` value:

    iex> MyCache.get_and_update(:a, fn current_value ->
    ...>   {current_value, nil}
    ...> end)
    {:ok, {"new value!", nil}}

Pop/remove value if it exists:

    iex> MyCache.get_and_update(:a, fn _ -> :pop end)
    {:ok, {nil, nil}}

Pop/remove nonexistent key:

    iex> MyCache.get_and_update(:b, fn _ -> :pop end)
    {:ok, {nil, nil}}

# `get_and_update`

```elixir
@callback get_and_update(dynamic_cache(), key(), get_and_update_fun(), opts()) ::
  ok_error_tuple({current_value, new_value})
when current_value: value(), new_value: value()
```

Same as `c:get_and_update/3`, but the command is executed on the cache
instance given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    iex> MyCache.get_and_update(MyCache1, :a, &{&1, "value!"}, [])
    {:ok, {nil, "value!"}}

# `get_and_update!`

```elixir
@callback get_and_update!(key(), get_and_update_fun(), opts()) ::
  {current_value, new_value}
when current_value: value(), new_value: value()
```

Same as `c:get_and_update/3` but raises an exception if an error occurs.

## Examples

    iex> MyCache.get_and_update!(:a, &{&1, "value!"})
    {nil, "value!"}

# `get_and_update!`

```elixir
@callback get_and_update!(dynamic_cache(), key(), get_and_update_fun(), opts()) ::
  {current_value, new_value}
when current_value: value(), new_value: value()
```

Same as `c:get_and_update/4` but raises an exception if an error occurs.

# `get_or_store`

```elixir
@callback get_or_store(key(), get_or_store_fun(), opts()) :: ok_error_tuple(value())
```

Gets the value for the given `key` from the cache. If the key is not
present, the provided anonymous function is executed and its result
is always cached under the given `key`.

Unlike `fetch_or_store/3`, this function caches the result whether it is a
success or an error tuple. Use it for negative caching, for backing off from
a failing service, and for expensive computations whose result should be
kept regardless of outcome.

## Options

Supports the following write options:

* `:ttl` (`t:timeout/0`) - The key's time-to-live (or expiry time) in **milliseconds**
  (`:infinity` to store indefinitely). The default value is `:infinity`.

* `:keep_ttl` (`t:boolean/0`) - Indicates whether to retain the time to live associated with the key.
  Otherwise, the value in the `:ttl` option overwrites the existing one. The default value is `false`.

See the ["Shared options"](#module-shared-options) section in the module
documentation for a list of supported options.

> #### Composite operation {: .warning}
>
> By default, this operation reads the value with `fetch` and, on a cache
> miss, runs the given function in the calling process and writes the
> result with `put`. Those steps are not atomic: concurrent misses on the
> same key can run the function more than once, and the last write wins.
>
> Adapters may override this implementation, including where the function
> runs and the atomicity it provides. See `Nebulex.Adapter.CompositeKV`.

## Examples

### Basic usage - caching any value

The function can return any value, and it will be cached:

    iex> MyCache.get_or_store("api_result", fn -> "data" end)
    {:ok, "data"}

    iex> MyCache.get_or_store("api_result", fn -> "new data" end)
    {:ok, "data"}  # Returns cached value, function not executed

### Caching tuples

This function caches the exact return value, including tuples:

    iex> MyCache.get_or_store("api_result_tuple", fn -> {:ok, "data"} end)
    {:ok, {:ok, "data"}}

    iex> MyCache.get_or_store("api_error", fn -> {:error, "rate_limited"} end)
    {:ok, {:error, "rate_limited"}}

### With TTL

    iex> MyCache.get_or_store(
    ...>   "with_ttl",
    ...>   fn -> "value" end,
    ...>   ttl: :timer.minutes(5)
    ...> )
    {:ok, "value"}

### Negative caching

Cache "not found" results to avoid repeated database queries:

    def get_user(user_id) do
      MyCache.get_or_store!(
        "user:#{user_id}",
        fn ->
          case Repo.get(User, user_id) do
            %User{} = user ->
              {:ok, user}

            nil ->
            # Cache the "not found" result
            {:error, :not_found}
          end
        end,
        ttl: :timer.minutes(5)
      )
    end

    # First call - queries database, caches {:error, :not_found}
    get_user(999)  #=> {:error, :not_found}

    # Second call - returns cached error, no database query
    get_user(999)  #=> {:error, :not_found}

### Rate limit protection

Cache error responses temporarily to prevent hammering external services:

    def fetch_from_api(endpoint) do
      MyCache.get_or_store(
        "api:#{endpoint}",
        fn ->
          case ExternalAPI.call(endpoint) do
            {:ok, data} ->
              {:ok, data}

            {:error, :rate_limited} = error ->
              # Cache rate limit error
              error

            {:error, _} = error ->
              error
          end
        end,
        ttl: :timer.seconds(30)
      )
    end

### Expensive computations

Cache results of expensive operations regardless of outcome:

    def calculate_report(params) do
      MyCache.get_or_store!(
        "report:#{hash(params)}",
        fn ->
          %{
            total_revenue: calculate_revenue(params),
            user_count: count_users(params),
            metrics: compute_metrics(params)
          }
        end,
        ttl: :timer.hours(1)
      )
    end

## Choosing between `get_or_store` and `fetch_or_store`

The difference is whether the function's error results are cached.
`get_or_store/3` caches whatever the function returns, so use it for
negative caching, for backing off from a rate-limited service, and for
expensive computations whose result should be kept regardless of outcome.
`fetch_or_store/3` caches only `{:ok, value}` results, so use it when
failures are transient and the next call should retry, such as external
API calls or database lookups where the record may not exist.

| | `get_or_store` | `fetch_or_store` |
|---|---|---|
| Function return | Any value | `{:ok, value}` or `{:error, reason}` |
| Caches error results | Yes | No |
| Validates the return | No | Raises on other shapes |

# `get_or_store`

```elixir
@callback get_or_store(dynamic_cache(), key(), get_or_store_fun(), opts()) ::
  ok_error_tuple(value())
```

Same as `c:get_or_store/3`, but the command is executed on the cache
instance given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    iex> MyCache.get_or_store(MyCache, "key", fn -> "value" end, [])
    {:ok, "value"}

# `get_or_store!`

```elixir
@callback get_or_store!(key(), get_or_store_fun(), opts()) :: value()
```

Same as `c:get_or_store/3` but raises an exception if a cache error occurs
(e.g., the adapter failed executing the command and returns an error).

The unwrapped cached value is returned, even when it is an error tuple the
function returned. Unlike `fetch_or_store!/3`, an error tuple from the
function is a cached value, not a failure.

## Examples

    iex> MyCache.get_or_store!("key", fn -> "value" end)
    "value"

    iex> MyCache.get_or_store!("key", fn -> {:ok, "value"} end)
    {:ok, "value"}

    iex> MyCache.get_or_store!("key", fn -> {:error, "error"} end)
    {:error, "error"}

    # get_or_store! caches and returns error tuples
    iex> MyCache.get_or_store!("key", fn -> {:error, :not_found} end)
    {:error, :not_found}

    # fetch_or_store! raises when function returns error
    iex> MyCache.fetch_or_store!("key", fn -> {:error, :not_found} end)
    ** (Nebulex.Error) fetch_or_store command failed with reason: :not_found

# `get_or_store!`

```elixir
@callback get_or_store!(dynamic_cache(), key(), get_or_store_fun(), opts()) :: value()
```

Same as `c:get_or_store!/3` but the command is executed on the cache
instance given at the first argument `dynamic_cache`.

# `update`

```elixir
@callback update(key(), initial :: value(), update_fun(), opts()) ::
  ok_error_tuple(value())
```

Updates the `key` in the cache with the given function.

If `key` is present in the cache, the existing value is passed to `fun` and
its result is used as the updated value of `key`. If `key` is not present in
the cache, `initial` is inserted as the value of `key`. The initial value
is not passed through the update function.

This function returns:

  * `{:ok, value}` - The value associated with the `key` is updated.

  * `{:error, reason}` - An error occurred executing the command.
    `reason` is the cause.

## Options

* `:ttl` (`t:timeout/0`) - The key's time-to-live (or expiry time) in **milliseconds**
  (`:infinity` to store indefinitely). The default value is `:infinity`.

* `:keep_ttl` (`t:boolean/0`) - Indicates whether to retain the time to live associated with the key.
  Otherwise, the value in the `:ttl` option overwrites the existing one. The default value is `false`.

See the ["Shared options"](#module-shared-options) section in the module
documentation for more options.

> #### Composite operation {: .warning}
>
> By default, this operation reads the value with `fetch`, runs the given
> function in the calling process, and writes the result with `put`. Those
> steps are not atomic: concurrent calls on the same key can overwrite
> each other's changes.
>
> Adapters may override this implementation, including where the function
> runs and the atomicity it provides. See `Nebulex.Adapter.CompositeKV`.

## Examples

    iex> MyCache.update(:a, 1, &(&1 * 2))
    {:ok, 1}
    iex> MyCache.update(:a, 1, &(&1 * 2))
    {:ok, 2}

# `update`

```elixir
@callback update(dynamic_cache(), key(), initial :: value(), update_fun(), opts()) ::
  ok_error_tuple(value())
```

Same as `c:update/4`, but the command is executed on the cache instance
given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    iex> MyCache.update(MyCache1, :a, 1, &(&1 * 2), [])
    {:ok, 1}

# `update!`

```elixir
@callback update!(key(), initial :: value(), update_fun(), opts()) :: value()
```

Same as `c:update/4` but raises an exception if an error occurs.

## Examples

    iex> MyCache.update!(:a, 1, &(&1 * 2))
    1

# `update!`

```elixir
@callback update!(dynamic_cache(), key(), initial :: value(), update_fun(), opts()) ::
  value()
```

Same as `c:update/5` but raises an exception if an error occurs.

# `count_all`
*optional* 

```elixir
@callback count_all(query_spec(), opts()) :: ok_error_tuple(non_neg_integer())
```

Counts all entries matching the query specified by the given `query_spec`.

See `c:get_all/2` for more information about the `query_spec`.

This function returns:

  * `{:ok, count}` - The cache executes the query successfully and
    returns the `count` of the matched entries.

  * `{:error, reason}` - An error occurred executing the command.
    `reason` is the cause.

Raises `NimbleOptions.ValidationError` if the query spec has invalid
options, or `ArgumentError` if it is not a keyword list. Adapters may raise
`Nebulex.QueryError` when the `:query` value is not supported.

## Options

See the ["Shared options"](#module-shared-options) section in the module
documentation for more options.

## Examples

Populate the cache with some entries:

    iex> Enum.each(1..5, &MyCache.put(&1, &1 * 2))
    :ok

Count all entries in cache (cache size):

    iex> MyCache.count_all()
    {:ok, 5}

Count all entries that match with the given query, assuming we are using
`Nebulex.Adapters.Local` adapter:

    iex> query = [{{:_, :"$1", :"$2", :_, :_}, [{:>, :"$2", 5}], [true]}]
    iex> {:ok, count} = MyCache.count_all(query: query)

See `c:get_all/2` for more query examples.

# `count_all`
*optional* 

```elixir
@callback count_all(dynamic_cache(), query_spec(), opts()) ::
  ok_error_tuple(non_neg_integer())
```

Same as `c:count_all/2`, but the command is executed on the cache instance
given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    iex> MyCache.count_all(MyCache1, [], [])
    {:ok, 0}

# `count_all!`
*optional* 

```elixir
@callback count_all!(query_spec(), opts()) :: non_neg_integer()
```

Same as `c:count_all/2` but raises an exception if an error occurs.

## Examples

    iex> MyCache.count_all!()
    0

# `count_all!`
*optional* 

```elixir
@callback count_all!(dynamic_cache(), query_spec(), opts()) :: non_neg_integer()
```

Same as `c:count_all/3` but raises an exception if an error occurs.

# `delete_all`
*optional* 

```elixir
@callback delete_all(query_spec(), opts()) :: ok_error_tuple(non_neg_integer())
```

Deletes all entries matching the query specified by the given `query_spec`.

See `c:get_all/2` for more information about the `query_spec`.

This function returns:

  * `{:ok, deleted_count}` - The cache executes the query successfully and
    returns the deleted entries count.

  * `{:error, reason}` - An error occurred executing the command.
    `reason` is the cause.

Raises `NimbleOptions.ValidationError` if the query spec has invalid
options, or `ArgumentError` if it is not a keyword list. Adapters may raise
`Nebulex.QueryError` when the `:query` value is not supported.

## Options

See the ["Shared options"](#module-shared-options) section in the module
documentation for more options.

## Examples

Populate the cache with some entries:

    iex> Enum.each(1..5, &MyCache.put(&1, &1 * 2))
    :ok

Delete all (default args):

    iex> MyCache.delete_all()
    {:ok, 5}

Delete only the requested keys (bulk delete):

    iex> MyCache.delete_all(in: [1, 2, 10])
    {:ok, 2}

Delete all entries that match with the given query, assuming we are using
`Nebulex.Adapters.Local` adapter:

    iex> query = [{{:_, :"$1", :"$2", :_, :_}, [{:>, :"$2", 5}], [true]}]
    iex> {:ok, deleted_count} = MyCache.delete_all(query: query)

See `c:get_all/2` for more query examples.

# `delete_all`
*optional* 

```elixir
@callback delete_all(dynamic_cache(), query_spec(), opts()) ::
  ok_error_tuple(non_neg_integer())
```

Same as `c:delete_all/2`, but the command is executed on the cache instance
given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    iex> MyCache.delete_all(MyCache1, [], [])
    {:ok, 0}

# `delete_all!`
*optional* 

```elixir
@callback delete_all!(query_spec(), opts()) :: non_neg_integer()
```

Same as `c:delete_all/2` but raises an exception if an error occurs.

## Examples

    iex> MyCache.delete_all!()
    0

# `delete_all!`
*optional* 

```elixir
@callback delete_all!(dynamic_cache(), query_spec(), opts()) :: non_neg_integer()
```

Same as `c:delete_all/3` but raises an exception if an error occurs.

# `get_all`
*optional* 

```elixir
@callback get_all(query_spec(), opts()) :: ok_error_tuple([any()])
```

Fetches all entries from the cache matching the given query specified through
the ["query-spec"](#c:get_all/2-query-specification).

This function returns:

  * `{:ok, result}` - The cache executes the query successfully. The `result`
    is a list with the matched entries.

  * `{:error, reason}` - An error occurred executing the command.
    `reason` is the cause.

Raises `NimbleOptions.ValidationError` if the query spec has invalid
options, or `ArgumentError` if it is not a keyword list. Adapters may raise
`Nebulex.QueryError` when the `:query` value is not supported.

## Query specification

There are two ways to use the Query API:

  * Fetch multiple keys (all at once), like a bulk fetch.
  * Fetch all entries from the cache matching a given query, more like a
    search (this is the most generic option).

Here is where the `query_spec` argument comes in to specify the type of query
to run.

The `query_spec` argument is a `t:keyword/0` with options defining the desired
query. The `query_spec` fields or options are:

* `:in` (list of `t:term/0`) - The list of keys to fetch. The value to return depends on the `:select`
  option. The `:in` option is a predefined query meant to fetch multiple
  keys simultaneously.

  If present, it overrides the `:query` option and instructs the underlying
  adapter to match the entries associated with the set of keys requested.
  For every key that does not hold a value or does not exist, it is ignored
  and not added to the returned list.

* `:query` (`t:term/0`) - The query specification to match entries in the cache.

  If present and set to `nil`, it matches all entries in the cache. The
  `nil` is a predefined value all adapters must support. Other than that,
  the value depends entirely on the adapter. The adapter is responsible
  for defining the query or matching specification. For example, the
  `Nebulex.Adapters.Local` adapter supports the
  [**"ETS Match Spec"**](https://www.erlang.org/doc/man/ets#match_spec).

  The default value is `nil`.

* `:select` - Selects which fields to choose from the entry.

  The possible values are:

    * `{:key, :value}` - (Default) Selects the key and the value from
      the entry. They are returned as a tuple `{key, value}`.
    * `:key` - Selects the key from the entry.
    * `:value` - Selects the value from the entry.
    * `:entry` - Selects the whole entry with its fields (use it carefully).
      The adapter defines the entry, the structure, and its fields.
      Therefore, Nebulex recommends checking the adapter's documentation to
      understand the entry's structure with its fields and to verify if the
      select option is supported.

  The default value is `{:key, :value}`.

### Fetching multiple keys

While you can perform any query using the `:query` option (even fetching
multiple keys), the option `:in` is preferable. For example:

    MyCache.get_all(in: ["a", "list", "of", "keys"])

### Fetching all entries matching a given query

As mentioned above, the option `:query` is the most generic way to match
entries in a cache. This option allows users to write custom queries
to be executed by the underlying adapter.

For matching all cached entries, you can skip the `:query` option or set it
to `nil` instead (the default). For example:

    MyCache.get_all() #=> Equivalent to MyCache.get_all(query: nil)

Using a custom query:

    MyCache.get_all(query: query_supported_by_the_adapter)

> _Nebulex recommends to see the adapter documentation when using this option._

## Options

See the ["Shared options"](#module-shared-options) section in the module
documentation for more options.

## Examples

Populate the cache with some entries:

    iex> MyCache.put_all(a: 1, b: 2, c: 3)
    :ok

Fetch all entries in the cache:

    iex> MyCache.get_all()
    {:ok, [a: 1, b: 2, c: 3]}

Fetch all entries returning only the keys:

    iex> MyCache.get_all(select: :key)
    {:ok, [:a, :b, :c]}

Fetch all entries returning only the values:

    iex> MyCache.get_all(select: :value)
    {:ok, [1, 2, 3]}

Fetch only the requested keys (bulk fetch):

    iex> MyCache.get_all(in: [:a, :b, :d])
    {:ok, [a: 1, b: 2]}

Fetch the requested keys returning only the keys or values:

    iex> MyCache.get_all(in: [:a, :b, :d], select: :key)
    {:ok, [:a, :b]}
    iex> MyCache.get_all(in: [:a, :b, :d], select: :value)
    {:ok, [1, 2]}

### Query examples for `Nebulex.Adapters.Local` adapter

The `Nebulex.Adapters.Local` adapter supports **"ETS Match Spec"** as query
values (in addition to `nil` or the option `:in`).

You must know the adapter's entry structure for match-spec queries, which is
`{:entry, key, value, touched, ttl}`. For example, one may write the following
query:

    iex> match_spec = [
    ...>   {
    ...>     {:entry, :"$1", :"$2", :_, :_},
    ...>     [{:>, :"$2", 1}],
    ...>     [{{:"$1", :"$2"}}]
    ...>   }
    ...> ]
    iex> MyCache.get_all(query: match_spec)
    {:ok, [b: 2, c: 3]}

#### Beyond basic queries

The `Nebulex.Adapters.Local` adapter also supports tags, references, and
query helpers. See the
[Local Adapter documentation](https://hexdocs.pm/nebulex_local/Nebulex.Adapters.Local.html).

# `get_all`
*optional* 

```elixir
@callback get_all(dynamic_cache(), query_spec(), opts()) :: ok_error_tuple([any()])
```

Same as `c:get_all/2`, but the command is executed on the cache instance
given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    iex> MyCache.get_all(MyCache1, [], [])
    {:ok, _matched_entries}

# `get_all!`
*optional* 

```elixir
@callback get_all!(query_spec(), opts()) :: [any()]
```

Same as `c:get_all/2` but raises an exception if an error occurs.

## Examples

    iex> MyCache.put_all(a: 1, b: 2, c: 3)
    :ok
    iex> MyCache.get_all!()
    [a: 1, b: 2, c: 3]
    iex> MyCache.get_all!(in: [:a, :b])
    [a: 1, b: 2]

# `get_all!`
*optional* 

```elixir
@callback get_all!(dynamic_cache(), query_spec(), opts()) :: [any()]
```

Same as `c:get_all/3` but raises an exception if an error occurs.

# `stream`
*optional* 

```elixir
@callback stream(query_spec(), opts()) :: ok_error_tuple(Enum.t())
```

Similar to `c:get_all/2`, but returns a lazy enumerable that emits all entries
matching the query specified by the given `query_spec`.

See `c:get_all/2` for more information about the `query_spec`.

This function returns:

  * `{:ok, stream}` - It returns a `stream` of values.

  * `{:error, reason}` - An error occurred executing the command.
    `reason` is the cause.

Raises `NimbleOptions.ValidationError` if the query spec has invalid
options, or `ArgumentError` if it is not a keyword list. Adapters may raise
`Nebulex.QueryError` when the `:query` value is not supported.

## Options

* `:max_entries` (`t:pos_integer/0`) - The number of entries to load from the cache as we stream. The default value is `100`.

See the ["Shared options"](#module-shared-options) section in the module
documentation for more options.

## Examples

Populate the cache with some entries:

    iex> MyCache.put_all(a: 1, b: 2, c: 3)
    :ok

Stream all (default args):

    iex> {:ok, stream} = MyCache.stream()
    iex> Enum.to_list(stream)
    [a: 1, b: 2, c: 3]

Stream all entries returning only the keys (with :max_entries option):

    iex> {:ok, stream} = MyCache.stream([select: :key], max_entries: 2)
    iex> Enum.to_list(stream)
    [:a, :b, :c]

Stream all entries returning only the values:

    iex> {:ok, stream} = MyCache.stream(select: :value)
    iex> Enum.to_list(stream)
    [1, 2, 3]

Stream only the requested keys (lazy bulk-fetch):

    iex> {:ok, stream} = MyCache.stream(in: [:a, :b, :d])
    iex> Enum.to_list(stream)
    [a: 1, b: 2]
    iex> {:ok, stream} = MyCache.stream(in: [:a, :b, :d], select: :key)
    iex> Enum.to_list(stream)
    [:a, :b]

# `stream`
*optional* 

```elixir
@callback stream(dynamic_cache(), query_spec(), opts()) :: ok_error_tuple(Enum.t())
```

Same as `c:stream/2`, but the command is executed on the cache instance
given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    iex> {:ok, stream} = MyCache.stream(MyCache1, [], [])
    iex> Enum.to_list(stream)
    []

# `stream!`
*optional* 

```elixir
@callback stream!(query_spec(), opts()) :: Enum.t()
```

Same as `c:stream/2` but raises an exception if an error occurs.

## Examples

    iex> MyCache.put_all(a: 1, b: 2, c: 3)
    :ok
    iex> MyCache.stream!() |> Enum.to_list()
    [a: 1, b: 2, c: 3]

# `stream!`
*optional* 

```elixir
@callback stream!(dynamic_cache(), query_spec(), opts()) :: Enum.t()
```

Same as `c:stream/3` but raises an exception if an error occurs.

# `in_transaction?`
*optional* 

```elixir
@callback in_transaction?(opts()) :: ok_error_tuple(boolean())
```

Returns `{:ok, true}` if the current process is inside a transaction;
otherwise, `{:ok, false}` is returned.

If there's an error with executing the command, `{:error, reason}`
is returned, where `reason` is the cause of the error.

## Options

See the ["Shared options"](#module-shared-options) section in the module
documentation for more options.

## Examples

    MyCache.in_transaction?()
    #=> {:ok, false}

    MyCache.transaction(fn ->
      MyCache.in_transaction? #=> {:ok, true}
    end)

# `in_transaction?`
*optional* 

```elixir
@callback in_transaction?(dynamic_cache(), opts()) :: ok_error_tuple(boolean())
```

Same as `c:in_transaction?/1`, but the command is executed on the cache instance
given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    MyCache.in_transaction?(MyCache1, [])

# `transaction`
*optional* 

```elixir
@callback transaction(fun(), opts()) :: ok_error_tuple(any())
```

Runs the given function inside a transaction.

If an Elixir exception occurs, the exception will bubble up from the
transaction function. If the cache aborts the transaction, it returns
`{:error, reason}`.

A successful transaction returns the value returned by the function wrapped
in a tuple as `{:ok, value}`.

### Nested transactions

If `transaction/2` is called inside another transaction, the cache executes
the function without wrapping the new transaction call in any way.

## Options

See the ["Shared options"](#module-shared-options) section in the module
documentation for more options.

## Examples

    MyCache.transaction(fn ->
      alice = MyCache.get(:alice)
      bob = MyCache.get(:bob)
      MyCache.put(:alice, %{alice | balance: alice.balance + 100})
      MyCache.put(:bob, %{bob | balance: bob.balance + 100})
    end)

We can provide the keys to lock when using the `Nebulex.Adapters.Local`
adapter (or any other adapter that supports key locking):

    MyCache.transaction(
      fn ->
        alice = MyCache.get(:alice)
        bob = MyCache.get(:bob)
        MyCache.put(:alice, %{alice | balance: alice.balance + 100})
        MyCache.put(:bob, %{bob | balance: bob.balance + 100})
      end,
      keys: [:alice, :bob]
    )

# `transaction`
*optional* 

```elixir
@callback transaction(dynamic_cache(), fun(), opts()) :: ok_error_tuple(any())
```

Same as `c:transaction/2`, but the command is executed on the cache instance
given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    MyCache.transaction(
      MyCache1,
      fn ->
        alice = MyCache.get(:alice)
        bob = MyCache.get(:bob)
        MyCache.put(:alice, %{alice | balance: alice.balance + 100})
        MyCache.put(:bob, %{bob | balance: bob.balance + 100})
      end,
      keys: [:alice, :bob]
    )

# `info`
*optional* 

```elixir
@callback info(spec :: info_spec(), opts()) :: ok_error_tuple(info_data())
```

Returns `{:ok, info}` where `info` contains the requested cache information,
as specified by the `spec`.

If there's an error with executing the command, `{:error, reason}`
is returned, where `reason` is the cause of the error.

The `spec` (information specification key) can be:

  * **The atom `:all`** - returns a map with all information items.
  * **An atom** - returns the value for the requested information item.
  * **A list of atoms** - returns a map only with the requested information
    items.

If the argument `spec` is omitted, all information items are returned;
same as if the `spec` was the atom `:all`.

The adapters are free to add the information specification keys they want.
However, Nebulex suggests the adapters add the following keys:

  * `:server` - General information about the cache server (e.g., cache name,
    adapter, PID, etc.).
  * `:memory` - Memory consumption information (e.g., used memory,
    allocated memory, etc.).
  * `:stats` - Cache statistics (e.g., hits, misses, etc.).

## Examples

The following examples assume the underlying adapter uses the implementation
provided by `Nebulex.Adapters.Common.Info`.

    iex> {:ok, info} = MyCache.info()
    iex> info
    %{
      server: %{
        nbx_version: "3.0.0",
        cache_module: MyCache,
        cache_adapter: Nebulex.Adapters.Local,
        cache_name: MyCache,
        cache_pid: #PID<0.111.0>
      },
      memory: %{
        total: 1_000_000,
        used: 0
      },
      stats: %{
        deletions: 0,
        evictions: 0,
        expirations: 0,
        hits: 0,
        misses: 0,
        updates: 0,
        writes: 0
      }
    }

    iex> {:ok, info} = MyCache.info(:server)
    iex> info
    %{
      nbx_version: "3.0.0",
      cache_module: MyCache,
      cache_adapter: Nebulex.Adapters.Local,
      cache_name: MyCache,
      cache_pid: #PID<0.111.0>
    }

    iex> {:ok, info} = MyCache.info([:server, :stats])
    iex> info
    %{
      server: %{
        nbx_version: "3.0.0",
        cache_module: MyCache,
        cache_adapter: Nebulex.Adapters.Local,
        cache_name: MyCache,
        cache_pid: #PID<0.111.0>
      },
      stats: %{
        deletions: 0,
        evictions: 0,
        expirations: 0,
        hits: 0,
        misses: 0,
        updates: 0,
        writes: 0
      }
    }

# `info`
*optional* 

```elixir
@callback info(dynamic_cache(), spec :: info_spec(), opts()) ::
  ok_error_tuple(info_data())
```

Same as `c:info/2`, but the command is executed on the cache
instance given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    MyCache.info(MyCache1, :all, [])

# `info!`
*optional* 

```elixir
@callback info!(spec :: info_spec(), opts()) :: info_data()
```

Same as `c:info/2` but raises an exception if an error occurs.

# `info!`
*optional* 

```elixir
@callback info!(dynamic_cache(), spec :: info_spec(), opts()) :: info_data()
```

Same as `c:info/3` but raises an exception if an error occurs.

# `register_event_listener`
*optional* 

```elixir
@callback register_event_listener(event_listener(), opts()) :: :ok | error_tuple()
```

Registers the cache event listener `event_listener`.

Returns `:ok` if successful; `{:error, reason}` otherwise.

Keep listeners fast: depending on the adapter, they can add latency to the
cache operation that triggers them.

Listeners:

  * are fired after the entry is mutated in the cache.
  * block the calling process until the listener returns,
    when the listener is invoked synchronously; depends on
    the adapter's implementation.

Listeners follow the observer pattern. An exception raised by a listener does
not cause the cache operation to fail.

Listeners can only raise `Nebulex.Error` exception. Caching implementations
must catch any other exception from a listener, then wrap and reraise it as
a `Nebulex.Error` exception.

With the default implementation, which is built on Telemetry handlers, a
listener or filter that raises or exits is logged and detached, so it stops
receiving events. Register it again to resume.

## Options

* `:id` (`t:term/0`) - A unique identifier for the event listener. An error will be returned if
  another listener with the same ID already exists. Defaults to the event
  listener function itself.

* `:filter` (`t:Nebulex.Event.filter/0`) - A function that may be used to check cache entry events prior to being
  dispatched to event listeners.

  A filter must not create side effects.

* `:metadata` (`t:Nebulex.Event.metadata/0`) - The metadata is provided when registering the listener and added to the
  event at invoking the listener and filter functions; the event must always
  have a metadata field. The default value is `[]`.

See the ["Shared options"](#module-shared-options) section in the module
documentation for more options.

## Examples

    iex> MyApp.Cache.register_event_listener(&MyApp.handle/1)
    :ok
    iex> MyApp.Cache.register_event_listener(&MyApp.handle/1,
    ...>   filter: &MyApp.filter/1
    ...> )
    :ok
    iex> MyApp.Cache.register_event_listener(&MyApp.handle/1,
    ...>   filter: &MyApp.filter/1,
    ...>   metadata: [foo: :bar]
    ...> )
    :ok

    # Register with `:id` (must be unregistered using the same `:id` value)
    iex> MyApp.Cache.register_event_listener(&MyApp.handle/1,
    ...>   id: :my_listener,
    ...>   filter: &MyApp.filter/1,
    ...>   metadata: [foo: :bar]
    ...> )
    :ok

# `register_event_listener`
*optional* 

```elixir
@callback register_event_listener(dynamic_cache(), event_listener(), opts()) ::
  :ok | error_tuple()
```

Same as `c:register_event_listener/2`, but the command is executed on the cache
instance given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    MyApp.Cache.register_event_listener(:my_cache, &MyApp.handle/1, [])

# `register_event_listener!`
*optional* 

```elixir
@callback register_event_listener!(event_listener(), opts()) :: :ok
```

Same as `c:register_event_listener/2` but raises an exception if an error
occurs.

# `register_event_listener!`
*optional* 

```elixir
@callback register_event_listener!(dynamic_cache(), event_listener(), opts()) :: :ok
```

Same as `c:register_event_listener/3` but raises an exception if an error
occurs.

# `unregister_event_listener`
*optional* 

```elixir
@callback unregister_event_listener(id :: any(), opts()) :: :ok | error_tuple()
```

Unregisters a cache event listener.

Returns `:ok` if successful; `{:error, reason}` otherwise.

## Options

See the ["Shared options"](#module-shared-options) section in the module
documentation for more options.

## Examples

    # Register with default ID
    iex> MyApp.Cache.register_event_listener(&MyApp.handle/1)
    :ok
    # Unregister with default ID
    iex> MyApp.Cache.unregister_event_listener(&MyApp.handle/1)
    :ok

    # Register with `:id`
    iex> MyApp.Cache.register_event_listener(&MyApp.handle/1, id: :listener)
    :ok
    # Unregister using the previously registered `:id`
    iex> MyApp.Cache.unregister_event_listener(:listener)
    :ok

# `unregister_event_listener`
*optional* 

```elixir
@callback unregister_event_listener(dynamic_cache(), id :: any(), opts()) ::
  :ok | error_tuple()
```

Same as `c:unregister_event_listener/2`, but the command is executed on the
cache instance given at the first argument `dynamic_cache`.

See the ["Dynamic caches"](#module-dynamic-caches) section in the
module documentation for more information.

## Examples

    MyApp.Cache.unregister_event_listener(:my_cache, &MyApp.handle/1, [])

# `unregister_event_listener!`
*optional* 

```elixir
@callback unregister_event_listener!(id :: any(), opts()) :: :ok
```

Same as `c:unregister_event_listener/2` but raises an exception if an error
occurs.

# `unregister_event_listener!`
*optional* 

```elixir
@callback unregister_event_listener!(dynamic_cache(), id :: any(), opts()) :: :ok
```

Same as `c:unregister_event_listener/3` but raises an exception if an error
occurs.

# `dynamic_cache`

```elixir
@type dynamic_cache() :: atom() | pid()
```

Dynamic cache value

# `entries`

```elixir
@type entries() :: map() | [{key(), value()}]
```

Cache entries

# `error_tuple`

```elixir
@type error_tuple() :: error_tuple(nbx_error_reason())
```

Common error type

# `error_tuple`

```elixir
@type error_tuple(reason) :: {:error, reason}
```

Error type for the given reason

# `event_filter`

```elixir
@type event_filter() :: Nebulex.Event.filter()
```

Proxy type to a cache event filter

# `event_listener`

```elixir
@type event_listener() :: Nebulex.Event.listener()
```

Proxy type to a cache event listener

# `fetch_error_reason`

```elixir
@type fetch_error_reason() :: Nebulex.KeyError.t() | nbx_error_reason()
```

Fetch error reason

# `fetch_or_store_fun`

```elixir
@type fetch_or_store_fun() :: (-&gt; {:ok, value()} | {:error, any()})
```

Fetch or store function

# `get_and_update_fun`

```elixir
@type get_and_update_fun() :: (value() -&gt;
                           {current_value :: value(), new_value :: value()}
                           | :pop)
```

Get and update function

# `get_or_store_fun`

```elixir
@type get_or_store_fun() :: (-&gt; any())
```

Get or store function

# `info_data`

```elixir
@type info_data() :: info_map() | info_item()
```

The data type for the cache information

# `info_item`

```elixir
@type info_item() :: any()
```

The type for the info item's value

# `info_map`

```elixir
@type info_map() :: %{optional(atom()) =&gt; any()}
```

Info map

# `info_spec`

```elixir
@type info_spec() :: :all | atom() | [atom()]
```

Specification key for the item(s) to include in the returned info

# `keep_ttl`

```elixir
@type keep_ttl() :: boolean()
```

Keep TTL flag

# `key`

```elixir
@type key() :: any()
```

Cache entry key

# `nbx_error_reason`

```elixir
@type nbx_error_reason() :: Nebulex.Error.t()
```

Proxy type for generic Nebulex error

# `ok_error_tuple`

```elixir
@type ok_error_tuple(ok) :: ok_error_tuple(ok, nbx_error_reason())
```

Ok/Error tuple with default error reasons

# `ok_error_tuple`

```elixir
@type ok_error_tuple(ok, error) :: {:ok, ok} | {:error, error}
```

Ok/Error type

# `opts`

```elixir
@type opts() :: keyword()
```

Cache action options

# `query_spec`

```elixir
@type query_spec() :: keyword()
```

The data type for a query spec.

See the ["query-spec"](#c:get_all/2-query-specification) section for more
information.

# `t`

```elixir
@type t() :: module()
```

Cache type

# `ttl`

```elixir
@type ttl() :: timeout()
```

TTL for a cache entry

# `update_fun`

```elixir
@type update_fun() :: (value() -&gt; value())
```

Update function

# `value`

```elixir
@type value() :: any()
```

Cache entry value

---

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