Global, code-defined list of metadata fields that catalogue resources (items and catalogues) can opt into.
Resources store chosen values in resource.data["meta"] as a flat map
keyed by the definition's :key — e.g. %{"color" => "red"}. Only
fields the user has explicitly added appear in that map.
The multilang layer owns the top-level data map (keys like _name,
_primary_language, per-language entries) — metadata lives strictly
under the "meta" sub-key so the two don't collide.
All fields are text inputs for now — typed inputs (decimal / enum /
etc.) can be reintroduced later by adding a :type field to the
definition shape and dispatching at render + cast time.
Edit definitions/1 to add/remove fields. Removing a field from the
list does not wipe stored values — resources that already hold a
value for the removed key will surface it as "Legacy" in the form so
the data isn't lost; the user can clear it manually.
Form helpers
Callers (LiveViews) drive the three-phase metadata flow through this module's pure helpers:
build_state/2turns aresource.datablob into%{attached, values}on mount (known keys first in definition order, legacy keys sorted).absorb_params/2folds the user's latest inputs from the form's"meta"submap intostate.valueson validate/save.inject_into_data/3casts values to storage shape and wedges them intoparams["data"]["meta"]right before hitting the context.
See PhoenixKitCatalogue.Web.ItemFormLive / CatalogueFormLive for
the reference wiring — one meta_state assign and three calls.
Summary
Functions
Merges whatever the user currently has typed into the metadata inputs
(delivered as params["meta"]) into the state's :values. Unattached
keys are ignored so the render doesn't resurrect a row the user just
removed.
Builds the initial meta state from a resource's data map.
Normalizes a raw form value for storage: trims whitespace, collapses
blanks to nil so callers can drop empty entries from the JSONB map.
Fetches a single definition by key. Returns nil if the key isn't in definitions/1.
The global list of metadata definitions for a given resource type. Order here is the order used for the "Add metadata" dropdown.
Casts the state into its storage shape and wedges it into
params["data"]["meta"]. Known-key values go through cast_value/2
(blanks become nil → the key drops). Legacy keys (no current
definition) pass through untouched so their data isn't silently
nuked by a save — the user clears them explicitly via the × button.
Types
Functions
Merges whatever the user currently has typed into the metadata inputs
(delivered as params["meta"]) into the state's :values. Unattached
keys are ignored so the render doesn't resurrect a row the user just
removed.
Returns the updated state; leaves the state untouched when the params
don't contain a "meta" submap.
@spec build_state(resource_type(), map() | struct() | nil) :: state()
Builds the initial meta state from a resource's data map.
Accepts either the raw resource.data (a map or nil) or a struct
with a :data field — both shapes surface in practice because new
structs default to %{} but a caller may pass nil if the field is
unset. Known keys (those in definitions(resource_type)) land first
in their declared order; legacy keys (stored but no longer defined)
come after, alphabetized, so the UI can flag them and offer a
remove-only action without dropping stored data.
Returns %{attached: [key], values: %{key => stringified_value}}.
Examples
iex> Metadata.build_state(:catalogue, %{"meta" => %{"brand" => "Acme"}})
%{attached: ["brand"], values: %{"brand" => "Acme"}}
iex> Metadata.build_state(:item, nil)
%{attached: [], values: %{}}
@spec cast_value(definition(), term()) :: String.t() | nil
Normalizes a raw form value for storage: trims whitespace, collapses
blanks to nil so callers can drop empty entries from the JSONB map.
@spec definition(resource_type(), String.t()) :: definition() | nil
Fetches a single definition by key. Returns nil if the key isn't in definitions/1.
@spec definitions(resource_type()) :: [definition()]
The global list of metadata definitions for a given resource type. Order here is the order used for the "Add metadata" dropdown.
The :label values are translated at call time via
PhoenixKitWeb.Gettext — do not cache the result across locale
changes. The :key is stable (it's the JSONB key) and never
translated.
@spec inject_into_data(map(), state(), resource_type()) :: map()
Casts the state into its storage shape and wedges it into
params["data"]["meta"]. Known-key values go through cast_value/2
(blanks become nil → the key drops). Legacy keys (no current
definition) pass through untouched so their data isn't silently
nuked by a save — the user clears them explicitly via the × button.