Peyk.Extensions.Diagnostics 1.0.0

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

Peyk

Peyk (Ottoman Turkish): a palace courier — a runner who carried the Sultan's messages.

A CQRS-native, source-generator based mediator for .NET.

CI License: MIT

Highlights

  • CQRS first-class. ICommand and IQuery are separate interfaces with separate, type-safe pipelines: transactions bind to commands only, caching to queries only — enforced at compile time, not by convention.
  • Zero reflection. A Roslyn incremental generator discovers handlers at compile time — including handlers in referenced assemblies (contracts and handlers in your Application layer, AddMediator() in your composition root) — and emits concrete DI registrations. No assembly scanning, no runtime type discovery.
  • NativeAOT-ready, really. [assembly: PeykBehavior(...)] closes open generic behaviors over every message at compile time, so even value-type responses (Guid, int) flow through pipelines under NativeAOT. CI publishes and runs a native binary on every commit — zero trim/AOT warnings.
  • Compile-time safety. Forgot a handler? warning PEYK001 at build time, not an exception in production. Two handlers for one command? PEYK002.
  • Fast. Immutable frozen route table, a zero-overhead dispatch path when a message has no pipeline services, and no reflection anywhere. Sub-60 ns dispatch, ~6 µs from empty container to first response (see Benchmarks).
  • Familiar API. IRequest, IRequestHandler, INotification, IPipelineBehavior follow the classic .NET mediator shape — most existing mediator-based codebases migrate mechanically (see Migrating from another mediator).
  • MIT, forever. This project will never move to a dual-license or source-available model. If sustainability ever becomes a concern, the answer will be GitHub Sponsors — not relicensing.

Packages

Package Description
Peyk Core runtime + DI extensions
Peyk.Abstractions Message/handler/pipeline interfaces (netstandard2.0) — reference this from your Application layer
Peyk.SourceGenerator Compile-time handler discovery and registration
Peyk.Analyzers Extra analyzers
Peyk.Extensions.FluentValidation Validation pipeline behavior
Peyk.Extensions.Transactions IUnitOfWork + transactional command behavior
Peyk.Extensions.Diagnostics OpenTelemetry (ActivitySource + Metrics)

All packages depend only on Peyk.* and Microsoft.* (plus FluentValidation in its optional extension). No third-party framework lock-in.

Quick start

dotnet add package Peyk
dotnet add package Peyk.SourceGenerator
using Peyk;

// Commands mutate state...
public sealed record CreateOrder(string Product) : ICommand<Guid>;

public sealed class CreateOrderHandler : ICommandHandler<CreateOrder, Guid>
{
    public Task<Guid> Handle(CreateOrder command, CancellationToken ct) =>
        Task.FromResult(Guid.NewGuid());
}

// ...queries read it.
public sealed record GetOrder(Guid Id) : IQuery<OrderDto?>;

public sealed class GetOrderHandler : IQueryHandler<GetOrder, OrderDto?>
{
    public Task<OrderDto?> Handle(GetOrder query, CancellationToken ct) => ...;
}
// Program.cs — AddMediator is GENERATED at compile time for this assembly.
builder.Services.AddMediator();

app.MapPost("/orders", (CreateOrder cmd, ISender sender, CancellationToken ct) => sender.Send(cmd, ct));

Message kinds

Interface Handler Semantics
ICommand<T> / ICommand ICommandHandler<,> / ICommandHandler<> Write; command pipeline (transactions…)
IQuery<T> IQueryHandler<,> Read; query pipeline (caching…)
IRequest<T> / IRequest IRequestHandler<,> / IRequestHandler<> General purpose
INotification INotificationHandler<> 0..n handlers; sequential or Task.WhenAll publisher
IStreamQuery<T> IStreamQueryHandler<,> IAsyncEnumerable<T> streaming

Pipeline

Fixed, documented layout — no ordering surprises:

exception handlers
└─ IPipelineBehavior (all requests, registration order)
   └─ ICommandPipelineBehavior / IQueryPipelineBehavior (typed)
      └─ pre-processors → handler → post-processors
builder.Services.AddMediator(options => options
    .AddDiagnosticsBehavior()      // OpenTelemetry spans + metrics
    .AddLoggingBehavior()
    .AddValidationBehavior()       // FluentValidation
    .AddTransactionalCommands());  // commands only — queries never open a transaction

Notification (event) pipeline

Events get pipelines too — a gap in most mediator libraries. INotificationPipelineBehavior<T> wraps the whole publish fan-out:

public sealed class OutboxBehavior<TNotification> : INotificationPipelineBehavior<TNotification>
    where TNotification : INotification
{
    public async Task Handle(TNotification notification, NotificationHandlerDelegate next, CancellationToken ct)
    {
        await _outbox.CaptureAsync(notification, ct); // runs even when the event has zero handlers
        await next();                                 // skip this call to suppress the event
    }
}
INotificationPipelineBehavior (registration order)
└─ INotificationPublisher (sequential / Task.WhenAll strategy)
   └─ handlers

Publish-level cross-cutting (outbox, auditing, tracing, filtering) goes in a behavior; per-handler concerns (retry, error isolation) go in a custom INotificationPublisher. Behaviors run even for notifications with zero handlers, so outbox capture never misses an event.

NativeAOT

Runtime open-generic DI registration breaks under NativeAOT when a response is a value type. Declare behaviors at compile time instead — the generator closes them over every discovered message:

[assembly: PeykBehavior(typeof(TransactionalCommandBehavior<,>), Order = 1,
                        Lifetime = PeykBehaviorLifetime.Scoped)]

dotnet publish -p:PublishAot=true → zero warnings. See tests/Peyk.AotTest: a 2.3 MB native binary exercising every message kind.

Benchmarks

.NET 10, x64, BenchmarkDotNet. Numbers below are indicative (ShortRun); the suite in benchmarks/ also measures other popular mediator implementations side by side — run it yourself for a full comparison on your hardware:

dotnet run -c Release --project benchmarks/Peyk.Benchmarks -- --filter "*"
Scenario Mean Allocated
Send (steady state, singleton) 54.96 ns 112 B
Publish (2 handlers) 86.24 ns 101 B
Container build + first Send (cold start) 5.78 µs 11.5 KB

Dispatch overhead is nanoseconds; if a handler touches I/O, the mediator is noise.

Samples

  • samples/CleanArchitecture — Domain / Application / Infrastructure / Api layering: contracts + handlers live in Application (references only Peyk.Abstractions), the generator runs in Api and finds them across the project boundary.

Migrating from another mediator

Mechanical for most codebases:

  1. Swap the package and reference Peyk.SourceGenerator.
  2. Change the namespace to Peyk. IRequest<T>, IRequest, IRequestHandler<,>, IRequestHandler<>, INotification, INotificationHandler<>, IPipelineBehavior<,>, IRequestPreProcessor<>, IRequestPostProcessor<,>, Unit, ISender, IPublisher, IMediator keep their names and shapes.
  3. Replace registration with services.AddMediator() — no assembly scanning; handlers in referenced assemblies are discovered at compile time.

Differences worth knowing:

  • Lifetimes default to Singleton (mediator and handlers). Handlers depending on scoped services: AddMediator(o => o.Lifetime = ServiceLifetime.Scoped).
  • Fixed pipeline order: exception handlers → IPipelineBehavior → typed command/query behaviors → pre → handler → post (registration order within each layer).
  • Exception handlers are IRequestExceptionHandler<TRequest, TResponse> (no TException generic — pattern-match the exception inside; reflection-on-exception-hierarchy is not AOT-safe).
  • Streams: IStreamRequest<T>IStreamQuery<T>, IStreamRequestHandler<,>IStreamQueryHandler<,>.
  • Not in v1: open generic handlers, polymorphic notification dispatch (handle the concrete type), and object-typed Send/Publish overloads.
  • New for free: turn IRequest<T> into ICommand<T>/IQuery<T> one message at a time to unlock the typed pipelines, and wrap Publish with INotificationPipelineBehavior<T> (no equivalent in classic mediators).

Contributing

PRs welcome — see CONTRIBUTING.md.

License

MIT. No strings, no tiers, no "contact us for pricing".

Product Compatible and additional computed target framework versions.
.NET 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 is compatible.  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. 
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
1.0.0 115 7/8/2026