Repository F# setup
open System
open System.IO
open System.Threading
open System.Threading.Tasks
open Axial
open Axial.Layers
open Axial.Console
open Axial.FileSystem
open Axial.Hosting
open Axial.Hosting.Browser
open Axial.Hosting.Node
open Axial.PlatformService
open Axial.State
open Axial.Telemetry
open Axial.Telemetry.JavaScript

Running Flows

Creating a Flow does not execute it. Start it explicitly at a boundary, and read the outcome from the Exit<'value, 'error> it returns:

Verified
> let workflow () : Flow<string> = Flow.succeed "Hello";;
> Flow.run () (workflow ());;
> match Flow.run () (workflow ()) with Exit.Success value -> value | Exit.Failure cause -> Cause.prettyPrint string cause;;
val it: Exit<string,Never> = Success "Hello"
val it: string = "Hello"

Flow.run blocks until the Exit is available. Flow.startTask starts the work at once and returns a task to await, and Flow.toAsync returns an async that starts nothing until it is run:

Verified
> let mutable started = 0;;
> let counted () : Flow<int> = Flow.delay (fun () -> started <- started + 1; Flow.ok started);;
> let cold = Flow.toAsync () (counted ()) in started;;
> (Flow.startTask () (counted ())).Result;;
> Flow.toAsync () (counted ()) |> Async.RunSynchronously;;
val started: int = 0
val it: int = 0
val it: Exit<int,Never> = Success 1
val it: Exit<int,Never> = Success 2

The name states when work begins. to* builds a description and starts nothing, start* begins execution immediately and hands back a handle, and run executes to completion:

Entry point Starts work?
Flow.run / RunSynchronously Yes, and blocks until the Exit is available
Flow.startTask / StartAsTask / StartAsValueTask Yes: the work is already running when it returns
Flow.toAsync / ToAsync No: nothing runs until the returned async is started

The members take an optional cancellationToken for interop callers; the module functions take none, which keeps the common path short. Use Flow.timeout to bound how long a workflow may run. On Fable, use ToAsync.

Passing an already-cancelled token to a member ends the run with Cause.Interrupt, not a value or a typed error:

Show page setup
// Setup for the checked example on this page.
open System
open System.Threading
open System.Threading.Tasks
open Axial
/// Fails the docs test when an example's result differs from the value shown.
Verified
let waitForever () : Flow<string> =
    Flow.fromTask (fun token ->
        task {
            do! Task.Delay(Timeout.InfiniteTimeSpan, token)
            return "never"
        })

let interrupted = new CancellationTokenSource()
interrupted.Cancel()

match (waitForever ()).RunSynchronously((), interrupted.Token) with
| Exit.Failure Cause.Interrupt -> ()
| other -> failwithf "expected interruption, got %A" other

Every call starts a fresh execution with its own root scope. Await the returned handle to receive the final Exit.

At interop boundaries, run a Flow directly. Applications normally start their root workflow with App.run.