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: Layers
Layers are for construction time, not business logic time.
Use a layer when you need to:
- build an environment from other services or config
- fail during provisioning before the workflow starts
- own resources that must be cleaned up exactly once
- compose several independent startup steps in parallel
1. The Workflow Still Targets An Environment
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
open System.Threading.Tasks
open Axial
open Axial.Layers
type IOrders =
abstract Save : string -> Task<unit>
type IClock =
abstract UtcNow : unit -> DateTimeOffset
type AppEnv =
{ Orders: IOrders
Clock: IClock }
let saveOrder (orderId: string) : Flow<AppEnv, string, string> =
flow {
let! env = Flow.env
do!
ColdTask(fun _ ->
task {
do! env.Orders.Save orderId
return ()
})
let today = env.Clock.UtcNow().ToString "yyyy-MM-dd"
return $"saved {orderId} at {today}"
}
SystemThreadingTasksAxialLayersFsLiveDocsGeneratedPage7_65D5665AADA9.IOrdersSave: IOrders -> string -> Task<unit>stringAn abbreviation for the CLI type . Basic Types
System.Threading.Tasks.Task`1Represents an asynchronous operation that can return a value. The type of the result produced by this .
unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
FsLiveDocsGeneratedPage7_65D5665AADA9.IClockUtcNow: IClock -> unit -> DateTimeOffsetSystem.DateTimeOffsetRepresents a point in time, typically expressed as a date and time of day, relative to Coordinated Universal Time (UTC).
FsLiveDocsGeneratedPage7_65D5665AADA9.AppEnvOrders: IOrdersClock: IClocksaveOrder: string -> Flow<AppEnv,string,string>orderId: stringAxial.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.
flow: FlowBuilderThe universal flow { } computation expression.
env: AppEnvAxial.Flowenv: Flow<'env,'error,'env>Reads the current environment as the successful flow value. Use this when the next step genuinely needs the whole environment value, for example when passing a request context to another helper. For a single dependency or configuration value, prefer Flow.envWith; it keeps the dependency local and makes the workflow easier to scan. A whose successful value is the current environment. let myFlow = Flow.env |> Flow.map (fun env -> env)
ColdTasktask: TaskBuilderBuilds a task using computation expression syntax.
Save: string -> Task<unit>today: stringUtcNow: unit -> DateTimeOffsetToString: string -> stringConverts the value of the current object to its equivalent string representation using the specified format. A format string. A string representation of the value of the current object, as specified by . The length of is one, and it is not one of the standard format specifier characters defined for . -or- does not contain a valid custom format pattern. The date and time is outside the range of dates supported by the calendar used by the current culture.
The workflow still depends on AppEnv. Layers only change how AppEnv gets built.
2. Build Small Layers
let persisted = ResizeArray<string>()
let ordersLayer : Layer<unit, string, IOrders> =
Layer.succeed
{ new IOrders with
member _.Save orderId =
task {
// The real dependency goes here: open a connection, run a transaction.
persisted.Add orderId
} }
let clockLayer : Layer<unit, string, IClock> =
Layer.succeed
{ new IClock with
member _.UtcNow() = DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero) }
persisted: 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
ordersLayer: Layer<unit,string,IOrders>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
FsLiveDocsGeneratedPage7_65D5665AADA9.IOrdersAxial.Layers.LayerModulesucceed: 'output -> Layer<'input,'error,'output>Creates a layer that succeeds with a fixed output value.
_: IOrdersSave: string -> Task<unit>orderId: stringtask: TaskBuilderBuilds a task using computation expression syntax.
Add: 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.
clockLayer: Layer<unit,string,IClock>FsLiveDocsGeneratedPage7_65D5665AADA9.IClock_: IClockUtcNow: unit -> DateTimeOffset``.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.
3. Merge Them Into An App Layer
let appLayer : Layer<unit, string, AppEnv> =
layer {
let! orders = ordersLayer
and! clock = clockLayer
return
{ Orders = orders
Clock = clock }
}
appLayer: Layer<unit,string,AppEnv>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
stringAn abbreviation for the CLI type . Basic Types
FsLiveDocsGeneratedPage7_65D5665AADA9.AppEnvlayer: LayerBuilderThe layer { } computation expression for provisioning explicit service environments.
orders: IOrdersordersLayer: Layer<unit,string,IOrders>clock: IClockclockLayer: Layer<unit,string,IClock>Orders: IOrdersClock: IClockUse plain let! when one provisioning step depends on another. Use sibling and! when the steps are independent.
4. Provision Failure Happens Before Business Logic
let failingOrdersLayer : Layer<unit, string, IOrders> =
Layer.fromTask (fun _ _ ->
task {
return Exit.Failure (Cause.Fail "database connection string missing")
})
failingOrdersLayer: Layer<unit,string,IOrders>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
stringAn abbreviation for the CLI type . Basic Types
FsLiveDocsGeneratedPage7_65D5665AADA9.IOrdersAxial.Layers.LayerModulefromTask: ('input * Scope -> CancellationToken -> Task<Exit<'output,'error>>) -> Layer<'input,'error,'output>Creates a layer from a raw task provisioning function. .NET only
task: TaskBuilderBuilds a task using computation expression syntax.
Axial.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.
persisted.Clear()
let failingAppLayer : Layer<unit, string, AppEnv> =
layer {
let! orders = failingOrdersLayer
and! clock = clockLayer
return { Orders = orders; Clock = clock }
}
saveOrder "A-100" |> Layer.provide failingAppLayer |> Flow.run () |> shouldEqual (Exit.Failure(Cause.Fail "database connection string missing"))
persisted.Count |> shouldEqual 0
persisted: ResizeArray<string>Clear: unit -> unitRemoves all elements from the .
failingAppLayer: Layer<unit,string,AppEnv>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
stringAn abbreviation for the CLI type . Basic Types
FsLiveDocsGeneratedPage7_65D5665AADA9.AppEnvlayer: LayerBuilderThe layer { } computation expression for provisioning explicit service environments.
orders: IOrdersfailingOrdersLayer: Layer<unit,string,IOrders>clock: IClockclockLayer: Layer<unit,string,IClock>Orders: IOrdersClock: IClocksaveOrder: string -> Flow<AppEnv,string,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.Layers.LayerModuleprovide: 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
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.
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.
Count: intGets the number of elements contained in the . The number of elements contained in the .
If provisioning fails, Layer.provide never runs the downstream business workflow: nothing was persisted. That
separation is one of the main reasons to use layers.
5. Resource Ownership
type FakeConnection() =
member val Disposed = false with get, set
interface IAsyncDisposable with
member this.DisposeAsync() =
this.Disposed <- true
ValueTask(Task.CompletedTask)
let connection = new FakeConnection()
let connectionLayer : Layer<unit, string, FakeConnection> =
Layer.acquireRelease
(Layer.succeed connection)
(fun connection _ct -> (connection :> IAsyncDisposable).DisposeAsync().AsTask())
FsLiveDocsGeneratedPage7_65D5665AADA9.FakeConnectionDisposed: FakeConnection -> unit -> boolSystem.IAsyncDisposableProvides a mechanism for releasing unmanaged resources asynchronously.
this: FakeConnectionDisposeAsync: FakeConnection -> unit -> ValueTaskDisposed: bool``.ctor``: Task -> unitInitializes a new instance of the class using the supplied task that represents the operation. The task that represents the operation.
System.Threading.Tasks.TaskRepresents an asynchronous operation.
CompletedTask: TaskGets a task that has already completed successfully. The successfully completed task.
connection: FakeConnectionconnectionLayer: Layer<unit,string,FakeConnection>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
stringAn abbreviation for the CLI type . Basic Types
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.
_ct: CancellationTokenDisposeAsync: unit -> ValueTaskPerforms application-defined tasks associated with freeing, releasing, or resetting unmanaged resources asynchronously. A task that represents the asynchronous dispose operation.
AsTask: unit -> TaskRetrieves a object that represents this . The object that is wrapped in this if one exists, or a new object that represents the result.
Flow.envWith (fun (open': FakeConnection) -> open'.Disposed)
|> Layer.provide connectionLayer
|> Flow.run ()
|> shouldEqual (Exit.Success false)
connection.Disposed |> shouldEqual true
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())
open': FakeConnectionFsLiveDocsGeneratedPage7_65D5665AADA9.FakeConnectionDisposed: bool(|>): '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.Layers.LayerModuleprovide: 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
connectionLayer: Layer<unit,string,FakeConnection>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
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.
connection: FakeConnectionWhile the workflow ran, the connection was open; once Layer.provide finished, it was disposed.
Layers also own what they acquire: acquired resources belong to the provisioning scope and are released when the provided workflow completes, fails, or is interrupted.
6. Run Through Layer.provide
persisted.Clear()
saveOrder "A-100"
|> Layer.provide appLayer
|> Flow.run ()
|> shouldEqual (Exit.Success "saved A-100 at 2026-01-01")
List.ofSeq persisted |> shouldEqual [ "A-100" ]
persisted: ResizeArray<string>Clear: unit -> unitRemoves all elements from the .
saveOrder: string -> Flow<AppEnv,string,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.Layers.LayerModuleprovide: 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
appLayer: Layer<unit,string,AppEnv>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.
The call site stays small:
- construct or choose the layer
- provide it once
- run the workflow
Feature entry points no longer open and close startup resources themselves.

