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.JavaScriptTutorial: Runtime Operations
This tutorial focuses on the operational helpers that sit around a workflow: timeout, retry, cancellation, annotations, and exception translation.
Use these helpers at the application boundary. They are not substitutes for domain rules.
A Small Workflow To Wrap
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.
open System
open System.Threading
type CheckoutError =
| GatewayUnavailable
| CheckoutTimedOut
| ReceiptStoreFailed
| UnexpectedGatewayFailure of string
let authorizeCard : Flow<ClockEnvironment, CheckoutError, string> =
flow {
do! Flow.sleep (TimeSpan.FromMilliseconds 50.0)
return "receipt-123"
}
let storeReceipt (receiptId: string) : Flow<ClockEnvironment, CheckoutError, unit> =
flow {
do! Flow.sleep (TimeSpan.FromMilliseconds 20.0)
return ()
}
let notifyCustomer (receiptId: string) : Flow<ClockEnvironment, CheckoutError, unit> =
flow {
do! Flow.sleep (TimeSpan.FromMilliseconds 20.0)
return ()
}
let checkout : Flow<ClockEnvironment, CheckoutError, string> =
flow {
let! receiptId = authorizeCard
do! storeReceipt receiptId
do! notifyCustomer receiptId
return receiptId
}
SystemThreadingFsLiveDocsGeneratedPage6_7555C511A9CE.CheckoutErrorGatewayUnavailableCheckoutTimedOutReceiptStoreFailedUnexpectedGatewayFailurestringAn abbreviation for the CLI type . Basic Types
authorizeCard: Flow<ClockEnvironment,CheckoutError,string>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.
Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
flow: FlowBuilderThe universal flow { } computation expression.
Axial.Flowsleep: TimeSpan -> Flow<'env,'error,unit>Suspends the flow for the specified duration, observing cancellation. The duration to sleep. A flow that completes after the specified delay, or is interrupted if cancelled first.
System.TimeSpanRepresents a time interval.
FromMilliseconds: float -> TimeSpanReturns a that represents a specified number of milliseconds. A number of milliseconds. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
storeReceipt: string -> Flow<ClockEnvironment,CheckoutError,unit>receiptId: stringunitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
notifyCustomer: string -> Flow<ClockEnvironment,CheckoutError,unit>checkout: Flow<ClockEnvironment,CheckoutError,string>Even in this tiny example there are already several composed steps. Runtime helpers answer how this execution should behave when one of those steps is slow, flaky, canceled, or throws.
Timeout
let checkoutWithTimeout =
checkout
|> Flow.timeoutToError (TimeSpan.FromMilliseconds 10.0) CheckoutTimedOut
checkoutWithTimeout: Flow<ClockEnvironment,CheckoutError,string>checkout: Flow<ClockEnvironment,CheckoutError,string>(|>): '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
Axial.FlowtimeoutToError: TimeSpan -> 'error -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Same as timeout; named to pair with timeoutToOk.
System.TimeSpanRepresents a time interval.
FromMilliseconds: float -> TimeSpanReturns a that represents a specified number of milliseconds. A number of milliseconds. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
CheckoutTimedOutAuthorizing the card takes 50 ms, so the 10 ms limit fails the checkout:
checkoutWithTimeout |> Flow.run (ClockEnvironment Clock.live) |> shouldEqual (Exit.Failure(Cause.Fail CheckoutTimedOut))
checkoutWithTimeout: Flow<ClockEnvironment,CheckoutError,string>(|>): '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
Axial.Flowrun: 'env -> Flow<'env,'error,'value> -> Exit<'value,'error>Runs the workflow and blocks until the final exit is available. The environment used by the workflow. The workflow to run. The final workflow exit. let exit = workflow |> Flow.run environment
``.ctor``: IClock -> ClockEnvironmentAxial.PlatformService.ClockHelpers for the clock service.
live: IClockCreates a live clock backed by and a monotonic timer.
shouldEqual: 'a -> 'a -> unitAxial.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.
Axial.Cause`1Represents the cause of a failed workflow. The type of the domain-specific failure value.
FailAn expected domain-specific failure.
CheckoutTimedOuttimeout, timeoutToError, timeoutToOk, and timeoutWith are boundary tools. They answer "what should this workflow do if it takes too long?"
Retry
let retryingCheckout =
checkout
|> Flow.retry (
Schedule.exponential (TimeSpan.FromMilliseconds 50.0)
|> Schedule.recursAtMost 3
|> Schedule.whileInput (function
| GatewayUnavailable
| ReceiptStoreFailed -> true
| _ -> false))
retryingCheckout: Flow<ClockEnvironment,CheckoutError,string>checkout: Flow<ClockEnvironment,CheckoutError,string>(|>): '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
Axial.Flowretry: Schedule<'env,'error,'output> -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Retries a flow's typed failures according to a schedule. The flow runs once, and after each Cause.Fail the schedule sees the error and decides whether to run it again and how long to wait. Defects and interruptions are never retried. When the schedule stops, the flow fails with the last error. Each attempt runs in its own child scope; a failed attempt's finalizers run before the next attempt starts. For the common case, build the schedule from a Retry record. Decides, from each typed error, whether to retry and after what delay. The flow to retry. A flow that succeeds as soon as an attempt succeeds. fetch |> Flow.retry (Schedule.recurs 3) fetch |> Flow.retry (Retry.schedule { Retry.defaults with When = HttpError.isTransient })
Axial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
exponential: TimeSpan -> Schedule<'env,'input,TimeSpan>Creates a schedule that recurs with exponential backoff. The initial delay for the first retry. A schedule that recurs indefinitely, doubling the delay each time (baseDelay * 2^attempt) and capping at instead of overflowing. Thrown when is negative. let backoff () = Schedule.exponential (TimeSpan.FromMilliseconds 100.0) // Delays: 100ms, 200ms, 400ms, 800ms...
System.TimeSpanRepresents a time interval.
FromMilliseconds: float -> TimeSpanReturns a that represents a specified number of milliseconds. A number of milliseconds. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
recursAtMost: int -> Schedule<'env,'input,'output> -> Schedule<'env,'input,'output>Stops a schedule after at most recurrences, keeping its output and delays. Like recurs, counts recurrences after the first run, so recursAtMost 3 with Flow.retry allows 4 executions in total. The maximum number of recurrences. The schedule to cap. Schedule.exponential (TimeSpan.FromMilliseconds 100.0) |> Schedule.recursAtMost 5
whileInput: ('input -> bool) -> Schedule<'env,'input,'output> -> Schedule<'env,'input,'output>Continues only while the input satisfies a predicate. With Flow.retry the input is the typed error, so this selects which errors are worth retrying; an error that fails the predicate stops the schedule and the flow fails with it. With Flow.repeat the input is the successful value. Returns true for inputs that may recur. The schedule consulted for inputs that satisfy the predicate. Schedule.exponential (TimeSpan.FromMilliseconds 200.0) |> Schedule.recursAtMost 3 |> Schedule.whileInput HttpError.isTransient
GatewayUnavailableReceiptStoreFailedretryingCheckout |> Flow.run (ClockEnvironment Clock.live) |> shouldEqual (Exit.Success "receipt-123")
retryingCheckout: Flow<ClockEnvironment,CheckoutError,string>(|>): '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
Axial.Flowrun: 'env -> Flow<'env,'error,'value> -> Exit<'value,'error>Runs the workflow and blocks until the final exit is available. The environment used by the workflow. The workflow to run. The final workflow exit. let exit = workflow |> Flow.run environment
``.ctor``: IClock -> ClockEnvironmentAxial.PlatformService.ClockHelpers for the clock service.
live: IClockCreates a live clock backed by and a monotonic timer.
shouldEqual: 'a -> 'a -> unitAxial.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.
Flow.retry takes a Schedule, which sees each typed error: Schedule.whileInput selects the errors worth retrying,
and Schedule.recursAtMost bounds the attempts. For the common case, Retry.schedule builds the same schedule from a record
with named fields.
Exceptions
let rawGatewayCall (clientExplodes: bool) : Flow<ClockEnvironment, CheckoutError, string> =
flow {
if clientExplodes then
return raise (InvalidOperationException "gateway client exploded")
return "receipt-123"
}
let safeGatewayCall =
rawGatewayCall false
|> Flow.catch (fun ex -> UnexpectedGatewayFailure ex.Message)
rawGatewayCall: bool -> Flow<ClockEnvironment,CheckoutError,string>clientExplodes: boolboolAn abbreviation for the CLI type . Basic Types
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.
Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
FsLiveDocsGeneratedPage6_7555C511A9CE.CheckoutErrorstringAn abbreviation for the CLI type . Basic Types
flow: FlowBuilderThe universal flow { } computation expression.
raise: Exception -> 'TRaises an exception The exception to raise. The result value. open System.IO exception FileNotFoundException of string let readFile (fileName: string) = if not (File.Exists(fileName)) then raise(FileNotFoundException(fileName)) File.ReadAllText(fileName) readFile "/this-file-doest-exist" When executed, raises a FileNotFoundException.
``.ctor``: string -> unitInitializes a new instance of the class with a specified error message. The message that describes the error.
safeGatewayCall: Flow<ClockEnvironment,CheckoutError,string>(|>): '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
Axial.Flowcatch: (exn -> 'error) -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Catches exceptions raised during execution and simple defect outcomes, then maps them to a typed error. Thrown exceptions and simple Cause.Die outcomes are converted to Cause.Fail. Existing typed failures and interruptions are preserved, and an OperationCanceledException thrown because the runtime's token was cancelled stays an interruption. Compound causes are preserved unchanged. A function of type exn -> 'error to map the exception. The source flow of type to monitor. A that converts recoverable exceptions into typed errors. let flow = Flow.die (System.Exception("boom")) |> Flow.catch (fun ex -> "caught: " + ex.Message)
ex: exnUnexpectedGatewayFailureMessage: stringGets a message that describes the current exception. The error message that explains the reason for the exception, or an empty string ("").
rawGatewayCall true
|> Flow.catch (fun ex -> UnexpectedGatewayFailure ex.Message)
|> Flow.run (ClockEnvironment Clock.live)
|> shouldEqual (Exit.Failure(Cause.Fail(UnexpectedGatewayFailure "gateway client exploded")))
rawGatewayCall: bool -> Flow<ClockEnvironment,CheckoutError,string>(|>): '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
Axial.Flowcatch: (exn -> 'error) -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Catches exceptions raised during execution and simple defect outcomes, then maps them to a typed error. Thrown exceptions and simple Cause.Die outcomes are converted to Cause.Fail. Existing typed failures and interruptions are preserved, and an OperationCanceledException thrown because the runtime's token was cancelled stays an interruption. Compound causes are preserved unchanged. A function of type exn -> 'error to map the exception. The source flow of type to monitor. A that converts recoverable exceptions into typed errors. let flow = Flow.die (System.Exception("boom")) |> Flow.catch (fun ex -> "caught: " + ex.Message)
ex: exnUnexpectedGatewayFailureMessage: stringGets a message that describes the current exception. The error message that explains the reason for the exception, or an empty string ("").
run: 'env -> Flow<'env,'error,'value> -> Exit<'value,'error>Runs the workflow and blocks until the final exit is available. The environment used by the workflow. The workflow to run. The final workflow exit. let exit = workflow |> Flow.run environment
``.ctor``: IClock -> ClockEnvironmentAxial.PlatformService.ClockHelpers for the clock service.
live: IClockCreates a live clock backed by and a monotonic timer.
shouldEqual: 'a -> 'a -> unitAxial.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.
Axial.Cause`1Represents the cause of a failed workflow. The type of the domain-specific failure value.
FailAn expected domain-specific failure.
Use Flow.catch when you are deliberately translating technical exceptions into your typed error channel. If you do not catch them, they surface as Cause.Die in the final Exit.
Cancellation
let runCancellable (cancellationToken: CancellationToken) =
task {
let! exit = checkout.StartAsTask(ClockEnvironment Clock.live, cancellationToken = cancellationToken)
match exit with
| Exit.Success receipt -> return $"Receipt {receipt}"
| Exit.Failure Cause.Interrupt -> return "Cancelled"
| Exit.Failure cause -> return Cause.prettyPrint string cause
}
runCancellable: CancellationToken -> Task<string>cancellationToken: CancellationTokenSystem.Threading.CancellationTokenPropagates notification that operations should be canceled.
task: TaskBuilderBuilds a task using computation expression syntax.
exit: Exit<string,CheckoutError>checkout: Flow<ClockEnvironment,CheckoutError,string>StartAsTask: ClockEnvironment * CancellationToken option -> Task<Exit<string,CheckoutError>>Starts the workflow immediately and returns a task handle for its final exit. The work is already in flight when this returns. Use ToAsync for a cold handle. The environment used by the workflow. The optional cancellation token. Defaults to . A task that completes with the workflow exit. .NET only
``.ctor``: IClock -> ClockEnvironmentAxial.PlatformService.ClockHelpers for the clock service.
live: IClockCreates a live clock backed by and a monotonic timer.
cancellationTokenAxial.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.
receipt: stringFailureThe workflow failed due to a specific cause.
Axial.Cause`1Represents the cause of a failed workflow. The type of the domain-specific failure value.
InterruptAn administrative signal to stop the workflow (e.g., cancellation).
cause: Cause<CheckoutError>Axial.CauseprettyPrint: ('error -> string) -> Cause<'error> -> stringPretty prints a cause tree for diagnostics.
string: 'T -> stringConverts the argument to a string using ToString. For standard integer and floating point values and any type that implements IFormattable, ToString conversion uses CultureInfo.InvariantCulture. The input value. The converted string. string 'A' // evaluates to "A" string 0xff // evaluates to "255" string -10 // evaluates to "-10"
runCancellable CancellationToken.None |> _.Result |> shouldEqual "Receipt receipt-123"
runCancellable (new CancellationToken(true)) |> _.Result |> shouldEqual "Cancelled"
runCancellable: CancellationToken -> Task<string>System.Threading.CancellationTokenPropagates notification that operations should be canceled.
None: CancellationTokenReturns an empty value. An empty cancellation token.
(|>): '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
_arg1: Task<string>Result: stringGets the result value of this . The result value of this , which is of the same type as the task's type parameter. The task was canceled. The collection contains a object. -or- An exception was thrown during the execution of the task. The collection contains information about the exception or exceptions.
shouldEqual: 'a -> 'a -> unitIf the host cancels the token, the flow finishes with Exit.Failure Cause.Interrupt.
Cancellation stays an interruption inside the workflow. Whoever requested it decides what it means, as runCancellable
does by matching Cause.Interrupt. That keeps a Canceled case out of every error type, and keeps retries and error
handlers from treating a cancellation as a failure.
Two helpers cover the edges:
Flow.ensureNotCanceledstops withCause.Interruptif the token has been cancelled. Put it at safe points in long work that does not otherwise observe cancellation, such as a CPU-bound loop.Flow.catchCancellationturns cancellation that a library raised for its own reasons, while the token was still live, into a typed error. Without it, that cancellation is a defect: nobody asked for it.
Annotations
let annotatedCharge : Flow<ClockEnvironment, CheckoutError, Map<string, string> * string option> =
flow {
let! annotations = Flow.annotations
let! traceId = Flow.traceId
return annotations, traceId
}
annotatedCharge: Flow<ClockEnvironment,CheckoutError,(Map<string,string> * string option)>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.
Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
FsLiveDocsGeneratedPage6_7555C511A9CE.CheckoutErrorMicrosoft.FSharp.Collections.FSharpMap`2Immutable maps based on binary trees, where keys are ordered by F# generic comparison. By default comparison is the F# structural comparison function or uses implementations of the IComparable interface on key values. See the module for further operations on maps. All members of this class are thread-safe and may be used concurrently from multiple threads.
stringAn abbreviation for the CLI type . Basic Types
optionThe type of optional values. When used from other CLI languages the empty option is the null value. Use the constructors Some and None to create values of this type. Use the values in the Option module to manipulate values of this type, or pattern match against the values directly. 'None' values will appear as the value null to other CLI languages. Instance methods on this type will appear as static methods to other CLI languages due to the use of null as a value representation. Options
flow: FlowBuilderThe universal flow { } computation expression.
annotations: Map<string,string>Axial.Flowannotations: Flow<'env,'error,Map<string,string>>Reads the current runtime annotations. A flow that succeeds with the ambient annotation map.
traceId: string optiontraceId: Flow<'env,'error,string option>Reads the current trace id annotation, if one is present. Set it with Flow.withTraceId. A flow that succeeds with the ambient trace_id value, if present.
annotatedCharge
|> Flow.annotate "order_id" "o-7"
|> Flow.withTraceId "trace-1"
|> Flow.run (ClockEnvironment Clock.live)
|> shouldEqual (Exit.Success(Map [ "order_id", "o-7"; "trace_id", "trace-1" ], Some "trace-1"))
annotatedCharge: Flow<ClockEnvironment,CheckoutError,(Map<string,string> * string option)>(|>): '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
Axial.Flowannotate: string -> string -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Adds a runtime annotation for the duration of the supplied flow. Annotations are runtime metadata for diagnostics, logging, metrics, and tracing. Nested annotations with the same key override the outer value for the nested flow only. The annotation key. The annotation value. The source flow. A flow that runs with the supplied annotation in the ambient runtime context.
withTraceId: string -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Adds the standard trace_id runtime annotation for the duration of the supplied flow. The trace identifier. The source flow. A flow that runs with the supplied trace id in the ambient runtime context.
run: 'env -> Flow<'env,'error,'value> -> Exit<'value,'error>Runs the workflow and blocks until the final exit is available. The environment used by the workflow. The workflow to run. The final workflow exit. let exit = workflow |> Flow.run environment
``.ctor``: IClock -> ClockEnvironmentAxial.PlatformService.ClockHelpers for the clock service.
live: IClockCreates a live clock backed by and a monotonic timer.
shouldEqual: 'a -> 'a -> unitAxial.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.
``.ctor``: (string * string) seq -> Map<string,string>Builds a map that contains the bindings of the given IEnumerable. The input sequence of key/value pairs. The resulting map. This is an O(n log n) operation, where n is the number of elements in the sequence. Map [ (1, "a"); (2, "b") ] // evaluates to map [(1, "a"); (2, "b")]
SomeThe representation of "Value of type 'T" The input value. An option representing the value.
Annotations are useful for observability and correlation. They belong to runtime mechanics, not to your domain model.
Pulling It Together
let guardedCheckout =
safeGatewayCall
|> Flow.bind (fun receiptId ->
flow {
do! storeReceipt receiptId
do! notifyCustomer receiptId
return receiptId
})
|> Flow.timeoutToError (TimeSpan.FromSeconds 2.0) CheckoutTimedOut
|> Flow.retry (
Retry.schedule
{ Retry.defaults with
When =
function
| GatewayUnavailable
| ReceiptStoreFailed -> true
| _ -> false })
guardedCheckout: Flow<ClockEnvironment,CheckoutError,string>safeGatewayCall: Flow<ClockEnvironment,CheckoutError,string>(|>): '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
Axial.Flowbind: ('value -> Flow<'env,'error,'next>) -> Flow<'env,'error,'value> -> Flow<'env,'error,'next>Sequences a dependent flow after a successful value. This is the flatmap operation for . The continuation only runs when the source flow succeeds, and it receives the successful value. Use bind when the next effect depends on the previous result; use map when the next step is pure. A function that takes the successful value and returns a new flow. The source flow to sequence. A representing the combined workflow. let flow = Flow.succeed 1 |> Flow.bind (fun x -> Flow.succeed (x + 1))
receiptId: stringflow: FlowBuilderThe universal flow { } computation expression.
storeReceipt: string -> Flow<ClockEnvironment,CheckoutError,unit>notifyCustomer: string -> Flow<ClockEnvironment,CheckoutError,unit>timeoutToError: TimeSpan -> 'error -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Same as timeout; named to pair with timeoutToOk.
System.TimeSpanRepresents a time interval.
FromSeconds: float -> TimeSpanReturns a that represents a specified number of seconds, where the specification is accurate to the nearest millisecond. A number of seconds, accurate to the nearest millisecond. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
CheckoutTimedOutretry: Schedule<'env,'error,'output> -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Retries a flow's typed failures according to a schedule. The flow runs once, and after each Cause.Fail the schedule sees the error and decides whether to run it again and how long to wait. Defects and interruptions are never retried. When the schedule stops, the flow fails with the last error. Each attempt runs in its own child scope; a failed attempt's finalizers run before the next attempt starts. For the common case, build the schedule from a Retry record. Decides, from each typed error, whether to retry and after what delay. The flow to retry. A flow that succeeds as soon as an attempt succeeds. fetch |> Flow.retry (Schedule.recurs 3) fetch |> Flow.retry (Retry.schedule { Retry.defaults with When = HttpError.isTransient })
Axial.RetryModuleConstructors for .
schedule: Retry<'input> -> Schedule<'env,'input,int>Builds the schedule a retry record describes. The retry description. A schedule that emits the retry number, starting at 0. Thrown when Retries is negative or a delay is negative.
Axial.Retry`1A retry described with named fields: how many retries, how long to wait, and which inputs to retry. Build one from Retry.defaults with record update syntax and pass it to Flow.retry through Retry.schedule. It is a shorthand for the common case; anything a record cannot express (jitter, elapsed-time limits, fixed-rate runs) is written as a Schedule directly. The input the retry decision sees: the typed error for Flow.retry, the defect for Flow.supervise.
defaults: Retry<'input>Three retries with exponential backoff from 100 ms, capped at 10 s, retrying every input. fetch |> Flow.retry (Retry.schedule { Retry.defaults with Retries = 5; When = HttpError.isTransient })
When: 'input -> boolReturns true for inputs that should be retried; others stop immediately.
GatewayUnavailableReceiptStoreFailedguardedCheckout |> Flow.run (ClockEnvironment Clock.live) |> shouldEqual (Exit.Success "receipt-123")
guardedCheckout: Flow<ClockEnvironment,CheckoutError,string>(|>): '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
Axial.Flowrun: 'env -> Flow<'env,'error,'value> -> Exit<'value,'error>Runs the workflow and blocks until the final exit is available. The environment used by the workflow. The workflow to run. The final workflow exit. let exit = workflow |> Flow.run environment
``.ctor``: IClock -> ClockEnvironmentAxial.PlatformService.ClockHelpers for the clock service.
live: IClockCreates a live clock backed by and a monotonic timer.
shouldEqual: 'a -> 'a -> unitAxial.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.
Each concern has its own place:
- domain validation decides whether the operation should happen at all
- runtime helpers decide how the host should run that operation
Flow.catchdecides which technical exceptions should become typed failures

