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.JavaScriptScopes and Resources
Scope owns cleanup for resources acquired during provisioning or execution. It is not a dependency container. It only
registers finalizers and closes them in a predictable order.
This solves a different problem from use / use! in flow { }.
Use use / use! when the resource lifetime is local to one lexical block. Use scoped acquisition when a resource is
acquired in one effect, layer, subflow, or parallel branch and must remain alive until the surrounding runtime or layer
scope closes. That is the important scope problem: a service can be provisioned before the user flow starts, consumed by
many subflows, and released only when the whole Layer.provide boundary finishes.
The contract is:
- finalizers run in reverse registration order
- finalizers run at most once
- registering after closure fails
- cleanup failures are aggregated
- cleanup failures are defects, not typed domain errors
- child scopes are owned by their parent and close deterministically with it
Local Acquire/Use/Release
Use Flow.acquireReleaseWith when acquisition, use, and release all belong to one flow expression.
let readFirstLine path =
Flow.acquireReleaseWith
(Flow.succeed (File.OpenText path))
(fun reader _ ->
reader.Dispose()
Task.CompletedTask)
(fun reader ->
flow {
return! ColdTask(fun _ -> reader.ReadLineAsync())
})
readFirstLine: string -> Flow<'a,'b,string>path: stringAxial.FlowacquireReleaseWith: Flow<'env,'error,'resource> -> ('resource -> CancellationToken -> Task) -> ('resource -> Flow<'env,'error,'value>) -> Flow<'env,'error,'value>Acquires a resource, uses it, and always runs the release action. The flow that acquires the resource. The release action to run after the resource is used. The flow that uses the acquired resource. A flow that releases the resource after use, including failure paths. Use this for lexical acquire/use/release. For resources that should live until the surrounding scope closes, use .
succeed: 'value -> Flow<'env,'error,'value>Alias for ok that reads well in some call sites. The value to wrap in a successful flow. A flow that always succeeds with the provided value. let result = Flow.succeed 42 |> Flow.run () // result = Success 42
System.IO.FileProvides static methods for the creation, copying, deletion, moving, and opening of a single file, and aids in the creation of objects.
OpenText: string -> StreamReaderOpens an existing UTF-8 encoded text file for reading. The file to be opened for reading. A on the specified path. The caller does not have the required permission. is a zero-length string, contains only white space, or contains one or more invalid characters as defined by . is . The specified path, file name, or both exceed the system-defined maximum length. The specified path is invalid, (for example, it is on an unmapped drive). The file specified in was not found. is in an invalid format.
reader: StreamReaderDispose: unit -> unitReleases all resources used by the object.
System.Threading.Tasks.TaskRepresents an asynchronous operation.
CompletedTask: TaskGets a task that has already completed successfully. The successfully completed task.
flow: FlowBuilderThe universal flow { } computation expression.
ColdTaskReadLineAsync: unit -> Task<string>Reads a line of characters asynchronously from the current stream and returns the data as a string. A task that represents the asynchronous read operation. The value of the parameter contains the next line from the stream, or is if all the characters have been read. The number of characters in the next line is larger than . The stream has been disposed. The reader is currently in use by a previous read operation.
Scoped Acquisition
Use Flow.acquireRelease when the acquired resource should live until the current runtime scope closes.
let acquireRequestCache =
Flow.acquireRelease
(Flow.succeed (new RequestCache()))
(fun cache _ ->
cache.Dispose()
Task.CompletedTask)Layer Resources
Use Layer.acquireRelease when a layer provisions a service implementation or resource that must be closed after the
provided flow finishes.
let connectionLayer : Layer<ConnectionString, DbError, IDbConnection> =
Layer.acquireRelease
(Layer.fromValueTask (fun (connectionString, _) _ ->
openConnection connectionString
|> Execution.ofValue))
(fun connection _ ->
connection.Dispose()
Task.CompletedTask)Flow.addFinalizer(fun cancellationToken ->
telemetry.FlushAsync(cancellationToken))The root scope is owned by the execution boundary or Layer.provide. Most application code should not create a scope directly. Use
Flow.acquireRelease, Layer.acquireRelease, and the finalizer helpers first. Use Flow.Runtime.scope only for advanced
helpers that need direct access to the scope object.
Child Scopes
Scope.AddChild() creates a parent-owned scope. Axial uses this internally for Layer.zipPar and Layer.merge so each
parallel provisioning branch can acquire resources independently.
If one parallel branch fails after another branch acquired resources, the successful branch cleanup still runs when
Layer.provide closes the root scope. Parent scopes close child scopes in a deterministic order, and each child still
applies its own reverse-registration finalizer order.

