Language-agnostic type discovery for Ash resources and types.
This module provides the core type discovery logic that recursively traverses the type dependency tree to find all Ash resources and types that need code generation.
Configuration
Type discovery is configured via a config map:
%{
# Required: function to get RPC resources from otp_app
get_rpc_resources: fn otp_app -> [...] end,
# Optional: function returning the declared action entrypoints, as
# %{resource: module, action: atom} maps or {module, atom} tuples.
# Supplying it scopes discovery to those actions; omitting it keeps every
# public action and field of every RPC resource in scope.
get_rpc_action_entrypoints: fn otp_app -> [...] end,
# Optional: callback name for field names (default: :interop_field_names)
field_names_callback: :interop_field_names,
# Optional: function to check if a module has the language extension
has_language_extension?: fn resource -> true/false end,
# Optional: language name for warnings (default: "Interop")
language_name: "TypeScript"
}Type Discovery
The discovery process handles:
- Ash resources (both embedded and non-embedded)
- TypedStruct modules
- Complex nested types (unions, maps, arrays, etc.)
- Recursive type references with cycle detection
- Path tracking for diagnostic purposes
Path Tracking
During traversal, paths are tracked as lists of segments:
{:root, ResourceModule}- Starting point{:attribute, :field_name}- Attribute field{:calculation, :calc_name}- Calculation{:aggregate, :agg_name}- Aggregate{:union_member, :type_name}- Union member{:array_items}- Array items{:map_field, :field_name}- Map field{:action, :action_name}- Action{:argument, :argument_name}- Action or calculation argument{:metadata, :metadata_name}- Action metadata:returns- Generic action return type
No :manifest here, deliberately
Every introspection read goes through AshIntrospection.ResourceInfo, like
the rest of lib/, but none of them is given the config map — they take the
reader's default, which is live Ash.Resource.Info. Threading a config
through this module's ten private traversal helpers would be work with a
known expiry date: issue #23's stage 4 deletes this module outright, because
Ash.Info.Manifest.Generator.Reachability and Generator.TypeResolver
already do its job and reach more than it does — accepted attributes, action
metadata and transitive relationship depth. Routing the calls keeps the
"one reader" invariant that makes the later stages greppable; threading the
config would not survive to use it.
Summary
Functions
Builds a formatted warning message for resources missing from RPC config.
Builds a warning message for non-RPC resources referenced by RPC resources.
Discovers embedded resources from RPC resources by scanning and filtering.
Discovers all types with field constraints referenced by the given resources.
Finds all non-RPC resources that are referenced by RPC resources.
Finds all non-RPC resources referenced by RPC resources, with paths showing where they're referenced.
Finds all embedded resources referenced by a single resource.
Finds all non-embedded resources referenced by a single resource.
Finds all Ash resources referenced by a single resource's public attributes, calculations, and aggregates.
Finds resources with a language extension that are not configured in any RPC block.
Finds all Ash resources used as struct arguments in RPC actions.
Formats a path (list of path segments) into a human-readable string.
Scans a single resource to find all referenced resources.
Finds all Ash resources referenced by RPC resources.
Traverses a fields keyword list (from Map/Keyword/Tuple/custom type constraints) to find any Ash resource references in the nested field types.
Recursively traverses a type and its constraints to find all Ash resource references.
Types
@type config() :: %{ optional(:get_rpc_resources) => (atom() -> [module()]), optional(:get_rpc_action_entrypoints) => (atom() -> [map() | {module(), atom()}]), optional(:field_names_callback) => atom(), optional(:has_language_extension?) => (module() -> boolean()), optional(:language_name) => String.t() }
Functions
Builds a formatted warning message for resources missing from RPC config.
Parameters
otp_app- The OTP application namemissing_resources- List of resource modulesconfig- Configuration map withlanguage_name
Returns
A formatted warning string.
Builds a warning message for non-RPC resources referenced by RPC resources.
Parameters
referenced_non_rpc_with_paths- Map of resource => [paths]config- Configuration map withlanguage_name
Returns
A formatted warning string.
Discovers embedded resources from RPC resources by scanning and filtering.
When config carries get_rpc_action_entrypoints, only the declared actions
are treated as entrypoints: a resource exposing nothing but a generic action
contributes only what that action names, not every embedded type hanging off
its attributes. Without that key every public action and field of every RPC
resource is in scope.
Parameters
otp_app- The OTP application nameconfig- Configuration map
Returns
A list of embedded resource modules.
Discovers all types with field constraints referenced by the given resources.
Scans public attributes of resources to find types with field constraints (Map with fields, Keyword with fields, Tuple with fields, Struct with fields, TypedStruct) in direct types, arrays, and union types.
Parameters
resources- A list of Ash resource modules to scanconfig- Configuration map
Returns
A list of unique type info maps containing:
:instance_of- The module (if available):constraints- The type constraints:field_name_mappings- Field name mappings (if available)
Finds all non-RPC resources that are referenced by RPC resources.
These are resources that appear in attributes, calculations, or aggregates of RPC resources but are not themselves configured as RPC resources.
Parameters
otp_app- The OTP application nameconfig- Configuration map
Returns
A list of non-RPC resource modules that are referenced by RPC resources.
Finds all non-RPC resources referenced by RPC resources, with paths showing where they're referenced.
Parameters
otp_app- The OTP application nameconfig- Configuration map
Returns
A map where keys are non-RPC resource modules and values are lists of formatted path strings showing where each resource is referenced.
Finds all embedded resources referenced by a single resource.
Parameters
resource- An Ash resource module to scan
Returns
A list of embedded resource modules.
Finds all non-embedded resources referenced by a single resource.
Parameters
resource- An Ash resource module to scan
Returns
A list of non-embedded resource modules.
Finds all Ash resources referenced by a single resource's public attributes, calculations, and aggregates.
Parameters
resource- An Ash resource module to scan
Returns
A list of Ash resource modules referenced by the given resource.
Finds resources with a language extension that are not configured in any RPC block.
Parameters
otp_app- The OTP application nameconfig- Configuration map withget_rpc_resourcesandhas_language_extension?
Returns
A list of non-embedded resource modules with the extension but not configured for RPC.
Finds all Ash resources used as struct arguments in RPC actions.
Scans the given actions' public arguments for:
:structorAsh.Type.Structwith aninstance_ofconstraint pointing at an Ash resource,- an embedded resource named directly as the argument's type,
- either of the above behind a NewType wrapper or an array.
Embedded resources are included. A generator that skipped them produced no type for an argument a client has to construct.
Parameters
actions- A list of action structs to scan
Returns
A list of unique Ash resource modules used as struct arguments.
Formats a path (list of path segments) into a human-readable string.
Parameters
path- A list of path segments
Returns
A formatted string representing the path.
Examples
iex> path = [{:root, MyApp.Todo}, {:attribute, :metadata}, {:union_member, :text}]
iex> TypeDiscovery.format_path(path)
"Todo -> metadata -> (union: text)"
Scans a single resource to find all referenced resources.
Covers the resource's public attributes, calculations (including their
arguments) and aggregates, plus every public action's arguments, :returns
type and metadata. An action is part of a resource's public surface, so a
type it names is a type the client has to be able to build or read.
Parameters
resource- An Ash resource modulevisited- A MapSet of already-visited resources (defaults to empty)
Returns
A tuple of {found_resources, updated_visited} where:
found_resources- List of{resource, path}tuplesupdated_visited- Updated MapSet of visited resources
Finds all Ash resources referenced by RPC resources.
Recursively scans the public attributes, calculations (with their arguments)
and aggregates of each entrypoint resource, plus each entrypoint action's
arguments, :returns type and metadata, traversing complex types like maps
with fields, unions and typed structs to find any Ash resource references.
Entrypoints come from get_rpc_action_entrypoints when the config supplies
it, and otherwise from get_rpc_resources with every public action in scope.
Parameters
otp_app- The OTP application name to scan for domains and RPC resourcesconfig- Configuration map withget_rpc_resourcescallback
Returns
A list of unique Ash resource modules that are referenced by RPC resources.
Traverses a fields keyword list (from Map/Keyword/Tuple/custom type constraints) to find any Ash resource references in the nested field types.
Parameters
fields- A keyword list where keys are field names and values are field configs
Returns
A list of Ash resource modules found in the field definitions.
Recursively traverses a type and its constraints to find all Ash resource references.
This function handles:
- Direct Ash resource module references
- Ash.Type.Struct with instance_of constraint
- Ash.Type.Union with multiple type members
- Ash.Type.Map, Ash.Type.Keyword, Ash.Type.Tuple with fields constraints
- Custom types with fields constraints
- Arrays of any of the above
Parameters
type- The type to traverse (module or type atom)constraints- The constraints keyword list for the type
Returns
A list of Ash resource modules found in the type tree.