Table of Contents

Getting Started

This guide walks through four small, composable examples, with brief explanations between code blocks. They use Universal pipelines (ValueTask) and the same step/mutator patterns used elsewhere in this library.

using K1vs.DotNetPipe;
using K1vs.DotNetPipe.Universal;
using K1vs.DotNetPipe.Mutations;

// 1) Delegates: a simple linear pipeline
var pipeline = Pipelines.CreatePipeline<int>("DemoPipeline")
    .StartWithLinear<int>("AddConst", async (input, next) =>
    {
        var result = input + 10;
        await next(result);
    })
    .HandleWith("Handler", async value =>
    {
        Console.WriteLine($"Result: {value}");
        await ValueTask.CompletedTask;
    })
    .BuildPipeline()
    .Compile();

await pipeline(5); // prints: Result: 15

The first example defines a linear step that adds a constant and a final handler. No extra allocations at runtime.

// 2) Delegates: add a mutator via cfg.Configure (modify the "AddConst" step)
var mutated = Pipelines.CreatePipeline<int>("DemoPipeline")
    .StartWithLinear<int>("AddConst", async (input, next) => await next(input + 10))
    .HandleWith("Handler", async _ => await ValueTask.CompletedTask)
    .BuildPipeline()
    .Compile(cfg =>
    {
        cfg.Configure(space =>
        {
            var step = space.GetRequiredLinearStep<int, int, int>("DemoPipeline", "AddConst");
            var mutator = new StepMutator<Pipe<int, int>>("AddConst*2", 1, pipe =>
            {
                return async (input, next) =>
                {
                    input *= 2;
                    await pipe(input, next);
                };
            });
            step.Mutators.AddMutator(mutator, AddingMode.ExactPlace);
        });
    });

await mutated(5); // the step will receive an already multiplied value

The mutator locates the step by name, casts it to Pipe<int,int>, and adds a wrapper without changing the original step code.

// 3) Classes: a simple linear pipeline
public sealed class AddConstStep : ILinearStep<int, int>
{
    public string Name => "AddConst";
    public async ValueTask Handle(int input, Handler<int> next)
    {
        await next(input + 10);
    }
}

public sealed class WriteHandler : IHandlerStep<int>
{
    public string Name => "Handler";
    public async ValueTask Handle(int input)
    {
        Console.WriteLine($"Result: {input}");
        await ValueTask.CompletedTask;
    }
}

var classPipeline = Pipelines.CreatePipeline<int>("DemoPipeline")
    .StartWithLinear(new AddConstStep())
    .HandleWith(new WriteHandler())
    .BuildPipeline()
    .Compile();

await classPipeline(5);

Class-based steps provide better readability and precise addressing by name for mutators.

// 4) Classes: a class-based mutator (IMutator<Space>)
public sealed class AddConstClassMutator : IMutator<Space>
{
    public void Mutate(Space space)
    {
        var step = space.GetRequiredLinearStep<int, int, int>("DemoPipeline", "AddConst");
        var mutator = new StepMutator<Pipe<int, int>>("AddConst+5", 1, pipe => async (input, next) => await pipe(input + 5, next));
        step.Mutators.AddMutator(mutator, AddingMode.ExactPlace);
    }
}

var classMutated = Pipelines.CreatePipeline<int>("DemoPipeline")
    .StartWithLinear(new AddConstStep())
    .HandleWith(new WriteHandler())
    .BuildPipeline()
    .Compile(cfg =>
    {
        cfg.Configure(new IMutator<Space>[] { new AddConstClassMutator() });
    });

await classMutated(5); // result: (5 + 5) + 10

You can mix delegates and classes in a single pipeline, and apply both delegate- and class-based mutators.