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.PlatformService
open Axial.State
open Axial.Telemetry
open Axial.Telemetry.JavaScript

Axial.PlatformService exposes five capabilities as dependencies instead of ambient state: DateTimeOffset.UtcNow, a logger, Random, Guid.NewGuid(), and Environment.GetEnvironmentVariable. Tests can then pin the time or generated identifier.

Verified
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

A function that declares IHasClock can be tested at a chosen instant:

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

The base runtime

Applications usually need all five. BaseRuntime bundles them, and it implements one contract per service, so a workflow requiring any combination is satisfied by the single value:

Verified
let liveRuntime : BaseRuntime = BaseRuntime.liveValue

BaseRuntime.live : Layer<unit, Never, BaseRuntime> provides the same bundle to a layer-composed runtime, and BaseRuntime.fromServiceProvider : Layer<IServiceProvider, BaseRuntimeError, BaseRuntime> 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:

Verified
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. Logging: ILog 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.