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.HttpClient
open Axial.PlatformService
open Axial.Process
open Axial.State
open Axial.Telemetry
open Axial.Telemetry.JavaScriptDefects and Exceptions
Axial distinguishes expected failures, interruption, and unexpected defects. Domain failures stay in the typed error channel. Defects are recorded in the execution outcome so cleanup and observers can see them.
Quick Start: Usage Patterns
Producing Failures
Choose the function that matches your intent:
| Intent | Function | Outcome |
|---|---|---|
| Domain Error (Expected) | Flow.fail "Not found" |
Cause.Fail "Not found" |
| Defect/Panic (Bug) | Flow.die (exn "Database down") |
Cause.Die exn |
| Interruption | Flow.interrupt or runtime cancellation |
Cause.Interrupt |
| Sequential Failures | Workflow fails, then cleanup fails | Cause.Then (workflowCause, cleanupCause) |
| Parallel Failures | Parallel branches both fail | Cause.Both (leftCause, rightCause) |
Bridging Exceptions
Use Flow.attemptAsync, Flow.attemptTask, or Flow.attemptValueTask when exceptions from an interop boundary are expected and should enter the typed error channel. These constructors return Cause.Fail exn for non-cancellation exceptions and Cause.Interrupt for cancellation.
let loadConfig : ExnFlow<string> =
Flow.attemptTask (fun token -> File.ReadAllTextAsync("appsettings.json", token))
loadConfig: ExnFlow<string>ExnFlowA flow that requires no environment and uses exceptions as recoverable typed errors.
stringAn abbreviation for the CLI type . Basic Types
Axial.FlowattemptTask: (CancellationToken -> Task<'value>) -> Flow<'env,exn,'value>Creates a flow from a cancellable task factory and treats thrown exceptions as recoverable typed errors. Successful completion returns Exit.Success. OperationCanceledException returns Cause.Interrupt. Other exceptions return Cause.Fail exn. Starts the operation, observing the supplied cancellation token. .NET only
token: CancellationTokenSystem.IO.FileProvides static methods for the creation, copying, deletion, moving, and opening of a single file, and aids in the creation of objects.
ReadAllTextAsync: string * CancellationToken -> Task<string>Asynchronously opens a text file, reads all the text in the file, and then closes the file. The file to open for reading. The token to monitor for cancellation requests. The default value is . A task that represents the asynchronous read operation, which wraps the string containing all text in the file.
let safeParse id =
flow {
let! json = Http.get id
return Json.parse json
}
|> Flow.catch (function
| :? JsonException as ex -> DomainError.InvalidFormat ex.Message
| ex -> raise ex)Rationale
Axial records defects in the Exit type for three reasons.
1. One Outcome Shape
In complex orchestration like Flow.zipPar (running two flows concurrently), the engine must coordinate the lifecycle of multiple fibers.
- Problem: If a defect is only a thrown exception, it escapes the return value. The engine has to handle two failure paths: returned failures and thrown exceptions.
- Approach: By capturing defects into the
Exittype, every flow execution returns a value. If one branch dies, the engine receives it as data, can interrupt the other branches, and returns one structured outcome.
2. Concurrency Coordination
When a fiber fails, you often need to perform cleanup (e.g., ensuring or onExit).
By recording defects as Cause.Die, Axial passes the original exception and stack trace to finalizers as a value. Finalizers can log why a background fiber died without adding try...with blocks around every cleanup action.
If cleanup itself fails after the workflow has already failed, Axial does not discard either side. It returns Cause.Then (workflowCause, cleanupCause) so observability and host boundaries can see the original failure and the cleanup defect in order.
3. Precision in Retries and Fallbacks
The distinction between Fail and Die gives retry and fallback code a clear default:
- Retries should usually target
Fail(e.g., a transient network error), but neverDie(e.g., aNullReferenceException). Retrying a bug is usually a waste of resources. - Fallbacks (
orElse) usually target domain failures. If a workflow has a defect, it usually indicates a corrupted state that fallback logic wasn't designed to handle.

