Packages
ash
3.26.0
3.29.3
3.29.2
3.29.1
3.29.0
3.28.0
3.27.8
3.27.7
3.27.6
3.27.5
3.27.4
3.27.3
3.27.2
3.27.1
3.27.0
3.26.0
3.25.2
3.25.1
3.25.0
retired
3.24.7
3.24.6
3.24.5
3.24.4
3.24.3
3.24.2
3.24.1
3.24.0
3.23.1
3.23.0
3.22.2
3.22.1
3.22.0
3.21.3
3.21.2
3.21.1
3.21.0
3.20.0
3.19.3
3.19.2
3.19.1
3.19.0
3.18.0
3.17.1
3.17.0
3.16.0
3.15.0
3.14.1
3.14.0
retired
3.13.2
3.13.1
3.13.0
3.12.0
3.11.3
3.11.2
3.11.1
3.11.0
3.10.1
3.10.0
3.9.0
3.8.0
3.7.6
3.7.5
3.7.4
3.7.3
3.7.2
3.7.1
3.7.0
retired
3.6.3
retired
3.6.2
3.6.1
3.6.0
3.5.43
3.5.42
3.5.41
3.5.40
3.5.39
3.5.38
3.5.37
3.5.36
3.5.35
3.5.34
3.5.33
3.5.32
3.5.31
3.5.30
3.5.29
3.5.28
3.5.27
3.5.26
3.5.25
3.5.24
3.5.23
3.5.22
3.5.21
3.5.20
3.5.19
3.5.18
3.5.17
3.5.16
3.5.15
3.5.14
3.5.13
3.5.12
3.5.11
3.5.10
3.5.9
3.5.8
3.5.7
3.5.6
3.5.5
3.5.4
3.5.3
3.5.2
3.5.1
3.5.0
3.4.74
retired
3.4.73
3.4.72
3.4.71
3.4.70
3.4.69
3.4.68
3.4.67
3.4.66
3.4.65
3.4.64
3.4.63
3.4.62
3.4.61
3.4.60
3.4.59
3.4.58
3.4.57
3.4.56
3.4.55
3.4.54
3.4.53
3.4.52
3.4.51
3.4.50
3.4.49
3.4.48
3.4.47
3.4.46
3.4.45
3.4.44
3.4.43
3.4.42
3.4.41
3.4.40
3.4.39
3.4.38
3.4.37
3.4.36
3.4.35
3.4.34
3.4.33
3.4.32
3.4.31
3.4.30
3.4.29
3.4.28
3.4.27
3.4.26
3.4.25
3.4.24
3.4.23
3.4.22
3.4.21
3.4.20
3.4.19
3.4.18
3.4.17
3.4.16
3.4.15
3.4.14
3.4.13
3.4.12
3.4.11
3.4.10
3.4.9
3.4.8
3.4.7
3.4.6
3.4.5
3.4.4
3.4.3
3.4.2
3.4.1
3.4.0
3.3.3
3.3.2
3.3.1
3.3.0
3.2.6
3.2.5
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.8
3.1.7
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.16
3.0.15
3.0.14
3.0.13
3.0.12
3.0.11
3.0.10
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.46
3.0.0-rc.45
3.0.0-rc.44
3.0.0-rc.43
3.0.0-rc.42
3.0.0-rc.41
3.0.0-rc.40
3.0.0-rc.39
3.0.0-rc.38
3.0.0-rc.37
3.0.0-rc.36
3.0.0-rc.35
3.0.0-rc.34
3.0.0-rc.33
3.0.0-rc.32
3.0.0-rc.31
3.0.0-rc.29
3.0.0-rc.28
3.0.0-rc.27
3.0.0-rc.26
3.0.0-rc.25
3.0.0-rc.24
3.0.0-rc.23
3.0.0-rc.22
3.0.0-rc.21
3.0.0-rc.20
3.0.0-rc.19
3.0.0-rc.18
3.0.0-rc.17
3.0.0-rc.16
3.0.0-rc.15
3.0.0-rc.14
3.0.0-rc.13
3.0.0-rc.12
3.0.0-rc.11
3.0.0-rc.10
3.0.0-rc.9
3.0.0-rc.8
3.0.0-rc.7
3.0.0-rc.6
3.0.0-rc.5
3.0.0-rc.4
3.0.0-rc.3
3.0.0-rc.1
3.0.0-rc.0
2.21.15
2.21.14
2.21.13
2.21.12
2.21.11
2.21.10
2.21.9
2.21.8
2.21.7
2.21.6
2.21.5
2.21.4
2.21.3
2.21.2
2.21.1
2.21.0
2.20.3
2.20.2
2.20.1
2.20.0
2.19.14
2.19.13
2.19.12
2.19.11
2.19.10
2.19.9
2.19.8
2.19.7
2.19.6
2.19.5
2.19.4
2.19.3
retired
2.19.2
retired
2.19.1
retired
2.19.0
retired
2.18.2
2.18.1
2.18.0
2.17.24
2.17.23
2.17.22
2.17.21
2.17.20
2.17.19
2.17.18
2.17.17
2.17.16
2.17.15
2.17.14
2.17.13
2.17.12
2.17.11
2.17.10
2.17.9
2.17.8
2.17.7
2.17.6
2.17.5
2.17.4
2.17.3
2.17.2
2.17.1
2.17.0
2.16.1
2.16.0
2.15.20
2.15.19
2.15.18
2.15.17
2.15.16
2.15.15
2.15.14
2.15.13
2.15.12
2.15.11
2.15.10
2.15.9
2.15.8
2.15.7
2.15.6
2.15.5
2.15.4
2.15.2
2.15.1
2.15.0
2.14.21
2.14.20
2.14.19
2.14.18
2.14.17
2.14.16
2.14.15
2.14.14
2.14.13
2.14.12
2.14.11
2.14.10
2.14.9
2.14.8
2.14.7
2.14.6
2.14.5
2.14.4
2.14.3
2.14.2
2.14.1
2.14.0
2.13.4
retired
2.13.3
2.13.2
2.13.1
2.13.0
2.12.1
2.12.0
2.11.11
2.11.10
2.11.9
2.11.8
2.11.7
2.11.6
2.11.5
2.11.4
2.11.3
2.11.2
2.11.1
2.11.0
2.11.0-rc.3
2.11.0-rc.2
2.11.0-rc.1
2.11.0-rc.0
2.10.2
2.10.1
2.10.0
2.9.29
2.9.28
2.9.27
2.9.26
2.9.25
2.9.24
2.9.23
2.9.22
2.9.21
2.9.20
2.9.19
2.9.18
2.9.17
2.9.16
2.9.15
2.9.14
2.9.13
2.9.12
2.9.11
2.9.10
2.9.9
2.9.8
2.9.7
2.9.6
2.9.5
2.9.4
2.9.3
2.9.2
2.9.1
2.9.0
2.8.1
2.8.0
2.7.1
2.7.0
2.6.31
2.6.30
2.6.29
2.6.28
2.6.27
2.6.26
2.6.25
2.6.24
2.6.23
2.6.22
2.6.21
2.6.20
2.6.19
2.6.18
2.6.17
2.6.16
2.6.15
2.6.14
2.6.13
2.6.11
2.6.10
2.6.9
2.6.8
2.6.7
2.6.6
2.6.5
2.6.4
2.6.3
2.6.2
2.6.1
2.6.0
2.5.16
2.5.15
2.5.14
2.5.13
2.5.12
2.5.11
2.5.10
2.5.9
2.5.8
2.5.7
2.5.6
2.5.5
2.5.4
2.5.3
2.5.2
2.5.1
2.5.0
2.5.0-rc.6
2.5.0-rc.5
2.5.0-rc.4
2.5.0-rc.3
2.5.0-rc.2
2.5.0-rc.1
2.5.0-rc.0
2.4.30
2.4.29
2.4.28
2.4.27
2.4.26
2.4.25
2.4.24
2.4.23
2.4.22
2.4.21
2.4.20
2.4.19
2.4.18
2.4.17
2.4.16
2.4.15
2.4.14
2.4.13
2.4.12
2.4.11
2.4.10
2.4.9
2.4.8
2.4.7
2.4.6
2.4.5
2.4.4
2.4.3
2.4.2
2.4.1
2.4.0
2.3.0
2.2.0
2.1.0
2.0.0
2.0.0-rc.15
2.0.0-rc.14
2.0.0-rc.13
2.0.0-rc.12
2.0.0-rc.11
2.0.0-rc.10
2.0.0-rc.9
2.0.0-rc.8
2.0.0-rc.7
2.0.0-rc.6
2.0.0-rc.5
2.0.0-rc.4
2.0.0-rc.3
2.0.0-rc.2
2.0.0-rc.1
2.0.0-rc.0
2.0.0-pre.8
2.0.0-pre.7
2.0.0-pre.6
2.0.0-pre.5
2.0.0-pre.4
2.0.0-pre.3
2.0.0-pre.2
2.0.0-pre.1
2.0.0-pre.0
1.53.3
1.53.2
1.53.0
1.52.0-rc.22
1.52.0-rc.21
1.52.0-rc.20
1.52.0-rc.19
1.52.0-rc.18
1.52.0-rc.17
1.52.0-rc.16
1.52.0-rc.15
1.52.0-rc.14
1.52.0-rc.13
1.52.0-rc.12
1.52.0-rc.11
1.52.0-rc.10
1.52.0-rc.9
1.52.0-rc.8
1.52.0-rc.7
1.52.0-rc.6
1.52.0-rc.5
1.52.0-rc.4
1.52.0-rc.3
1.52.0-rc.2
1.52.0-rc.1
1.52.0-rc.0
1.51.2
1.51.1
retired
1.51.0
1.50.21
1.50.20
1.50.19
1.50.18
1.50.17
1.50.16
1.50.15
1.50.14
1.50.13
1.50.12
1.50.11
1.50.10
1.50.9
1.50.8
1.50.7
1.50.6
1.50.5
1.50.4
1.50.3
1.50.2
1.50.1
1.50.0
1.49.0
1.48.0-rc.30
1.48.0-rc.29
1.48.0-rc.28
1.48.0-rc.27
1.48.0-rc.26
1.48.0-rc.25
1.48.0-rc.24
1.48.0-rc.23
1.48.0-rc.22
1.48.0-rc.21
1.48.0-rc.20
1.48.0-rc.19
1.48.0-rc.18
1.48.0-rc.17
1.48.0-rc.16
1.48.0-rc.15
1.48.0-rc.14
1.48.0-rc.13
1.48.0-rc.12
1.48.0-rc.11
1.48.0-rc.10
1.48.0-rc.9
1.48.0-rc.8
1.48.0-rc.7
1.48.0-rc.6
1.48.0-rc.5
1.48.0-rc.4
1.48.0-rc.3
1.48.0-rc.2
1.48.0-rc.1
1.48.0-rc.0
1.47.12
1.47.11
1.47.10
1.47.9
1.47.8
1.47.7
1.47.6
1.47.5
1.47.4
1.47.3
1.47.2
1.47.1
1.47.0
1.46.13
1.46.12
1.46.11
1.46.10
1.46.9
1.46.8
1.46.7
1.46.6
1.46.5
1.46.4
1.46.3
1.46.2
1.46.1
1.46.0
1.45.0-rc9
1.45.0-rc8
1.45.0-rc7
1.45.0-rc6
1.45.0-rc5
1.45.0-rc4
1.45.0-rc3
1.45.0-rc20
1.45.0-rc2
1.45.0-rc19
1.45.0-rc18
1.45.0-rc17
1.45.0-rc16
1.45.0-rc15
1.45.0-rc14
1.45.0-rc13
1.45.0-rc12
1.45.0-rc11
1.45.0-rc10
1.45.0-rc1
1.45.0-rc0
1.44.13
1.44.12
1.44.11
1.44.10
1.44.9
1.44.8
1.44.7
1.44.6
1.44.5
1.44.4
1.44.3
1.44.2
1.44.1
1.44.0
1.43.12
1.43.11
1.43.10
1.43.9
1.43.8
1.43.7
1.43.6
1.43.5
1.43.4
1.43.3
1.43.2
1.43.1
1.43.0
1.42.0
1.41.12
1.41.11
1.41.10
1.41.9
1.41.8
1.41.7
1.41.6
1.41.5
1.41.4
1.41.3
1.41.2
1.41.1
1.41.0
1.40.0
1.39.7
1.39.6
1.39.5
1.39.4
1.39.3
1.39.2
1.39.1
1.39.0
1.38.0
1.37.2
1.37.1
1.37.0
1.36.22
1.36.21
1.36.19
1.36.18
1.36.17
1.36.16
1.36.15
1.36.14
1.36.13
1.36.12
1.36.11
1.36.10
1.36.9
1.36.8
1.36.7
1.36.6
1.36.5
1.36.4
1.36.3
1.36.2
1.36.0
1.35.1
1.35.0
1.34.9
1.34.8
1.34.7
1.34.6
1.34.5
1.34.4
1.34.3
1.34.2
1.34.1
1.34.0
1.33.0
1.32.2
1.32.1
1.32.0
1.31.1
1.31.0
1.30.2
1.30.1
1.29.0-rc1
1.29.0-rc0
1.28.1
1.28.0
1.27.1
1.27.0
1.26.13
1.26.12
1.26.11
1.26.10
1.26.9
1.26.8
1.26.7
1.26.6
1.26.5
1.26.4
1.26.2
1.26.1
1.26.0
1.25.8
1.25.7
1.25.6
1.25.5
1.25.4
1.25.3
1.25.2
1.25.1
1.25.0
1.24.2
1.24.1
1.24.0
1.23.3
1.23.2
1.23.1
1.23.0
1.22.1
1.22.0
1.20.1
1.20.0
1.19.1
1.19.0
1.18.1
1.18.0
1.17.1
1.17.0
1.16.2
1.15.1
1.15.0
1.14.0
1.13.4
1.13.3
1.13.2
1.13.1
1.13.0
1.12.0
1.11.1
1.11.0
1.10.0
1.9.0
1.8.0
1.7.0
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.4.1
1.4.0
1.3.1
1.3.0
1.2.1
1.2.0
1.1.3
1.1.2
1.1.0
1.0.3
1.0.2
1.0.1
1.0.0
0.13.1
0.13.0
0.12.0
0.10.0
0.9.1
0.9.0
0.8.0
0.7.0
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.0
0.3.0
0.2.0
0.1.9
0.1.8
0.1.3
0.1.1
0.1.0
A declarative, extensible framework for building Elixir applications.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/ash/info/manifest/generator.ex
# SPDX-FileCopyrightText: 2025 ash contributors <https://github.com/ash-project/ash/graphs/contributors>
#
# SPDX-License-Identifier: MIT
defmodule Ash.Info.Manifest.Generator do
@moduledoc """
Main pipeline for generating an `%Ash.Info.Manifest{}` from an OTP app's Ash domains.
Pipeline:
1. Discover domains and resources
2. Optionally filter to specified actions
3. Run reachability analysis
4. Build resource and type structs
5. Produce `%Ash.Info.Manifest{}`
"""
alias Ash.Info.Manifest.Generator.{
ActionBuilder,
CapabilitiesBuilder,
Reachability,
ResourceBuilder,
TypeResolver
}
@doc """
Generate an API specification.
## Options
* `:otp_app` - The OTP app to scan (required)
* `:action_entrypoints` - Optional list of action entrypoints. Each entry can be:
* `{resource_module, action_name}` — simple tuple (config defaults to `%{}`)
* `%{resource: module, action: atom, config: map}` — with extension-specific config
When omitted, all actions across all domains are included.
* `:overrides` - Optional keyword list of overrides:
* `:always` - Keyword list of items to always include regardless of reachability:
* `:resources` - List of resource modules. These are added as reachability roots
(with no action arguments traversed) so their field types and relationships
are also discovered.
* `:types` - List of Ash type modules to include as standalone types directly.
## Visibility Options
By default, only items with `public?: true` are included. Set any of these to `true`
to also include private items:
* `:include_private_attributes?` - Include private attributes (default: `false`)
* `:include_private_calculations?` - Include private calculations (default: `false`)
* `:include_private_aggregates?` - Include private aggregates (default: `false`)
* `:include_private_relationships?` - Include private relationships (default: `false`)
* `:include_private_arguments?` - Include private action arguments (default: `false`)
* `:include_private_actions?` - Include private actions (default: `false`).
Private actions (`public? false`) are omitted from `entrypoints` even
when explicitly named in `:action_entrypoints`.
"""
@build_opt_keys [
:include_private_attributes?,
:include_private_calculations?,
:include_private_aggregates?,
:include_private_relationships?,
:include_private_arguments?,
:include_private_actions?
]
@spec generate(keyword()) :: {:ok, Ash.Info.Manifest.t()} | {:error, term()}
def generate(opts) do
otp_app = Keyword.fetch!(opts, :otp_app)
action_filter = Keyword.get(opts, :action_entrypoints)
overrides = Keyword.get(opts, :overrides, [])
build_opts = Keyword.take(opts, @build_opt_keys)
always_opts = Keyword.get(overrides, :always, [])
always_resources = Keyword.get(always_opts, :resources, [])
always_types = Keyword.get(always_opts, :types, [])
# Discover all domains and their resources
domains = Ash.Info.domains(otp_app)
# Normalize entrypoints and build resource → action_names map for reachability
normalized_entries = normalize_action_filter(action_filter)
resource_action_map = build_resource_action_map(domains, action_filter)
# Build reachability entries: when filtering by actions, pass {resource, action_names}
# so reachability only traverses arguments of included actions
reachability_entries =
if action_filter do
Enum.map(resource_action_map, fn {resource, action_names} ->
{resource, action_names || []}
end)
else
Map.keys(resource_action_map)
end
# Add always-resources as reachability roots with [] action names
# (include fields/relationships but no action arguments)
always_resource_entries = Enum.map(always_resources, &{&1, []})
reachability_entries = reachability_entries ++ always_resource_entries
# Run reachability analysis
{reachable_resources, standalone_types} =
Reachability.find_reachable(reachability_entries, build_opts)
# Merge always-resources and always-types into reachability results
reachable_resources = Enum.uniq(reachable_resources ++ always_resources)
standalone_types = Enum.uniq(standalone_types ++ always_types)
# Embedded resources are types, not resources — they live in `manifest.types`
# as `kind: :embedded_resource` entries with their full Resource definition
# nested in `type.resource`. Domain-owned resources go in `manifest.resources`.
{embedded_resource_modules, domain_resource_modules} =
Enum.split_with(reachable_resources, &Ash.Resource.Info.embedded?/1)
resources_by_data_layer = collect_resources_by_data_layer(domain_resource_modules)
{filter_capabilities, sort_capabilities} =
CapabilitiesBuilder.build(resources_by_data_layer: resources_by_data_layer)
# Build resource specs (no actions — those live in entrypoints)
resources =
domain_resource_modules
|> Enum.sort_by(&Module.split/1)
|> Enum.map(fn resource ->
ResourceBuilder.build(resource, build_opts, filter_capabilities)
end)
# Build entrypoints — one per normalized entry (not per unique action)
entrypoints =
build_entrypoints(
normalized_entries,
resource_action_map,
build_opts
)
# Build standalone type specs (full definitions, not references) for
# named types (enums, NewTypes) plus embedded resources.
embedded_type_entries =
embedded_resource_modules
|> Enum.sort_by(&Module.split/1)
|> Enum.map(fn mod ->
resource = ResourceBuilder.build(mod, build_opts, filter_capabilities)
%Ash.Info.Manifest.Type{
kind: :embedded_resource,
name: resource.name,
module: mod,
resource_module: mod,
resource: resource
}
end)
named_type_entries =
standalone_types
|> Enum.sort_by(fn module ->
if is_atom(module) and Code.ensure_loaded?(module) == true do
Module.split(module)
else
[to_string(module)]
end
end)
|> Enum.map(fn type_module ->
TypeResolver.resolve_definition(type_module)
end)
types = named_type_entries ++ embedded_type_entries
{:ok,
%Ash.Info.Manifest{
resources: resources,
types: types,
entrypoints: entrypoints,
filter_capabilities: filter_capabilities,
sort_capabilities: sort_capabilities
}}
end
# Groups reachable resources by their data layer, dropping resources whose
# `Ash.DataLayer.data_layer/1` returns `nil`. The resulting map is what
# `CapabilitiesBuilder` needs to pass a real (non-nil) resource into each
# data layer's `functions/1` callback.
defp collect_resources_by_data_layer(resources) do
resources
|> Enum.group_by(&Ash.DataLayer.data_layer/1)
|> Map.delete(nil)
end
# Normalizes action_filter entries into {resource, action_name, config} triples.
# Returns nil when no filter (all actions included).
defp normalize_action_filter(nil), do: nil
defp normalize_action_filter(entries) when is_list(entries) do
Enum.map(entries, fn
{resource, action_name} ->
{resource, action_name, %{}}
%{resource: resource, action: action_name} = entry ->
{resource, action_name, Map.get(entry, :config, %{})}
end)
end
# Builds resource → unique action_names map for reachability.
defp build_resource_action_map(domains, nil) do
for domain <- domains,
resource <- Ash.Domain.Info.resources(domain),
reduce: %{} do
acc -> Map.put(acc, resource, nil)
end
end
defp build_resource_action_map(_domains, action_filter) when is_list(action_filter) do
Enum.reduce(action_filter, %{}, fn entry, map ->
{resource, action_name, _config} =
case entry do
{r, a} -> {r, a, %{}}
%{resource: r, action: a} -> {r, a, %{}}
end
Map.update(map, resource, [action_name], fn names ->
if action_name in names, do: names, else: [action_name | names]
end)
end)
end
# When no filter: one entrypoint per action on each resource
defp build_entrypoints(nil, resource_action_map, opts) do
include_private? = Keyword.get(opts, :include_private_actions?, false)
resource_action_map
|> Enum.flat_map(fn {resource, _action_names} ->
resource
|> Ash.Resource.Info.actions()
|> Enum.filter(&include_action?(&1, include_private?))
|> Enum.map(fn action ->
%Ash.Info.Manifest.Entrypoint{
resource: resource,
action: ActionBuilder.build(resource, action, opts)
}
end)
end)
|> Enum.sort_by(fn e -> {Module.split(e.resource), e.action.name} end)
end
# When filtered: one entrypoint per normalized entry (preserves duplicates with different configs)
defp build_entrypoints(normalized_entries, _resource_action_map, opts) do
include_private? = Keyword.get(opts, :include_private_actions?, false)
normalized_entries
|> Enum.flat_map(fn {resource, action_name, config} ->
with %{} = action <- Ash.Resource.Info.action(resource, action_name),
true <- include_action?(action, include_private?) do
[
%Ash.Info.Manifest.Entrypoint{
resource: resource,
action: ActionBuilder.build(resource, action, opts),
config: config
}
]
else
_ -> []
end
end)
|> Enum.sort_by(fn e -> {Module.split(e.resource), e.action.name} end)
end
defp include_action?(action, include_private?) do
include_private? || Map.get(action, :public?, true)
end
end