Pure flow execution logic with no side effects.
All functions return {context, effects} tuples where effects are
instructions for the caller (typically Hotline.Flow.Engine) to execute.
This separation makes flows trivially testable — call start/3 and
handle_update/2 directly with hand-built update structs.
Effect Types
{:send_message, chat_id, text, keyboard}— send a Telegram message{:done, context}— the flow completed{:cancel, context}— the flow was cancelled
Example
{ctx, effects} = Runner.start(MyFlow, 123)
assert [{:send_message, 123, "What's your name?", nil}] = effects
update = %{message: %{text: "Alice", chat: %{id: 123}}}
{ctx, effects} = Runner.handle_update(ctx, update)
Summary
Functions
Process an incoming update against the current flow context.
Initialize a flow and return the first step's prompt effects.
Types
@type effect() :: {:send_message, integer(), String.t(), map() | nil} | {:done, Hotline.Flow.Context.t()} | {:cancel, Hotline.Flow.Context.t()}
Functions
@spec handle_update(Hotline.Flow.Context.t(), map()) :: {Hotline.Flow.Context.t(), [effect()]}
Process an incoming update against the current flow context.
@spec start(module(), integer(), map()) :: {Hotline.Flow.Context.t(), [effect()]}
Initialize a flow and return the first step's prompt effects.