FsFlow 0.6.0
Prefix Reserveddotnet add package FsFlow --version 0.6.0
NuGet\Install-Package FsFlow -Version 0.6.0
<PackageReference Include="FsFlow" Version="0.6.0" />
<PackageVersion Include="FsFlow" Version="0.6.0" />
<PackageReference Include="FsFlow" />
paket add FsFlow --version 0.6.0
#r "nuget: FsFlow, 0.6.0"
#:package FsFlow@0.6.0
#addin nuget:?package=FsFlow&version=0.6.0
#tool nuget:?package=FsFlow&version=0.6.0
FsFlow
API Still stabilising - wait for 1.0 to avoid breaking changes
<picture> <source media="(prefers-color-scheme: dark)" srcset="docs/content/img/fsflow-readme-dark.svg"> <source media="(prefers-color-scheme: light)" srcset="docs/content/img/fsflow-readme-light.svg"> <img alt="FsFlow" src="docs/content/img/fsflow-readme-light.svg" width="160"> </picture>
FsFlow provides structured composition over normal F#/.NET code. It is a coherent application architecture model for F# on .NET, centered on a unified effect system.
Write small predicate checks with Check, keep fail-fast logic in standard Result, accumulate sibling
validation with Validation and validate {}, then lift the same logic into Flow
when the boundary needs environment access, async work, task interop, or runtime policy.
Coherent Application Architecture
FsFlow is built around one progression:
Check -> Result -> Validation -> Flow
The same vocabulary stays the same while the execution context grows.
- Structured Composition: A single
flow {}builder that bindsResult,Option,Async,Task, andColdTaskdirectly, eliminating the "adapter tax" of switching helper families at every boundary. - Architectural Honesty: Distinguishes between your Explicit Environment (business dependencies like repositories) and the Ambient Runtime (operational services like clock and logging).
- ZIO-Style Execution: Preserves the critical distinction between typed domain failures, cancellations, and unhandled defects.
- Composable State: Built-in Software Transactional Memory (STM) for atomic coordination across multiple variables without manual lock management.
Install
FsFlowforFlowand the supporting validation/runtime helpers
Example
Start with a reusable check and a fail-fast result:
open System.Threading.Tasks
open FsFlow
type RegistrationError =
| EmailMissing
| SaveFailed of string
let validateEmail (email: string) : Result<string, RegistrationError> =
email
|> Check.notBlank
|> Check.orError EmailMissing
Use the same validation logic directly inside a task-oriented workflow:
open System.Threading.Tasks
open FsFlow
type User =
{ Email: string }
type RegistrationEnv =
{ LoadUser: int -> Task<Result<User, RegistrationError>>
SaveUser: User -> Task<Result<unit, RegistrationError>> }
let registerUser userId : Flow<RegistrationEnv, RegistrationError, unit> =
flow {
let! loadUser = Flow.read _.LoadUser
let! saveUser = Flow.read _.SaveUser
let! user = loadUser userId
do! validateEmail user.Email
return! saveUser user
}
validateEmail is just a plain Result<string, RegistrationError>.
flow lifts it directly with do!.
The same builder also binds Async, Task, ValueTask, and ColdTask directly.
Semantic Boundary
FsFlow is for short-circuiting, ordered workflows:
Check,Result,Validation, andFlowstop on the first typed failure.Validationandvalidate {}accumulate sibling failures in a structured diagnostics graph.- The flow families are for orchestration, dependency access, async or task execution, and runtime concerns.
If you need accumulated validation, use Validation and validate {} explicitly instead of
trying to hide it inside a flow builder.
What You Get
FsFlow stays close to standard F# and .NET:
flow { ... }binds toResultandOptionflow { ... }also binds toAsync,Async<Option<_>>,Async<ValueOption<_>>, andAsync<Result<_,_>>flow { ... }also binds toTask,ValueTask,Task<_>,ValueTask<_>, andColdTaskresult {}keeps fail-fast pure code readablevalidate {}keeps sibling validation accumulation explicit
Because tasks are hot, FsFlow includes ColdTask: a small wrapper around CancellationToken -> Task.
flow handles token passing for you and keeps reruns explicit.
This is the file-oriented example shape. The full runnable example is in
examples/FsFlow.ReadmeExample/Program.fs.
dotnet run --project examples/FsFlow.ReadmeExample/FsFlow.ReadmeExample.fsproj --nologo
Supporting types in the full example are just:
ReadmeEnv = { Root: string }FileReadError = NotFound
let readTextFile (path: string) : Flow<ReadmeEnv, FileReadError, string> =
flow {
// In production, map access and path exceptions separately at the boundary.
do! okIf (File.Exists path) |> orElse (NotFound path) // from Validate
return! ColdTask(fun ct -> File.ReadAllTextAsync(path, ct)) // ColdTask<string>
}
let program : Flow<ReadmeEnv, FileReadError, string * string> =
flow {
let! root = Flow.read _.Root // ReadmeEnv.Root -> string
let settingsFile = Path.Combine(root, "settings.json")
let featureFlagsFile = Path.Combine(root, "feature-flags.json")
let! settings = readTextFile settingsFile // Flow<ReadmeEnv, FileReadError, string>
let! featureFlags = readTextFile featureFlagsFile // Flow<ReadmeEnv, FileReadError, string>
return settings, featureFlags // Flow<ReadmeEnv, FileReadError, string * string>
}
It reads Root from 'env, performs two file reads in one flow {}, and keeps failure typed at the boundary.
Getting Started
- Docs site for guides and API reference
- Validation & Results for the validation-first story
- Getting Started for the core workflow guide
- Straightforward Examples for small runnable snippets
- Execution and Outcomes for running and combining flows
examples/for runnable repo examples
| 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
- Fable.Core (>= 4.3.0)
- FSharp.Core (>= 10.1.300)
-
net8.0
- Fable.Core (>= 4.3.0)
- FSharp.Core (>= 10.1.300)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on FsFlow:
| Package | Downloads |
|---|---|
|
FsFlow.Net
.NET task-oriented workflows, task interop, and task-specific runtime helpers for FsFlow. |
GitHub repositories
This package is not used by any popular GitHub repositories.
Folded the task-oriented workflow surface into the main FsFlow package and namespace.