Stella.Ergosfare.Plugins.Outbox 2.17.1-preview

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

Stella.Ergosfare.Plugins.Outbox

An opt-in outbox using ordinary Ergosfare command and event plans. The NuGet package includes the runtime, an InMemory store, a bounded hosted worker, and its own source generator under analyzers/dotnet/cs. No changes to the Ergosfare engine are required.

Define a message and its handler

using Stella.Ergosfare.Plugins.Outbox;
using Stella.Ergosfare.Core.Abstractions;

[OutboxMessage("product-created/v1")]
public sealed partial record ProductCreated(Guid ProductId, string Name);

public sealed class ProductCreatedHandler : IOutboxHandler<ProductCreated>
{
    public ValueTask HandleAsync(OutboxEvent<ProductCreated> delivery, ErgosfareContext context)
    {
        ProductCreated message = delivery.Message;
        // Background work. Make side effects safe to retry.
        return ValueTask.CompletedTask;
    }
}

The POCO implements no Ergosfare interface. [OutboxMessage] generates JSON reading, writing and registration without reflection or a user-written JsonSerializerContext. The optional contract defaults to the fully qualified type name plus /v1. For persisted messages, choose an explicit stable contract before renaming types or namespaces.

IOutboxHandler<T> inherits IEventHandler<OutboxEvent<T>>. An ordinary IEventHandler<T> does not receive this wrapper. Generic event interceptors still follow normal Ergosfare composition rules. The handler's generic interface graph also names the closed save participant, allowing the existing generator to discover both wrapper plans.

Configure

using Microsoft.Extensions.DependencyInjection;
using Stella.Ergosfare.Commands.Extensions.MicrosoftDependencyInjection;
using Stella.Ergosfare.Core.Extensions.MicrosoftDependencyInjection;
using Stella.Ergosfare.Events.Extensions.MicrosoftDependencyInjection;

services.AddErgosfare(options =>
{
    options.AddCommandModule(commands => commands.AddGenerated());
    options.AddEventModule(events => events.AddGenerated());
    options.AddOutboxPlugin(outbox =>
    {
        outbox.UseInMemory();
        outbox.MaxConcurrency = 8;
    });
});

Keep the existing Stella.Ergosfare.SourceGenerator analyzer reference as well. Both generators read original user source independently; neither consumes the other's output. For repository ProjectReference consumers, reference the outbox generator project as an analyzer explicitly (project analyzer references do not propagate). NuGet consumers get it automatically from the outbox package.

The existing plugin surface requires opting into ERGOEXP002. This first-party assembly uses ErgosfareSourceGeneratorForceScanReferences to opt into the existing reference scan. Choose a store explicitly. Both command and event mediators are checked when the worker starts, after module configuration, so registration order is immaterial. A .NET Generic Host starts the registered worker; building a DI container alone does not run it.

Enqueue

Inside an ordinary generated command or event handler:

await context.EnqueueOutboxAsync(new ProductCreated(product.Id, product.Name));

The existing Start hook attaches the calling scope's command mediator. The extension opens a child context and dispatches OutboxEntry<T>. Its save handler serializes a snapshot and calls the scoped store. Neither request context nor live objects are stored. The default discovery key is supported. A manually created context, or a child context before entering a generated pipeline, has no binding and fails explicitly.

Enqueue completion means store acceptance, not background completion. Transactional adapters must stage the write in the application's unit of work; enqueue does not commit that transaction. Every enqueue generates a new ID and represents a distinct request.

Worker

The worker claims records and publishes OutboxEvent<T> through a mediator resolved in a new scope. It acknowledges only after the entire pipeline succeeds. A handled/swallowed exception counts as success. Custom event participants can be composed normally.

  • MaxConcurrency bounds hosted slots through dispatch, acknowledgment and disposal. ProcessOneAsync is available for manual hosts, which own their concurrency limit.
  • Claims have fencing tokens; stale owners cannot acknowledge reclaimed records.
  • Long deliveries renew leases using separate persistence scopes. Renewal failures cancel dispatch. The handler's DbContext is never used concurrently by the renewal loop.
  • Failures wait RetryDelay, stopping at MaxAttempts. InMemory dead letters are retained; operator listing/requeue is not implemented yet. Completed InMemory records are removed.
  • Shutdown uses a bounded independent token for cleanup. Crashes and acknowledgment failures leave reclaimable leases. Delivery is at least once; earlier broadcast handlers can run again if a later handler fails.

JSON support and diagnostics

The first generator supports public, top-level, non-generic partial classes and records. Properties must have public getters. Public constructors must map their parameters to properties; remaining properties need public set/init accessors. Public fields, inheritance, indexers and custom JSON attributes are rejected with OUTBOX001, never reflection fallback.

Supported values: string, bool, integer types, float, double, decimal, Guid, DateTime, DateTimeOffset, nullable values, one-dimensional arrays and List<T> of supported values. Nested object DTOs, enums, dictionaries and custom converters are not yet supported. Property names are preserved. Reads require every declared field; extra JSON fields are ignored. Schema changes therefore require explicit compatibility/version planning. Duplicate contracts in one compilation produce OUTBOX002; cross-assembly collisions are detected when constructing options.

Advanced unannotated types can still use outbox.Register(contract, JsonTypeInfo<T>, groups) with an existing source-generated JSON context. Annotated types register automatically and must not also be manually registered. There is no dynamic assembly scanning.

Persistence adapters

UseStore(Action<IServiceCollection>) lets separate provider packages register one IOutboxStore, optionally scoped. Append must join domain persistence for atomic writes. Claims and state updates must check ownership atomically. Worker operations use independent scopes/transactions; custom stores must honor cancellation.

InMemory is for tests/development: records are immediately visible, are not rolled back with domain work, and disappear at process exit. It provides neither durability nor domain transaction atomicity. EF Core/Redis adapters are not included in this first package.

The package follows the repository's common version and release workflow.

Product Compatible and additional computed target framework versions.
.NET 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.  net11.0 is compatible. 
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.17.1-preview 60 9/16/2026
2.17.0-preview 56 9/15/2026