WorkflowStem.Compiler (workflow_stem v0.2.0)

Copy Markdown View Source

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/2

opts 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

descriptor()

@type descriptor() :: {atom(), term(), map()} | {atom(), term()}

primitive_tuple()

@type primitive_tuple() :: tuple()

Functions

components_for(spec)

@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.

components_for_engine(spec)

@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.

engine_routing(spec)

@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.

has_routes?(spec)

@spec has_routes?(WorkflowStem.IR.t()) :: boolean()

Returns true if any state in the spec declares a :route.

validate(spec)

@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.