Swolfkrow 0.4.0

dotnet add package Swolfkrow --version 0.4.0
NuGet\Install-Package Swolfkrow -Version 0.4.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="Swolfkrow" Version="0.4.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Swolfkrow --version 0.4.0
#r "nuget: Swolfkrow, 0.4.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.
// Install Swolfkrow as a Cake Addin
#addin nuget:?package=Swolfkrow&version=0.4.0

// Install Swolfkrow as a Cake Tool
#tool nuget:?package=Swolfkrow&version=0.4.0

Swolfkrow

Swolfkrow (pronounced /ˈwɜː(r)kˌfləʊs/) is a Domain Specific Language (DSL) designed to declaratively compose asynchronous workflows through a fluent API.

Asynchronous workflows

In the context of Swolfkrow, asynchronous workflows can be understood as asynchronous computations that yield an asynchronous stream of events signaling progress, outcomes, and errors.

In practice, asynchronous workflows are objects that implement the IAsyncEnumerable<out T> interface, with a few semantic constraints:

  • The generic type T, named TEvent across the Swolfkrow library, represents the base type from which all events potentially yielded by the asynchronous workflow derive.
  • Yielded TEvent objects are assumed to describe relevant events occured during the asynchronous workflow's execution.
  • When enumerated, asynchronous workflows may execute arbitrary asynchronous logic in between consecutively yielded TEvent objects.

Swolfkrow provides the programmatic glue required to declaratively compose simpler asynchronous workflows together into more complex ones. It also provides support to build asynchronous workflows from other primitives, like IEnumerable<TEvent>, Task<TEvent>, or ValueTask<TEvent>.

Fluent API

Swolfkrow's fluent API can be visualized as a state machine, where the (arguably less relevant) supporting classes are states, and the composition operators are transitions:

stateDiagram-v2
    state "Workflow" as workflow
    state "Trigger" as trigger

    [*] --> workflow : Workflow.Start(...)
    workflow --> [*] : IAsyncEnumerable
    workflow --> workflow : .Then(...)\n.While(...)\n.Until(...)\n.Do(...)
    workflow --> trigger : .When(...)
    
    %%coming soon!
    %%trigger --> trigger : .Times(...)
    trigger --> workflow : .Then(...)
    trigger --> workflow : .Do(...)

The entry point to the DSL is always one of the many Workflow.Start factory method overloads, all of which return an initial Workflow<Event> instance.

The Workflow<TEvent> class exposes a number of operators that perform single-step compositions and return a new Workflow<TEvent> instance:

  • Continuations: workflow.Then(...)
  • Side-effects: workflow.Do(...)
  • Interruptions: workflow.While(...), workflow.Until(...)

The Workflow<TEvent> class also exposes a Workflow<TEvent>.When operator that enables two-step triggered compositions:

  • Triggered continuations: workflow.When(...).Then(...)
  • Triggered side-effects: workflow.When(...).Do(...)

Triggered compositions rely on an intermediate Trigger<TEvent, ...> instance produced by the initial call to Workflow<TEvent>.When.

The API can be exited after any of the operators that return a Workflow<TEvent> instance, given that the Workflow<TEvent> class itself implements IAsyncEnumerable<TEvent>.

The Workflow<TEvent> class

The Workflow<TEvent> class provides a semantic anchor with a self-descriptive name, as well as a convenient container for the fluent API operators. It is meaningful only during the composition of asynchronous workflows and uninteresting outside that context. Composed asynchronous workflows can be exposed directly as IAsyncEnumerable<TEvent> instances.

Overview

The following subsections briefly describe the different types of asynchronous workflow compositions that can be performed through Swolfkrow's fluent API operators.

Initiations

The family of Start method overloads provides a single entry point to the DSL:

record EventBase(string Description);

IAsyncEnumerable<EventBase> Step1() { ... }

IAsyncEnumerable<EventBase> ComposeWorkflow()
    => Workflow
        .Start(Step1);

Continuations

Asynchronous workflows can be composed as sequences of simpler workflows, where one workflow starts when the previous one finishes yielding events:

record EventBase(string Description);

IAsyncEnumerable<EventBase> Step1() { ... }
IAsyncEnumerable<EventBase> Step2() { ... }

IAsyncEnumerable<EventBase> ComposedWorkflow()
    => Workflow
        .Start(Step1)
        .Then(Step2);

Stateful continuations

Workflow continuations can be based on state explicitly folded from the events yielded by the workflow:

record EventBase(string Description);

IAsyncEnumerable<EventBase> Step1() { ... }
IAsyncEnumerable<EventBase> Step2(int someInfo) { ... }

IAsyncEnumerable<EventBase> ComposedWorkflow()
    => Workflow
        .Start(Step1)
        .Then(
            createContinuation: currentState => currentState * 2,
            computeNextState: (currentState, nextEvent) => currentState + 1,
            initialState: 0);

Intercalations

Asynchronous workflows can be intercalated and executed in the middle of other asynchronous workflows, triggered by events of a specific type and/or satisfying a predicate:

record EventBase(string Description);
record SomethingHappened(string Description) : EventBase(Description);

IAsyncEnumerable<EventBase> Step1() { ... }
IAsyncEnumerable<EventBase> Step2(SomethingHappened somethingHappened) { ... }

IAsyncEnumerable<EventBase> ComposedWorkflow()
    => Workflow
        .Start(Step1)
        .When<SomethingHappened>().Then(Step2);

Side effects

Side effects can be deliberately injected into an asynchronous workflow:

record EventBase(string Description);

IAsyncEnumerable<EventBase> Step1() { ... }
IAsyncEnumerable<EventBase> Step2() { ... }

void LogEvent(EventBase someEvent)
    => Console.WriteLine($"Something happened: {EventBase}")

IAsyncEnumerable<EventBase> ComposedWorkflow()
    => Workflow
        .Start(Step1)
        .Then(Step2)
        .Do(LogEvent);

Interruptions

Asynchronous workflows can be interrupted based on a condition computed on each of the yielded events:

public record EventBase(string Description);
public record SomeError(string Description, Exception Exception) : EventBase(Description);

IAsyncEnumerable<EventBase> Step1() { ... }
IAsyncEnumerable<EventBase> Step2(int someInfo) { ... }

bool IsError(EventBase nextEvent) => nextEvent is SomeError;

IAsyncEnumerable<EventBase> ComposedWorkflow()
    => Workflow
        .Start(Step1)
        .Then(Step2, 42)
        .Until(IsError);
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 was computed.  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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  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.4.0 157 9/1/2023
0.3.2 144 7/22/2023
0.3.1 171 7/3/2023
0.3.0 156 7/2/2023
0.2.5 145 6/18/2023
0.2.4 146 6/12/2023
0.2.3 164 6/9/2023
0.2.2 161 6/6/2023
0.2.1 135 6/4/2023
0.2.0 147 5/29/2023
0.1.3 150 5/29/2023
0.1.2 152 5/25/2023
0.1.1 155 5/22/2023
0.1.0 150 5/21/2023

# 0.4.0 (2023-09-01)
- Target .NET Standard 2.0
# 0.3.2 (2023-07-22)
- Added interruption overloads with additional arguments.
# 0.3.1 (2023-07-04)
- Fixed the return types of some public api methods.
# 0.3.0 (2023-07-02)
- BREAKING CHANGE: rewritten extension methods as members of a newly added `Workflow<TEvent>` class.
- BREAKING CHANGE: rewritten intercalations as two-step compositions.
# 0.2.5 (2023-06-19)
- Added intercalation overloads.
# 0.2.4 (2023-06-13)
- Added interruptions.
# 0.2.3 (2023-06-10)
- Full review of the comment-based documentation.
- Added `IEnumerable` based overloads of start, continuation, and event continuation operators.
# 0.2.2 (2023-06-07)
- Added synchronous continuations.
# 0.2.1 (2023-06-04)
- Start/continue asynchronous workflows from `Task` and `ValueTask`
# 0.2.0 (2023-05-29)
- Simplified stateful continuations.
# 0.1.3 (2023-05-26)
- Added event continuations
# 0.1.2 (2023-05-25)
- Added `Start` and `Then` overloads with additional arguments
# 0.1.1 (2023-05-22)
- Added stateful continuations.
# 0.1.0 (2023-05-21)
- Initial Version.
- Added workflow continuations.