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.
Show page 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 actualopen 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`3Axial.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 1767268800000LSystemAxialPlatformServiceruntime: unit -> 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.
now: Flow<'env,'error,DateTimeOffset>Reads the current UTC timestamp from an explicit clock service.
Axial.Flow`3Axial.NeverRepresents an error channel that cannot occur.
System.DateTimeOffsetRepresents a point in time, typically expressed as a date and time of day, relative to Coordinated Universal Time (UTC).
(|>): '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)
_arg1: DateTimeOffsetHour: intGets the hour component of the time represented by the current object. The hour component of the current object. This property uses a 24-hour clock; the value ranges from 0 to 23.
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
utcDateTime: Flow<'env,'error,DateTime>Reads the current UTC date/time from an explicit clock service.
System.DateTimeRepresents an instant in time, typically expressed as a date and time of day.
_arg1: DateTimeKind: DateTimeKindGets a value that indicates whether the time represented by this instance is based on local time, Coordinated Universal Time (UTC), or neither. One of the enumeration values that indicates what the current time represents. The default is .
unixTimeSeconds: Flow<'env,'error,int64>Reads the current Unix timestamp in seconds from an explicit clock service.
int64An abbreviation for the CLI type . Basic Types
unixTimeMilliseconds: Flow<'env,'error,int64>Reads the current Unix timestamp in milliseconds from an explicit clock service.
None 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 do not use it to measure durations. 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`3Axial.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 gate = obj()
let mutable logged = []
let testRuntime =
{ BaseRuntime.liveValue with
Clock = Clock.fromValue (DateTimeOffset(2026, 1, 1, 12, 0, 0, TimeSpan.Zero))
Log = Log.fromSink (fun _ message -> lock gate (fun () -> logged <- message :: logged)) }
timedReport |> Flow.run testRuntime |> shouldEqual (Exit.Success "report")
lock gate (fun () -> List.rev logged) |> shouldEqual [ "report built in 0 ms" ]
gate: obj``.ctor``: unit -> unitInitializes a new instance of the class.
logged: string listtestRuntime: 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: stringlock: 'Lock -> (unit -> 'T) -> 'TExecute the function as a mutual-exclusion region using the input value as a lock. The object to be locked. The action to perform during the lock. The resulting value. open System.Linq /// A counter object, supporting unlocked and locked increment type TestCounter () = let mutable count = 0 /// Increment the counter, unlocked member this.IncrementWithoutLock() = count <- count + 1 /// Increment the counter, locked member this.IncrementWithLock() = lock this (fun () -> count <- count + 1) /// Get the count member this.Count = count let counter = TestCounter() // Create a parallel sequence to that uses all our CPUs (seq {1..100000}).AsParallel() .ForAll(fun _ -> counter.IncrementWithoutLock()) // Evaluates to a number between 1-100000, non-deterministically because there is no locking counter.Count let counter2 = TestCounter() // Create a parallel sequence to that uses all our CPUs (seq {1..100000}).AsParallel() .ForAll(fun _ -> counter2.IncrementWithLock()) // Evaluates to 100000 deterministically because the increment to the counter object is locked counter2.Count
(::)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.
rev: 'T list -> 'T listReturns a new list with the elements in reverse order. The input list. The reversed list. let inputs = [ 0; 1; 2 ] inputs |> List.rev Evaluates to [ 2; 1; 0 ]. This is an O(n) operation, where n is the length of the list.
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
// The workflow registers its sleep asynchronously, so wait for it before advancing.
while clock.Sleepers = 0 do
System.Threading.Thread.SpinWait 20
clock.AdvanceBy(TimeSpan.FromSeconds 5.0)
pending.Wait()
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`3Axial.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.
Sleepers: intThe number of delays waiting for the clock.
(=): 'T -> 'T -> boolStructural equality The first parameter. The second parameter. The result of the comparison. 5 = 5 // Evaluates to true 5 = 6 // Evaluates to false [1; 2] = [1; 2] // Evaluates to true (1, 5) = (1, 6) // Evaluates to false
SystemSpinWait: int -> unitCauses a thread to wait the number of times defined by the parameter. A 32-bit signed integer that defines how long a thread is to wait.
ThreadingSystem.Threading.ThreadCreates and controls a thread, sets its priority, and gets its status.
AdvanceBy: TimeSpan -> unitAdvances wall and monotonic time together and wakes due delays.
Wait: unit -> unitWaits for the to complete execution. The has been disposed. 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.
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)) }
FsLiveDocsGeneratedPage25_2D306DEB1823.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 fixed clock does not advance. When a test needs time to move, use ManualClock and advance it explicitly, so the
test controls how time moves instead of depending on wall-clock timing.
IClock reports the time; it does not schedule. Delays, timeouts, and retry policies are Flow runtime
concerns; see scheduling and retries.

