AshWorkflow.Info (AshWorkflow v0.6.0)

Copy Markdown View Source

Introspection helpers for AshWorkflow resources.

Summary

Types

The merged view of a transition name, as returned by transition/2.

Functions

Returns the list of user-facing action names available at a given step.

Returns true if the given record is in a terminal state.

Returns the initial step for the workflow.

Returns the composite indexes that make the generated Oban triggers cheap, as a list of attribute-name lists, most useful first.

Returns every AshWorkflow.Scheduler.Work the workflow declares.

Returns the workflow's scheduler as {module, options}.

Returns the attribute the workflow stores its current step in.

Returns a single workflow step by name, or nil if not found.

Returns all workflow step entities for a resource.

Returns true if the given step is terminal (an end state with no outgoing transitions).

Returns the merged view of a transition name, or nil if no step declares it.

Returns the workflow's transition_log configuration, or nil if no transition log is configured.

Returns the workflow's undo configuration, or nil if undo is not enabled.

Returns true if the forward move from_state -> to_state may be rewound.

Returns the set of state changes that may be rewound, as {from_state, to_state} tuples describing the forward move.

Returns a graph representation of the workflow as a map.

Types

merged_transition()

@type merged_transition() :: %{
  name: atom(),
  from: [atom()],
  routes: [%{from: atom(), to: atom(), when: Ash.Expr.t() | nil}],
  accepted_inputs: [atom()],
  generated_action: atom()
}

The merged view of a transition name, as returned by transition/2.

Functions

available_actions(resource, step_name)

@spec available_actions(Ash.Resource.t(), atom()) :: [atom()]

Returns the list of user-facing action names available at a given step.

For manual steps, returns the transition names. For automatic and terminal steps, returns an empty list.

in_terminal_state?(record)

@spec in_terminal_state?(Ash.Resource.record()) :: boolean()

Returns true if the given record is in a terminal state.

The record must have its state attribute loaded.

initial_step(resource)

@spec initial_step(Ash.Resource.t()) :: AshWorkflow.Entities.Step.t() | nil

Returns the initial step for the workflow.

scheduled_work(resource)

@spec scheduled_work(Ash.Resource.t() | map()) :: [AshWorkflow.Scheduler.Work.t()]

Returns every AshWorkflow.Scheduler.Work the workflow declares.

One per automatic step and one per timeout, as AshWorkflow.Transformers.AddScheduler built them and handed them to the selected scheduler. A runtime scheduler reads this rather than re-deriving the list from steps and timeouts, so both see exactly the same work.

scheduler(resource)

@spec scheduler(Ash.Resource.t() | map()) :: {module(), keyword()}

Returns the workflow's scheduler as {module, options}.

Falls back to the :scheduler application environment for :ash_workflow, and then to AshWorkflow.Scheduler.Oban.

state_attribute(resource)

@spec state_attribute(Ash.Resource.t() | map()) :: atom()

Returns the attribute the workflow stores its current step in.

Defaults to :state. A workflow overrides it with state_attribute on the workflow section, which is passed down to ash_state_machine.

step(resource, step_name)

@spec step(Ash.Resource.t(), atom()) :: AshWorkflow.Entities.Step.t() | nil

Returns a single workflow step by name, or nil if not found.

steps(resource)

@spec steps(Ash.Resource.t() | map()) :: [AshWorkflow.Entities.Step.t()]

Returns all workflow step entities for a resource.

Accepts either a compiled resource module or an in-progress DSL state, so transformers can share the same introspection.

terminal?(resource, step_name)

@spec terminal?(Ash.Resource.t(), atom()) :: boolean()

Returns true if the given step is terminal (an end state with no outgoing transitions).

transition(resource, name)

@spec transition(Ash.Resource.t() | map(), atom()) :: merged_transition() | nil

Returns the merged view of a transition name, or nil if no step declares it.

A transition name declared on more than one step becomes a single generated action, and the declarations merge: from lists every step the action moves out of, routes holds one entry per declared target with the when expression that selects it, and accepted_inputs is the union of every declaration's accept.

A route from a static transition has a when of nil. The step it leaves is on the route itself, so a caller reading a route never has to pair it back up with a step.

Example

AshWorkflow.Info.transition(MyApp.OnboardingWorkflow, :complete)
#=> %{
#=>   name: :complete,
#=>   from: [:initial_review, :detailed_review],
#=>   routes: [
#=>     %{from: :initial_review, to: :detailed_review, when: nil},
#=>     %{from: :detailed_review, to: :approved, when: nil}
#=>   ],
#=>   accepted_inputs: [:notes],
#=>   generated_action: :complete
#=> }

transition_log(resource)

@spec transition_log(Ash.Resource.t() | map()) ::
  AshWorkflow.Entities.TransitionLog.t() | nil

Returns the workflow's transition_log configuration, or nil if no transition log is configured.

undo(resource)

@spec undo(Ash.Resource.t() | map()) :: AshWorkflow.Entities.Undo.t() | nil

Returns the workflow's undo configuration, or nil if undo is not enabled.

undoable_edge?(resource, from_state, to_state)

@spec undoable_edge?(Ash.Resource.t() | map(), atom(), atom()) :: boolean()

Returns true if the forward move from_state -> to_state may be rewound.

undoable_edges(resource)

@spec undoable_edges(Ash.Resource.t() | map()) :: [{atom(), atom()}]

Returns the set of state changes that may be rewound, as {from_state, to_state} tuples describing the forward move.

A conditional transition contributes one edge per route, so undo permits exactly the moves the transition could actually have made. Returns an empty list when undo is not enabled.

Example

AshWorkflow.Info.undoable_edges(MyApp.OnboardingWorkflow)
#=> [{:review, :approved}, {:review, :rejected}]

workflow_graph(resource)

@spec workflow_graph(Ash.Resource.t()) :: %{required(atom()) => map()}

Returns a graph representation of the workflow as a map.

Each key is a step name, and the value describes that step and every edge leaving it, so a caller can draw and label the whole workflow without reaching back into the DSL.

The step itself carries:

  • :name — the step name, repeated so an entry stands alone once taken out of the map
  • :action — the action an automatic step runs, nil for every other step
  • :policy — the step's policy check, or nil
  • :retry — the step's AshWorkflow.Entities.Retry, or nil
  • :initialtrue for the step the workflow starts in, as AshWorkflow.Entities.Step.find_initial/1 picks it
  • :terminaltrue for an end state
  • :manualtrue when nothing runs on entry
  • :wait_statetrue when a timeout is the step's only exit

The edges are four lists:

  • :transitions — one entry per reachable target, as %{name:, to:, condition:, undoable?:, accept:}. A conditional transition contributes one entry per route, each carrying that route's when expression as :condition; a simple transition contributes one entry with a nil condition. :undoable? is true only when the workflow declares an undo block and the transition opts in.
  • :on_success — one entry per declared on_success route, as %{to:, condition:}.
  • :on_error — the step an automatic step falls to on failure, or nil.
  • :timeouts — one entry per timeout, as %{name:, to:, fire_after:, field:, action:, repeat:, retry:}. A timeout that runs an action rather than moving the workflow has a nil :to and a non-nil :action.

Example

AshWorkflow.Info.workflow_graph(MyApp.OnboardingWorkflow)
#=> %{
#=>   screening: %{
#=>     name: :screening,
#=>     action: nil,
#=>     policy: nil,
#=>     retry: nil,
#=>     initial: true,
#=>     terminal: false,
#=>     manual: true,
#=>     wait_state: false,
#=>     transitions: [
#=>       %{name: :advance, to: :interviewing, condition: nil, undoable?: true, accept: [:notes]},
#=>       %{name: :reject, to: :rejected, condition: nil, undoable?: false, accept: []}
#=>     ],
#=>     on_success: [],
#=>     on_error: nil,
#=>     timeouts: [
#=>       %{name: :chase, to: nil, fire_after: {3, :days}, field: :state_entered_at, action: :send_reminder, repeat: true, retry: nil}
#=>     ]
#=>   },
#=>   ...
#=> }