Behaviour for the storefront's product/category read path.
Two adapters implement it: PhoenixKitEcommerce.ProductSource.Legacy
(today's phoenix_kit_shop_products/phoenix_kit_shop_categories
tables, unchanged) and, once phoenix_kit_catalogue is present,
PhoenixKitEcommerce.ProductSource.Catalogue (reads catalogue items
and returns hand-built %Product{}/%Category{} view-structs so the
facade, guards, CartItem, Options and SeoHelpers need no changes).
current/0 picks the adapter at runtime; PhoenixKitEcommerce's
public read functions delegate to it so callers never choose an
adapter themselves.
Summary
Callbacks
@callback get_category(String.t(), keyword()) :: PhoenixKitEcommerce.Category.t() | nil
@callback get_category_by_any_slug(String.t(), keyword()) :: {:ok, PhoenixKitEcommerce.Category.t(), String.t()} | {:error, :not_found}
@callback get_category_by_slug_localized(String.t(), String.t(), keyword()) :: {:ok, PhoenixKitEcommerce.Category.t()} | {:error, :not_found}
@callback get_product(String.t(), keyword()) :: PhoenixKitEcommerce.Product.t() | nil
@callback get_product_by_any_slug(String.t(), keyword()) :: {:ok, PhoenixKitEcommerce.Product.t(), String.t()} | {:error, :not_found}
@callback get_product_by_slug_localized(String.t(), String.t(), keyword()) :: {:ok, PhoenixKitEcommerce.Product.t()} | {:error, :not_found}
@callback list_categories(keyword()) :: [PhoenixKitEcommerce.Category.t()]
@callback list_products(keyword()) :: [PhoenixKitEcommerce.Product.t()]
@callback list_products_by_ids([String.t()]) :: [PhoenixKitEcommerce.Product.t()]
@callback list_products_with_count(keyword()) :: {[PhoenixKitEcommerce.Product.t()], non_neg_integer()}
@callback product_counts_by_category() :: %{required(String.t()) => non_neg_integer()}
Functions
Returns the adapter module for the currently active product source.
Catalogue only when phoenix_kit_catalogue is loaded AND the
shop_product_source config key (phoenix_kit_shop_config) is
"catalogue"; Legacy otherwise — including when the key is absent
or the catalogue module isn't loaded, so a host without the optional
phoenix_kit_catalogue dependency always gets Legacy regardless of
the stored key.
Reads the config on every call rather than caching it here:
PhoenixKitEcommerce.get_config/1 is a plain primary-key
repo().get/2 against phoenix_kit_shop_config (no ETS/settings-cache
layer sits in front of it — the key lives in the shop config table,
not in PhoenixKit.Settings, and every writer, the test suite
included, updates that row directly), so this is one point read per
facade call. Accepted so that the switch takes effect without a
restart: a per-process memo would go stale in a long-lived LiveView
process, and a node-wide cache has no invalidation hook because the
row is written without going through this module. The Code. ensure_loaded?/1 guard runs FIRST so a host without the optional
phoenix_kit_catalogue dependency never pays for the read at all.