defmodule Image.Plug.VariantStore.Persistence do @moduledoc """ Behaviour for persisting variants across application restarts. A persistence backend is responsible for two things: * `c:load/1` — called once at boot when the variant store initialises. Returns the list of previously-stored variants. The store seeds them into its table before applying any application-env seeds. * `c:write/4` — called after every successful `put` or `delete`. Write-through, fire-and-forget: failures are logged at `:warn` but do not fail the originating CRUD operation. ### Built-in backends * `Image.Plug.VariantStore.Persistence.File` — JSON-on-disk. ### Configuration Configure on the ETS variant store: plug Image.Plug, ... variant_store: {Image.Plug.VariantStore.ETS, [ persistence: {Image.Plug.VariantStore.Persistence.File, path: "/var/lib/image_plug/variants.json"} ]} ### Persistable variants Only variants whose `:options` field is set (i.e. variants created from a CDN-grammar options string) are persisted. Variants built programmatically from an `Image.Plug.Pipeline` struct (no options string) cannot be round-tripped without a pipeline codec and are skipped with a warning. If you build pipelines programmatically and want them persisted, also set the `:options` field with an equivalent options string. ### Provider Persisted variants store the original options string, not the parsed pipeline. At load time the string is re-parsed via the `:provider` option (defaulting to `Image.Plug.Provider.Cloudflare`). This dodges the JSON-serialisation problem for pipeline ops, which embed atoms and struct types that don't survive a round-trip cleanly. """ alias Image.Plug.Variant @doc """ Loads previously-persisted variants. ### Arguments * `options` is the keyword list configured on the persistence entry. ### Returns * `{:ok, [variant]}` on success. Empty list is fine — first run. * `{:error, reason}` to abort boot. Use sparingly; a fresh install with no persisted state is the normal first-boot condition and should return `{:ok, []}`, not an error. """ @callback load(options :: keyword()) :: {:ok, [Variant.t()]} | {:error, term()} @doc """ Writes a single variant change. Called after every successful `put` or `delete` on the variant store. Receives the action (`:put` or `:delete`), the variant name, and either the new variant struct (for `:put`) or `nil` (for `:delete`). ### Returns * `:ok` on success. * `{:error, reason}` on failure. The store logs the error at `:warn` but does not fail the originating CRUD call — persistence is fire-and-forget. """ @callback write( action :: :put | :delete, name :: String.t(), variant :: Variant.t() | nil, options :: keyword() ) :: :ok | {:error, term()} end