FlowKit 0.1.0
Renamed to FsFlow
dotnet add package FlowKit --version 0.1.0
NuGet\Install-Package FlowKit -Version 0.1.0
<PackageReference Include="FlowKit" Version="0.1.0" />
<PackageVersion Include="FlowKit" Version="0.1.0" />
<PackageReference Include="FlowKit" />
paket add FlowKit --version 0.1.0
#r "nuget: FlowKit, 0.1.0"
#:package FlowKit@0.1.0
#addin nuget:?package=FlowKit&version=0.1.0
#tool nuget:?package=FlowKit&version=0.1.0
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.
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
Resultfor expected business errorsAsyncor.NET Taskfor 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
Asyncand.NET Taskinterop - 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.readandFlow.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
AsyncorTask - 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/Taskcode - 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
Resultfunction - 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
Resultwhen 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
.NETAppHost + 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
.NETAppHost + 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
Asyncand.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
Resultalready reads well - a direct
Task<'T>boundary is the clearest option
Learn The Library In This Order
docs/GETTING_STARTED.mddocs/TINY_EXAMPLES.mddocs/ARCHITECTURAL_STYLES.mddocs/WHY_FLOWKIT.mddocs/TASK_ASYNC_INTEROP.mddocs/FSTOOLKIT_MIGRATION.mddocs/ENV_SLICING.mddocs/SEMANTICS.mdexamples/README.mddocs/TROUBLESHOOTING_TYPES.mdsrc/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.fromAsyncResultFlow.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 | Versions 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. |
-
.NETStandard 2.1
- FSharp.Core (>= 10.1.201)
-
net8.0
- FSharp.Core (>= 10.1.201)
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 |
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.