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.JavaScriptApplication Lifecycle
App runs one root Flow as an owned application. Use it when the workflow represents the lifetime of a CLI,
desktop process, browser mount, Node process, worker, or another application rather than one request or operation.
Application code remains an ordinary Flow value. Provision its environment before handing it to App:
Shared setup
// Setup for the checked examples on this page.
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
/// Fails the docs test when an example's result differs from the value shown.
let shouldEqual expected actual =
if actual <> expected then failwithf "Expected %A but got %A" expected actual
SystemIOThreadingTasksAxialLayersConsoleFileSystemHostingBrowserNodePlatformServiceStateTelemetryJavaScriptshouldEqual: 'a -> 'a -> unitexpected: 'aactual: 'a(<>): 'T -> 'T -> boolStructural inequality The first parameter. The second parameter. The result of the comparison. 5 <> 5 // Evaluates to false 5 <> 6 // Evaluates to true [1; 2] <> [1; 2] // Evaluates to false
failwithf: Printf.StringFormat<'T,'Result> -> 'TPrint to a string buffer and raise an exception with the given result. Helper printers must return strings. The formatter. The formatted result. See Printf.failwithf (link: ) for examples.
type AppError =
| ConfigurationError of string
| OrderError of string
static member describe(error: AppError) =
match error with
| ConfigurationError message
| OrderError message -> message
type IOrderRepository =
abstract ProcessPending: unit -> Result<unit, AppError>
type AppEnv =
{ Orders: IOrderRepository
Log: ILog }
type StartupInputs = { PendingOrders: int }
let program : Flow<AppEnv, AppError, unit> =
flow {
let! orders = Flow.envWith _.Orders
return! orders.ProcessPending()
}
let appLayer : Layer<StartupInputs, AppError, AppEnv> =
Layer.envWith (fun inputs ->
{ Orders =
{ new IOrderRepository with
member _.ProcessPending() =
if inputs.PendingOrders >= 0 then Ok() else Error(OrderError "negative backlog") }
Log = Log.live })
let root : Flow<StartupInputs, AppError, unit> =
program
|> Layer.provide appLayer
FsLiveDocsGeneratedPage5_7555C511A9CE.AppErrorConfigurationErrorstringAn abbreviation for the CLI type . Basic Types
OrderErrordescribe: AppError -> stringerror: AppErrormessage: stringFsLiveDocsGeneratedPage5_7555C511A9CE.IOrderRepositoryProcessPending: IOrderRepository -> unit -> Result<unit,AppError>unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
Microsoft.FSharp.Core.FSharpResult`2Helper type for error handling without exceptions. Choices and Results
FsLiveDocsGeneratedPage5_7555C511A9CE.AppEnvOrders: IOrderRepositoryLog: ILogAxial.PlatformService.ILogProvides synchronous access to workflow logging as an explicit service.
FsLiveDocsGeneratedPage5_7555C511A9CE.StartupInputsPendingOrders: intintAn abbreviation for the CLI type . Basic Types
program: Flow<AppEnv,AppError,unit>Axial.Flow`3Represents a cold workflow that reads an environment, returns a typed result, and is executed explicitly through one of its execution members such as ToTask, ToAsync, or RunSynchronously. The type of the environment dependency. The type of the failure value. The type of the success value.
flow: FlowBuilderThe universal flow { } computation expression.
orders: IOrderRepositoryAxial.FlowenvWith: ('env -> 'value) -> Flow<'env,'error,'value>Projects one value from the current environment. This is the primary way to access app dependencies, configuration, or request metadata stored in env. The projection runs only when the flow is executed, so constructing the flow is still pure and side-effect free. Prefer small projections over passing a large environment deeper into reusable helpers. A function that extracts a value from the environment. A containing the projected value. let currentTime () = Flow.envWith (fun (environment: BaseRuntime) -> environment.Clock.UtcNow())
_arg1: AppEnvProcessPending: unit -> Result<unit,AppError>appLayer: Layer<StartupInputs,AppError,AppEnv>Axial.Layers.Layer`3Represents a provisioning step that builds an explicit environment inside a scope. The input environment required to build the layer. The typed failure produced during provisioning. The environment or service bundle produced by the layer.
Axial.Layers.LayerModuleenvWith: ('input -> 'output) -> Layer<'input,'error,'output>Projects part of the input environment into the layer output.
inputs: StartupInputs_: IOrderRepository(>=): 'T -> 'T -> boolStructural greater-than-or-equal The first parameter. The second parameter. The result of the comparison. 5 >= 1 // Evaluates to true 5 >= 5 // Evaluates to true [1; 5] >= [1; 6] // Evaluates to false
OkRepresents an OK or a Successful result. The code succeeded with a value of 'T.
ErrorRepresents an Error or a Failure. The code failed with a value of 'TError representing what went wrong.
Axial.PlatformService.LogHelpers for the logging service.
live: ILogCreates a no-op logger for tests and local service bundles.
root: Flow<StartupInputs,AppError,unit>(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
provide: Layer<'input,'error,'environment> -> Flow<'environment,'error,'value> -> Flow<'input,'error,'value>Builds an environment with a layer, runs a downstream flow, and always closes the layer scope. This is the provisioning boundary. It creates a fresh scope, builds the supplied layer inside that scope, runs the downstream flow with the built environment, and finalizes all acquired resources when the downstream flow completes or fails. The layer that builds the downstream environment. The flow to run with the provided environment. A flow that requires only the input environment of the layer. let program () = let runtimeLayer = Layer.succeed "production" let workflow = Flow.envWith String.length Layer.provide runtimeLayer workflow
root is the complete application description: startup inputs in, typed application failures out, and all resources
acquired by Live.appLayer scoped to the root execution.
Run a Finite Application
Use App.run when the caller only needs the final outcome:
let run inputs = async {
let! exit = App.run inputs root
match exit with
| Exit.Success () -> return 0
| Exit.Failure cause ->
eprintfn "%s" (Cause.prettyPrint AppError.describe cause)
return 1
}
run: StartupInputs -> Async<int>inputs: StartupInputsasync: AsyncBuilderBuilds an asynchronous workflow using computation expression syntax. let sleepExample() = async { printfn "sleeping" do! Async.Sleep 10 printfn "waking up" return 6 } sleepExample() |> Async.RunSynchronously
exit: Exit<unit,AppError>Axial.AppStarts and controls root Flow applications without requiring an external hosting framework.
run: 'env -> Flow<'env,'error,'value> -> Async<Exit<'value,'error>>Runs a root workflow to completion using the caller's asynchronous cancellation token. The explicit environment supplied to the root workflow. The root workflow to run. The final exit after the root scope has closed. Fable compatible
root: Flow<StartupInputs,AppError,unit>Axial.Exit`2Represents the final outcome of a workflow execution. The type of the success value. The type of the domain-specific failure value.
SuccessThe workflow completed successfully.
FailureThe workflow failed due to a specific cause.
cause: Cause<AppError>eprintfn: Printf.TextWriterFormat<'T> -> 'TPrint to stderr using the given format, and add a newline. The formatter. The formatted result. See Printf.eprintfn (link: ) for examples.
Axial.CauseprettyPrint: ('error -> string) -> Cause<'error> -> stringPretty prints a cause tree for diagnostics.
FsLiveDocsGeneratedPage5_7555C511A9CE.AppErrordescribe: AppError -> stringrun { PendingOrders = 3 } |> Async.RunSynchronously |> shouldEqual 0
run { PendingOrders = -1 } |> Async.RunSynchronously |> shouldEqual 1
run: StartupInputs -> Async<int>PendingOrders: int(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
Microsoft.FSharp.Control.FSharpAsyncHolds static members for creating and manipulating asynchronous computations. See also F# Language Guide - Async Workflows. Async Programming
RunSynchronously: Async<'T> * int option * CancellationToken option -> 'TRuns the asynchronous computation and await its result. If an exception occurs in the asynchronous computation then an exception is re-raised by this function. If no cancellation token is provided then the default cancellation token is used. The computation is started on the current thread if is null, has of true, and no timeout is specified. Otherwise the computation is started by queueing a new work item in the thread pool, and the current thread is blocked awaiting the completion of the computation. The timeout parameter is given in milliseconds. A value of -1 is equivalent to . The computation to run. The amount of time in milliseconds to wait for the result of the computation before raising a . If no value is provided for timeout then a default of -1 is used to correspond to . The cancellation token to be associated with the computation. If one is not supplied, the default cancellation token is used. The result of the computation. Starting Async Computations printfn "A" let result = async { printfn "B" do! Async.Sleep(1000) printfn "C" 17 } |> Async.RunSynchronously printfn "D" Prints "A", "B" immediately, then "C", "D" in 1 second. result is set to 17.
shouldEqual: 'a -> 'a -> unitApp.run uses the caller's F# async cancellation token. It waits until the root scope closes, so layer and Flow
finalizers have finished when the returned Exit becomes available.
Own a Long-Running Application
Use App.start when another module controls when the application stops:
let service : Flow<StartupInputs, AppError, unit> = Flow.never
service: Flow<StartupInputs,AppError,unit>Axial.Flow`3Represents a cold workflow that reads an environment, returns a typed result, and is executed explicitly through one of its execution members such as ToTask, ToAsync, or RunSynchronously. The type of the environment dependency. The type of the failure value. The type of the success value.
FsLiveDocsGeneratedPage5_7555C511A9CE.StartupInputsFsLiveDocsGeneratedPage5_7555C511A9CE.AppErrorunitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
Axial.Flownever: Flow<'env,'error,'value>A flow that never completes on its own; it ends only when it is interrupted. Use it to keep a flow running until its fiber or scope is interrupted, such as a service's main loop that only waits for shutdown. It holds no thread while it waits. // Runs the worker until the service is interrupted, then lets it drain its queue. let serve (jobs: Queue<string>) (worker: Flow<unit, Never, unit>) : Flow<unit, Never, unit> = flow { let! _ = worker |> Flow.forkGraceful (Dequeue.shutdown jobs) (TimeSpan.FromSeconds 5.0) return! Flow.never }
let running = App.start { PendingOrders = 0 } service
running.Status |> shouldEqual AppStatus.Running
// Called later by a signal handler, window close event, or UI unmount:
let finalExit = running.Stop() |> Async.RunSynchronously
(match finalExit with Exit.Failure cause -> Cause.isInterrupted cause | _ -> false) |> shouldEqual true
running.Status |> shouldEqual AppStatus.Completed
running: AppHandle<AppError,unit>Axial.AppStarts and controls root Flow applications without requiring an external hosting framework.
start: 'env -> Flow<'env,'error,'value> -> AppHandle<'error,'value>Starts a root workflow and returns a handle that owns its lifetime. The explicit environment supplied to the root workflow. The root workflow to start. A handle for observing completion or requesting coordinated stop. Fable compatible
PendingOrders: intservice: Flow<StartupInputs,AppError,unit>Status: AppStatusGets the current application lifecycle state.
(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
shouldEqual: 'a -> 'a -> unitAxial.AppStatusDescribes the lifecycle state of a running application.
RunningThe root workflow is running.
finalExit: Exit<unit,AppError>Stop: unit -> Async<Exit<unit,AppError>>Requests cooperative interruption and waits for the final application exit.
Microsoft.FSharp.Control.FSharpAsyncHolds static members for creating and manipulating asynchronous computations. See also F# Language Guide - Async Workflows. Async Programming
RunSynchronously: Async<'T> * int option * CancellationToken option -> 'TRuns the asynchronous computation and await its result. If an exception occurs in the asynchronous computation then an exception is re-raised by this function. If no cancellation token is provided then the default cancellation token is used. The computation is started on the current thread if is null, has of true, and no timeout is specified. Otherwise the computation is started by queueing a new work item in the thread pool, and the current thread is blocked awaiting the completion of the computation. The timeout parameter is given in milliseconds. A value of -1 is equivalent to . The computation to run. The amount of time in milliseconds to wait for the result of the computation before raising a . If no value is provided for timeout then a default of -1 is used to correspond to . The cancellation token to be associated with the computation. If one is not supplied, the default cancellation token is used. The result of the computation. Starting Async Computations printfn "A" let result = async { printfn "B" do! Async.Sleep(1000) printfn "C" 17 } |> Async.RunSynchronously printfn "D" Prints "A", "B" immediately, then "C", "D" in 1 second. result is set to 17.
Axial.Exit`2Represents the final outcome of a workflow execution. The type of the success value. The type of the domain-specific failure value.
FailureThe workflow failed due to a specific cause.
cause: Cause<AppError>Axial.CauseisInterrupted: Cause<'error> -> boolReturns whether the cause tree contains an interruption signal.
CompletedThe root workflow and all of its scope finalizers have completed.
An AppHandle<'error,'value> exposes:
Status:Running,Stopping, orCompleted.Completion: the one finalExit, available to any number of observers.Stop(): requests cooperative interruption and waits for cleanup.
Calling Stop() several times is safe. Every caller observes the same final exit. Disposing the handle requests stop
but cannot await asynchronous finalizers; application shutdown code should await Stop() or Completion.
External Cancellation
Use App.startWithCancellation or App.runWithCancellation when an existing owner already supplies a
CancellationToken:
let startWithHost (hostStopping: CancellationToken) =
App.startWithCancellation hostStopping { PendingOrders = 0 } service
startWithHost: CancellationToken -> AppHandle<AppError,unit>hostStopping: CancellationTokenSystem.Threading.CancellationTokenPropagates notification that operations should be canceled.
Axial.AppStarts and controls root Flow applications without requiring an external hosting framework.
startWithCancellation: CancellationToken -> 'env -> Flow<'env,'error,'value> -> AppHandle<'error,'value>Starts a root workflow linked to an external cancellation token. A host-owned token that requests application stop when cancelled. The explicit environment supplied to the root workflow. The root workflow to start. A handle for observing completion or requesting coordinated stop. Fable compatible
PendingOrders: intservice: Flow<StartupInputs,AppError,unit>Cancellation is administrative interruption. It becomes Cause.Interrupt; it is not mapped into the application's
typed error channel.
App and Direct Flow Execution
Flow.run, Flow.startTask, and Flow.toAsync remain the direct execution interface for individual workflows and
interop boundaries. App adds ownership around a root workflow:
| Use | Entry point |
|---|---|
| Execute one operation | workflow |> Flow.run env or workflow |> Flow.startTask env |
| Run a finite root application | App.run env application |
| Start and later stop a root application | App.start env application |
| Integrate with .NET Generic Host | Axial.Hosting |
| Run under Node signals | Node hosting |
| Tie lifetime to a browser owner | Browser hosting |
App does not render errors, choose process exit codes, or subscribe to platform lifecycle events. Those decisions
belong to the application edge or one of the platform hosting packages.

