Turns an extended workflow spec (see WorkflowStem.SpecBehaviour) into a
list of ALF component descriptors.
Bridges the data-only spec format and ALF's full DSL. Specs that declare
no routes fall through as an empty list — the caller should keep using
the static WorkflowStem.Pipelines.Stepwise pipeline in that case.
Supported primitives (1:1 with ALF DSL — see deps/alf/lib/dsl.ex)
{:stage, name_or_mod, opts} # stage/2
{:switch, name, %{key => body}} # switch/2
{:composer, module, opts} # composer/2 (fan-out/in)
{:goto, name, opts} # goto/2
{:goto_point, name} # goto_point/2
{:done, name, opts} # done/2
{:dead_end, name} # dead_end/2
{:from, module, opts} # from/2
{:plug_with, module, body} # plug_with/2 + do block
{:tbd, name} # tbd/2opts is a keyword list (all optional): :count, :opts (a keyword list
of user options passed to the component), and :to/:if for :goto,
:memo for :composer.
A state's :route may be a single tuple or a list. Inside branches (the
bodies of :switch and :plug_with), the contents are lists of the same
tuple shapes — compiled recursively.
Descriptors are plain data so they can be inspected in tests and cached
without ALF being started. A later step will emit these into a real ALF
module via Code.compile_quoted/1.
Summary
Functions
Returns a flat list of component descriptors for every :route in the
spec. Each descriptor's :state field records the owning state name.
Returns the full engine-topology descriptor list for a spec, ready to
be emitted via WorkflowStem.Pipeline.Builder.build/3.
Returns the routing map that must be passed to Builder.build/3
alongside components_for_engine/1.
Returns true if any state in the spec declares a :route.
Non-raising spec validation. Walks every route (including nested
:switch branches and :plug_with bodies) and checks that each
:switch/:goto primitive's routing name resolves.
Types
Functions
@spec components_for(WorkflowStem.IR.t()) :: [descriptor()]
Returns a flat list of component descriptors for every :route in the
spec. Each descriptor's :state field records the owning state name.
Raises ArgumentError if a :switch or :goto primitive references a
routing name absent from the spec's :routing map, or if a primitive
tuple is malformed. Use validate/1 for a non-raising check.
@spec components_for_engine(WorkflowStem.IR.t()) :: [descriptor()]
Returns the full engine-topology descriptor list for a spec, ready to
be emitted via WorkflowStem.Pipeline.Builder.build/3.
The topology mirrors the static WorkflowStem.Pipelines.Stepwise
pipeline but replaces its single StepwiseAction stage with a
switch(:__current_state__, branches: ...) that dispatches per-state:
[
stage(StepwiseContextMerge),
switch(:__current_state__, branches: %{
state_a: <descriptors from state_a's :route, or default>,
state_b: <descriptors from state_b's :route, or default>,
...
}),
stage(StepwiseAdvance),
stage(StepwiseEntryAction),
stage(FsmBreakpoint),
stage(StepwiseProjection)
]States without a :route get a default body of [stage(StepwiseAction)]
so the existing per-state action dispatcher keeps working for them.
Use engine_routing/1 to get the routing-resolver map that must
accompany these descriptors when calling Builder.build/3.
@spec engine_routing(WorkflowStem.IR.t()) :: map()
Returns the routing map that must be passed to Builder.build/3
alongside components_for_engine/1.
Merges the spec's user-declared :routing with the internal
:__current_state__ delegate that the synthesised wrapper switch
needs to resolve.
@spec has_routes?(WorkflowStem.IR.t()) :: boolean()
Returns true if any state in the spec declares a :route.
@spec validate(WorkflowStem.IR.t()) :: :ok | {:error, {:missing_routing, atom()} | {:bad_primitive, term()}}
Non-raising spec validation. Walks every route (including nested
:switch branches and :plug_with bodies) and checks that each
:switch/:goto primitive's routing name resolves.