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.JavaScriptA layer builds an environment, and building it may itself need flow capabilities: awaiting a
connection, reading configuration, failing with a typed startup error, or acquiring something that
must be released again. Axial.Layers is a separate package because most applications never need
that.
Use a record when you can. Construct the environment directly and hand it to the workflow:
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.
type RegionEnv = { Clock: IClock; Region: string }
let describeRegion : Flow<RegionEnv, Never, string> =
flow {
let! clock = Flow.envWith _.Clock
let! region = Flow.envWith _.Region
return $"{region} at {clock.UtcNow().Year}"
}
FsLiveDocsGeneratedPage8_65D5665AADA9.RegionEnvClock: IClockAxial.IClockSupplies wall time, monotonic time, and cancellable delays from one source. Wall time is for timestamps. Use differences between Elapsed readings for durations.
Region: stringstringAn abbreviation for the CLI type . Basic Types
describeRegion: Flow<RegionEnv,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.NeverRepresents an error channel that cannot occur.
flow: FlowBuilderThe universal flow { } computation expression.
clock: IClockAxial.FlowenvWith: ('env -> 'value) -> Flow<'env,'error,'value>Projects one value from the current environment. This is the primary way to access app dependencies, configuration, or request metadata stored in env. The projection runs only when the flow is executed, so constructing the flow is still pure and side-effect free. Prefer small projections over passing a large environment deeper into reusable helpers. A function that extracts a value from the environment. A containing the projected value. let currentTime () = Flow.envWith (fun (environment: BaseRuntime) -> environment.Clock.UtcNow())
_arg1: RegionEnvregion: string_arg3: RegionEnvlet env = { Clock = Clock.fromValue (DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero)); Region = "eu" }
describeRegion |> Flow.run env |> shouldEqual (Exit.Success "eu at 2026")
env: RegionEnvClock: 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.
Region: stringdescribeRegion: Flow<RegionEnv,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.
That covers most applications, needs no package beyond the services themselves, and is what dependencies documents.
Use a layer when construction is itself effectful:
- provisioning can fail, and the failure should be a typed startup error rather than an exception
- a service must be acquired and released, and its lifetime is the runtime's
- independent parts of the environment should be built in parallel
- a service needs another service in order to be constructed
type Connection(name: string, closed: ResizeArray<string>) =
member _.Name = name
member _.Close() = closed.Add name
type AppEnv = { Clock: IClock; Connection: Connection }
type AppError = QueryFailed of string
let connectionLayer (closed: ResizeArray<string>) : Layer<unit, Never, Connection> =
Layer.acquireRelease
(Layer.succeed (Connection("orders-db", closed)))
(fun connection _ ->
connection.Close()
Task.CompletedTask)
let runtime (closed: ResizeArray<string>) : Layer<unit, Never, AppEnv> =
Layer.merge Clock.layer (connectionLayer closed)
|> Layer.map (fun (clock, connection) -> { Clock = clock; Connection = connection })
let query : Flow<AppEnv, AppError, string> =
Flow.envWith (fun env -> env.Connection.Name)
let program (closed: ResizeArray<string>) : Flow<unit, AppError, string> =
Layer.provide (Layer.widenError (runtime closed)) query
FsLiveDocsGeneratedPage8_65D5665AADA9.Connectionname: stringstringAn abbreviation for the CLI type . Basic Types
closed: ResizeArray<string>ResizeArrayAn abbreviation for the CLI type
_: ConnectionName: Connection -> unit -> stringClose: Connection -> unit -> unitAdd: 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.
FsLiveDocsGeneratedPage8_65D5665AADA9.AppEnvClock: IClockAxial.IClockSupplies wall time, monotonic time, and cancellable delays from one source. Wall time is for timestamps. Use differences between Elapsed readings for durations.
Connection: ConnectionFsLiveDocsGeneratedPage8_65D5665AADA9.AppErrorQueryFailedconnectionLayer: ResizeArray<string> -> Layer<unit,Never,Connection>Axial.Layers.Layer`3Represents a provisioning step that builds an explicit environment inside a scope. The input environment required to build the layer. The typed failure produced during provisioning. The environment or service bundle produced by the layer.
unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
Axial.NeverRepresents an error channel that cannot occur.
Axial.Layers.LayerModuleacquireRelease: Layer<'input,'error,'resource> -> ('resource -> CancellationToken -> Platform.Deed) -> Layer<'input,'error,'resource>Acquires a resource and registers its release with the layer scope. The layer that acquires the resource. The release action to run when the layer scope closes. A layer that succeeds with the acquired resource. Use this for service implementations or provisioned resources that must live for the full Flow.provide boundary rather than only for the construction expression.
succeed: 'output -> Layer<'input,'error,'output>Creates a layer that succeeds with a fixed output value.
``.ctor``: string * ResizeArray<string> -> Connectionconnection: ConnectionClose: unit -> unitSystem.Threading.Tasks.TaskRepresents an asynchronous operation.
CompletedTask: TaskGets a task that has already completed successfully. The successfully completed task.
runtime: ResizeArray<string> -> Layer<unit,Never,AppEnv>merge: Layer<'input,'error,'left> -> Layer<'input,'error,'right> -> Layer<'input,'error,('left * 'right)>Merges two independent service layers in parallel. merge is the layer-domain name for zipPar. Use it when combining service bundles or environment fragments that do not depend on each other.
Axial.PlatformService.ClockHelpers for the clock service.
layer: Layer<unit,Never,IClock>Builds the live clock as a layer.
(|>): '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
map: ('output -> 'next) -> Layer<'input,'error,'output> -> Layer<'input,'error,'next>Maps the successful output of a layer.
clock: IClockquery: Flow<AppEnv,AppError,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.FlowenvWith: ('env -> 'value) -> Flow<'env,'error,'value>Projects one value from the current environment. This is the primary way to access app dependencies, configuration, or request metadata stored in env. The projection runs only when the flow is executed, so constructing the flow is still pure and side-effect free. Prefer small projections over passing a large environment deeper into reusable helpers. A function that extracts a value from the environment. A containing the projected value. let currentTime () = Flow.envWith (fun (environment: BaseRuntime) -> environment.Clock.UtcNow())
env: AppEnvName: stringprogram: ResizeArray<string> -> Flow<unit,AppError,string>provide: Layer<'input,'error,'environment> -> Flow<'environment,'error,'value> -> Flow<'input,'error,'value>Builds an environment with a layer, runs a downstream flow, and always closes the layer scope. This is the provisioning boundary. It creates a fresh scope, builds the supplied layer inside that scope, runs the downstream flow with the built environment, and finalizes all acquired resources when the downstream flow completes or fails. The layer that builds the downstream environment. The flow to run with the provided environment. A flow that requires only the input environment of the layer. let program () = let runtimeLayer = Layer.succeed "production" let workflow = Flow.envWith String.length Layer.provide runtimeLayer workflow
widenError: Layer<'input,Never,'output> -> Layer<'input,'error,'output>Gives a layer that cannot fail with a typed error any error type. Layer.provide and the layer combinators require one error type. Widen a layer that cannot fail, such as BaseRuntime.live, to combine it with layers or workflows that can. let runtime : Layer<unit, string, int> = Layer.succeed 1 |> Layer.widenError
runtime cannot fail, so its error type is Never; Layer.widenError lets it provide a workflow that fails with
AppError. The connection is closed when program ends:
let closed = ResizeArray<string>()
program closed |> Flow.run () |> shouldEqual (Exit.Success "orders-db")
List.ofSeq closed |> shouldEqual [ "orders-db" ]
closed: 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
program: ResizeArray<string> -> Flow<unit,AppError,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.
Layer.provide is the boundary: it opens a scope, builds the layer inside it, runs the downstream
flow with the result, and closes the scope afterwards whether the flow succeeds, fails, or is
interrupted.
Two real examples
Axial's own packages contain both of the cases that justify a layer.
Provisioning that can fail with a typed error. Axial.PlatformService builds the five standard services from a
host container:
let servicesFromServiceProvider
: Layer<IServiceProvider, BaseRuntimeError, IClock * ILog * IRandom * IGuid * IEnvironmentVariables> =
Layer.fromValueTask (fun (provider, _) _ ->
task {
match tryService<IClock> provider, tryService<ILog> provider, tryService<IRandom> provider,
tryService<IGuid> provider, tryService<IEnvironmentVariables> provider with
| Ok clock, Ok log, Ok random, Ok guid, Ok environmentVariables ->
return Exit.Success(clock, log, random, guid, environmentVariables)
| Error name, _, _, _, _ | _, Error name, _, _, _ | _, _, Error name, _, _
| _, _, _, Error name, _ | _, _, _, _, Error name ->
return Exit.Failure(Cause.Fail(BaseRuntimeError.MissingService name))
})Read the error channel: BaseRuntimeError, not Never. Construction itself can fail, and it fails with a typed
error naming the missing service. A record cannot express that; you would throw, or return an option and push the
problem onto every caller. Layer.provide surfaces it as a typed startup failure before any workflow runs.
A service built from another service. Axial.Hosting turns what the host container has into what workflows
need:
let layer (categoryName: string) : Layer<ILoggerFactory, Never, ILog> =
Layer.fromValueTask (fun (loggerFactory, _) _ ->
ValueTask<Exit<ILog, Never>>(Exit.Success(fromFactory categoryName loggerFactory)))The type says it: consumes an ILoggerFactory, produces an ILog. The factory does not exist until the host starts,
so there is no record field to put it in. The layer does the conversion.
Compare with the case that does not need a layer. Wrapping a value that is already built and cannot fail is
Layer.succeed, which provisions nothing:
Layer.succeed Console.live
Axial.Layers.LayerModulesucceed: 'output -> Layer<'input,'error,'output>Creates a layer that succeeds with a fixed output value.
Axial.Console.ConsoleModulelive: IConsoleCreates a live console service backed by .
Console, FileSystem, HttpClient and Process are all of this shape, which is why none of them depends on this package.
Scopes are not part of this package
Scopes and acquireRelease are core, and work without layers. See
scopes and resources.

