Introduction and modes

Copy Markdown View Source

MaxoAdapt turns callbacks inside a behaviour block into documented, typed facade functions:

defmodule Storage do
  use MaxoAdapt

  behaviour do
    @callback get(binary()) :: {:ok, term()} | {:error, atom()}
    @callback put(binary(), term()) :: :ok | {:error, atom()}
  end
end

The module remains a normal Elixir behaviour, so implementations use @behaviour and @impl normally. Required callback name/arity pairs are validated when a mutable mode is configured. Declared @optional_callbacks remain optional behaviour declarations and are not generated as mandatory facade functions.

Generated API

Every adapter facade exposes:

  • one function for each required callback;
  • configure/1 (raising in immutable :get_compiled mode); and
  • __maxo_adapt__/0, which returns the effective implementation or nil.

Callback specs and consecutive @doc attributes are copied to generated facade functions.

:compile mode

This is the default. MaxoAdapt creates a private redirect module under the MaxoAdapt.* namespace and the facade delegates to it. configure/1 validates the new implementation and regenerates direct delegates. Calls remain comparable to ordinary defdelegate dispatch, while occasional runtime switching remains possible.

Configuration is process-global because the generated module is global. Do not concurrently reconfigure the same facade from unrelated owners.

:get_compiled mode

The implementation is read through Application.compile_env/3 while the facade compiles. Generated functions delegate directly to that implementation. Runtime configure/1 raises because changing application environment cannot change already-compiled delegates.

Use this mode when the implementation is fixed per build/release.

:get_env mode

Every call resolves the implementation with Application.get_env/3. Runtime configuration uses Application.put_env/4. Switching is inexpensive, but every dispatched call pays for an application-environment lookup and configuration is global to the BEAM instance.

:get_dict mode

Every process can configure its own implementation. On a local miss, MaxoAdapt searches live ancestor process dictionaries and caches the first result locally. This works naturally with Task descendants and allows async tests to select different adapters.

The cache is a snapshot: changing a parent after a child has inherited an adapter does not invalidate the child's cached value. Missing or dead ancestors are ignored safely.