FlowKit 0.1.0

Suggested Alternatives

FsFlow

Additional Details

Renamed to FsFlow

dotnet add package FlowKit --version 0.1.0
                    
NuGet\Install-Package FlowKit -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="FlowKit" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FlowKit" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="FlowKit" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add FlowKit --version 0.1.0
                    
#r "nuget: FlowKit, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package FlowKit@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=FlowKit&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=FlowKit&version=0.1.0
                    
Install as a Cake Tool

FlowKit

Simple to use F# flow {} computation expression for unifying Dependencies (Reader), Error Handling (Result), and Async/Task.

The modern way to build F# application use cases.

ci NuGet License

When one F# use case starts mixing Result, async {}, .NET Task, and dependency injection, the code often stops reading like the happy path.

Why This Exists In F#

Most real F# application code ends up mixing:

  • dependencies passed through several layers
  • Result for expected business errors
  • Async or .NET Task for IO

That often turns into:

AppEnv -> Async<Result<'value, 'error>>

plus helper modules, adapters, and wrapper-specific boilerplate.

FlowKit is a minimal, idiomatic way to represent that shape directly in F#.

It gives that use case one shape:

Flow<'env, 'error, 'value>

and one workflow:

flow { ... }

so env access, typed failures, Async, and Task stay in one place instead of spreading across helper modules, adapters, and wrapper-specific CEs.

Before And After

Before:

let handle userId (env: AppEnv) =
    async {
        let! loaded = env.LoadName userId |> Async.AwaitTask

        match loaded with
        | Error error ->
            return Error (GatewayFailed error)
        | Ok loadedName ->
            match validateName loadedName with
            | Error error ->
                return Error error
            | Ok validName ->
                return Ok $"{env.Prefix} {validName}"
    }

After:

let handle userId : Flow<AppEnv, AppError, string> =
    flow {
        let! env = Flow.env
        let! loadedName =
            env.LoadName userId
            |> Flow.mapError GatewayFailed
        let! validName = validateName loadedName
        return $"{env.Prefix} {validName}"
    }

This is the same application flow without the plumbing taking over the happy path.

What It Actually Is

FlowKit is a small, focused F# library built around composable flows:

  • explicit environment requirements
  • typed failures via Result
  • cancellation-aware execution without passing tokens through every step
  • direct Async and .NET Task interop
  • helpers for retry, timeout, logging, and scoped cleanup

The point is to keep that code in one place, with one workflow type, while staying in ordinary F#:

  • one computation expression: flow {}
  • one CE that binds Result, Async, Task, and env access in one place
  • explicit environment access through Flow.read and Flow.env
  • explicit execution through Flow.run env cancellationToken flow

It does not replace F# Async, .NET Task, or Result. It gives you a smaller, more consistent way to compose them in application code. Cancellation stays explicit at the runtime boundary and in cold task signatures, but usually disappears inside flow {} itself.

What It Is Not

FlowKit is not trying to become a new runtime platform.

  • it does not reimplement Async or Task
  • it does not introduce its own concurrency system
  • it does not hide when effects run
  • it stays explicit at the execution boundary with Flow.run

The library is intentionally narrow:

  • better DX for mixed application workflows
  • better readability across Result / Async / Task code
  • less wrapper and adapter noise around the happy path

Reader-style composition can feel imported in F# when it arrives as a larger FP stack. FlowKit keeps the same practical benefits in plain F# terms:

  • one computation expression
  • plain functions
  • explicit environment access
  • no transformer stacks or typeclass machinery

Flows are cold by default. Building a flow does not run it.

Full Code

type AppEnv =
    { Prefix: string
      LoadName: int -> Task<Result<string, AppError>> }

type AppError =
    | MissingName
    | GatewayFailed of string

let validateName (name: string) =
    if System.String.IsNullOrWhiteSpace name then
        Error MissingName
    else
        Ok name

let greet userId : Flow<AppEnv, AppError, string> =
    flow {
        let! loadName = Flow.read _.LoadName
        let! loadedName =
            loadName userId
            |> Flow.mapError GatewayFailed

        let! validName = validateName loadedName
        let! prefix = Flow.read _.Prefix
        return $"{prefix} {validName}"
    }

let result =
    greet 42
    |> Flow.run
        { Prefix = "Hello"
          LoadName = fun _ -> Task.FromResult(Ok "Ada") }
        System.Threading.CancellationToken.None
    |> Async.RunSynchronously

This full example shows the intended shape in one place:

  • one env dependency through Flow.read
  • one plain Result function
  • one Task<Result<_,_>> boundary
  • one happy-path workflow in flow {}

Where To Use It

Use FlowKit at the effectful application boundary:

  • handlers
  • use cases
  • service orchestration
  • infrastructure-facing application services

Keep the domain plain F# where possible:

  • plain functions
  • plain domain types
  • plain Result when that already reads well

Keep domain code plain. Use flow {} by default in the application layer.

Supported Architectural Styles

FlowKit supports three valid architectural styles:

  • Booted App Environment
  • Explicit Dependencies + Context
  • Standard .NET AppHost + DI

The library does not require one application shape. Choose the style that fits your codebase and team:

  • use Booted App Environment when app composition simplicity matters most
  • use Explicit Dependencies + Context when feature locality and testability matter most
  • use standard .NET AppHost + DI when familiarity and incremental adoption matter most

Read docs/ARCHITECTURAL_STYLES.md for examples and trade-offs.

When FlowKit Fits Well

FlowKit is a good fit when:

  • a workflow needs 2 to 5 dependencies
  • validation, IO, and error translation all belong in one use case
  • your code touches both Async and .NET Task
  • you want expected failures in the type rather than scattered exception handling
  • retry, timeout, and cleanup belong close to the business flow

FlowKit is usually not worth it when:

  • the code is mostly pure
  • plain Result already reads well
  • a direct Task<'T> boundary is the clearest option

Learn The Library In This Order

  1. docs/GETTING_STARTED.md
  2. docs/TINY_EXAMPLES.md
  3. docs/ARCHITECTURAL_STYLES.md
  4. docs/WHY_FLOWKIT.md
  5. docs/TASK_ASYNC_INTEROP.md
  6. docs/FSTOOLKIT_MIGRATION.md
  7. docs/ENV_SLICING.md
  8. docs/SEMANTICS.md
  9. examples/README.md
  10. docs/TROUBLESHOOTING_TYPES.md
  11. src/FlowKit/Flow.fs

Compatibility

AOT Verified

NativeAOT is verified in this repo through a small publish-and-run probe application.

.NET only - no Fable story

The design is .NET-first. Cancellation is explicit in the Flow execution model, and task interop is part of the first-class public surface. This means we don't have a Fable story (yet).

Existing Shapes

FlowKit builds on existing F# and .NET primitives rather than replacing them. If a direct Result, Async<'T>, or Task<'T> boundary is already the clearest shape, use that shape directly.

If you already have Async<Result<_,_>> or FsToolkit-style workflows, you can adopt Flow per use case and interoperate through adapters like:

  • Flow.fromAsyncResult
  • Flow.toAsyncResult

Run The Repo

Run the examples:

# Longer main example
dotnet run --project examples/FlowKit.Examples/FlowKit.Examples.fsproj

# Maintenance example:
dotnet run --project examples/FlowKit.MaintenanceExamples/FlowKit.MaintenanceExamples.fsproj

# Minimal playground example:
dotnet run --project examples/FlowKit.Playground/FlowKit.Playground.fsproj

Run the test suite:

dotnet run --project tests/FlowKit.Tests/FlowKit.Tests.fsproj

Run the NativeAOT probe:

bash scripts/run-aot-probe.sh
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0 81 4/26/2026 0.1.0 is deprecated.

Initial public preview release of FlowKit with the core Flow abstraction, task and async interop, cancellation-aware execution, retry and timeout helpers, scoped cleanup, runnable examples, and GitHub Pages API documentation.