Skip to main content

lib/ash_jido/resource/transformers/compile_publications.ex

defmodule AshJido.Resource.Transformers.CompilePublications do
  @moduledoc """
  Compile-time transformer that validates and compiles signal publications.

  Responsibilities:
  - validate `publish` actions exist on the resource
  - expand `publish_all` declarations into concrete action publications
  - normalize publication action names to lists
  - persist compiled publications for runtime lookup
  """

  use Spark.Dsl.Transformer

  alias Spark.Dsl.Transformer

  @doc false
  def transform(dsl_state) do
    resource = Transformer.get_persisted(dsl_state, :module)
    actions = Transformer.get_entities(dsl_state, [:actions])
    action_names = actions |> Enum.map(& &1.name) |> MapSet.new()
    jido_entities = Transformer.get_entities(dsl_state, [:jido])

    explicit_publications =
      jido_entities
      |> Enum.filter(&match?(%AshJido.Publication{}, &1))
      |> Enum.map(fn publication ->
        %AshJido.Publication{} = publication
        actions_list = List.wrap(publication.actions)

        Enum.each(actions_list, fn action_name ->
          unless MapSet.member?(action_names, action_name) do
            raise Spark.Error.DslError,
              module: resource,
              path: [:jido, :publish],
              message: """
              Action #{inspect(action_name)} referenced in `publish` does not exist on #{inspect(resource)}.

              Available actions: #{inspect(MapSet.to_list(action_names))}
              """
          end
        end)

        %AshJido.Publication{
          actions: actions_list,
          signal_type: publication.signal_type,
          include: publication.include,
          metadata: publication.metadata,
          condition: publication.condition
        }
      end)

    expanded_publications =
      jido_entities
      |> Enum.filter(&match?(%AshJido.Resource.PublishAll{}, &1))
      |> Enum.flat_map(fn publish_all ->
        %AshJido.Resource.PublishAll{} = publish_all

        actions
        |> Enum.filter(&(&1.type == publish_all.action_type))
        |> Enum.map(fn action ->
          %AshJido.Publication{
            actions: [action.name],
            signal_type: publish_all.signal_type,
            include: publish_all.include,
            metadata: publish_all.metadata,
            condition: nil
          }
        end)
      end)

    publications = explicit_publications ++ expanded_publications

    {:ok, Transformer.persist(dsl_state, :jido_publications, publications)}
  end

  @doc false
  def before?(_), do: false

  @doc false
  def after?(Ash.Resource.Transformers.ValidateRelationshipAttributes), do: true
  def after?(_), do: false
end