Discovers the schema version of every installed PhoenixKit module that owns its migrations.
Modules that ship their own tables implement PhoenixKit.Module.migration_module/0,
returning a coordinator with current_version/0 (what the code needs) and
migrated_version_runtime/1 (what the database has). Examples in the wild:
phoenix_kit_inbox, phoenix_kit_boards, phoenix_kit_web_analytics,
phoenix_kit_legal, phoenix_kit_stats.
This module is the shared read side of that contract. mix phoenix_kit.status
uses it to report per-module versions and mix phoenix_kit.update uses it
to decide what to migrate — before this existed each task had its own copy
of the discovery logic, and only update had it at all, which is why
status never mentioned modules.
Everything here is read-only and defensive: a module whose coordinator raises,
exits, or reports a non-integer version is recorded as :error with the
message, never crashing the caller. A broken third-party module must not take
down mix phoenix_kit.status.
Example
iex> PhoenixKit.Migrations.Modules.list()
[
%{
name: "Inbox",
module: PhoenixKitInbox,
migration_module: PhoenixKitInbox.Migrations,
installed: 1,
target: 1,
status: :up_to_date,
error: nil
}
]
Summary
Functions
Classifies one module's installed version against the version its code wants.
Filters a list/1 result down to entries whose coordinator raised. Surfaced
separately so tasks can warn about a broken module instead of quietly
omitting it from the report.
Every discovered module that owns migrations, sorted by display name.
Filters a list/1 result down to the entries a mix phoenix_kit.update run
would act on — those needing their tables created or upgraded.
Types
@type entry() :: %{ name: String.t(), module: module(), migration_module: module(), installed: non_neg_integer(), target: non_neg_integer() | nil, status: status(), error: String.t() | nil }
@type status() :: :not_installed | :needs_update | :up_to_date | :error
Where one module's schema stands.
:not_installed— the code is present but its tables have never been created (installed version 0). The nextmix phoenix_kit.updatecreates them.:needs_update— tables exist at an older version than the code expects.:up_to_date— database matches (or exceeds) what the code needs.:error— the module's coordinator raised, exited, or reported a non-integer version; the:errorfield has the message.
Functions
Classifies one module's installed version against the version its code wants.
Public because it is the whole read-side decision — mix phoenix_kit.status
renders it and mix phoenix_kit.update migrates off it — and a private
version could only be tested through a hand-built entry that supplied the
answer, which is no test at all.
Both versions must be integers. A coordinator that reports anything else
(nil for "no version comment found" is the tempting one) is :error, never
silently :up_to_date: under Erlang term ordering nil >= 2 is true, so an
unguarded comparison would mark a module with no tables as current and skip
its migration forever.
iex> alias PhoenixKit.Migrations.Modules
iex> {Modules.classify(2, 2), Modules.classify(3, 2)}
{:up_to_date, :up_to_date}
iex> {Modules.classify(0, 1), Modules.classify(1, 5)}
{:not_installed, :needs_update}
iex> Modules.classify(nil, 2)
:error
Filters a list/1 result down to entries whose coordinator raised. Surfaced
separately so tasks can warn about a broken module instead of quietly
omitting it from the report.
Pure filter, same reasoning as pending/1.
Every discovered module that owns migrations, sorted by display name.
Returns [] when module discovery itself fails (no beam files, app not
loaded) rather than raising — callers are CLI tasks that should degrade to
"no modules" instead of blowing up.
Options
:prefix— Postgres schema the install lives in. Defaults to"public".
Filters a list/1 result down to the entries a mix phoenix_kit.update run
would act on — those needing their tables created or upgraded.
A pure filter, deliberately: an earlier version also accepted options and did
its own list/1, which made pending([]) ambiguous — an empty entry list
and empty options are the same term, so "nothing to filter" silently became
"go query the database". Callers that want both compose them:
Modules.list(prefix: prefix) |> Modules.pending()