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 using flow capabilities when construction must await a connection, read configuration, fail with a typed startup error, or acquire a resource that must be released. Most applications do not need the separate Axial.Layers package.
Use a record when you can. Construct the environment directly and hand it to the workflow:
Show page setup
// Setup for the checked examples on this page.
open System
open Axial
open Axial.Layers
open Axial.Console
open Axial.PlatformService
/// 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 actualtype 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}"
}
FsLiveDocsGeneratedPage9_F9357565CFEE.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`3Axial.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: RegionEnvRequires & types
Requires: open Axial (packages: Axial)val describeRegion: Flow<RegionEnv,Never,string>let 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.
Requires & types
Requires: open Axial; open Axial.PlatformService; open System (packages: Axial, Axial.PlatformService)val env: RegionEnvDirect construction needs no package beyond the services themselves. See dependencies.
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 for the duration of the flow
Layer.provideruns: acquired during provisioning and released when that flow ends, whether it succeeds, fails, or is interrupted - 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 ->
Flow.delay (fun () ->
connection.Close()
Flow.ok ()))
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
FsLiveDocsGeneratedPage9_F9357565CFEE.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.
FsLiveDocsGeneratedPage9_F9357565CFEE.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: ConnectionFsLiveDocsGeneratedPage9_F9357565CFEE.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 -> Flow<'input,Never,unit>) -> Layer<'input,'error,'resource>Acquires a resource and registers its release with the layer scope. The layer that acquires the resource. The release flow run, with the layer's input as its environment, 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. The release runs without interruption; a defect it raises becomes a defect of the provided flow. To acquire with a flow, use Layer.fromFlow (Flow.scopeAcquireRelease acquire release).
succeed: 'output -> Layer<'input,'error,'output>Creates a layer that succeeds with a fixed output value.
``.ctor``: string * ResizeArray<string> -> Connectionconnection: ConnectionAxial.Flowdelay: (unit -> Flow<'env,'error,'value>) -> Flow<'env,'error,'value>Defers flow construction until execution time. A function that returns the flow to execute. Recursive calls deferred here are consumed by the executor without growing a chain of parent tasks. Work that must run after the recursive call still needs storage until that call finishes. A flow that lazily evaluates the factory when executed. let flow = Flow.delay (fun () -> Flow.succeed 42)
Close: unit -> unitok: '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.
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`3envWith: ('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
Requires & types
Requires: open Axial; open Axial.Layers; open Axial.PlatformService (packages: Axial, Axial.Layers, Axial.PlatformService)val connectionLayer: ResizeArray<string> -> Layer<unit,Never,Connection>val runtime: ResizeArray<string> -> Layer<unit,Never,AppEnv>val query: Flow<AppEnv,AppError,string>val program: ResizeArray<string> -> Flow<unit,AppError,string>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.
Requires & types
Requires: open Axial (packages: Axial)val closed: ResizeArray<string>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 packages use layers for typed provisioning errors and service construction from other services.
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.fromFlow (
Flow.envWith (fun provider ->
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 ->
Ok(clock, log, random, guid, environmentVariables)
| Error name, _, _, _, _ | _, Error name, _, _, _ | _, _, Error name, _, _
| _, _, _, Error name, _ | _, _, _, _, Error name ->
Error(BaseRuntimeError.MissingService name))
|> Flow.bind Flow.fromResult
)The error channel is BaseRuntimeError, not Never. Construction can fail with a typed error naming the missing
service. A factory returning Result<'env, BaseRuntimeError> could model that typed failure in a record, but a layer
keeps the failure inside the provisioning step: Layer.provide runs the workflow only after every step succeeds,
releases resources acquired by earlier steps when one fails, and lets independent steps build in parallel with
merge. A missing service surfaces 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.envWith (fromFactory categoryName)The layer consumes the host's ILoggerFactory and produces an ILog named by the category. The factory arrives only
after the host starts, so Layer.envWith (fromFactory categoryName) adapts it at the provide boundary. This
demonstrates adapting one dependency into another — not a limitation of records, since a record built after startup
could hold the factory or the resulting logger directly.
An already-built value that cannot fail needs no provisioning. Layer.succeed wraps it:
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 .
Requires & types
Requires: open Axial.Console; open Axial.Layers (packages: Axial.Console, Axial.Layers)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.

