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

Error Handling

A Flow has a typed channel for failures the caller is expected to handle:

> type PaymentError = CardDeclined | AccountClosed;;
> let charge () : Flow<PaymentError, decimal> = Flow.fail CardDeclined;;
> Flow.run () (charge ());;
val it: Exit<decimal,PaymentError> = Failure (Fail CardDeclined)

Unexpected exceptions are defects. They are retained in the execution outcome rather than being added silently to the workflow's typed error:

> open System;;
> type PaymentError = CardDeclined | AccountClosed;;
> let broken () : Flow<PaymentError, decimal> = Flow.die (InvalidOperationException "broken invariant");;
> match Flow.run () (broken ()) with Exit.Failure (Cause.Die error) -> error.Message | _ -> "";;
val it: string = "broken invariant"

An Exit distinguishes the cases:

Cause Meaning
Cause.Fail error Expected typed failure
Cause.Die exception Unexpected defect
Cause.Interrupt Cooperative cancellation or interruption
Cause.Then (first, second) Sequentially combined causes
Cause.Both (left, right) Concurrently combined causes
Cause.Traced (cause, trace) Cause with diagnostic context

Use an attempt constructor only when an exception from an interop API is an expected outcome. Use Flow.catch only when the application deliberately translates a defect into a typed error.

Exit.toResult is intentionally lossy. Use it only at a boundary that has decided how defects, interruption, and combined causes should be represented.

Learn more