Baubit.Mediation 2026.22.1

dotnet add package Baubit.Mediation --version 2026.22.1
                    
NuGet\Install-Package Baubit.Mediation -Version 2026.22.1
                    
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="Baubit.Mediation" Version="2026.22.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Baubit.Mediation" Version="2026.22.1" />
                    
Directory.Packages.props
<PackageReference Include="Baubit.Mediation" />
                    
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 Baubit.Mediation --version 2026.22.1
                    
#r "nuget: Baubit.Mediation, 2026.22.1"
                    
#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 Baubit.Mediation@2026.22.1
                    
#: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=Baubit.Mediation&version=2026.22.1
                    
Install as a Cake Addin
#tool nuget:?package=Baubit.Mediation&version=2026.22.1
                    
Install as a Cake Tool

Baubit.Mediation

CircleCI codecov<br/> NuGet NuGet <br/> .NET Standard 2.0 License: MIT<br/> Known Vulnerabilities

A lightweight mediator pattern with cache-backed async request/response routing, smoothing out producer backpressure by buffering messages for consumers that process at different rates.<br/><br/> DI extension: Baubit.Mediation.DI
For persisted mediation: Baubit.Caching.LiteDB

Installation

dotnet add package Baubit.Mediation

Quick Start

using Baubit.Mediation;
using Baubit.Caching;
using Baubit.Caching.InMemory;
using Microsoft.Extensions.Logging;
using System.Threading;

// Create dependencies
var loggerFactory = LoggerFactory.Create(b => b.AddConsole());
var configuration = new Baubit.Caching.Configuration();
Func<long?, long?> nextIdFactory = (lastId) => Interlocked.Increment(ref lastId ?? 0);
var store = new Baubit.Caching.InMemory.Store<long, object>(null, null, nextIdFactory, loggerFactory);
var metadata = new Baubit.Caching.InMemory.Metadata<long>(configuration, loggerFactory);
var cache = new Baubit.Caching.OrderedCache<long, object>(configuration, null, store, metadata, loggerFactory);

// Create mediator
var mediator = new Mediator(cache, loggerFactory);

// Define request/response
public class GetUserRequest : IRequest<GetUserResponse>
{
    public int UserId { get; set; }
}

public class GetUserResponse : IResponse
{
    public string Name { get; set; }
}

// Define handler
public class GetUserHandler : IRequestHandler<GetUserRequest, GetUserResponse>
{
    public GetUserResponse Handle(GetUserRequest request, CancellationToken cancellationToken = default)
    {
        return new GetUserResponse { Name = $"User {request.UserId}" };
    }
}

// Register handler and publish request
using var cts = new CancellationTokenSource();
_ = mediator.SubscribeAsync<GetUserRequest, GetUserResponse>(new GetUserHandler(), true, cts.Token);

var response = await mediator.PublishAsync<GetUserRequest, GetUserResponse>(new GetUserRequest { UserId = 1 });
Console.WriteLine(response.Name); // "User 1"

Features

  • Asynchronous request/response handling with optional buffering
  • Cache-backed async processing pipeline
  • Notification pub/sub with typed subscribers
  • Built-in middleware pipeline for notification processing
    • Compose reusable Segment middleware using PipelineBuilder<T>
    • Segments execute in declaration order; any segment may short-circuit the chain
  • Notification and request delivery with buffering control
    • Buffered mode (enableBuffering: true): Messages pass through an ordered cache before delivery. Useful when handlers are required to process events in the order of occurrence and/or the system requires durability/rewind-replay (look at Baubit.Caching.LiteDB for persistence)
    • Unbuffered mode (enableBuffering: false): Messages delivered directly to handlers for low-latency processing
  • Handler registration with cancellation token lifecycle
  • Thread-safe concurrent access
  • Function-based handler subscriptions

API Reference

IMediator

Method Description
Publish<T>(notification, cancellationToken) Publish a notification synchronously to subscribers. Checks cancellation before delivering to each subscriber.
PublishAsync<T>(notification, cancellationToken) Publish a notification asynchronously (fire and forget). Passes cancellation token to Publish.
PublishAsync<TRequest, TResponse>(request, cancellationToken) Publish a request and await response from registered handler. Cancellation token is monitored during processing.
PublishAsync<TRequest, TSegment, TResponse>(request, cancellationToken) Publish a stream request and iterate segments from the registered handler. Returns IAsyncEnumerable<TSegment>.
SubscribeAsync<T>(subscriber, enableBuffering, cancellationToken) Subscribe to notifications with ISubscriber<T>. Cancellation token ends subscription.
SubscribeAsync<T>(subscriber, enableBuffering, name, cancellationToken) Subscribe to notifications with named cache enumerator.
SubscribeAsync<T>(pipelineBuildAction, enableBuffering, cancellationToken) Subscribe to notifications through a PipelineBuilder<T>-configured middleware pipeline.
SubscribeAsync<T>(pipelineBuildAction, enableBuffering, name, cancellationToken) Subscribe to notifications through a PipelineBuilder<T>-configured middleware pipeline with a named cache enumerator.
SubscribeAsync<TRequest, TResponse>(handler, enableBuffering, cancellationToken) Register request handler with IRequestHandler<TRequest, TResponse>.
SubscribeAsync<TRequest, TResponse>(handler, enableBuffering, name, cancellationToken) Register request handler with named cache enumerator.
SubscribeAsync<TRequest, TResponse>(handler, enableBuffering, cancellationToken) Register async request handler with IAsyncRequestHandler<TRequest, TResponse>.
SubscribeAsync<TRequest, TResponse>(handler, enableBuffering, name, cancellationToken) Register async request handler with named cache enumerator.
SubscribeAsync<TNotification>(func, enableBuffering, cancellationToken) Subscribe to notifications using function handler. Function receives cancellation token.
SubscribeAsync<TNotification>(func, enableBuffering, name, cancellationToken) Subscribe to notifications using function handler with named enumerator.
SubscribeAsync<TRequest, TResponse>(func, enableBuffering, cancellationToken) Register async request handler using function. Function receives cancellation token.
SubscribeAsync<TRequest, TResponse>(func, enableBuffering, name, cancellationToken) Register async request handler using function with named enumerator.
SubscribeAsync<TRequest, TSegment, TResponse>(handler, enableBuffering, cancellationToken) Register stream request handler with IAsyncStreamRequestHandler<TRequest, TSegment, TResponse>.
SubscribeAsync<TRequest, TSegment, TResponse>(handler, enableBuffering, name, cancellationToken) Register stream request handler with named cache enumerator.
SubscribeAsync<TRequest, TSegment, TResponse>(func, enableBuffering, cancellationToken) Register stream request handler using function Func<TRequest, CancellationToken, IAsyncEnumerable<TSegment>>.
SubscribeAsync<TRequest, TSegment, TResponse>(func, enableBuffering, name, cancellationToken) Register stream request handler using function with named enumerator.

Handler Interfaces

  • IRequestHandler<TRequest, TResponse> - Synchronous handler with Handle(TRequest, CancellationToken) method. Receives subscription's cancellation token.
  • IAsyncRequestHandler<TRequest, TResponse> - Asynchronous handler with HandleAsync(TRequest, CancellationToken) method. Receives subscription's cancellation token.
  • IAsyncStreamRequestHandler<TRequest, TSegment, TResponse> - Asynchronous stream handler with HandleAsync(TRequest, CancellationToken) returning IAsyncEnumerable<TSegment>.
  • ISubscriber<T> - Notification subscriber with OnNext(T, CancellationToken) method. Receives subscription's cancellation token.

Stream Request Types

  • IStreamRequest<TSegment, TResponse> - Marker interface for stream requests that produce sequences of TSegment values.
  • ISegment<TResponse> - Marker interface for a single segment in a streamed response sequence.

Pipeline API

IPipeline<T> and PipelineBuilder<T> enable composable middleware processing for notifications.

Type Description
IPipeline<T> Contract for a middleware pipeline that processes items of type T. Exposes RunAsync(T, CancellationToken).
IPipeline<T>.Segment Delegate for a single middleware unit. Receives the item, a Next continuation for the remainder of the chain, and a cancellation token.
PipelineBuilder<T> Fluent builder for composing Segment delegates into an IPipeline<T>.
PipelineBuilder<T>.Use(segment) Registers a segment. Duplicate delegate references are ignored.
PipelineBuilderExtensions.Use(segment) Extension for result-wrapped builders — chains Use in a fluent, railway-oriented style.
PipelineBuilderExtensions.WithBuildAction(action) Applies an imperative configuration action to the wrapped builder.
PipelineBuilderExtensions.Build() Finalises the builder and returns the constructed IPipeline<T>.

Usage Examples

Notification Aggregation with Caching (Buffering)

When enableBuffering is true (default), notifications are persisted to the cache before delivery. This enables message replay, durability, and distributed pub/sub capabilities backed by Baubit.Caching.

// Define notification type
public class OrderCreated
{
    public int OrderId { get; set; }
    public decimal Amount { get; set; }
}

// Define subscriber
public class OrderNotificationSubscriber : ISubscriber<OrderCreated>
{
    public bool OnNext(OrderCreated notification, CancellationToken cancellationToken = default)
    {
        Console.WriteLine($"Order {notification.OrderId} created: ${notification.Amount}");
        return true;
    }

    public bool OnError(Exception error)
    {
        Console.WriteLine($"Error: {error.Message}");
        return true;
    }

    public bool OnCompleted() => true;
    public void Dispose() { }
}

// Setup mediator
var loggerFactory = LoggerFactory.Create(b => b.AddConsole());
var configuration = new Baubit.Caching.Configuration();
Func<long?, long?> nextIdFactory = (lastId) => Interlocked.Increment(ref lastId ?? 0);
var store = new Baubit.Caching.InMemory.Store<long, object>(null, null, nextIdFactory, loggerFactory);
var metadata = new Baubit.Caching.InMemory.Metadata<long>(configuration, loggerFactory);
var cache = new Baubit.Caching.OrderedCache<long, object>(configuration, null, store, metadata, loggerFactory);
var mediator = new Mediator(cache, loggerFactory);
using var cts = new CancellationTokenSource();

// Subscribe with buffering enabled (default)
var subscriber = new OrderNotificationSubscriber();
var subscribeTask = mediator.SubscribeAsync(subscriber, enableBuffering: true, cts.Token);

// Publish notification - stored in cache then delivered
mediator.Publish(new OrderCreated { OrderId = 1, Amount = 99.99m });

// Notifications persist in cache for replay or distributed scenarios
Console.WriteLine($"Cached notifications: {cache.Count}");

Notification Aggregation without Caching (Direct Delivery)

When enableBuffering is false, notifications bypass the cache and are delivered directly to subscribers. This provides minimal latency for scenarios where persistence is not required.

// Same notification and subscriber types as above

// Subscribe with buffering disabled
var subscriber = new OrderNotificationSubscriber();
var subscribeTask = mediator.SubscribeAsync(subscriber, enableBuffering: false, cts.Token);

// Publish notification - delivered directly without caching
mediator.Publish(new OrderCreated { OrderId = 2, Amount = 149.99m });

// No caching overhead - immediate delivery
Console.WriteLine($"Cached notifications: {cache.Count}"); // 0

Mixed Buffering Scenarios

Different subscribers can use different buffering strategies for the same notification type:

var bufferedSubscriber = new OrderNotificationSubscriber();
var directSubscriber = new OrderNotificationSubscriber();

// One subscriber with caching, one without
var bufferedTask = mediator.SubscribeAsync(bufferedSubscriber, enableBuffering: true, cts.Token);
var directTask = mediator.SubscribeAsync(directSubscriber, enableBuffering: false, cts.Token);

// Publish once - buffered subscriber gets it from cache, direct subscriber gets immediate delivery
mediator.Publish(new OrderCreated { OrderId = 3, Amount = 199.99m });

// Both subscribers receive the notification via their preferred delivery mechanism

Request/Response Mediation

// Publish request asynchronously and await response
var response = await mediator.PublishAsync<GetUserRequest, GetUserResponse>(
    new GetUserRequest { UserId = 1 }
);

// All request handling is asynchronous
// Buffered mode (enableBuffering: true) tracks request/response through cache
// Unbuffered mode (enableBuffering: false) delivers directly to handler

Stream Request/Response Mediation

Stream requests produce a sequence of segments (IAsyncEnumerable<TSegment>) rather than a single response. Use this when a handler needs to push back multiple values over time.

// Types
public class ChatRequest : IStreamRequest<TextChunk, ChatResponse> { public string Prompt { get; set; } }
public class TextChunk   : ISegment<ChatResponse>                  { public string Text   { get; set; } }
public class ChatResponse : IResponse { }

public class SpeechRequest : IStreamRequest<AudioChunk, AudioResponse> { public string Text { get; set; } }
public class AudioChunk   : ISegment<AudioResponse>                   { public byte[] Pcm  { get; set; } }
public class AudioResponse : IResponse { }

// Handlers
public class ChatHandler : IAsyncStreamRequestHandler<ChatRequest, TextChunk, ChatResponse>
{
    public async IAsyncEnumerable<TextChunk> HandleAsync(ChatRequest request,
        [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        await foreach (var update in chatClient.CompleteChatStreamingAsync(request.Prompt, cancellationToken))
            foreach (var part in update.ContentUpdate)
                if (!string.IsNullOrEmpty(part.Text))
                    yield return new TextChunk { Text = part.Text };
    }
}

public class SpeechHandler : IAsyncStreamRequestHandler<SpeechRequest, AudioChunk, AudioResponse>
{
    public async IAsyncEnumerable<AudioChunk> HandleAsync(SpeechRequest request,
        [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        await foreach (var buffer in ttsClient.SynthesisStreamingAsync(request.Text, cancellationToken))
            yield return new AudioChunk { Pcm = buffer };
    }
}

// Usage — each text chunk is forwarded as a new request to the TTS handler
using var cts = new CancellationTokenSource();
_ = mediator.SubscribeAsync<ChatRequest, TextChunk, ChatResponse>(new ChatHandler(), enableBuffering: true, cts.Token);
_ = mediator.SubscribeAsync<SpeechRequest, AudioChunk, AudioResponse>(new SpeechHandler(), enableBuffering: true, cts.Token);

await foreach (var textChunk in mediator.PublishAsync<ChatRequest, TextChunk, ChatResponse>(
    new ChatRequest { Prompt = "..." }, cts.Token))
{
    await foreach (var audioChunk in mediator.PublishAsync<SpeechRequest, AudioChunk, AudioResponse>(
        new SpeechRequest { Text = textChunk.Text }, cts.Token))
    {
        await audioPlayer.PlayAsync(audioChunk.Pcm, cts.Token);
    }
}

cts.Cancel();

Function-Based Subscriptions

For scenarios where creating a full handler class is unnecessary, use function-based subscriptions:

Notification Handler Functions
// Subscribe to notifications using a function handler
using var cts = new CancellationTokenSource();
var subscribeTask = mediator.SubscribeAsync<OrderCreated>(
    async (notification, ct) =>
    {
        Console.WriteLine($"Order {notification.OrderId} received");
        await ProcessOrderAsync(notification, ct);
        return true;
    },
    enableBuffering: true,
    cts.Token
);

// Publish notifications
mediator.Publish(new OrderCreated { OrderId = 1, Amount = 99.99m });
mediator.Publish(new OrderCreated { OrderId = 2, Amount = 149.99m });

// Cancel subscription when done
cts.Cancel();
Async Request Handler Functions
// Subscribe to requests using a function handler
using var cts = new CancellationTokenSource();
var subscribeTask = mediator.SubscribeAsync<GetUserRequest, GetUserResponse>(
    async (request, ct) =>
    {
        var user = await database.GetUserAsync(request.UserId, ct);
        return new GetUserResponse { Name = user.Name };
    },
    enableBuffering: true,
    cts.Token
);

// Publish async request - function handler processes it
var response = await mediator.PublishAsync<GetUserRequest, GetUserResponse>(
    new GetUserRequest { UserId = 1 },
    CancellationToken.None
);

// Cancel subscription when done
cts.Cancel();

Middleware Pipeline for Notifications

PipelineBuilder<T> lets you compose reusable middleware segments that are chained together and run in declaration order for each incoming notification. Use it to add cross-cutting concerns such as logging, validation, or filtering without modifying your core handler logic.

// Subscribe using a middleware pipeline
using var cts = new CancellationTokenSource();

var subscribeTask = mediator.SubscribeAsync<OrderCreated>(
    pb =>
    {
        // Segment 1 — logging
        pb.Use(async (order, next, ct) =>
        {
            Console.WriteLine($"[LOG] Received order {order.OrderId}");
            return await next(order, ct); // call next to continue the chain
        });

        // Segment 2 — validation / filtering
        pb.Use(async (order, next, ct) =>
        {
            if (order.Amount <= 0)
            {
                Console.WriteLine($"[SKIP] Order {order.OrderId} has non-positive amount — dropping");
                return false; // short-circuit: segment 3 will not run
            }
            return await next(order, ct);
        });

        // Segment 3 — actual processing
        pb.Use(async (order, next, ct) =>
        {
            await ProcessOrderAsync(order, ct);
            return await next(order, ct);
        });
    },
    enableBuffering: true,
    cts.Token
);

mediator.Publish(new OrderCreated { OrderId = 1, Amount = 99.99m });
mediator.Publish(new OrderCreated { OrderId = 2, Amount = -5m }); // filtered out by segment 2

cts.Cancel();

Key behaviours:

  • Segments run in the order they are registered via Use.
  • Calling next passes control to the following segment; the implicit terminal segment at the end of every chain returns true.
  • Omitting the next call short-circuits the chain and the return value of the current segment is used as the pipeline result.
  • Registering the same delegate reference more than once is a no-op (the second registration is silently ignored).
  • Use the named overload (name parameter) when multiple independent pipeline subscriptions for the same notification type each need their own cache enumerator position:
// Two independent pipelines with separate named enumerators
var sub1 = mediator.SubscribeAsync<OrderCreated>(
    pb => pb.Use(async (order, next, ct) => { /* audit log */ return await next(order, ct); }),
    enableBuffering: true,
    name: "audit-pipeline",
    cts.Token);

var sub2 = mediator.SubscribeAsync<OrderCreated>(
    pb => pb.Use(async (order, next, ct) => { /* billing */ return await next(order, ct); }),
    enableBuffering: true,
    name: "billing-pipeline",
    cts.Token);

mediator.Publish(new OrderCreated { OrderId = 1, Amount = 99.99m });
// Both pipelines receive the notification independently via their own named cache positions

Architecture Notes

Cache-Backed Async Mediation:

Baubit.Mediation is powered by Baubit.Caching, a high-performance hybrid cache. Baubit.Caching is being extended to support distributed systems - once complete, Baubit.Mediation will natively support distributed mediation scenarios.

License

MIT

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.  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 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 (1)

Showing the top 1 NuGet packages that depend on Baubit.Mediation:

Package Downloads
Baubit.Mediation.DI

DI support for Baubit.Mediation

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2026.22.1 214 5/29/2026
2026.19.1 167 5/7/2026
2026.18.4 157 5/3/2026
2026.18.1 188 4/27/2026
2026.4.1 206 1/25/2026
2026.3.1-prerelease 174 1/13/2026
2026.1.2-prerelease 174 1/4/2026
2026.1.1 232 12/31/2025
2026.1.1-prerelease 131 12/31/2025
2025.51.2 305 12/19/2025
2025.51.1 359 12/19/2025
2025.49.1 780 12/1/2025
2025.48.4 165 11/28/2025
2025.48.1 228 11/27/2025