defmodule Mix.Tasks.Pgflow.Setup do
@shortdoc "Generates one Ecto migration that installs pgflow's core schema + helper functions"
@moduledoc """
Generates an Ecto migration in the consumer app that installs the pgflow
schema by calling `PgFlow.Migration.up/0` and `PgFlow.HelpersMigration.up/0`
(and optionally `PgFlowDashboard.Migration.up/0`) in the correct order.
This mirrors `mix tango.setup` and `mix good_analytics.setup`. The output
is a single wrapper migration, not per-statement migrations — SQL is
vendored inside pgflow.
## Usage
mix pgflow.setup
mix pgflow.setup --repo MyApp.OtherRepo
mix pgflow.setup --no-helpers
mix pgflow.setup --dashboard
## Options
* `--repo` - Ecto repo module to install against. Defaults to the first
entry in `config :my_app, ecto_repos: [...]`.
* `--no-helpers` - Skip `PgFlow.HelpersMigration` (Elixir-binding SQL
helpers: worker registration, flow input/output queries). Default:
helpers are installed.
* `--dashboard` - Also install `PgFlowDashboard.Migration` (dashboard
views). Default: skipped. Add this if you use the PgFlow LiveView
dashboard.
## Prerequisites
Before `mix ecto.migrate` picks up the generated migration, the consumer
must have:
* pgmq installed (run `mix pgflow.gen.pgmq_migration` to generate a
migration that installs pgmq via SQL-only method — required unless
your Postgres already provides pgmq, e.g. Supabase).
* `pg_cron` extension registered (`CREATE EXTENSION pg_cron` in an
earlier migration).
* `citext`, `pg_trgm`, `pgcrypto` extensions registered.
The typical migration order is:
1. `install_extensions` (citext, pg_trgm, pgcrypto, pg_cron)
2. `install_pgmq` (from `mix pgflow.gen.pgmq_migration`)
3. `setup_pgflow` (this task)
"""
use Mix.Task
alias Mix.Tasks.Pgflow.Helpers
@impl Mix.Task
def run(args) do
{opts, _, _} =
OptionParser.parse(args,
switches: [repo: :string, helpers: :boolean, dashboard: :boolean]
)
repo = Helpers.resolve_repo(opts[:repo])
helpers? = Keyword.get(opts, :helpers, true)
dashboard? = Keyword.get(opts, :dashboard, false)
migrations_dir = Path.join(Helpers.priv_path(repo), "migrations")
File.mkdir_p!(migrations_dir)
timestamp = timestamp()
filename = "#{timestamp}_setup_pgflow.exs"
full_path = Path.join(migrations_dir, filename)
File.write!(full_path, migration_content(repo, helpers?, dashboard?))
Mix.shell().info("""
Created migration: #{full_path}
Run `mix ecto.migrate` to apply.
Ensure pgmq is installed before this migration runs (via
`mix pgflow.gen.pgmq_migration`) unless your Postgres already ships pgmq.
""")
end
defp timestamp do
{{y, m, d}, {hh, mm, ss}} = :calendar.universal_time()
:io_lib.format("~4..0B~2..0B~2..0B~2..0B~2..0B~2..0B", [y, m, d, hh, mm, ss])
|> IO.iodata_to_binary()
end
defp migration_content(repo, helpers?, dashboard?) do
up_steps =
[
"PgFlow.Migration.up()",
if(helpers?, do: "PgFlow.HelpersMigration.up()"),
if(dashboard?, do: "PgFlowDashboard.Migration.up()")
]
|> Enum.reject(&is_nil/1)
down_steps =
[
if(dashboard?, do: "PgFlowDashboard.Migration.down()"),
if(helpers?, do: "PgFlow.HelpersMigration.down()"),
"PgFlow.Migration.down()"
]
|> Enum.reject(&is_nil/1)
up_body = up_steps |> Enum.map_join("\n ", & &1)
down_body = down_steps |> Enum.map_join("\n ", & &1)
gen_command =
"mix pgflow.setup" <>
if(helpers?, do: "", else: " --no-helpers") <>
if(dashboard?, do: " --dashboard", else: "")
sources = installed_sources_note(helpers?, dashboard?)
"""
defmodule #{inspect(repo)}.Migrations.SetupPgflow do
@moduledoc \"\"\"
Installs pgflow's core schema + Elixir helper functions.
Generated by: `#{gen_command}`
SQL sources (vendored inside pgflow):
#{sources}
`PgFlow.Migration.up/0` runs via EctoEvolver with statement-level
splitting — the V01 core bundle contains 138 statements and
Postgrex rejects multi-statement queries (error `42601`).
`PgFlow.HelpersMigration.up/0` adds Elixir-binding SQL functions
(`flow_exists`, `register_worker`, `get_flow_input`, ...) that the
library's query layer calls via RPC.
Idempotent: rerun is a no-op via EctoEvolver's tracking comment on
`pgflow.pgflow_version`.
Prerequisites (applied as earlier migrations):
1. Postgres extensions — `mix pgflow.gen.postgres_extensions_migration`
2. pgmq schema + functions — `mix pgflow.gen.pgmq_migration` (or a
native `CREATE EXTENSION pgmq` on Supabase/atlas-postgres-pgflow)
\"\"\"
use Ecto.Migration
def up do
#{up_body}
end
def down do
#{down_body}
end
end
"""
end
defp installed_sources_note(helpers?, dashboard?) do
[
{true,
" - priv/pgflow_core/sql/versions/v01/v01_up.sql (core: flows, runs, steps, workers, step_tasks, step_states, deps)"},
{helpers?,
" - priv/pgflow_helpers/sql/versions/v01/v01_up.sql (Elixir-binding RPC helpers)"},
{dashboard?,
" - priv/pgflow_dashboard/sql/versions/v01/v01_up.sql (LiveView dashboard views + functions)"}
]
|> Enum.filter(fn {include?, _} -> include? end)
|> Enum.map_join("\n ", fn {_, line} -> line end)
end
end