defmodule Mnemonix.Stores.Meta.PassThrough do @moduledoc """ A `Mnemonix.Store` that caches reads from a backend store into a frontend one. Writes and removals are applied to both stores. Works best with quicker or closer stores in the frontend, like in-memory ones; with a store-wide ttl to keep their footprint light. iex> {:ok, backend} = Mnemonix.Stores.Redix.start_link() iex> {:ok, frontend} = Mnemonix.Stores.ETS.start_link() iex> {:ok, passthrough} = Mnemonix.Stores.Meta.PassThrough.start_link(frontend: frontend, backend: backend) iex> Mnemonix.put(backend, "foo", "bar") iex> Mnemonix.get(frontend, "foo") nil iex> Mnemonix.get(passthrough, "foo") "bar" iex> Mnemonix.get(frontend, "foo") "bar" iex> Mnemonix.put(passthrough, "foo", "baz") iex> Mnemonix.get(frontend, "foo") "baz" iex> Mnemonix.get(backend, "foo") "baz" iex> Mnemonix.delete(passthrough, "foo") iex> Mnemonix.get(frontend, "foo") nil iex> Mnemonix.get(backend, "foo") nil iex> {nil, ^passthrough} = Mnemonix.get_and_update(passthrough, "foo", &({&1, {&1, &1}})) iex> Mnemonix.get(passthrough, "foo") {nil, nil} This store raises errors on the functions in `Mnemonix.Features.Enumerable`. """ alias Mnemonix.Store use Store.Behaviour use Store.Translator.Raw #### # Mnemonix.Store.Behaviours.Core ## @doc """ Tracks frontend and backend stores furnished in `opts`. ## Options - `frontend:` A store reference to cache reads from the backend store in. - `backend:` A store reference to use as the canonical uncached source of truth. """ @impl Store.Behaviours.Core @spec setup(Store.options()) :: {:ok, state :: term} | {:stop, reason :: any} def setup(opts) do {:ok, %{frontend: Keyword.fetch!(opts, :frontend), backend: Keyword.fetch!(opts, :backend)}} end #### # Mnemonix.Store.Behaviours.Map ## @impl Store.Behaviours.Map @spec delete(Store.t(), Mnemonix.key()) :: Store.Server.instruction() def delete(%Store{state: state} = store, key) do %{frontend: frontend, backend: backend} = state with ^frontend <- Mnemonix.delete(frontend, key), ^backend <- Mnemonix.delete(backend, key) do {:ok, store} end end @impl Store.Behaviours.Map @spec fetch(Store.t(), Mnemonix.key()) :: Store.Server.instruction({:ok, Mnemonix.value()} | :error) def fetch(%Store{state: state} = store, key) do %{frontend: frontend, backend: backend} = state if value = Mnemonix.get(frontend, key) do {:ok, store, {:ok, value}} else if value = Mnemonix.get(backend, key) do with ^frontend <- Mnemonix.put(frontend, key, value) do {:ok, store, {:ok, value}} end else {:ok, store, :error} end end end @impl Store.Behaviours.Map @spec put(Store.t(), Mnemonix.key(), Mnemonix.value()) :: Store.Server.instruction() def put(%Store{state: state} = store, key, value) do %{frontend: frontend, backend: backend} = state with ^frontend <- Mnemonix.put(frontend, key, value), ^backend <- Mnemonix.put(backend, key, value) do {:ok, store} end end end