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 System.IO
open System.Threading
open System.Threading.Tasks
open Axial
open Axial.Console
open Axial.FileSystem
open Axial.PlatformService
open Axial.Process
/// 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
type AppEnvironment =
{ Processes: IProcess }
interface IHasProcess with member this.Process = this.Processes
let appEnvironment =
{ Processes = Process.live Clock.live FileSystem.live Console.live }
AxialConsoleFileSystemPlatformServiceProcessFsLiveDocsGeneratedPage5_333522998462.AppEnvironmentProcesses: IProcessAxial.Process.IProcessInterprets process specifications as lazy Axial workflows for a concrete host platform.
Axial.Process.IHasProcessDeclares that an environment supplies the process service. Implement this on the environment record supplied at the host edge. A workflow that runs processes constrains its environment with 'env :> IHasProcess.
this: AppEnvironmentProcess: AppEnvironment -> unit -> IProcessappEnvironment: AppEnvironmentAxial.Process.ProcessModulelive: IClock -> IFileSystem -> IConsole -> IProcessCreates a live process service using an explicit clock for transcript timestamps and durations.
Axial.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 .
The 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 =
Process.command $"git rev-parse --short HEAD"
|> Process.capture
return result.StdOut.Trim()
}
readRevision: Flow<AppEnvironment,ProcessError,string>Axial.Flow`3Represents a cold workflow that reads an environment, returns a typed result, and is executed explicitly through one of its execution members such as ToTask, ToAsync, or RunSynchronously. The type of the environment dependency. The type of the failure value. The type of the success value.
FsLiveDocsGeneratedPage5_333522998462.AppEnvironmentAxial.Process.ProcessErrorA recoverable process startup, timeout, stage, or I/O failure. Interrupting the workflow is not an error: it terminates the processes and surfaces as <c>Cause.Interrupt</c>.
stringAn abbreviation for the CLI type . Basic Types
flow: FlowBuilderThe universal flow { } computation expression.
result: ProcessResultAxial.Process.ProcessModulecommand: FormattableString -> ProcessSpecCreates a safely tokenized one-command process specification from an interpolated command template. Each interpolation hole is one native argument. Example: Process.command $"git show {revision}" |> Process.toFlow
(|>): '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
capture: ProcessSpec -> Flow<'env,ProcessError,ProcessResult>Runs a process specification with complete stdout and stderr capture. Example: Process.command $"dotnet --info" |> Process.capture
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: stringmatch 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.
Process.capture and Process.console are convenient forms that configure output and then create a Flow.
The latter preserves the output policy already carried by a ProcessSpec.
Put a fake in the record for a unit test
IProcess is the interface used by the Processes field. It has two operations: Run returns the completed result,
and Stream returns output events. A focused test usually needs only Run; return a prepared ProcessResult and
inspect the received specification when the test cares about command construction.
let expectedResult : ProcessResult =
{ ExitCode = 0
ExitCodes = [ 0 ]
StdOut = "abc1234\n"
StdErr = ""
StdOutCapture = { Text = "abc1234\n"; Bytes = [||]; Truncated = false }
StdErrCapture = { Text = ""; Bytes = [||]; Truncated = false }
Stages = []
StartedAt = DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero)
Duration = TimeSpan.Zero }
let mutable requested = None
let fakeProcess =
{ new IProcess with
member _.Run specification =
requested <- Some specification
Flow.succeed expectedResult
member _.Stream _ =
FlowStream.singleton (ProcessEvent.Completed expectedResult) }
let testEnvironment = { Processes = fakeProcess }
expectedResult: ProcessResultAxial.Process.ProcessResultThe complete structured transcript returned by a process execution.
ExitCode: intExitCodes: int listStdOut: stringStdErr: stringStdOutCapture: CapturedOutputText: stringBytes: byte arrayTruncated: 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.IProcessInterprets process specifications as lazy Axial workflows for a concrete host platform.
_: IProcessRun: ProcessSpec -> Flow<unit,ProcessError,ProcessResult>Returns a lazy workflow that runs the specification when composed into a Flow runtime.
specification: ProcessSpecSomeThe representation of "Value of type 'T" The input value. An option representing the value.
Axial.Flowsucceed: '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
Stream: ProcessSpec -> FlowStream<unit,ProcessError,ProcessEvent>Returns a lazy, backpressured stream of output and completion events.
Axial.FlowStreamModulesingleton: 'value -> FlowStream<'env,'error,'value>Creates a stream containing one value. FlowStream.singleton 42
Axial.Process.ProcessEventValues emitted by a native process FlowStream.
CompletedtestEnvironment: AppEnvironmentProcesses: IProcessFlow.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.ProcessModulerender: ProcessSpec -> stringRenders a redacted shell-like description of the complete process specification.
SomeThe representation of "Value of type 'T" The input value. An option representing the value.
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.runWith is for a command-line host that already owns a larger environment. It runs a
ProcessError workflow with that record and converts failure to a process exit code. The environment must also supply
IConsole, used for error reporting. Most applications instead call Flow.run themselves and handle the resulting
Exit according to their own boundary policy.

