Runnable Examples

Application-shaped examples that are executed during docs generation and mirrored back into the site.

Runnable Examples

This page shows the examples that are executed during the docs build, so the public docs stay tied to real code and observed output.

The examples below are built from the repository projects, run with the current source, and then written back into this page.

The code blocks keep the important API calls on the same lines as the values they bind, with trailing comments where that makes the signature easier to read. The examples prefer the normal direct-bind style inside computation expressions, so the docs reflect the recommended day-to-day usage.

Request Boundary Example

This example shows a request boundary that pulls a user from a database-like environment, threads a trace id through the request context, and reuses the same validation shape across Flow.

Run it:

FSFLOW_EXAMPLE=request-boundary dotnet run --project examples/FsFlow.Examples/FsFlow.Examples.fsproj --nologo

Source:

Source code:

module RequestBoundaryExample

open System
open System.Threading
open System.Threading.Tasks
open FsFlow

type User =
    { Id: int
      Name: string }

type AppDb =
    { FindUser: int -> User option }

type RequestEnv =
    { TraceId: Guid
      Prefix: string
      Db: AppDb
      LoadSuffix: Task<string> }

let validateName (name: string) : Result<string, string> =
    Check.notBlank name
    |> Check.orError "name is required"

let loadUser : Flow<RequestEnv, string, User> =
    flow {
        let! db = Flow.read _.Db // Flow<RequestEnv, string, AppDb>
        let! user = db.FindUser 42 |> Flow.fromOption "user not found" // Flow<RequestEnv, string, User>
        return user
    }

let renderTrace : Flow<RequestEnv, string, string> =
    flow {
        let! env = Flow.env // Flow<RequestEnv, string, RequestEnv>
        let! user = loadUser // Flow<RequestEnv, string, User>
        let! validName = validateName user.Name // Flow<RequestEnv, string, string>
        return $"{env.Prefix} [{env.TraceId}] {validName}"
    }

let publishResponse : Flow<RequestEnv, string, string> =
    flow {
        let! env = Flow.env // Flow<RequestEnv, string, RequestEnv>
        let! user = loadUser // Flow<RequestEnv, string, User>
        let! suffix = env.LoadSuffix // Flow<RequestEnv, string, string>
        return $"{env.Prefix} [{env.TraceId}] {user.Name}{suffix}"
    }

let run () =
    let environment =
        { TraceId = Guid.Parse "11111111-1111-1111-1111-111111111111"
          Prefix = "Hello"
          Db =
            { FindUser =
                function
                | 42 -> Some { Id = 42; Name = "Ada" }
                | _ -> None }
          LoadSuffix = Task.FromResult "!" }

    let syncResult =
        loadUser
        |> Flow.run environment
        |> fun t -> t.AsTask().GetAwaiter().GetResult()

    let asyncResult =
        renderTrace
        |> Flow.run environment
        |> fun t -> t.AsTask().GetAwaiter().GetResult()

    let taskResult =
        publishResponse
        |> Flow.run environment
        |> fun t -> t.AsTask().GetAwaiter().GetResult()

    printfn "Flow result: %A" syncResult
    printfn "Flow result: %A" asyncResult
    printfn "Flow result: %A" taskResult
    // Flow result: Ok { Id = 42; Name = "Ada" }
    // Flow result: Ok "Hello [11111111-1111-1111-1111-111111111111] Ada"
    // Flow result: Ok "Hello [11111111-1111-1111-1111-111111111111] Ada!"

Diagnostics Example

This example shows a JSON-shaped request boundary with a root-level error, nested child branches, and a display-friendly diagnostics tree.

Run it:

FSFLOW_EXAMPLE=diagnostics dotnet run --project examples/FsFlow.Examples/FsFlow.Examples.fsproj --nologo

Source:

Source code:

module DiagnosticsExample

open System.Text.Json
open FsFlow

type CustomerLine =
    { Name: string }

type CustomerAddress =
    { City: string }

type Customer =
    { Name: string
      Address: CustomerAddress
      Lines: CustomerLine list }

type CreateCustomerRequest =
    { RequestId: string
      Customer: Customer }

type ApiError =
    { path: string
      message: string }

type ApiErrorResponse =
    { errors: ApiError list }

let jsonOptions = JsonSerializerOptions(WriteIndented = true)

let validateAddress address =
    validate.key "address" {
        let! city =
            validate.name "City" {
                return! address.City |> Check.notBlank |> Check.orError "City required"
            }

        return { address with City = city }
    }

let validateCustomer customer =
    validate {
        let! name =
            validate.name "Name" {
                return! customer.Name |> Check.notBlank |> Check.orError "Name required"
            }

        and! address = validateAddress customer.Address

        and! lines =
            validate.key "lines" {
                return!
                    customer.Lines
                    |> Validation.traverseIndexed (fun index line ->
                        validate.name "Name" {
                            let! name =
                                line.Name |> Check.notBlank |> Check.orError $"Line {index} name required"

                            return { Name = name }
                        }
                    )
            }

        return
            { customer with
                Name = name
                Address = address
                Lines = lines }
    }

let renderPath (path: PathSegment list) =
    path
    |> List.map (function
        | PathSegment.Key value
        | PathSegment.Name value -> value
        | PathSegment.Index index -> $"[{index}]")
    |> String.concat "."

let toApiErrors (graph: Diagnostics<'error>) =
    { errors =
        graph
        |> Diagnostics.flatten
        |> List.map (fun diagnostic ->
            { path = renderPath diagnostic.Path
              message = string diagnostic.Error }) }

let validateCreateCustomerRequest request =
    validate {
        let! requestId =
            validate.name "RequestId" {
                return! request.RequestId |> Check.notBlank |> Check.orError "RequestId required"
            }

        and! customer =
            validate.key "customer" {
                return! validateCustomer request.Customer
            }

        return { request with RequestId = requestId; Customer = customer }
    }

let run () =
    let requestJson =
        """{
  "requestId": "",
  "customer": {
    "name": "",
    "address": { "city": "" },
    "lines": [ { "name": "" } ]
  }
}"""

    let badRequest =
        { RequestId = ""
          Customer =
            { Name = ""
              Address = { City = "" }
              Lines = [ { Name = "" } ] } }

    let diagnosticsText =
        validateCreateCustomerRequest badRequest
        |> Validation.toResult
        |> Result.mapError (toApiErrors >> fun payload -> JsonSerializer.Serialize(payload, jsonOptions))
        |> function
            | Ok _ -> "Ok"
            | Error text -> text

    printfn "Request JSON:\n%s" requestJson
    printfn "API error JSON:\n%s" diagnosticsText
    // Request JSON:
    // {
    //   "requestId": "",
    //   "customer": {
    //     "name": "",
    //     "address": { "city": "" },
    //     "lines": [ { "name": "" } ]
    //   }
    // }
    // API error JSON:
    // {
    //   "errors": [
    //     {
    //       "path": "customer.address.City",
    //       "message": "City required"
    //     },
    //     {
    //       "path": "customer.lines.[0].Name",
    //       "message": "Line 0 name required"
    //     },
    //     {
    //       "path": "customer.Name",
    //       "message": "Name required"
    //     },
    //     {
    //       "path": "RequestId",
    //       "message": "RequestId required"
    //     }
    //   ]
    // }

CAPS Core Example

This example shows the sync-first FsFlow.Caps.Core surface: fixed and live capability providers, plus typed errors for missing and invalid environment variables.

Run it:

dotnet run --project examples/FsFlow.Caps.Core.Examples/FsFlow.Caps.Core.Examples.fsproj --nologo

Source:

Source code:

namespace FsFlow.Caps.Core.Examples

open System
open FsFlow
open FsFlow.Caps.Core

module CoreCapabilitiesExample =
    type private AppCaps =
        {
            Clock: IClock
            Random: IRandom
            Guid: IGuid
            EnvVars: IEnvironmentVariables
        }
        interface Needs<IClock> with member this.Dep = this.Clock
        interface Needs<IRandom> with member this.Dep = this.Random
        interface Needs<IGuid> with member this.Dep = this.Guid
        interface Needs<IEnvironmentVariables> with member this.Dep = this.EnvVars

    let private renderExit formatter result =
        match result with
        | Exit.Success value -> $"Ok {formatter value}"
        | Exit.Failure (Cause.Fail error) -> $"Error {EnvironmentVariableErrors.describe error}"
        | Exit.Failure cause -> $"Failure {cause}"

    let run () =
        let caps =
            {
                Clock = Clock.fromValue (DateTimeOffset(2026, 5, 10, 12, 0, 0, TimeSpan.Zero))
                Random = Random.fromValue 7
                Guid = Guid.fromValue (global.System.Guid.Parse "11111111-1111-1111-1111-111111111111")
                EnvVars =
                    EnvironmentVariables.fromPairs
                        [ "FSFLOW_CAPS_PORT", "8080"
                          "FSFLOW_CAPS_ENABLED", "true"
                          "FSFLOW_CAPS_SESSION", "22222222-2222-2222-2222-222222222222"
                          "FSFLOW_CAPS_PORT_TEXT", "abc" ]
            }

        let run flow = Flow.run caps flow |> fun t -> t.AsTask().GetAwaiter().GetResult()

        printfn "clock=%O" (run Clock.now)
        printfn "random=%d" (run (Random.nextInt 0 10) |> function Exit.Success v -> v | _ -> -1)
        printfn "guid=%O" (run Guid.newGuid)
        printfn "port=%s" (renderExit string (run (EnvironmentVariable.getInt "FSFLOW_CAPS_PORT")))
        printfn "enabled=%s" (renderExit string (run (EnvironmentVariable.getBool "FSFLOW_CAPS_ENABLED")))
        printfn "session=%s" (renderExit string (run (EnvironmentVariable.getGuid "FSFLOW_CAPS_SESSION")))
        printfn "missing=%s" (renderExit string (run (EnvironmentVariable.get "FSFLOW_CAPS_MISSING")))
        printfn "invalid=%s" (renderExit string (run (EnvironmentVariable.getInt "FSFLOW_CAPS_PORT_TEXT")))

Playground Example

This example shows the same core boundary across Flow using the normal direct-bind style inside each computation expression.

Run it:

dotnet run --project examples/FsFlow.Playground/FsFlow.Playground.fsproj --nologo

Source:

Source code:

open System
open System.Threading
open System.Threading.Tasks
open FsFlow

type AppEnv =
    { Prefix: string
      Name: string
      LoadSuffix: Task<string> }

let greetingFlow : Flow<AppEnv, string, string> =
    Flow.read (fun env -> $"{env.Prefix} {env.Name}") // Flow<AppEnv, string, string>

let greetingAsync : Flow<AppEnv, string, string> =
    flow {
        let! greeting = greetingFlow
        return greeting.ToUpperInvariant()
    }

let greetingTask : Flow<AppEnv, string, string> =
    flow {
        let! env = Flow.env // Flow<AppEnv, string, AppEnv>
        let! greeting = greetingFlow // Flow<AppEnv, string, string>
        let! suffix = env.LoadSuffix // Flow<AppEnv, string, string>
        return $"{greeting}{suffix}"
    }

[<EntryPoint>]
let main _ =
    let env =
        { Prefix = "Hello"
          Name = "Ada"
          LoadSuffix = Task.FromResult "!" }

    let syncResult =
        greetingFlow
        |> Flow.run env
        |> fun t -> t.AsTask().GetAwaiter().GetResult()

    let asyncResult =
        greetingAsync
        |> Flow.run env
        |> fun t -> t.AsTask().GetAwaiter().GetResult()

    let taskResult =
        greetingTask
        |> Flow.run env
        |> fun t -> t.AsTask().GetAwaiter().GetResult()

    printfn "Flow: %A" syncResult
    printfn "Async: %A" asyncResult
    printfn "Task: %A" taskResult
    // Flow: Ok "Hello Ada"
    // Async: Ok "HELLO ADA"
    // Task: Ok "Hello Ada!"
    0

Maintenance Example

This example shows smaller, focused shapes for maintenance and interop scenarios without switching away from the normal direct-bind style.

Run it:

dotnet run --project examples/FsFlow.MaintenanceExamples/FsFlow.MaintenanceExamples.fsproj --nologo

Source:

Source code:

open System
open System.Threading
open System.Threading.Tasks
open FsFlow

let runFlow label env workflow =
    let result = Flow.run env workflow |> fun t -> t.AsTask().GetAwaiter().GetResult()
    printfn "%s: %A" label result

let runAsyncExample label env workflow =
    let result =
        workflow
        |> Flow.run env
        |> fun t -> t.AsTask().GetAwaiter().GetResult()

    printfn "%s: %A" label result

let runTaskExample label env workflow =
    let result =
        workflow
        |> Flow.run env
        |> fun t -> t.AsTask().GetAwaiter().GetResult()

    printfn "%s: %A" label result

let syncExample : Flow<int, string, int> =
    Flow.read id // Flow<int, string, int>
    |> Flow.map ((+) 1)

let asyncExample : Flow<int, string, int> =
    flow {
        let! value = async { return 21 }
        return value * 2
    }

let taskExample : Flow<int, string, int> =
    flow {
        let! env = Flow.read id
        let! suffix = Task.FromResult 5
        return env + suffix
    }

[<EntryPoint>]
let main _ =
    runFlow "Flow" 20 syncExample
    runAsyncExample "Async" 20 asyncExample
    runTaskExample "Task" 20 taskExample
    // Flow: Ok 21
    // Async: Ok 42
    // Task: Ok 25
    0