EricksonLopez.Outbox.Storage.Oracle 2.0.0

Requires NuGet 6.0.0 or higher.

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

EricksonLopez.Outbox

High-performance, zero-allocation, NativeAOT-ready Transactional Outbox and Idempotent Inbox ecosystem for modern .NET.

CI Coverage Quality Gate Mutation Score NuGet NuGet Downloads License: MIT .NET NativeAOT

EricksonLopez.Outbox is an enterprise-grade, high-throughput, cloud-native implementation of the Transactional Outbox and Idempotent Inbox patterns targeting .NET 8, .NET 9, and .NET 10. Engineered for zero heap allocations on the hot path, NativeAOT compilation, and strict compile-time safety via Roslyn Analyzers, it completely eliminates the Dual-Write Problem and guarantees resilient At-Least-Once Delivery across heterogeneous databases and message brokers without distributed transactions.


Table of Contents


๐ŸŽฏ What Problem It Solves

The Perils of Traditional Messaging

In distributed architectures, persisting domain state changes while publishing messages to an external broker (e.g., RabbitMQ, Apache Kafka, Azure Service Bus) introduces severe failure modes:

  1. The Dual-Write Problem: Updating a relational database and publishing an event to a broker over two distinct network calls cannot be atomically coordinated without heavy, unscalable Two-Phase Commit (2PC) distributed transactions. If the database transaction commits but the network drop causes the broker publish to fail, downstream microservices become permanently desynchronized. Conversely, publishing first risks broadcasting phantom events if the subsequent database commit aborts.
  2. At-Least-Once Delivery Duplicates: Network partitions, transient client disconnects, and retry policies inevitably produce duplicate message deliveries at the consumer. Without a deterministic, thread-safe deduplication mechanism, consumers re-execute non-idempotent operations (e.g., duplicate payments, redundant inventory deductions).
  3. Allocation & Reflection Overhead in Traditional Outbox Libraries: Legacy outbox implementations rely on runtime Reflection (MakeGenericType, Activator.CreateInstance), heavyweight ORM Change Trackers (DbContext.SaveChanges), and synchronous thread-pool blocking. This creates substantial Gen 0/1/2 GC pressure, thread starvation, and prevents compilation with NativeAOT.
  4. Head-of-Line Blocking & Poison Messages: Unhandled poison messages often block dispatch workers indefinitely, starving healthy messages and causing exponential message queue backlogs.

How EricksonLopez.Outbox Solves This

  • Atomic Database Transactions: Outbox messages are serialized and inserted into the database within the exact same ACID transaction as your business entities (DbTransactionContext, EF Core IDbContextTransaction, or native MongoDB sessions). If the transaction rolls back, the message never exists.
  • Optimistic Consumer Deduplication (Inbox): The standalone EricksonLopez.Inbox engine intercepts incoming messages and atomically registers unique message fingerprints (INSERT ... ON CONFLICT DO NOTHING), guaranteeing idempotent execution without locking.
  • Zero-Allocation Hot Path: Utilizes ref struct OutboxMessageBuilder, readonly record struct OutboxMessage, [ThreadStatic] ArrayPoolBufferWriter<byte>, and ValueTask across the entire pipeline to achieve 0 bytes allocated during steady-state processing.
  • NativeAOT-First & Compile-Time Safety: Zero runtime reflection. Type aliases are resolved in ~1.4 ns via FrozenDictionary, and payload serialization is strictly handled by System.Text.Json Source Generators.
  • Adaptive Poller & Non-Blocking Bounded Channels: Employs System.Threading.Channels with backpressure and adaptive polling (snaps to 0ms interval under load, backs off exponentially when idle) alongside database-native lock skipping (SKIP LOCKED, READPAST) for seamless multi-pod Kubernetes horizontal scaling.
  • Dead-Letter Queue (DLQ) & Automated Stale Lease Recovery: Poison messages are safely quarantined to IDeadLetterRepository without blocking healthy queues, while crashed instances are automatically recovered via ReclaimStaleMessagesAsync.

โšก Key Features

  • ๐Ÿ”’ Guaranteed ACID Atomicity: Integrates natively with ADO.NET (DbTransaction), Entity Framework Core, Dapper-free raw SQL pipelines, and MongoDB transactional sessions.
  • โšก Extreme Zero-Allocation Throughput: Optimized with ReadOnlyMemory<T>, ValueTask, array pooling, and ref struct builders, running 3.3ร— faster with 73% less memory than CAP and 99ร— faster than NServiceBus.
  • ๐ŸŒ NativeAOT Ready & Zero Reflection: Full compatibility with Ahead-Of-Time (PublishAot=true) compilation and trim analyzers via Roslyn incremental source generators.
  • ๐Ÿ”„ At-Least-Once Delivery Guarantee: End-to-end delivery resilience with exponential backoff retries, dead-letter queue isolation, and automatic stale lease recovery.
  • ๐ŸŽ๏ธ Adaptive Dispatcher & Parallel Channel Draining: Backpressure-aware bounded channels (System.Threading.Channels) with multi-worker concurrent dispatching (MaxDegreeOfParallelism) and database lock-free polling (SKIP LOCKED).
  • ๐Ÿ“ฅ Standalone Idempotent Inbox Engine: Independent consumer deduplication library (EricksonLopez.Inbox) and HTTP Idempotency-Key endpoint filters for ASP.NET Core.
  • ๐Ÿ“Š Native OpenTelemetry Observability: Zero-allocation structured logging via [LoggerMessage], standard W3C TraceContext propagation, and BCL System.Diagnostics.Metrics.
  • ๐Ÿ›ก๏ธ Compile-Time Roslyn Analyzers: Ships with 13 custom analyzers (OUTBOX001โ€“OUTBOX013) and automated CodeFix providers to prevent architectural anti-patterns in the IDE.
  • ๐Ÿ”Œ Modular Ecosystem with 36 Packages: Pluggable storage providers (7 SQL/NoSQL engines), broker publishers (8 transports), binary serializers (Protobuf, MessagePack), and enterprise framework adapters (MassTransit, Mediator, NServiceBus, Rebus, Brighter, Dapr, Aspire).

๐Ÿ“ฆ Ecosystem

The EricksonLopez.Outbox ecosystem is partitioned into 36 modular, fine-grained NuGet packages:

Package Version Description
EricksonLopez.Outbox NuGet Core outbox engine, background dispatcher daemon, retry pipeline, and testing harness
EricksonLopez.Outbox.Abstractions NuGet Foundational client contracts (IOutbox, IOutboxTransactionContext, OutboxMessageMetadata)
EricksonLopez.Inbox NuGet Standalone consumer idempotency and message deduplication engine (IInboxStore)
EricksonLopez.Inbox.Abstractions NuGet Foundational contracts for consumer idempotency and deduplication (IdempotencyKey)
EricksonLopez.Outbox.Events NuGet Domain & integration events integration with EricksonLopez.Events (OutboxEventPublisher)
EricksonLopez.Outbox.Inbox.Events NuGet Idempotent event handler pipeline integration (IdempotentEventHandler<TEvent>)
EricksonLopez.Outbox.Inbox NuGet Outbox-to-Inbox bridge deduplication filter
EricksonLopez.Outbox.Inbox.AspNetCore NuGet ASP.NET Core HTTP Idempotency-Key endpoint filter
EricksonLopez.Outbox.EntityFrameworkCore NuGet Entity Framework Core DbContext integration and model builder extensions
EricksonLopez.Outbox.Storage.PostgreSql NuGet PostgreSQL native storage provider (Npgsql with FOR UPDATE SKIP LOCKED & UNNEST)
EricksonLopez.Outbox.Storage.SqlServer NuGet SQL Server native storage provider (Microsoft.Data.SqlClient with READPAST)
EricksonLopez.Outbox.Storage.MySql NuGet MySQL native storage provider (MySqlConnector with SKIP LOCKED)
EricksonLopez.Outbox.Storage.MariaDb NuGet MariaDB native storage provider (MySqlConnector)
EricksonLopez.Outbox.Storage.Oracle NuGet Oracle Database native storage provider (Oracle.ManagedDataAccess.Core)
EricksonLopez.Outbox.Storage.Sqlite NuGet SQLite embedded storage provider (Microsoft.Data.Sqlite)
EricksonLopez.Outbox.Storage.MongoDb NuGet MongoDB transactional document storage (MongoDB.Driver with IClientSessionHandle)
EricksonLopez.Outbox.Brokers.RabbitMQ NuGet RabbitMQ physical broker publisher (RabbitMQ.Client 7.x)
EricksonLopez.Outbox.Brokers.Kafka NuGet Apache Kafka physical broker publisher (Confluent.Kafka 2.x)
EricksonLopez.Outbox.Brokers.AzureServiceBus NuGet Azure Service Bus broker publisher (Azure.Messaging.ServiceBus 7.x)
EricksonLopez.Outbox.Brokers.AzureEventHubs NuGet Azure Event Hubs streaming broker publisher (Azure.Messaging.EventHubs 5.x)
EricksonLopez.Outbox.Brokers.AwsSqs NuGet AWS SQS physical broker publisher (AWSSDK.SQS 3.x)
EricksonLopez.Outbox.Brokers.GooglePubSub NuGet Google Cloud Pub/Sub broker publisher (Google.Cloud.PubSub.V1 3.x)
EricksonLopez.Outbox.Brokers.Nats NuGet NATS physical broker publisher (NATS.Client.Core 2.x)
EricksonLopez.Outbox.Brokers.RedisStreams NuGet Redis Streams broker publisher (StackExchange.Redis 2.x)
EricksonLopez.Outbox.MassTransit NuGet MassTransit IBrokerPublisher adapter and InboxIdempotencyFilter
EricksonLopez.Outbox.Mediator NuGet High-performance NativeAOT source-generated mediator adapter
EricksonLopez.Outbox.MediatR NuGet Legacy MediatR adapter (deprecated in favor of Mediator, see ADR-036)
EricksonLopez.Outbox.NServiceBus NuGet NServiceBus outgoing pipeline behavior and feature integration
EricksonLopez.Outbox.Rebus NuGet Rebus outgoing pipeline step and decorator integration
EricksonLopez.Outbox.Brighter NuGet Paramore.Brighter command processor producer adapter
EricksonLopez.Outbox.Dapr NuGet Dapr Pub/Sub cloud-native broker adapter
EricksonLopez.Outbox.Aspire NuGet .NET Aspire cloud-native component for metrics, tracing, and health checks
EricksonLopez.Outbox.Serialization.Protobuf NuGet Binary serializer using Protocol Buffers (protobuf-net)
EricksonLopez.Outbox.Serialization.MessagePack NuGet Binary serializer using MessagePack (MessagePack-CSharp)
EricksonLopez.Outbox.SourceGenerators NuGet Incremental Roslyn source generator for compile-time type mapping
EricksonLopez.Outbox.Analyzers NuGet Roslyn analyzers and automated CodeFix providers (OUTBOX001โ€“OUTBOX013)

๐Ÿ“š Documentation

๐ŸŒ Official Documentation Hub: https://github.com/ericksonlopezf/dotnet-outbox/tree/main/docs

๐ŸŽ“ Step-by-Step Interactive Showcase (Levels 00 to 13)

Level Topic Description
Level 00 Architecture & Philosophy Core architectural foundations, Dual-Write problem, and zero-allocation guarantees
Level 01 Getting Started & Primitives Fundamental usage, message decoration, and primary IOutbox store APIs
Level 02 Configuration & Registration Complete DI setup, storage providers, serializers, and dispatcher tuning
Level 03 Real-World Use Cases Clean Architecture handlers, multi-step workflows, and idempotent consumers
Level 04 Domain Events Integration EF Core entity change tracking interceptor and EricksonLopez.Events bridge
Level 05 Processing & Dispatcher Engine Adaptive Poller mechanics, bounded channels, and worker thread lifecycle
Level 06 Error Handling & Dead Letters Retry policies, exponential backoff, circuit breaking, and DLQ management
Level 07 Scalability & Partitioning Multi-instance Kubernetes concurrency, row-level locks, and multi-tenancy
Level 08 Customization & Middlewares Building custom IOutboxMiddleware pipelines, header enrichment, and filters
Level 09 Framework Extensions MassTransit, Mediator, NServiceBus, Rebus, Brighter, and Dapr integrations
Level 10 Enterprise Architecture & Aspire Cloud-native .NET Aspire deployment, service defaults, and distributed tracing
Level 11 Administration & Maintenance Table cleanup background services, index tuning, and database retention
Level 12 Testing & Verification Unit testing with InMemoryOutboxStore, fake brokers, and Testcontainers
Level 13 Diagnostics & Observability OpenTelemetry meters, counters, histograms, activity sources, and Grafana

๐Ÿ“– Technical Reference & Architecture Guides


๐Ÿ“ฅ Installation

Install the required core package and your chosen storage provider and broker publisher via the .NET CLI:

1. Core Package (Required)

dotnet add package EricksonLopez.Outbox
dotnet add package EricksonLopez.Outbox.Abstractions

2. Choose Storage Provider (Select 1)

# PostgreSQL (Raw ADO.NET with SKIP LOCKED & UNNEST)
dotnet add package EricksonLopez.Outbox.Storage.PostgreSql

# SQL Server (Raw ADO.NET with UPDLOCK & READPAST)
dotnet add package EricksonLopez.Outbox.Storage.SqlServer

# Entity Framework Core Integration
dotnet add package EricksonLopez.Outbox.EntityFrameworkCore

# MySQL / MariaDB / Oracle / SQLite / MongoDB
dotnet add package EricksonLopez.Outbox.Storage.MySql
dotnet add package EricksonLopez.Outbox.Storage.MariaDb
dotnet add package EricksonLopez.Outbox.Storage.Oracle
dotnet add package EricksonLopez.Outbox.Storage.Sqlite
dotnet add package EricksonLopez.Outbox.Storage.MongoDb

3. Choose Message Broker Publisher (Select 1 or more)

dotnet add package EricksonLopez.Outbox.Brokers.RabbitMQ
dotnet add package EricksonLopez.Outbox.Brokers.Kafka
dotnet add package EricksonLopez.Outbox.Brokers.AzureServiceBus
dotnet add package EricksonLopez.Outbox.Brokers.AzureEventHubs
dotnet add package EricksonLopez.Outbox.Brokers.AwsSqs
dotnet add package EricksonLopez.Outbox.Brokers.GooglePubSub
dotnet add package EricksonLopez.Outbox.Brokers.Nats
dotnet add package EricksonLopez.Outbox.Brokers.RedisStreams

4. Optional Idempotent Inbox & Framework Integrations

# Standalone Consumer Idempotency & Inbox Deduplication
dotnet add package EricksonLopez.Inbox
dotnet add package EricksonLopez.Outbox.Inbox.AspNetCore

# Enterprise Framework Adapters
dotnet add package EricksonLopez.Outbox.Mediator
dotnet add package EricksonLopez.Outbox.MassTransit
dotnet add package EricksonLopez.Outbox.Aspire

# High-Performance Binary Serialization
dotnet add package EricksonLopez.Outbox.Serialization.Protobuf
dotnet add package EricksonLopez.Outbox.Serialization.MessagePack

๐Ÿš€ Quick Start

1. Define Message Contracts with Source Generation

Decorate message records with [OutboxMessage] to assign stable, versioned type aliases:

using System;
using System.Text.Json.Serialization;
using EricksonLopez.Outbox;

namespace MyApp.Contracts;

[OutboxMessage("order.created.v1")]
public sealed record OrderCreatedEvent(
    Guid OrderId,
    string CustomerId,
    decimal TotalAmount,
    DateTimeOffset CreatedAt);

// NativeAOT JSON context for zero-reflection serialization
[JsonSerializable(typeof(OrderCreatedEvent))]
public partial class AppJsonSerializerContext : JsonSerializerContext;

2. Configure Services in Program.cs

using EricksonLopez.Outbox;
using EricksonLopez.Outbox.Serialization;
using EricksonLopez.Outbox.Storage.PostgreSql;
using EricksonLopez.Outbox.Brokers.RabbitMQ;
using MyApp.Contracts;

var builder = WebApplication.CreateBuilder(args);

// 1. Register Core Outbox & NativeAOT Serializer
builder.Services.AddOutbox(options =>
{
    options.UseSerializer(new NativeAotJsonSerializer(AppJsonSerializerContext.Default));
    options.ThrowOnUnregisteredType = true;
});

// 2. Register Storage Provider (PostgreSQL Raw ADO.NET)
builder.Services.AddScoped<IOutboxRepository, PostgreSqlOutboxRepository>();

// 3. Register Broker Publisher (RabbitMQ)
builder.Services.AddSingleton<IBrokerPublisher, RabbitMQBrokerPublisher>();

// 4. Register Background Dispatcher Daemon with Adaptive Polling
builder.Services.AddOutboxDispatcher(options =>
{
    options.BatchSize = 100;
    options.UseAdaptivePolling = true;
    options.MaxDegreeOfParallelism = Environment.ProcessorCount;
    options.DeleteOnDispatch = true; // Prevents table bloat
});

var app = builder.Build();

3. Store Messages Atomically in Database Transactions

using System;
using System.Threading;
using System.Threading.Tasks;
using EricksonLopez.Outbox;
using EricksonLopez.Outbox.Persistence;
using MyApp.Contracts;
using Npgsql;

public sealed class OrderService
{
    private readonly NpgsqlDataSource _dataSource;
    private readonly IOutbox _outbox;

    public OrderService(NpgsqlDataSource dataSource, IOutbox outbox)
    {
        _dataSource = dataSource;
        _outbox = outbox;
    }

    public async Task PlaceOrderAsync(Guid orderId, string customerId, decimal total, CancellationToken ct)
    {
        await using var conn = await _dataSource.OpenConnectionAsync(ct);
        await using var tx = await conn.BeginTransactionAsync(ct);

        // 1. Mutate domain state in PostgreSQL
        await using var cmd = new NpgsqlCommand(
            "INSERT INTO orders (id, customer_id, total, status) VALUES (@id, @cid, @tot, 'Created')",
            conn, tx);
        cmd.Parameters.AddWithValue("id", orderId);
        cmd.Parameters.AddWithValue("cid", customerId);
        cmd.Parameters.AddWithValue("tot", total);
        await cmd.ExecuteNonQueryAsync(ct);

        // 2. Store outbox message in the exact same transaction context
        var @event = new OrderCreatedEvent(orderId, customerId, total, DateTimeOffset.UtcNow);
        await _outbox.StoreAsync(@event, tx.ToOutboxContext(), ct);

        // 3. Commit atomically โ€” both writes succeed or both rollback
        await tx.CommitAsync(ct);
    }
}

4. Zero-Allocation Fluent Message Construction

// Fluent zero-allocation publishing with Correlation ID and scheduled delivery
await _outbox.Publish(@event)
    .WithCorrelationId(Guid.NewGuid().ToString("N"))
    .WithCausationId("cmd-checkout-9812")
    .WithHeader("tenant-id", "tenant-eu-01")
    .WithDelay(TimeSpan.FromMinutes(5)) // Delayed dispatch
    .StoreAsync(tx.ToOutboxContext(), ct);

5. Deduplicate Consumer Execution with the Idempotent Inbox

using System.Threading;
using System.Threading.Tasks;
using EricksonLopez.Inbox;
using MyApp.Contracts;

public sealed class OrderCreatedConsumer
{
    private readonly IInboxIdempotencyChecker _inboxChecker;

    public OrderCreatedConsumer(IInboxIdempotencyChecker inboxChecker)
    {
        _inboxChecker = inboxChecker;
    }

    public async Task HandleAsync(OrderCreatedEvent message, string messageId, CancellationToken ct)
    {
        // Atomically checks unique key; returns false if already processed
        if (!await _inboxChecker.ShouldProcessAsync(messageId, consumerId: "order-billing-service", ct))
        {
            return; // Duplicate delivery safely skipped
        }

        // Execute critical idempotent business logic
        await ProcessBillingAsync(message, ct);
    }

    private static Task ProcessBillingAsync(OrderCreatedEvent message, CancellationToken ct) => Task.CompletedTask;
}

๐Ÿ’ก Core Use Cases

Use Case 1: Clean Architecture CQRS Command Handler with ADO.NET

using System;
using System.Threading;
using System.Threading.Tasks;
using EricksonLopez.Outbox;
using EricksonLopez.Outbox.Persistence;
using Npgsql;

public sealed record CreateProductCommand(Guid ProductId, string Sku, decimal Price);
public sealed record ProductCreatedEvent(Guid ProductId, string Sku, decimal Price);

public sealed class CreateProductCommandHandler
{
    private readonly NpgsqlDataSource _dataSource;
    private readonly IOutbox _outbox;

    public CreateProductCommandHandler(NpgsqlDataSource dataSource, IOutbox outbox)
    {
        _dataSource = dataSource;
        _outbox = outbox;
    }

    public async Task HandleAsync(CreateProductCommand command, CancellationToken ct)
    {
        await using var conn = await _dataSource.OpenConnectionAsync(ct);
        await using var tx = await conn.BeginTransactionAsync(ct);

        await using var cmd = new NpgsqlCommand(
            "INSERT INTO products (id, sku, price) VALUES (@id, @sku, @price)", conn, tx);
        cmd.Parameters.AddWithValue("id", command.ProductId);
        cmd.Parameters.AddWithValue("sku", command.Sku);
        cmd.Parameters.AddWithValue("price", command.Price);
        await cmd.ExecuteNonQueryAsync(ct);

        await _outbox.StoreAsync(
            new ProductCreatedEvent(command.ProductId, command.Sku, command.Price),
            tx.ToOutboxContext(),
            ct);

        await tx.CommitAsync(ct);
    }
}

Use Case 2: Entity Framework Core Aggregate Persistence

using System.Threading;
using System.Threading.Tasks;
using EricksonLopez.Outbox;
using EricksonLopez.Outbox.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

public sealed class OrderAppService
{
    private readonly AppDbContext _dbContext;
    private readonly IOutbox _outbox;

    public OrderAppService(AppDbContext dbContext, IOutbox outbox)
    {
        _dbContext = dbContext;
        _outbox = outbox;
    }

    public async Task ConfirmOrderAsync(Order order, CancellationToken ct)
    {
        await using var tx = await _dbContext.Database.BeginTransactionAsync(ct);

        order.Status = OrderStatus.Confirmed;
        await _dbContext.SaveChangesAsync(ct);

        var @event = new OrderConfirmedEvent(order.Id, order.Total);
        await _outbox.StoreAsync(@event, tx.ToOutboxContext(), ct);

        await tx.CommitAsync(ct);
    }
}

Use Case 3: High-Throughput Zero-Allocation Batch Publishing

using System;
using System.Buffers;
using System.Threading;
using System.Threading.Tasks;
using EricksonLopez.Outbox;
using EricksonLopez.Outbox.Persistence;

public sealed class TelemetryBatchService
{
    private readonly IOutbox _outbox;

    public TelemetryBatchService(IOutbox outbox) => _outbox = outbox;

    public async ValueTask PublishBatchAsync(
        ReadOnlyMemory<DeviceTelemetryEvent> telemetrySlice,
        IOutboxTransactionContext txContext,
        CancellationToken ct)
    {
        // Inserts contiguous memory slice via single SQL UNNEST batch (0 bytes heap allocated)
        await _outbox.StoreAsync(telemetrySlice, txContext, ct);
    }
}

Use Case 4: Scheduled & Delayed Delivery

// Schedule an outbox message to be dispatched strictly after 24 hours
var reminderEvent = new PaymentReminderEvent(invoice.Id, invoice.DueDate);

await _outbox.Publish(reminderEvent)
    .WithCorrelationId(invoice.Id.ToString())
    .WithDelay(TimeSpan.FromHours(24))
    .StoreAsync(tx.ToOutboxContext(), ct);

Use Case 5: ASP.NET Core HTTP Idempotency-Key Endpoint Filter

using EricksonLopez.Outbox.Inbox.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddInboxHttpIdempotency(options =>
{
    options.HeaderName = "Idempotency-Key";
    options.ExpiryWindow = TimeSpan.FromHours(1);
});

var app = builder.Build();

app.MapPost("/api/checkout", async (CheckoutRequest request) =>
{
    return Results.Ok(new { status = "Payment Processed" });
})
.RequireIdempotency(); // Automatically rejects duplicate HTTP requests

Use Case 6: MassTransit / Mediator Pipeline Consumer Deduplication

using System.Threading.Tasks;
using EricksonLopez.Outbox.Inbox;
using MassTransit;

public sealed class ProcessPaymentConsumer : IConsumer<ProcessPaymentCommand>
{
    private readonly IInboxConsumerFilter _inboxFilter;

    public ProcessPaymentConsumer(IInboxConsumerFilter inboxFilter) => _inboxFilter = inboxFilter;

    public async Task Consume(ConsumeContext<ProcessPaymentCommand> context)
    {
        var shouldExecute = await _inboxFilter.EvaluateAsync(
            messageId: context.MessageId.ToString()!,
            consumerName: nameof(ProcessPaymentConsumer),
            cancellationToken: context.CancellationToken);

        if (!shouldExecute)
        {
            return; // Duplicate message delivery discarded safely
        }

        // Execute payment processing
    }
}

๐Ÿ”Œ Configuration & Integrations

ASP.NET Core & Minimal APIs

builder.Services.AddOutbox(options =>
{
    options.MaxPayloadSizeInBytes = 2 * 1024 * 1024; // 2 MB guard
    options.MaxHeaderSizeInBytes = 64 * 1024;        // 64 KB guard
    options.ThrowOnUnregisteredType = true;          // Fail-fast type safety
});

builder.Services.AddOutboxDispatcher(options =>
{
    options.BatchSize = 250;
    options.PollingInterval = TimeSpan.FromMilliseconds(500);
    options.UseAdaptivePolling = true;
    options.MaxDegreeOfParallelism = Environment.ProcessorCount;
    options.ChannelCapacity = 2000;
    options.DeleteOnDispatch = true;
    options.MaxRetryCount = 5;
});

builder.Services.AddHealthChecks()
    .AddCheck<OutboxHealthCheck>("outbox_storage");

OpenTelemetry & Diagnostics

EricksonLopez.Outbox integrates seamlessly with OpenTelemetry, publishing native BCL System.Diagnostics.Metrics and ActivitySource traces:

using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing =>
    {
        tracing.AddSource("EricksonLopez.Outbox");
        tracing.AddOtlpExporter();
    })
    .WithMetrics(metrics =>
    {
        metrics.AddMeter("EricksonLopez.Outbox");
        metrics.AddOtlpExporter();
    });

.NET Aspire Cloud-Native Component

// Integrates telemetry, health checks, and options configuration automatically
builder.AddOutbox("outboxDb");

NativeAOT JSON Serialization Context

To ensure 100% NativeAOT compatibility without reflection, declare your message contracts inside a JsonSerializerContext:

using System.Text.Json.Serialization;
using MyApp.Contracts;

[JsonSourceGenerationOptions(
    PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
    GenerationMode = JsonSourceGenerationMode.Default)]
[JsonSerializable(typeof(OrderCreatedEvent))]
[JsonSerializable(typeof(OrderConfirmedEvent))]
[JsonSerializable(typeof(ProductCreatedEvent))]
public partial class AppOutboxJsonContext : JsonSerializerContext;

Roslyn Diagnostic Analyzers

The EricksonLopez.Outbox.Analyzers package enforces compile-time architectural integrity directly in your IDE:

Diagnostic ID Severity Category Description CodeFix
OUTBOX001 Error Architecture Event class missing [OutboxMessage] attribute โœ… Yes
OUTBOX002 Error Reliability Message stored without active transaction context โœ… Yes
OUTBOX003 Warning Design Invalid message type alias formatting โœ… Yes
OUTBOX004 Error Design Outbox message type must not be an abstract class โŒ No
OUTBOX005 Error Serialization Missing public parameterless ctor or init accessors โœ… Yes
OUTBOX006 Error Usage Invalid attribute target usage โŒ No
OUTBOX007 Warning Configuration Incompatible broker configuration parameters โŒ No
OUTBOX008 Error Reliability Unsafe async fire-and-forget inside handlers โœ… Yes
OUTBOX009 Warning Performance Redundant serializer registration detected โœ… Yes
OUTBOX010 Error Lifecycle Transaction context lifetime mismatch โŒ No
OUTBOX011 Warning Reliability Stale lease timeout configured too low (< 30s) โœ… Yes
OUTBOX012 Warning Idempotency Missing idempotency configuration on consumer handler โœ… Yes
OUTBOX013 Error NativeAOT Missing [JsonSerializable] attribute in serialization context โœ… Yes

๐Ÿงช Testing & Quality

EricksonLopez.Outbox is engineered under the highest standards of automated quality assurance.

In-Memory Verification Harness

Test command handlers and business services without spinning up real database containers:

using System;
using System.Threading.Tasks;
using EricksonLopez.Outbox.Testing;
using Xunit;

public sealed class OrderServiceTests
{
    [Fact]
    public async Task PlaceOrder_StoresMessageInOutbox()
    {
        // Arrange
        var fakeStore = new InMemoryOutboxStore();
        var fakeTx = new FakeOutboxTransactionContext();
        var service = new OrderService(fakeStore);

        // Act
        await service.CreateOrderAsync(Guid.NewGuid(), "cust-1", 100m, fakeTx);

        // Assert
        var messages = fakeStore.GetStoredMessages();
        Assert.Single(messages);
        Assert.Equal("order.created.v1", messages[0].MessageType);
    }
}

Mutation Testing & Quality Gates

Code coverage alone is insufficient for mission-critical transactional infrastructure. We enforce mutation testing via Stryker.NET across the entire solution:

  • Target Mutation Score: 100% mutant kill rate.
  • Enforced Build Break Threshold: โ‰ฅ95% mutation score required in CI.
  • SonarCloud Quality Gate: Zero bugs, zero vulnerabilities, Maintainability Rating A.
  • AOT Smoke Tests: Every commit executes automated NativeAOT linux-x64 binary compilations with warnings treated as errors (DOTNET_EnableAotCompilationWarningsAsErrors=true).

โšก Performance Benchmarks

Environment: .NET 10.0.10 (10.0.1026.32716), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI, BenchmarkDotNet v0.13.12, Windows 11.
Storage: InMemoryOutboxStore (isolates framework CPU/GC overhead from network I/O).

Competitor Comparison โ€” StoreAsync (Single Message)

Method Mean Error StdDev Ratio Allocated Alloc Ratio
EricksonLopez.Outbox 256.3 ns ยฑ1.43 ns ยฑ1.27 ns 1.00 448 B 1.00
CAP StoreAsync 855.7 ns ยฑ7.30 ns ยฑ6.47 ns 3.34 1,664 B 3.71
NServiceBus StoreAsync 25,423.8 ns ยฑ194.01 ns ยฑ181.48 ns 99.19 5,457 B 12.18

Serialization โ€” IBufferWriter<byte> vs Allocating Path

Method Payload Mean P50 Ratio Allocated Alloc Ratio
Serialize_Allocating (baseline) 512 B 89.43 ns 88.97 ns 1.00 592 B 1.00
Serialize_BufferWriter 512 B 65.84 ns 65.72 ns 0.74 32 B 0.05
Serialize_Allocating (baseline) 10 KB 593.30 ns 589.95 ns 1.00 10,320 B 1.00
Serialize_BufferWriter 10 KB 336.76 ns 337.06 ns 0.57 32 B 0.003
Serialize_Allocating (baseline) 100 KB 7,766.59 ns 7,771.87 ns 1.00 102,573 B 1.000
Serialize_BufferWriter 100 KB 3,379.60 ns 3,378.79 ns 0.44 32 B ~0

Concurrency โ€” Parallel StoreAsync Scaling

Threads Mean StdDev P50 P95 Ops/sec Allocated
1 846.7 ns ยฑ10.68 ns 844.8 ns 864.4 ns 1,181,111 728 B
4 1,545.6 ns ยฑ20.46 ns 1,539.0 ns 1,579.6 ns 646,999 2,600 B
16 4,474.8 ns ยฑ509.67 ns 4,316.9 ns 5,529.0 ns 223,472 9,800 B
64 9,699.6 ns ยฑ131.66 ns 9,702.9 ns 9,879.6 ns 103,097 38,601 B

Type Resolution via FrozenDictionary

Method Mean Allocated
GetAlias (Type โ†’ string) 1.369 ns 0 B
Resolve (string โ†’ Type) 2.594 ns 0 B

Key Takeaways:

  • 3.3ร— faster and 73% less memory than CAP in store operations.
  • 99ร— faster and 92% less memory than NServiceBus.
  • IBufferWriter<byte> pool serialization allocates a constant 32 bytes regardless of payload size (up to 99.97% allocation reduction vs traditional byte[] arrays).
  • Scales linearly to 64 concurrent threads with zero lock contention.

๐ŸŒ Compatibility & Technical Matrix

.NET Support Policy

This library supports only .NET frameworks with active official support from Microsoft:

Framework Type Microsoft Support End Date Status
.NET 8 LTS November 10, 2026 โœ… Supported
.NET 9 STS November 10, 2026 โœ… Supported
.NET 10 LTS November 2028 โœ… Supported

Package Compatibility Matrix

Package Category Packages .NET 8.0 .NET 9.0 .NET 10.0 NativeAOT Ready Trimmable
Core Outbox EricksonLopez.Outbox, Abstractions โœ… โœ… โœ… โœ… โœ…
Standalone Inbox EricksonLopez.Inbox, Abstractions, Bridge โœ… โœ… โœ… โœ… โœ…
Events Integration Outbox.Events, Outbox.Inbox.Events โœ… โœ… โœ… โœ… โœ…
HTTP Idempotency Outbox.Inbox.AspNetCore โœ… โœ… โœ… โœ… โœ…
Storage Providers Storage.* (all 7 engines) โœ… โœ… โœ… โœ… โœ…
EF Core Provider EntityFrameworkCore โœ… โœ… โœ… โš ๏ธ (EF Core limitation) โœ…
Broker Publishers Brokers.* (all 8 brokers) โœ… โœ… โœ… โš ๏ธ (Broker SDK dependent) โœ…
Mediator Adapter Outbox.Mediator โœ… โœ… โœ… โœ… โœ…
MediatR Adapter Outbox.MediatR โœ… โœ… โœ… โŒ (Legacy non-AOT) โŒ
Enterprise Buses NServiceBus, Rebus, Brighter, Dapr โœ… โœ… โœ… โš ๏ธ (Host dependent) โœ…
Aspire Integration Outbox.Aspire โœ… โœ… โœ… โœ… โœ…
Binary Serializers Protobuf, MessagePack โœ… โœ… โœ… โœ… โœ…
Source Generators SourceGenerators netstandard2.0 netstandard2.0 netstandard2.0 N/A (compile tool) N/A
Roslyn Analyzers Analyzers netstandard2.0 netstandard2.0 netstandard2.0 N/A (dev tool) N/A

Storage Providers Matrix

Database Engine Storage Package Client Driver Concurrency Strategy Production Status
PostgreSQL Storage.PostgreSql Npgsql FOR UPDATE SKIP LOCKED + UNNEST โญ Reference Standard
SQL Server Storage.SqlServer Microsoft.Data.SqlClient WITH (UPDLOCK, READPAST, ROWLOCK) โœ… Enterprise Production
MySQL Storage.MySql MySqlConnector FOR UPDATE SKIP LOCKED (MySQL 8.0+) โœ… Recommended
MariaDB Storage.MariaDb MySqlConnector FOR UPDATE SKIP LOCKED (MariaDB 10.6+) โœ… Recommended
Oracle Storage.Oracle Oracle.ManagedDataAccess.Core FOR UPDATE SKIP LOCKED (12c+) โœ… Enterprise Production
MongoDB Storage.MongoDb MongoDB.Driver Atomic FindOneAndUpdate + Sessions โœ… Enterprise Production
SQLite Storage.Sqlite Microsoft.Data.Sqlite WAL-Mode Table Locking โš ๏ธ Dev / Embedded Only

Message Brokers Matrix

Broker Package Underlying SDK Delivery Semantics
RabbitMQ Brokers.RabbitMQ RabbitMQ.Client 7.x At-Least-Once
Apache Kafka Brokers.Kafka Confluent.Kafka 2.x At-Least-Once
Azure Service Bus Brokers.AzureServiceBus Azure.Messaging.ServiceBus 7.x At-Least-Once
Azure Event Hubs Brokers.AzureEventHubs Azure.Messaging.EventHubs 5.x At-Least-Once
AWS SQS Brokers.AwsSqs AWSSDK.SQS 3.x At-Least-Once
Google Pub/Sub Brokers.GooglePubSub Google.Cloud.PubSub.V1 3.x At-Least-Once
NATS Brokers.Nats NATS.Client.Core 2.x At-Least-Once
Redis Streams Brokers.RedisStreams StackExchange.Redis 2.x At-Least-Once

๐Ÿ›๏ธ Architecture & Design Principles

System Architecture & Data Flow

flowchart TD
    subgraph ClientApp["Client Application Layer"]
        Service["Application Service / Command Handler"]
        Domain["Domain Entities & Aggregates"]
    end

    subgraph CoreAbstractions["Core Abstractions & Domain Contracts"]
        IOutbox["IOutbox / OutboxMessageBuilder"]
        IOutboxTx["IOutboxTransactionContext"]
        IOutboxSerializer["IOutboxSerializer"]
        Contracts["[OutboxMessage] / [InboxConsumer]"]
    end

    subgraph PersistenceLayer["Persistence & Storage Layer"]
        OutboxRepo["IOutboxRepository (SQL / NoSQL)"]
        DLQRepo["IDeadLetterRepository"]
        InboxRepo["IIdempotencyRepository"]
        DB[(Target Database - PostgreSQL / SQL Server / etc.)]
    end

    subgraph DispatcherLayer["Dispatcher & Processing Engine"]
        Poller["AdaptivePoller / BackgroundService"]
        Channel["Channel<OutboxMessage> (Backpressure)"]
        Pipeline["OutboxPipeline (Middlewares)"]
        RateLimiter["RateLimiter / LeakyBucket"]
    end

    subgraph TransportLayer["Transport & Broker Layer"]
        BrokerPub["IBrokerPublisher"]
        Retry["RetryPolicy / CircuitBreaker"]
        Broker[(External Broker - RabbitMQ / Kafka / Azure SB)]
    end

    subgraph ConsumerLayer["Consumer & Inbox Deduplication Layer"]
        InboxFilter["IInboxConsumerFilter / IdempotentEndpointFilter"]
        ConsumerHandler["Consumer Message Handler"]
    end

    Service -->|1. StoreAsync| IOutbox
    Domain -.->|Events| Service
    IOutbox -->|2. Serialize| IOutboxSerializer
    IOutbox -->|3. Insert in TX| OutboxRepo
    OutboxRepo -->|4. Atomic Write| DB
    
    Poller -->|5. FetchPendingAsync (SKIP LOCKED)| OutboxRepo
    Poller -->|6. Write to Channel| Channel
    Channel -->|7. Consume Parallel| Pipeline
    Pipeline -->|8. Publish via BrokerPublisher| BrokerPub
    BrokerPub -->|9. Physical Network Publish| Broker
    BrokerPub -->|10. DispatchResult| Pipeline
    Pipeline -->|11a. Success: MarkAsDispatched| OutboxRepo
    Pipeline -->|11b. Fatal: MarkAsFailed / DLQ| DLQRepo
    
    Broker -->|12. Deliver Message| ConsumerHandler
    ConsumerHandler -->|13. Intercept & Deduplicate| InboxFilter
    InboxFilter -->|14. Check / Record Processed| InboxRepo

Message Lifecycle State Machine

stateDiagram-v2
    [*] --> Pending: StoreAsync() in DB Transaction
    Pending --> InFlight: AdaptivePoller FetchPendingAsync (SKIP LOCKED)
    InFlight --> Dispatched: BrokerPublisher returns DispatchResult.Ok()
    InFlight --> Pending: Transient failure & retryCount < MaxRetryCount (Exponential Backoff)
    InFlight --> DeadLettered: Fatal failure OR retryCount >= MaxRetryCount
    InFlight --> Pending: Crash Recovery (ReclaimStaleMessagesAsync after ReclaimTimeout)
    Dispatched --> [*]: Purged by OutboxCleanupService (or DeleteOnDispatch)
    DeadLettered --> [*]: Manual inspection / Deleted via IDeadLetterRepository

Sequential Dispatch Pipeline

sequenceDiagram
    autonumber
    participant D as OutboxDispatcher
    participant R as IOutboxRepository
    participant P as OutboxPipeline
    participant M as IOutboxMiddleware
    participant B as IBrokerPublisher
    participant K as Broker (RabbitMQ/Kafka)
    participant DLQ as IDeadLetterRepository

    D->>R: FetchPendingAsync(batchSize) [SKIP LOCKED]
    R-->>D: List<OutboxMessage>
    loop For each message in batch
        D->>P: ExecuteAsync(message, context)
        P->>M: InvokeAsync(context, next)
        M->>B: PublishRawAsync(message, metadata)
        B->>K: Send Physical Packet
        alt Physical Send Succeeded
            K-->>B: ACK
            B-->>M: DispatchResult.Ok()
            M-->>P: DispatchResult.Ok()
            P-->>D: Success
            D->>R: MarkAsDispatchedAsync([message])
        else Transient Failure (Network/Timeout)
            K-->>B: NACK / Timeout Exception
            B-->>M: DispatchResult.FailAndRetry(ex)
            M-->>P: DispatchResult.FailAndRetry(ex)
            P-->>D: Retry
            D->>R: MarkAsFailedAsync([message], ex.Message, isDeadLetter: false)
        else Fatal Failure (Poison Message / Schema Invalid)
            B-->>M: DispatchResult.FailFatal(ex)
            M-->>P: DispatchResult.FailFatal(ex)
            P-->>D: Fatal
            D->>DLQ: InsertAsync(DeadLetterMessage)
            D->>R: MarkAsFailedAsync([message], ex.Message, isDeadLetter: true)
        end
    end

Architectural Boundaries & Non-Goals

EricksonLopez.Outbox is strictly scoped to solve the Dual-Write Problem with maximum performance and rock-solid reliability:

  • โŒ Not an In-Process Event Bus: In-process domain event dispatching belongs to EricksonLopez.Mediator.
  • โŒ Not an Event Store: Event sourcing aggregate reconstruction is a distinct paradigm.
  • โŒ Not a Saga Orchestrator: Complex stateful workflows belong to dedicated saga engines.
  • โŒ Not a General Job Scheduler: DeliverAt provides delayed message dispatch, not recurring cron execution.
  • โŒ No Exactly-Once Guarantees: Exactly-once messaging across heterogeneous systems is mathematically impossible without distributed locks; consumers must be idempotent (use EricksonLopez.Inbox).

๐Ÿ›ก๏ธ Best Practices & Anti-Patterns

Scenario โŒ Avoid โœ… Recommended
Transaction Boundary Opening a second DB connection or calling outbox.StoreAsync after tx.Commit() Passing tx.ToOutboxContext() to StoreAsync and committing atomically at the end
Serialization Relying on runtime reflection (Newtonsoft.Json or unconfigured System.Text.Json) Using NativeAotJsonSerializer with compiled JsonSerializerContext
Dispatch Table Retention Keeping all dispatched messages in the primary outbox table indefinitely Enabling DeleteOnDispatch = true or scheduling OutboxCleanupService
Consumer Idempotency Assuming message brokers will never deliver duplicate messages Wrapping consumer handlers with IInboxIdempotencyChecker
High-Throughput Ingestion Calling StoreAsync in a tight loop with individual single-record inserts Using ReadOnlyMemory<TMessage> batch inserts (UNNEST SQL batching)
Dispatcher Threading Running unbounded Task.Run loops that exhaust the .NET ThreadPool Configuring MaxDegreeOfParallelism on backpressure-aware Channels
Poison Messages Retrying permanently invalid messages infinitely, blocking the queue Letting fatal errors route to IDeadLetterRepository (DLQ)

โš ๏ธ Troubleshooting & Common Pitfalls

Always verify that your database transaction is active and uncommitted when invoking IOutbox.StoreAsync. Committing before calling StoreAsync violates ACID guarantees and will trigger analyzer rule OUTBOX002.

1. Messages Persisted in Database but Never Published

  • Symptom: outbox_messages rows remain stuck in Pending (state 0); broker receives nothing.
  • Root Cause: The background dispatcher daemon was not registered, or the database connection in the background service is failing health checks.
  • Remedy: Ensure builder.Services.AddOutboxDispatcher() is called in Program.cs. Inspect background service logs for connection pool exhaustion.

2. High Duplicate Message Volume Under Load

  • Symptom: The consumer receives identical messages 5โ€“10 times during traffic spikes.
  • Root Cause: Broker publish timeout is shorter than the actual acknowledgment round-trip. The dispatcher assumes timeout failure, marks for retry, and republishes.
  • Remedy: Increase broker socket timeout in publisher options and implement consumer-side deduplication via EricksonLopez.Inbox (OUTBOX012).

3. Relation "outbox_messages" Does Not Exist (42P01)

  • Symptom: Application throws PostgresException or SQL Server error on first message store.
  • Root Cause: Database schema migrations or initialization DDL scripts have not been executed.
  • Remedy: If using EF Core, call modelBuilder.ApplyOutboxEntityConfigurations() in OnModelCreating() and run dotnet ef database update. For raw ADO.NET, execute the schema DDL scripts provided in the respective storage package docs.

4. OutboxException: Type not found for alias 'X'

  • Symptom: Dispatcher throws runtime exception when deserializing payload.
  • Root Cause: The event class was not decorated with [OutboxMessage("X")] or was omitted from the JsonSerializerContext.
  • Remedy: Decorate the class with [OutboxMessage("...")] and add [JsonSerializable(typeof(YourEvent))] to your serializer context (OUTBOX001, OUTBOX013).

5. High Idle CPU Usage from Background Dispatcher

  • Symptom: Application consumes 10โ€“20% CPU on idle pods when no messages are pending.
  • Root Cause: Adaptive polling disabled or MaxDegreeOfParallelism configured excessively high.
  • Remedy: Set options.UseAdaptivePolling = true (default) and adjust MaxDegreeOfParallelism to match available CPU cores.

๐ŸŒ Part of the EricksonLopez Ecosystem


๐Ÿค Contributing

We welcome community contributions, bug reports, and optimizations!

Local Development Setup

  1. Prerequisites:

  2. Build the Solution:

    dotnet build --configuration Release
    
  3. Run Fast Unit Tests:

    dotnet test --filter "Category!=Integration" --nologo
    
  4. Run Full Test Suite (with Testcontainers):

    dotnet test --nologo
    
  5. Run Stryker.NET Mutation Testing:

    dotnet stryker -c stryker-config-unit.json
    

Please review our Contributing Guide, Code of Conduct, and Security Policy before submitting pull requests.


๐Ÿ“„ License

Distributed under the MIT License.
Copyright ยฉ 2026 Erickson Lopez.

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 is compatible.  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
2.0.0 104 8/31/2026
1.0.0 254 8/8/2026