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.JavaScriptIClock provides UtcNow() for timestamps, Elapsed() for monotonic durations, and Sleep() for cancellable delays. Timed flows require IHasClock in their environment, so a host or test supplies one source for all three operations.
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 Axial
open Axial.PlatformService
SystemAxialPlatformServicelet expiresWithin (window: TimeSpan) (expiry: DateTimeOffset) : Flow<#IHasClock, Never, bool> =
Clock.now |> Flow.map (fun now -> expiry - now <= window)
expiresWithin: TimeSpan -> DateTimeOffset -> Flow<'a,Never,bool>window: TimeSpanSystem.TimeSpanRepresents a time interval.
expiry: DateTimeOffsetSystem.DateTimeOffsetRepresents a point in time, typically expressed as a date and time of day, relative to Coordinated Universal Time (UTC).
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.IHasClockDeclares the clock required by timed workflows and fiber diagnostics.
Axial.NeverRepresents an error channel that cannot occur.
boolAn abbreviation for the CLI type . Basic Types
Axial.PlatformService.ClockHelpers for the clock service.
now: Flow<'env,'error,DateTimeOffset>Reads the current UTC timestamp from an explicit clock service.
(|>): '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.Flowmap: ('value -> 'next) -> Flow<'env,'error,'value> -> Flow<'env,'error,'next>Transforms the successful value of a flow. If the source fails, the is not executed. The original failure cause is preserved, including typed failures, interruption, and defects. Use map for pure value transformations after an effect has succeeded. A function of type 'value -> 'next to transform the successful value. The source flow of type to transform. A new with the transformed success value of type 'next. let flow = Flow.succeed 1 |> Flow.map (fun x -> x + 1)
now: DateTimeOffset(-): ^T1 -> ^T2 -> ^T3Overloaded subtraction operator The first parameter. The second parameter. The result of the operation. 10 - 2 // Evaluates to 8
(<=): 'T -> 'T -> boolStructural less-than-or-equal comparison The first parameter. The second parameter. The result of the comparison. 5 <= 1 // Evaluates to false 5 <= 5 // Evaluates to true [1; 5] <= [1; 6] // Evaluates to true
Clock.now returns a DateTimeOffset. The other readers derive from it. Here they run against the base runtime
with its clock fixed at noon UTC on 1 January 2026:
> open System;;
> open Axial.PlatformService;;
> let runtime () = { BaseRuntime.liveValue with Clock = Clock.fromValue (DateTimeOffset(2026, 1, 1, 12, 0, 0, TimeSpan.Zero)) };;
> (Clock.now : Flow<BaseRuntime, Never, DateTimeOffset>) |> Flow.map _.Hour |> Flow.run (runtime ());;
> (Clock.utcDateTime : Flow<BaseRuntime, Never, DateTime>) |> Flow.map _.Kind |> Flow.run (runtime ());;
> (Clock.unixTimeSeconds : Flow<BaseRuntime, Never, int64>) |> Flow.run (runtime ());;
> (Clock.unixTimeMilliseconds : Flow<BaseRuntime, Never, int64>) |> Flow.run (runtime ());;val it: Exit<int,Never> = Success 12
val it: Exit<DateTimeKind,Never> = Success Utc
val it: Exit<int64,Never> = Success 1767268800L
val it: Exit<int64,Never> = Success 1767268800000LNone of them produce a typed failure, so the error channel stays free for the workflow's own errors.
Measuring durations
Wall-clock time can jump when the system clock is adjusted, so it is the wrong tool for measuring how long something
took. IClock.Elapsed is a monotonic reading for that job, and Clock.timed wraps a flow with it:
let buildReport : Flow<BaseRuntime, Never, string> = Flow.ok "report"
let timedReport : Flow<BaseRuntime, Never, string> =
flow {
let! report, took = buildReport |> Clock.timed
do! Log.info $"report built in {took.TotalMilliseconds:F0} ms"
return report
}
buildReport: Flow<BaseRuntime,Never,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.PlatformService.BaseRuntimeGroups the standard operational services commonly used by workflow hosts.
Axial.NeverRepresents an error channel that cannot occur.
stringAn abbreviation for the CLI type . Basic Types
Axial.Flowok: 'value -> Flow<'env,'error,'value>Creates a successful synchronous flow. The value to wrap in a successful flow. A flow that always succeeds with the provided value.
timedReport: Flow<BaseRuntime,Never,string>flow: FlowBuilderThe universal flow { } computation expression.
report: stringtook: TimeSpan(|>): '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.PlatformService.ClockHelpers for the clock service.
timed: Flow<'env,'error,'value> -> Flow<'env,'error,('value * TimeSpan)>Runs a flow and returns its value together with how long it took. Measured with the clock's monotonic timer, so the duration is unaffected by system clock adjustments and is deterministic under a test clock. Failures propagate unchanged and are not timed. The flow to measure. let! report, took = buildReport |> Clock.timed
Axial.PlatformService.LogHelpers for the logging service.
info: string -> Flow<'env,'error,unit>Writes an informational log message through an explicit logging service.
Clock.elapsed reads the timer directly; only the difference between two readings is meaningful. Measuring through
the clock instead of Stopwatch keeps durations deterministic in tests: under Clock.fromValue every duration is
zero:
let logged = ResizeArray<string>()
let testRuntime =
{ BaseRuntime.liveValue with
Clock = Clock.fromValue (DateTimeOffset(2026, 1, 1, 12, 0, 0, TimeSpan.Zero))
Log = Log.fromSink (fun _ message -> logged.Add message) }
timedReport |> Flow.run testRuntime |> shouldEqual (Exit.Success "report")
List.ofSeq logged |> shouldEqual [ "report built in 0 ms" ]
logged: ResizeArray<string>``.ctor``: unit -> unitInitializes a new instance of the class that is empty and has the default initial capacity.
stringAn abbreviation for the CLI type . Basic Types
testRuntime: BaseRuntimeAxial.PlatformService.BaseRuntimeGroups the standard operational services commonly used by workflow hosts.
Axial.PlatformService.BaseRuntimeModuleHelpers for constructing the standard explicit service bundle used by workflow hosts.
liveValue: BaseRuntimeCreates the standard live base runtime as an explicit service bundle.
Clock: IClockAxial.PlatformService.ClockHelpers for the clock service.
fromValue: DateTimeOffset -> IClockCreates a deterministic clock that always returns the supplied instant; measured durations are zero.
``.ctor``: int * int * int * int * int * int * TimeSpan -> unitInitializes a new instance of the structure using the specified year, month, day, hour, minute, second, and offset. The year (1 through 9999). The month (1 through 12). The day (1 through the number of days in ). The hours (0 through 23). The minutes (0 through 59). The seconds (0 through 59). The time's offset from Coordinated Universal Time (UTC). does not represent whole minutes. is less than one or greater than 9999. -or- is less than one or greater than 12. -or- is less than one or greater than the number of days in . -or- is less than zero or greater than 23. -or- is less than 0 or greater than 59. -or- is less than 0 or greater than 59. -or- is less than -14 hours or greater than 14 hours. -or- The property is earlier than or later than .
System.TimeSpanRepresents a time interval.
Zero: TimeSpanRepresents the zero value. This field is read-only.
Log: ILogAxial.PlatformService.LogHelpers for the logging service.
fromSink: (LogLevel -> string -> unit) -> ILogCreates a logger from a synchronous sink function. Exceptions are appended to the message text.
message: stringAdd: string -> unitAdds an object to the end of the . The object to be added to the end of the . The value can be for reference types.
timedReport: Flow<BaseRuntime,Never,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
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.
Microsoft.FSharp.Collections.ListModuleContains operations for working with values of type . Operations for collections such as lists, arrays, sets, maps and sequences. See also F# Collection Types in the F# Language Guide.
ofSeq: 'T seq -> 'T listBuilds a new list from the given enumerable object. The input sequence. The list of elements from the sequence. let inputs = seq { 1; 2; 5 } inputs |> List.ofSeq Evaluates to [ 1; 2; 5 ]. This is an O(n) operation, where n is the length of the sequence.
Supplying the service
Clock.live reads DateTimeOffset.UtcNow and a monotonic process timer. Clock.layer is the same value as a Layer<unit, Never, IClock>. Most
applications get the clock as part of the base runtime rather than wiring it alone.
Testing
Clock.fromValue pins the instant for read-only tests. It deliberately rejects Sleep; use ManualClock for a workflow that sleeps, times out, retries, or forks fibers. Advance it after the expected sleeper has registered:
let clock = ManualClock(DateTimeOffset.Parse "2026-01-01T00:00:00Z")
let env = ClockEnvironment(clock :> IClock)
let sleeping : Flow<ClockEnvironment, Never, unit> = Flow.sleep (TimeSpan.FromSeconds 5.0)
let pending = sleeping |> Flow.toAsync env |> Async.StartAsTask
// Wait until clock.Sleepers reports one waiting operation.
clock.AdvanceBy(TimeSpan.FromSeconds 5.0)
clock: ManualClock``.ctor``: DateTimeOffset -> ManualClockSystem.DateTimeOffsetRepresents a point in time, typically expressed as a date and time of day, relative to Coordinated Universal Time (UTC).
Parse: string -> DateTimeOffsetConverts the specified string representation of a date, time, and offset to its equivalent. A string that contains a date and time to convert. An object that is equivalent to the date and time that is contained in . The offset is greater than 14 hours or less than -14 hours. is . does not contain a valid string representation of a date and time. -or- contains the string representation of an offset value without a date or time.
env: ClockEnvironment``.ctor``: IClock -> ClockEnvironmentAxial.IClockSupplies wall time, monotonic time, and cancellable delays from one source. Wall time is for timestamps. Use differences between Elapsed readings for durations.
sleeping: Flow<ClockEnvironment,Never,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.
Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
Axial.NeverRepresents an error channel that cannot occur.
unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
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.
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 .
pending: Task<Exit<unit,Never>>(|>): '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
toAsync: 'env -> Flow<'env,'error,'value> -> Async<Exit<'value,'error>>Builds a cold async that runs the workflow when it is started. Nothing executes until the returned async is run. The environment used by the workflow. The workflow to describe. A cold async that completes with the workflow exit. let handle = workflow |> Flow.toAsync environment
Microsoft.FSharp.Control.FSharpAsyncHolds static members for creating and manipulating asynchronous computations. See also F# Language Guide - Async Workflows. Async Programming
StartAsTask: Async<'T> * TaskCreationOptions option * CancellationToken option -> Task<'T>Executes a computation in the thread pool. If no cancellation token is provided then the default cancellation token is used. A that will be completed in the corresponding state once the computation terminates (produces the result, throws exception or gets canceled) Starting Async Computations printfn "A" let t = async { printfn "B" do! Async.Sleep(1000) printfn "C" } |> Async.StartAsTask printfn "D" t.Wait() printfn "E" Prints "A", then "D", "B" quickly in any order, then "C", "E" in 1 second.
AdvanceBy: TimeSpan -> unitAdvances wall and monotonic time together and wakes due delays.
ManualClock is currently available on .NET. Clock.live supports .NET and Fable. A fixed clock is enough for a time-read assertion:
On Fable, an F# record may use a field named Clock and implement IHasClock.Clock. Axial marks the interface for
Fable name mangling so the generated JavaScript does not confuse the record field with the interface getter. If
handwritten JavaScript supplies an environment, construct an Axial environment in F# (or use the generated
ClockEnvironment constructor) rather than passing a plain { Clock: clock } object. The latter lacks the interface
method the compiled Flow calls. This rule also applies to the other IHas* service interfaces.
type ClockEnv =
{ Clock: IClock }
interface IHasClock with
member this.Clock = this.Clock
let atNoon = { Clock = Clock.fromValue (DateTimeOffset(2026, 1, 1, 12, 0, 0, TimeSpan.Zero)) }
FsLiveDocsGeneratedPage22_4556FC416C2B.ClockEnvClock: IClockAxial.IClockSupplies wall time, monotonic time, and cancellable delays from one source. Wall time is for timestamps. Use differences between Elapsed readings for durations.
Axial.IHasClockDeclares the clock required by timed workflows and fiber diagnostics.
this: ClockEnvClock: ClockEnv -> unit -> IClockatNoon: ClockEnvAxial.PlatformService.ClockHelpers for the clock service.
fromValue: DateTimeOffset -> IClockCreates a deterministic clock that always returns the supplied instant; measured durations are zero.
``.ctor``: int * int * int * int * int * int * TimeSpan -> unitInitializes a new instance of the structure using the specified year, month, day, hour, minute, second, and offset. The year (1 through 9999). The month (1 through 12). The day (1 through the number of days in ). The hours (0 through 23). The minutes (0 through 59). The seconds (0 through 59). The time's offset from Coordinated Universal Time (UTC). does not represent whole minutes. is less than one or greater than 9999. -or- is less than one or greater than 12. -or- is less than one or greater than the number of days in . -or- is less than zero or greater than 23. -or- is less than 0 or greater than 59. -or- is less than 0 or greater than 59. -or- is less than -14 hours or greater than 14 hours. -or- The property is earlier than or later than .
System.TimeSpanRepresents a time interval.
Zero: TimeSpanRepresents the zero value. This field is read-only.
expiresWithin (TimeSpan.FromHours 1.0) (DateTimeOffset(2026, 1, 1, 12, 30, 0, TimeSpan.Zero))
|> Flow.run atNoon
|> shouldEqual (Exit.Success true)
expiresWithin (TimeSpan.FromHours 1.0) (DateTimeOffset(2026, 1, 1, 15, 0, 0, TimeSpan.Zero))
|> Flow.run atNoon
|> shouldEqual (Exit.Success false)
expiresWithin: TimeSpan -> DateTimeOffset -> Flow<'a,Never,bool>System.TimeSpanRepresents a time interval.
FromHours: float -> TimeSpanReturns a that represents a specified number of hours, where the specification is accurate to the nearest millisecond. A number of hours accurate to the nearest millisecond. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
``.ctor``: int * int * int * int * int * int * TimeSpan -> unitInitializes a new instance of the structure using the specified year, month, day, hour, minute, second, and offset. The year (1 through 9999). The month (1 through 12). The day (1 through the number of days in ). The hours (0 through 23). The minutes (0 through 59). The seconds (0 through 59). The time's offset from Coordinated Universal Time (UTC). does not represent whole minutes. is less than one or greater than 9999. -or- is less than one or greater than 12. -or- is less than one or greater than the number of days in . -or- is less than zero or greater than 23. -or- is less than 0 or greater than 59. -or- is less than 0 or greater than 59. -or- is less than -14 hours or greater than 14 hours. -or- The property is earlier than or later than .
Zero: TimeSpanRepresents the zero value. This field is read-only.
(|>): '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
atNoon: ClockEnvshouldEqual: '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.
A clock that returns a fixed instant does not advance, which is usually what you want for assertions. When a test
needs time to move, supply an IClock closing over a mutable field and step it explicitly, so the test controls
how time moves instead of depending on wall-clock timing.
Note that IClock reports the time; it does not schedule. Delays, timeouts, and retry policies are Flow runtime
concerns; see scheduling and retries.

