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.JavaScript

Axial.PlatformService covers the five capabilities that would otherwise be read straight from static globals: DateTimeOffset.UtcNow, a logger, Random, Guid.NewGuid(), and Environment.GetEnvironmentVariable. Each becomes a declared dependency, which is what makes a workflow that reads the time or generates an identifier reproducible in a test.

open System
open Axial
open Axial.PlatformService
| Service | Replaces | Module | | --- | --- | --- | | `IClock` | `DateTimeOffset.UtcNow` | `Clock` | | `ILog` | An ambient logger | `Log` | | `IRandom` | `System.Random` | `Random` | | `IGuid` | `Guid.NewGuid()` | `Guid` | | `IEnvironmentVariables` | `Environment.GetEnvironmentVariable` | `EnvironmentVariables`, `EnvironmentVariable` |

These are the smallest services Axial ships, and the most valuable to make explicit. A function that reads the clock directly cannot be tested at a chosen instant; one that declares IHasClock can:

let isExpired (expiry: DateTimeOffset) : Flow<#IHasClock, Never, bool> =
    Clock.now |> Flow.map (fun now -> now >= expiry)
## The base runtime

Applications rarely want one of these — they want all five. BaseRuntime is the record that bundles them, and it implements one contract per service, so a workflow requiring any combination is satisfied by the single value:

let liveRuntime : BaseRuntime = BaseRuntime.liveValue
`BaseRuntime.live : Layer` provides the same bundle to a layer-composed runtime, and `BaseRuntime.fromServiceProvider : Layer` builds it from a host container, turning missing registrations into typed startup errors.

Most applications extend BaseRuntime rather than replacing it — see Tutorial: Composing Built-in Services for the composition, and platform services getting started for the shortest path to a running host.

Deterministic implementations

Every module ships test doubles beside its live value, so a test rarely needs to write an object expression:

let fixedRuntime : BaseRuntime =
    { Clock = Clock.fromValue (DateTimeOffset.Parse "2026-01-01T00:00:00Z")
      Log = Log.live
      Random = Random.fromValue 7
      Guid = Guid.fromValue (System.Guid.Parse "00000000-0000-0000-0000-000000000001")
      EnvironmentVariables = EnvironmentVariables.fromPairs [ "AXIAL_ENV", "test" ] }
`Clock.fromValue`, `Guid.fromValue`, `Random.fromValue`, `Random.fromFixed`, `EnvironmentVariables.fromPairs`, and `Log.fromSink` each pin one service to a known answer.

In this section

  1. Clock — the current instant, and why it belongs in the environment.
  2. LoggingILog levels, sinks, and its relationship to telemetry.
  3. Randomness and GUIDs — non-determinism you can pin in a test.
  4. Environment variables — typed reads with EnvironmentVariableError.