defmodule Cachex.Actions.Invoke do @moduledoc false # Command module to enable custom command invocation. # # This module relies on commands attached to a cache at startup, and # does not allow for registration afterward. # # Invocations which require writes to the table are executed inside a # transactional context to ensure consistency. alias Cachex.Actions alias Cachex.Services.Locksmith # add our imports import Cachex.Spec import Cachex.Error ############## # Public API # ############## @doc """ Invokes a custom command on a cache. Command invocations allow a developer to attach common functions directly to a cache in order to easily share logic around a codebase. Values are passed through to a custom command for a given key, and based on the type of command might be written back into the cache table. """ def execute(cache(commands: commands) = cache, cmd, key, _options) do commands |> Map.get(cmd) |> invoke(cache, key) end ############### # Private API # ############### # Executes a read command on the backing cache table. # # Values read back will be passed directly to the custom command implementation. # It should be noted that expirations are taken into account, and nil will be # passed through in expired/missing cases. defp invoke(command(type: :read, execute: exec), cache, key) do {_status_, value} = Cachex.get(cache, key, []) {:ok, exec.(value)} end # Executes a write command on the backing cache table. # # This will initialize a transactional context to ensure that modifications are # kept in sync with other actions happening at the same time. The return format # is enforced per the documentation and will crash out if something unexpected # is returned (i.e. a non-Tuple, or a Tuple with invalid size). defp invoke(command(type: :write, execute: exec), cache() = cache, key) do Locksmith.transaction(cache, [key], fn -> {_label, value} = Cachex.get(cache, key, []) {return, tempv} = exec.(value) tempv == value || apply( Cachex, Actions.write_op(value), [cache, key, tempv, []] ) {:ok, return} end) end # Returns an error due to a missing command. defp invoke(_invalid, _cache, _key), do: error(:invalid_command) end