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.HttpClient
open Axial.PlatformService
open Axial.Process
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.
Start with a record. Construct the environment directly and hand it to the workflow:
let env = { Clock = Clock.live; Log = Log.live; FileSystem = FileSystem.live }
let exit = workflow |> Flow.run envReach for 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
open Axial.Layers
let runtime : Layer<unit, Never, AppEnv> =
Layer.merge clockLayer connectionLayer
|> Layer.map (fun (clock, connection) -> { Clock = clock; Connection = connection })
let program : Flow<unit, AppError, unit> = Layer.provide runtime workflowTwo 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))
})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)))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 .
Scopes are not part of this package
Scopes and acquireRelease are core, and work without layers. See
scopes and resources.

