Packages

A durable, resumable workflow engine for Elixir

Current section

Files

Jump to
durable lib mix tasks durable.run.ex
Raw

lib/mix/tasks/durable.run.ex

defmodule Mix.Tasks.Durable.Run do
@shortdoc "Starts a workflow execution"
@moduledoc """
Starts a workflow execution.
## Usage
mix durable.run MODULE [options]
## Options
* `--input JSON` - JSON input for the workflow (must be an object)
* `--workflow NAME` - The workflow name within the module
* `--queue QUEUE` - The queue to run on (default: "default")
* `--priority N` - Priority level (default: 0)
* `--name NAME` - The Durable instance name (default: Durable)
## Examples
mix durable.run MyApp.OrderWorkflow
mix durable.run MyApp.OrderWorkflow --input '{"order_id": 123}'
mix durable.run MyApp.OrderWorkflow --workflow process_order --queue high_priority
"""
use Mix.Task
alias Durable.Mix.Helpers
@impl Mix.Task
def run(args) do
Helpers.ensure_started_readonly()
{opts, positional, _} =
OptionParser.parse(args,
strict: [
input: :string,
workflow: :string,
queue: :string,
priority: :integer,
name: :string
]
)
case positional do
[module_str | _] ->
start_workflow(module_str, opts)
[] ->
Mix.shell().error(
"Usage: mix durable.run MODULE [--input JSON] [--workflow NAME] [--queue QUEUE]"
)
end
end
defp start_workflow(module_str, opts) do
durable_name = Helpers.get_durable_name(opts)
module = Module.concat([module_str])
with :ok <- validate_module(module),
{:ok, input} <- parse_input(opts),
{:ok, start_opts} <- build_opts(opts, durable_name) do
case Durable.Executor.start_workflow(module, input, start_opts) do
{:ok, workflow_id} ->
Mix.shell().info("Workflow started: #{workflow_id}")
{:error, reason} ->
Mix.shell().error("Failed to start workflow: #{inspect(reason)}")
end
end
end
defp validate_module(module) do
cond do
not Code.ensure_loaded?(module) ->
Mix.shell().error("Module #{inspect(module)} not found.")
:error
not function_exported?(module, :__workflows__, 0) ->
Mix.shell().error("Module #{inspect(module)} is not a Durable workflow.")
:error
true ->
:ok
end
end
defp parse_input(opts) do
case Keyword.get(opts, :input) do
nil ->
{:ok, %{}}
json_str ->
case Jason.decode(json_str) do
{:ok, input} when is_map(input) ->
{:ok, input}
{:ok, _} ->
Mix.shell().error("Input must be a JSON object, not an array or scalar.")
:error
{:error, %Jason.DecodeError{} = error} ->
Mix.shell().error("Invalid JSON input: #{Exception.message(error)}")
:error
end
end
end
defp build_opts(opts, durable_name) do
start_opts = [durable: durable_name]
start_opts =
case Keyword.get(opts, :workflow) do
nil -> start_opts
name -> Keyword.put(start_opts, :workflow, name)
end
start_opts =
case Keyword.get(opts, :queue) do
nil -> start_opts
queue -> Keyword.put(start_opts, :queue, queue)
end
start_opts =
case Keyword.get(opts, :priority) do
nil -> start_opts
priority -> Keyword.put(start_opts, :priority, priority)
end
{:ok, start_opts}
end
end