Repository F# setup
open System
open System.IO
open System.Threading
open System.Threading.Tasks
open Axial
open Axial.Console
open Axial.FileSystem
open Axial.PlatformService
open Axial.ProcessRun a process in an application Flow
An Axial Flow receives the application's environment record when it runs. If part of the application needs to start commands, put a process service in that record. The Flow reads that service when it reaches a process step.
In production, the record contains Axial's live process service. In a unit test, it can contain a small fake that returns the result the test needs. The application code that builds the command does not change.
Put a process service in the application record
Process.live builds the real service. It is a function because it combines the
services it needs. Clock.live, FileSystem.live, and Console.live are already-created service values, so they are its arguments.
Show page setup
// Setup for the checked examples on this page.
open System
open Axial
open Axial.Console
open Axial.FileSystem
open Axial.PlatformService
open Axial.Process
open Axial.Process.DSL
/// 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 Axial.Console
open Axial.FileSystem
open Axial.PlatformService
open Axial.Process
open Axial.Process.DSL
type AppEnvironment =
{ Processes: IProcess }
interface IHasProcess with member this.Process = this.Processes
let appEnvironment =
{ Processes = Process.live Clock.live FileSystem.live Console.live }
AxialConsoleFileSystemPlatformServiceProcessAxial.Process.DSLFsLiveDocsGeneratedPage5_99FDB6C21194.AppEnvironmentProcesses: IProcessAxial.Process.IProcessExecutes lazily, awaiting each observation with backpressure.
Axial.Process.IHasProcessthis: AppEnvironmentProcess: AppEnvironment -> unit -> IProcessappEnvironment: AppEnvironmentAxial.Process.Processlive: IClock -> IFileSystem -> IConsole -> IProcessAxial.PlatformService.ClockHelpers for the clock service.
live: IClockCreates a live clock backed by and a monotonic timer.
Axial.FileSystem.FileSystemModulelive: IFileSystemCreates a live file-system service backed by , , and .
Axial.Console.ConsoleModulelive: IConsoleCreates a live console service backed by .
Requires & types
Requires: open Axial.Console; open Axial.FileSystem; open Axial.PlatformService; open Axial.Process (packages: Axial.Console, Axial.FileSystem, Axial.PlatformService, Axial.Process)val appEnvironment: AppEnvironmentThe clock timestamps execution. The filesystem opens file input and output targets. The console receives configured
console output. The live process builder combines them into the Processes value; it does not start a process.
Convert a command to a Flow
Process.toFlow makes the command one lazy step in the application workflow. Flow.run starts the complete
application Flow with its environment.
let readRevision : Flow<AppEnvironment, ProcessError, string> =
flow {
let! result =
cmd $"git rev-parse --short HEAD"
|> toFlow
return result.StdOut.Trim()
}
readRevision: Flow<AppEnvironment,ProcessError,string>Axial.Flow`3FsLiveDocsGeneratedPage5_99FDB6C21194.AppEnvironmentAxial.Process.ProcessErrorExpected process errors. Runtime cancellation remains Cause.Interrupt.
stringAn abbreviation for the CLI type . Basic Types
flow: FlowBuilderThe universal flow { } computation expression.
result: ProcessResultcmd: FormattableString -> ProcessCommand(|>): '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
toFlow: ^a -> Flow<'b,ProcessError,ProcessResult>Trim: unit -> stringRemoves all leading and trailing white-space characters from the current object. The string that remains after all white-space characters are removed from the start and end of the current string. If no characters can be trimmed from the current instance, the method returns the current instance unchanged.
StdOut: stringRequires & types
Requires: open Axial; open Axial.Process; open Axial.Process.DSL (packages: Axial, Axial.Process)val readRevision: Flow<AppEnvironment,ProcessError,string>match Flow.run appEnvironment readRevision with
| Exit.Success revision -> (revision.Length >= 7) |> shouldEqual true
| other -> failwithf "unexpected %A" other
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
appEnvironment: AppEnvironmentreadRevision: Flow<AppEnvironment,ProcessError,string>Axial.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.
revision: stringLength: intGets the number of characters in the current object. The number of characters in the current string.
(>=): 'T -> 'T -> boolStructural greater-than-or-equal The first parameter. The second parameter. The result of the comparison. 5 >= 1 // Evaluates to true 5 >= 5 // Evaluates to true [1; 5] >= [1; 6] // Evaluates to false
(|>): '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
shouldEqual: 'a -> 'a -> unitother: Exit<string,ProcessError>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.
Requires & types
Requires: open Axial (packages: Axial)Process.toFlow preserves routing and capture policies. Batch execution captures all stdout and a separate
64 KiB stderr tail by default. Streaming constructors require IHasClock in the application environment as well.
Put a fake in the record for a unit test
IProcess.Execute is the single service operation. It returns a lazy completion Flow and awaits each supplied
observer with backpressure. A focused batch test can return a prepared result without emitting output.
let expectedResult : ProcessResult =
{ ExitCode = 0
ExitCodes = [ 0 ]
StdOut = "abc1234\n"
StdErr = ""
StdOutCapture =
{ Text = "abc1234\n"
Bytes = System.Text.Encoding.UTF8.GetBytes "abc1234\n"
Truncated = false
Available = true }
StdErrCapture = { Text = ""; Bytes = [||]; Truncated = false; Available = true }
Stages = []
StartedAt = DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero)
Duration = TimeSpan.Zero }
let mutable requested = None
let fakeProcess =
{ new IProcess with
member _.Execute(specification, _) =
Flow.delay (fun () ->
requested <- Some specification
Flow.succeed expectedResult) }
let testEnvironment = { Processes = fakeProcess }
expectedResult: ProcessResultAxial.Process.ProcessResultCompletion of the complete topology; ExitCode belongs to the final stage.
ExitCode: intExitCodes: int listStdOut: stringStdErr: stringStdOutCapture: CapturedOutputText: stringBytes: byte arraySystemUTF8: Text.EncodingGets an encoding for the UTF-8 format. An encoding for the UTF-8 format.
TextSystem.Text.EncodingRepresents a character encoding.
GetBytes: string -> byte arrayWhen overridden in a derived class, encodes all the characters in the specified string into a sequence of bytes. The string containing the characters to encode. A byte array containing the results of encoding the specified set of characters. is . A fallback occurred (see Character Encoding in .NET for complete explanation) -and- is set to .
Truncated: boolAvailable: boolStdErrCapture: CapturedOutputStages: StageResult listStartedAt: 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.
Duration: TimeSpanrequested: ProcessSpec optionNoneThe representation of "No value"
fakeProcess: IProcessAxial.Process.IProcessExecutes lazily, awaiting each observation with backpressure.
_: IProcessExecute: ProcessSpec * (ProcessOutput -> Async<unit>) -> Flow<unit,ProcessError,ProcessResult>specification: ProcessSpecAxial.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)
SomeThe representation of "Value of type 'T" The input value. An option representing the value.
succeed: 'value -> Flow<'env,'error,'value>Same as ok. 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
testEnvironment: AppEnvironmentProcesses: IProcessRequires & types
Requires: open Axial; open Axial.Process; open System (packages: Axial, Axial.Process)val expectedResult: ProcessResultval requested: ProcessSpec optionval fakeProcess: IProcessval testEnvironment: AppEnvironmentFlow.run testEnvironment readRevision |> shouldEqual (Exit.Success "abc1234")
requested |> Option.map Process.render |> shouldEqual (Some "git rev-parse --short HEAD")
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
testEnvironment: AppEnvironmentreadRevision: Flow<AppEnvironment,ProcessError,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
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.
requested: ProcessSpec optionMicrosoft.FSharp.Core.OptionModuleContains operations for working with options. Options
map: ('T -> 'U) -> 'T option -> 'U optionmap f inp evaluates to match inp with None -> None | Some x -> Some (f x). A function to apply to the option value. The input option. An option of the input value after applying the mapping function, or None if the input is None. None |> Option.map (fun x -> x * 2) // evaluates to None Some 42 |> Option.map (fun x -> x * 2) // evaluates to Some 84
Axial.Process.Processrender: ^a -> stringSomeThe representation of "Value of type 'T" The input value. An option representing the value.
Requires & types
Requires: open Axial; open Axial.Process (packages: Axial, Axial.Process)This keeps a unit test deterministic: it starts no native child process, and it can assert the executable, arguments,
working directory, timeout, and output policy on requested. Use the live interpreter in integration tests when the
contract with the operating system matters.
Use an existing application environment at a host boundary
Process.tryRunWith runs commands, specs, or an application Flow with the supplied environment and returns the
complete Exit. Command execution needs only IHasProcess; it does not require a reporting console.
Process.runWith returns the successful value but raises ProcessRunException on failure. In an application,
prefer Flow.run environment for the enclosing workflow; both Process runners are boundary conveniences.

