SelectoMix

Alpha software. Generation flows are usable but still evolving.

selecto_mix is the tooling package for setting up Selecto in an Elixir project.

Use it when you want to:

mix selecto_mix.verify --output PATH exhaustively checks the built-in finite model for atom/string key equivalence, construction-order independence, and JSON round-trip preservation. It exits non-zero with reproducible counterexamples when an invariant fails.

Installation

def deps do
[
{:selecto_mix, ">= 0.4.7 and < 0.6.0"},
{:selecto, ">= 0.4.6 and < 0.6.0"},
{:selecto_db_postgresql, ">= 0.4.4 and < 0.6.0"},
{:postgrex, ">= 0.0.0"},
{:ecto, "~> 3.10"}
]
end

Then run:

mix igniter.install selecto_mix
mix assets.build

For local multi-repo workspace development:

mix selecto.install --development-mode --source your-github-user

Database Adapters

Direct-database features (--adapter on gen.domain/gen.saved_views/etc., and mix selecto.setup) need the matching selecto_db_* adapter package added to your deps:

--adapter valueHex package
postgres / postgresqlselecto_db_postgresql
mysqlselecto_db_mysql
mariadbselecto_db_mariadb
sqliteselecto_db_sqlite
duckdbselecto_db_duckdb
mssql / sqlserverselecto_db_mssql

If the adapter module isn't loaded, SelectoMix's error/warning messages name the exact package to add (e.g. {:selecto_db_postgresql, ">= 0.0.0"}) so you can add it to mix.exs and run mix deps.get.

Quick Start

Generate a domain from one schema:

mix selecto.gen.domain MyApp.Catalog.Product

Generate domains for all schemas:

mix selecto.gen.domain --all

Generate domains for schemas under a module prefix (wildcard):

mix selecto.gen.domain MyApp.Catalog.*

Generate a domain plus LiveView wiring:

mix selecto.gen.domain MyApp.Catalog.Product --live

Generate a working LiveView without Ecto

Database-backed generation can introspect an existing table and generate the domain, overlay, and SelectoComponents LiveView together:

mix selecto.gen.domain \
--adapter postgresql \
--table equipment \
--database-url postgres://postgres:postgres@localhost/forge_works_dev \
--connection-name ForgeWorks.Database \
--live

The generated LiveView includes Aggregate, Detail, and Graph views. Database credentials are used only during generation and are never copied into generated source. At runtime, supervise a connection under the module name passed with --connection-name:

# config/dev.exs
config :forge_works, :database,
hostname: "localhost",
database: "forge_works_dev",
username: "postgres",
password: "postgres"
# lib/forge_works/application.ex
database_options = Application.fetch_env!(:forge_works, :database)
children = [
{Postgrex, Keyword.put(database_options, :name, ForgeWorks.Database)},
ForgeWorksWeb.Endpoint
]

If --connection-name is omitted, the generator uses <ApplicationModule>.Database and prints the exact expected runtime name. Use --live --saved-views to generate adapter-backed saved-view persistence alongside the DB-backed LiveView when the selected adapter supports it.

Generate a trusted provider module for Studio's host-app artifact registry:

mix selecto.gen.domain MyApp.Catalog.Product --studio-artifacts

The generated provider uses core Selecto.Domain inspection APIs. Register it with config :selecto_studio, :domain_artifacts in the host app when you want SelectoStudioWeb.DomainInspectionController to preload that domain.

The generated router notice includes the LiveView route plus optional SelectoComponents.QueryContract.Plug, SelectoComponents.QueryContract.Guide.Plug, and SelectoComponents.QueryContract.IntentValidator.Plug routes for serving query-contract.json, a compact Markdown query-guide.md, and a non-executing query intent validator.

Generate an Updato API endpoint and control panel:

mix selecto.gen.api products --domain MyApp.SelectoDomains.ProductDomain --schema MyApp.Catalog.Product

Generated Updato control panels can render write fields backed by Selecto choice_sources. To enable option loading and membership validation, assign choice-source resolvers and scope from the LiveView socket or session:

socket
|> assign(
choice_source_domain: MyApp.SelectoDomains.ProductChoiceSources.domain(),
choice_source_options_resolver: &MyApp.SelectoDomains.ProductChoiceSources.resolve_options/1,
choice_source_membership_resolver: &MyApp.SelectoDomains.ProductChoiceSources.resolve_membership/1,
choice_source_value_parser: &MyApp.SelectoDomains.ProductChoiceSources.parse_value/2,
choice_source_scope: %{
actor: socket.assigns.current_scope.user,
tenant: socket.assigns.current_scope.user.account_id,
context: %{surface: :updato_control_panel}
}
)

Keep actor, tenant, and required domain filters server-owned. Browser payloads may provide search text or a selected id, but they should not be trusted for tenant or authorization scope.

For choice sources whose Domain-of-Interest filters are security-sensitive, declare a fail-closed policy in the domain overlay:

defchoice_source(:product_assignees, %{
domain: :employees,
value_field: :id,
label_field: :full_name,
constraint_policy: %{domain_of_interest: :fail_closed}
})

Resolvers should return a closed option/membership result when that policy is present and any trusted filter cannot be enforced.

Core Workflow

Recommended workflow:

  1. Generate the base domain from an Ecto schema or database relation.
  2. Keep schema-derived structure in the generated file.
  3. Put custom filters, columns, and named functions in overlays when possible.
  4. Re-run generation when schemas change.

That keeps generated structure and user-authored behavior separate.

Common Tasks

After mix selecto.gen.domain creates a domain, it prints the matching export/check/import/inspect/describe/diagram/docs follow-up commands with suggested priv/selecto/*.normalized.json, priv/selecto/*.inspection.json, and docs/selecto/*.diagram.mmd / docs/selecto/*.md artifact paths.

Export a normalized domain JSON artifact:

mix selecto.domain.export MyApp.SelectoDomains.ProductDomain --output priv/selecto/product.normalized.json

Runtime-only values such as function captures are emitted as explicit placeholder metadata so the artifact remains JSON-safe for tools.

Check an exported artifact without loading the original domain module:

mix selecto.domain.check priv/selecto/product.normalized.json

Preview the current import/readback plan:

mix selecto.domain.import priv/selecto/product.normalized.json --check

The import check includes a generated-domain preview with the target module, target file, reconstructed sections, and runtime placeholders that still need manual handling. It also parses the source preview and checks that the target module and domain/0 are present without executing the code.

Add --source to print the would-be Elixir module source without writing it, or use --format json to include the source preview in the import plan.

Write the generated module only after that preview is fully validated and has no runtime placeholders:

mix selecto.domain.import priv/selecto/product.normalized.json --write --target-file lib/my_app/selecto_domains/product_domain.ex

Existing files are preserved by default; pass --force when you intentionally want to overwrite the target.

Inspect the same artifact for a compact sections/counts/registries summary:

mix selecto.domain.inspect priv/selecto/product.normalized.json

Generate Studio/tooling inspection JSON from the same artifact:

mix selecto.domain.describe priv/selecto/product.normalized.json --output priv/selecto/product.inspection.json

Generate a Mermaid diagram from the inspection artifact:

mix selecto.domain.diagram priv/selecto/product.inspection.json --output docs/selecto/product.diagram.mmd

Generate Markdown docs from the same artifact, including capability usage tables when the domain declares capability references:

mix selecto.domain.docs priv/selecto/product.normalized.json --output docs/selecto/product.md

Diff two artifacts:

mix selecto.domain.diff priv/selecto/old.normalized.json priv/selecto/new.normalized.json

UDF Workflow

Generated domains include a stable functions: %{} section.

Generated overlays include deffunction examples so named function registrations can live outside regenerated files.

Recommended UDF pattern:

  1. generate the domain
  2. keep structural metadata in the generated domain file
  3. add custom deffunction definitions in the overlay
  4. regenerate safely as schemas evolve

Status

Current 0.4.x scope:

Demos And Tutorials