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 construct environments before workflows run.
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
Show page setup
// Setup for the checked examples on this page.
open System
open System.Threading.Tasks
open Axial
open Axial.Layers
/// 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 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}"
}
SystemThreadingTasksAxialLayersFsLiveDocsGeneratedPage8_F9357565CFEE.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
FsLiveDocsGeneratedPage8_F9357565CFEE.IClockUtcNow: IClock -> unit -> DateTimeOffsetSystem.DateTimeOffsetRepresents a point in time, typically expressed as a date and time of day, relative to Coordinated Universal Time (UTC).
FsLiveDocsGeneratedPage8_F9357565CFEE.AppEnvOrders: IOrdersClock: IClocksaveOrder: string -> Flow<AppEnv,string,string>orderId: stringAxial.Flow`3flow: 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.
Requires & types
Requires: open Axial; open System; open System.Threading.Tasks (packages: Axial)val saveOrder: string -> Flow<AppEnv,string,string>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
FsLiveDocsGeneratedPage8_F9357565CFEE.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>FsLiveDocsGeneratedPage8_F9357565CFEE.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.
Requires & types
Requires: open Axial.Layers; open System (packages: Axial.Layers)val persisted: ResizeArray<string>val ordersLayer: Layer<unit,string,IOrders>val clockLayer: Layer<unit,string,IClock>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
FsLiveDocsGeneratedPage8_F9357565CFEE.AppEnvlayer: LayerBuilderThe layer { } computation expression for provisioning explicit service environments.
orders: IOrdersordersLayer: Layer<unit,string,IOrders>clock: IClockclockLayer: Layer<unit,string,IClock>Orders: IOrdersClock: IClockRequires & types
Requires: open Axial.Layers (packages: Axial.Layers)val appLayer: Layer<unit,string,AppEnv>Use 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.fromFlow (Flow.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
FsLiveDocsGeneratedPage8_F9357565CFEE.IOrdersAxial.Layers.LayerModulefromFlow: Flow<'input,'error,'output> -> Layer<'input,'error,'output>Creates a layer that builds its output by running a flow with the layer's input as its environment. The flow runs in the layer's scope, so what it registers with Flow.scopeAcquireRelease, Flow.scopeFinalizer, or Flow.scopeResource stays open until the Layer.provide boundary closes, and is released in reverse acquisition order. A typed failure fails provisioning; whatever was acquired before it is released. The flow that builds the layer output. A layer that succeeds with the flow's value. let database : Layer<AppConfig, DbError, Database> = Layer.fromFlow (Flow.scopeAcquireRelease (Database.connect ()) Database.close)
Axial.Flowfail: 'error -> Flow<'env,'error,'value>Same as error. The error value to wrap in a failing flow. A flow that always fails with the provided error. let result = Flow.fail "error" |> Flow.run () // result = Failure (Cause.Fail "error")
Requires & types
Requires: open Axial; open Axial.Layers (packages: Axial, Axial.Layers)val failingOrdersLayer: Layer<unit,string,IOrders>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
FsLiveDocsGeneratedPage8_F9357565CFEE.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 .
Requires & types
Requires: open Axial; open Axial.Layers (packages: Axial, Axial.Layers)val failingAppLayer: Layer<unit,string,AppEnv>If provisioning fails, Layer.provide never runs the downstream workflow: nothing was persisted.
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 connections = ResizeArray<FakeConnection>()
let connectionLayer : Layer<unit, string, FakeConnection> =
Layer.fromFlow (
flow {
let connection = new FakeConnection()
connections.Add connection
do! Flow.scopeAsyncDisposable connection
return connection
}
)
FsLiveDocsGeneratedPage8_F9357565CFEE.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.
connections: ResizeArray<FakeConnection>``.ctor``: unit -> unitInitializes a new instance of the class that is empty and has the default initial capacity.
connectionLayer: 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.LayerModulefromFlow: Flow<'input,'error,'output> -> Layer<'input,'error,'output>Creates a layer that builds its output by running a flow with the layer's input as its environment. The flow runs in the layer's scope, so what it registers with Flow.scopeAcquireRelease, Flow.scopeFinalizer, or Flow.scopeResource stays open until the Layer.provide boundary closes, and is released in reverse acquisition order. A typed failure fails provisioning; whatever was acquired before it is released. The flow that builds the layer output. A layer that succeeds with the flow's value. let database : Layer<AppConfig, DbError, Database> = Layer.fromFlow (Flow.scopeAcquireRelease (Database.connect ()) Database.close)
flow: FlowBuilderThe universal flow { } computation expression.
connection: FakeConnectionAdd: FakeConnection -> 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.
Axial.FlowscopeAsyncDisposable: IAsyncDisposable -> Flow<'env,'error,unit>Registers an asynchronously disposable resource with the current runtime scope. The async disposable resource to close when the current scope closes. A flow that registers the resource.
Requires & types
Requires: open Axial; open Axial.Layers; open System; open System.Threading.Tasks (packages: Axial, Axial.Layers)val connections: ResizeArray<FakeConnection>val connectionLayer: Layer<unit,string,FakeConnection>connections.Clear()
for _ in 1..2 do
Flow.envWith (fun (open': FakeConnection) -> open'.Disposed)
|> Layer.provide connectionLayer
|> Flow.run ()
|> shouldEqual (Exit.Success false)
connections.Count |> shouldEqual 2
List.ofSeq connections |> List.map (fun connection -> connection.Disposed) |> shouldEqual [ true; true ]
connections: ResizeArray<FakeConnection>Clear: unit -> unitRemoves all elements from the .
(..): ^T -> ^T -> ^T seqThe standard overloaded range operator, e.g. [n..m] for lists, seq {n..m} for sequences The start value of the range. The end value of the range. The sequence spanning the range. [1..4] // Evaluates to [1; 2; 3; 4] [1.5..4.4] // Evaluates to [1.5; 2.5; 3.5] ['a'..'d'] // Evaluates to ['a'; 'b'; 'c'; 'd'] [|1..4|] // Evaluates to an array [|1; 2; 3; 4|] { 1..4 } // Evaluates to a sequence [1; 2; 3; 4])
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': FakeConnectionFsLiveDocsGeneratedPage8_F9357565CFEE.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.
Count: intGets the number of elements contained in the . The number of elements contained in the .
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.
map: ('T -> 'U) -> 'T list -> 'U listBuilds a new collection whose elements are the results of applying the given function to each of the elements of the collection. The function to transform elements from the input list. The input list. The list of transformed elements. let inputs = [ "a"; "bbb"; "cc" ] inputs |> List.map (fun x -> x.Length) Evaluates to [ 1; 3; 2 ] This is an O(n) operation, where n is the length of the list.
connection: FakeConnectionRequires & types
Requires: open Axial; open Axial.Layers (packages: Axial, Axial.Layers)Acquisition happens inside the layer, so each Layer.provide run builds a fresh connection and registers its
release in the provisioning scope. While each workflow ran, its connection was open; once Layer.provide finished,
that connection was disposed.
Layer.fromFlow runs its flow in the provisioning scope, so whatever it registers there (Flow.scopeAsyncDisposable
here, or Flow.scopeAcquireRelease) is 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.
Requires & types
Requires: open Axial; open Axial.Layers (packages: Axial, Axial.Layers)Feature entry points provide the layer once and run the workflow without opening or closing startup resources themselves.

