PhoenixKit.Migrations.Modules (phoenix_kit v2.0.1)

Copy Markdown View Source

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

Types

Where one module's schema stands.

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

entry()

@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
}

status()

@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 next mix phoenix_kit.update creates 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 :error field has the message.

Functions

classify(installed, target)

@spec classify(term(), term()) :: status()

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

failed(entries)

@spec failed([entry()]) :: [entry()]

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.

list(opts \\ [])

@spec list(keyword()) :: [entry()]

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".

pending(entries)

@spec pending([entry()]) :: [entry()]

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()