FluxFlow.Engine.DurableOutput 3.0.0

There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package FluxFlow.Engine.DurableOutput --version 3.0.0
                    
NuGet\Install-Package FluxFlow.Engine.DurableOutput -Version 3.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="FluxFlow.Engine.DurableOutput" Version="3.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FluxFlow.Engine.DurableOutput" Version="3.0.0" />
                    
Directory.Packages.props
<PackageReference Include="FluxFlow.Engine.DurableOutput" />
                    
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 FluxFlow.Engine.DurableOutput --version 3.0.0
                    
#r "nuget: FluxFlow.Engine.DurableOutput, 3.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 FluxFlow.Engine.DurableOutput@3.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=FluxFlow.Engine.DurableOutput&version=3.0.0
                    
Install as a Cake Addin
#tool nuget:?package=FluxFlow.Engine.DurableOutput&version=3.0.0
                    
Install as a Cake Tool

FluxFlow.Engine.DurableOutput

Optional provider-neutral capture and serial renewable leased at-least-once delivery for explicitly selected FluxFlow application outputs. Capture, delivery, and dead-letter administration are separate capabilities: a host pays only for the parts it registers and uses.

Registration

using FluxFlow.Composition.Addressing;
using FluxFlow.Engine.DurableOutput;

services.AddSingleton<HostDurableOutputStore>();
services.AddSingleton<IDurableOutputStore>(provider =>
    provider.GetRequiredService<HostDurableOutputStore>());
services.AddSingleton<IDurableOutputDeliveryStore>(provider =>
    provider.GetRequiredService<HostDurableOutputStore>());
services.AddSingleton<IDurableOutputDeadLetterStore>(provider =>
    provider.GetRequiredService<HostDurableOutputStore>());

services
    .AddFluxFlowDurableOutput(outputs =>
    {
        outputs.Capture(
            ApplicationAddress.WorkflowPort("Orders", "Complete", "Output"),
            "orders.completed.v1",
            ApplicationJsonContext.Default.OrderCompleted);
    })
    .AddSingleton<IDurableOutputDeliveryHandler, OrderDeliveryHandler>()
    .AddFluxFlowDurableOutputDelivery(delivery =>
    {
        delivery.LeaseDuration = TimeSpan.FromMinutes(1);
        delivery.LeaseRenewalInterval = TimeSpan.FromSeconds(20);
        delivery.RetryDelay = TimeSpan.FromSeconds(10);
        delivery.IdleDelay = TimeSpan.FromMilliseconds(500);
        delivery.MaxDeliveryAttempts = 5;
    });

Registration is flat, validates and freezes immutable settings, and performs no provider I/O. LeaseRenewalInterval must be positive and shorter than LeaseDuration; the defaults are 10 seconds and 30 seconds respectively. MaxDeliveryAttempts is nullable and defaults to null, which preserves unlimited retry. A configured value must be positive.

Delivery activation requires exactly one IDurableOutputDeliveryStore and one IDurableOutputDeliveryHandler. The delivery registration supplies neither. IDurableOutputDeadLetterStore is an independently resolvable optional operator capability; the dispatcher does not depend on it.

Guarantees

For a selected output, Enqueued or equivalent-content AlreadyExists is required before ordinary Engine output dispatch begins. Conflict, serialization failure, or a store exception faults the output instead of dispatching uncaptured data. Unselected outputs retain the lightweight in-process path.

The optional dispatcher leases one output at a time and calls the host-owned handler serially. A short handler incurs no renewal call. While a handler keeps running, the dispatcher extends the exact current unexpired token at the configured interval. If renewal reports ownership loss, the handler is canceled and observed and no stale settlement is attempted. On success it completes the current token. On handler failure it retries after the fixed delay while the attempt is below a configured maximum. Failure on the final configured attempt atomically dead-letters the lease with the stable reason HandlerFailure. Unlimited mode never dead-letters automatically.

Delivery remains at-least-once. If a destination accepts a side effect and the process fails before completion commits, the same envelope can be delivered again. Use DurableOutputEnvelope.Key as the destination idempotency key when possible. A stopped process leaves an active lease for expiry recovery.

Instrumentation

The package publishes BCL signals through ActivitySource and Meter, both named FluxFlow.Engine.DurableOutput. Producer fluxflow.durable_output.capture activities cover selected-output capture; consumer fluxflow.durable_output.deliver activities cover leased delivery. Counters and millisecond histograms describe capture results, leases, handler results, delivery settlements/ownership loss, renewal results, store failures, and capture/delivery duration.

Metric dimensions are bounded semantic outcomes, results, and store operation names. They exclude addresses, contracts, message/tracing identities, payloads, headers, lease data, provider settings, exception text, connection details, and secrets. The host owns listener/exporter configuration. The package registers none, does not poll operational status, and isolates listener failure from capture, handler, and settlement behavior.

Dead-Letter Operations

Capable providers may expose IDurableOutputDeadLetterStore:

var page = await deadLetters.ListAsync(
    new DurableOutputDeadLetterQuery(pageSize: 50),
    cancellationToken);

var details = await deadLetters.GetAsync(key, cancellationToken);

var now = clock.GetUtcNow();
var replay = await deadLetters.ReplayAsync(
    new DurableOutputReplay(
        key,
        expectedGeneration: details!.Generation,
        replayedAt: now,
        nextAttemptAt: now),
    cancellationToken);

Listing is bounded metadata-only keyset pagination. Exact lookup returns the complete current dead-letter envelope. Replay is explicit, single-record, and generation-protected; it returns the row to pending, resets the attempt count, and does not invoke the handler immediately. There is no automatic replay.

Provider Boundaries And Limits

IDurableOutputStore owns atomic idempotent capture. IDurableOutputDeliveryStore separately owns lease, renew, complete, retry, and dead-letter compare-and-set transitions. IDurableOutputDeadLetterStore separately owns operator inspection and replay. A custom provider may support capture without either later capability, or delivery without operator APIs.

The host owns IDurableOutputDeliveryHandler; this package has no transport or destination adapter. It also has no exponential backoff, batching, parallel dispatch, automatic replay or purge, administration endpoint/UI, distributed coordination, workflow checkpoint, producer/business-state transaction, or exactly-once guarantee. Provider settings and migrations stay outside FluxFlowApplicationOptions.

The optional retention capability below is an explicit host-invoked operation, not a dispatcher policy or automatic service.

Version 3.0 adds exact-token renewal to IDurableOutputDeliveryStore and makes the flat renewal interval required by DurableOutputDeliveryOptions; custom 2.x delivery providers and direct options construction must adopt both changes.

Verifying A Custom Provider

The repository's durable-output test project contains reusable provider conformance suites for capture, delivery, and dead-letter operations. A custom provider test project supplies a fresh explicit context for the capabilities it implements and inherits the matching suites. The shared specification covers idempotent capture, deterministic and exclusive leasing, exact lease renewal, token/expiry compare-and-set transitions, retry and replay scheduling, terminal-state eligibility, bounded metadata-only listing, exact envelope fidelity, generation protection, keyset ordering, and one-winner concurrency.

These are behavioral tests, not a runtime provider framework. They use no reflection or provider discovery and do not require capture, delivery, and operator interfaces to be the same object. Each provider must separately test its schema or document shape, migrations, registration ownership, locking, corruption behavior, restart/persistence, deployment model, and resource lifecycle against its real backend.

Optional Operational Status

IDurableOutputStatusStore is a separate optional capability. It reports immutable payload-free capture and delivery state at an explicit caller-owned observation time:

var statusStore = provider.GetRequiredService<IDurableOutputStatusStore>();
var status = await statusStore.GetStatusAsync(
    new DurableOutputStatusQuery(timeProvider.GetUtcNow()),
    cancellationToken);

The snapshot distinguishes captures not yet materialized for delivery from pending, leased, completed, and dead-lettered delivery state. Inspection does not backfill delivery state, lease work, replay a record, or expose envelope, key, token, owner, or failure data.

Optional Terminal Retention

IDurableOutputRetentionStore is a separate optional capability for explicit, bounded deletion of completed or dead-lettered output captures:

var retention = provider.GetRequiredService<IDurableOutputRetentionStore>();
var result = await retention.PurgeCompletedAsync(
    new DurableOutputRetentionRequest(
        terminalBefore: timeProvider.GetUtcNow().AddDays(-30),
        maxCount: 250),
    cancellationToken);

The provider selects only the requested terminal delivery state and deletes the capture parent so its delivery row is removed atomically through the existing cascade. Pending, leased, replayed, opposite-terminal, and unmaterialized captures are preserved. The host owns scheduling and repetition. Purging a completed capture ends its idempotency window; purging a dead letter permanently removes its inspection and replay source.

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 (2)

Showing the top 2 NuGet packages that depend on FluxFlow.Engine.DurableOutput:

Package Downloads
FluxFlow.Engine.DurableOutput.SqlFile

Production SQLite single-file durable-output capture, renewable leased delivery, dead-letter, and replay store for local FluxFlow hosts.

FluxFlow.Engine.DurableOutput.TSql

Production networked T-SQL durable-output capture, renewable leased delivery, dead-letter inspection, and generation-protected replay provider.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
4.0.0-rc.1 76 8/9/2026
3.0.0 167 8/3/2026

Breaking: adds exact-token renewal to the cohesive delivery-store contract, replaces the four-argument options constructor, and requires a flat renewal interval; custom providers and direct options callers must update. No dependency was added.