Fluens.Kernel 0.7.7

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

Fluens.Kernel

DDD building blocks and Result pattern. Zero dependencies.

Installation

dotnet add package Fluens.Kernel

Usage

Result Pattern

// Result without value
Result result = Result.Success();
Result failure = Result.Failure(new NotFoundError("User not found"));

// Result with value
Result<int> result = Result.Success(42);
Result<int> failure = Result.Failure<int>(new ValidationError("Invalid input"));

// Pattern matching
string message = result.Match(
    onSuccess: value => $"Got: {value}",
    onFailure: error => $"Error: {error.Message}"
);

Both Result and Result<T> are readonly struct types — zero heap allocations, default(Result) is a valid success.

Both types implement IEquatable<> (Equals, GetHashCode, ==, !=), comparing IsFailure and Error (structurally, via Error's own equality — see below), plus the stored value for Result<T> on the success branch. Result.Success().Equals(Result.Success()) and default(Result<T>).Equals(Result.Success(default(T))) both return true; a success and a failure are never equal. This makes both types safe as dictionary keys or HashSet<> entries, replacing the reflection-based ValueType.Equals fallback that MA0065 used to flag.

Result.Error and Result<T>.Error are Error?null exactly when IsSuccess is true. Always check IsFailure (or use Match) before reading Error, or use a null-safe form such as result.Error?.Code.

Result<T>.Value is state-guarded: on success it returns the stored value (including a legitimately stored null/default); on failure it throws Result.MissingValueException. The guard keys on the result state (IsFailure), not on a null check of the backing field — always check IsSuccess/IsFailure (or use Match) before reading Value.

Result<int> failure = Result.Failure<int>(new ValidationError("Invalid input"));
int value = failure.Value; // throws Result.MissingValueException
Conversions
// Implicit: value → Result<T>
Result<int> result = 42;

// Explicit: Result<T> → Result (discards value)
Result plain = (Result)result;

// As<T>(): failure Result → Result<T>
Result failure = Result.Failure(new NotFoundError("Missing"));
Result<int> typed = failure.As<int>(); // preserves error

// As<T>() on success throws InvalidOperationException

Result.Success<T>(value) guards against silently swallowing a failure: when T is one of the four ambiguous carrier shapes — object, ValueType, Result, or a constructed Result<U> — and value is itself a failed result, it throws Result.FailureSwallowedException instead of yielding a success. Use As<T>() to move a failure across result types; Result.Success<T>(value) is reserved for actual success values.

Result failed = Result.Failure(new NotFoundError("Missing"));

Result<object> boxed = Result.Success<object>(failed); // throws Result.FailureSwallowedException
Result<object> typed = failed.As<object>();             // correct: preserves the failure

Result<int> ok = 42; // still compiles and succeeds — int is not an ambiguous carrier

Built-in error types: NotFoundError, ValidationError, ConflictError, ForbiddenError.

Message Arguments

Errors support parameterized message arguments for translations via MessageArgs and fluent WithArg:

// Create error with message arguments for parameterized translations
var error = new NotFoundError("Order {orderId} not found")
    .WithArg("orderId", orderId.ToString());

// Access arguments
error.MessageArgs!["orderId"]; // "123"

// Chain multiple arguments
var error = new Error("CUSTOM", "Hello {name}, you have {count} items")
    .WithArg("name", "John")
    .WithArg("count", "5");

WithArg is immutable — each call returns a new Error instance without mutating the original. MessageArgs is IReadOnlyDictionary<string, string>? (null by default).

Error equality compares MessageArgs structurally and order-independently: two errors are equal when their EqualityContract (runtime record type), Code, Message and MessageArgs all match — a null MessageArgs and an empty dictionary are treated as equivalent, and key order does not matter. This makes Error values built through WithArg safe to use as dictionary keys or inside a HashSet<Error>; two errors built through identical WithArg chains deduplicate to one entry. A derived error type (e.g. NotFoundError) never equals a differently-typed Error with the same code and message, because the EqualityContract check still applies.

Error Codes

Standard error codes are centralized in the ErrorCodes static class:

ErrorCodes.NotFound    // "NOT_FOUND"
ErrorCodes.Validation  // "VALIDATION"
ErrorCodes.Conflict    // "CONFLICT"
ErrorCodes.Forbidden   // "FORBIDDEN"

Use these constants instead of hardcoded strings when comparing error codes:

if (result.Error is { } error && error.Code == ErrorCodes.NotFound)
{
    // handle not found
}

DDD Building Blocks

// ITypedIdentifier<out T> keeps `out T` for signature consistency, but the annotation is inert:
// `where T : struct` means no two value types share an implicit reference conversion, so no
// usable covariance actually exists here.
public readonly record struct OrderId(int Value) : ITypedIdentifier<int>;

// Entities with identity-based equality
public class Order : AggregateRoot<OrderId>
{
    public string Number { get; private set; } = "";

    public void Place()
    {
        AddEvent(new OrderPlacedEvent(ID));
    }
}

// Value objects with structural equality
public sealed class Money : ValueObject
{
    public decimal Amount { get; }
    public string Currency { get; }

    public Money(decimal amount, string currency)
    {
        Amount = amount;
        Currency = currency;
    }

    protected override IEnumerable<object?> GetAtomicValues()
    {
        yield return Amount;
        yield return Currency;
    }
}

ValueObject is an abstract class, not a recordEquals(object?) and GetHashCode() are sealed on the base type, so a derived value object cannot reintroduce member-wise equality that bypasses GetAtomicValues(). Derived value objects must therefore be sealed class with explicit constructors and get-only properties, not sealed record — they lose with-expressions, deconstruction and a synthesized ToString() as a consequence.

Interfaces: IAuditable (CreatedAtUtc, UpdatedAtUtc), IDeletable (DeletedAtUtc), IDomainEvent (marker interface — no members).

DeletableExtensions

Filter out soft-deleted entities from IQueryable<T> sources:

IQueryable<Order> activeOrders = dbContext.Orders.WithoutDeleted();

WithoutDeleted() returns only entities where DeletedAtUtc is null.

Translatable Messages

TranslatableMessage is a structure for storing translatable content (e.g. audit log entries) as JSON in a database. The message can be translated at read time using a ResourceManager:

// Create a translatable message with arguments
var message = new TranslatableMessage("order.status.changed")
    .WithArg("orderId", "ORD-123")
    .WithArg("oldStatus", "Pending")
    .WithArg("newStatus", "Shipped");

// Serialize to JSON for database storage (compact property names)
var json = JsonSerializer.Serialize(message, TranslatableMessageSerializerContext.Default.TranslatableMessage);
// {"c":"order.status.changed","a":{"orderId":"ORD-123","oldStatus":"Pending","newStatus":"Shipped"}}

// Translate at read time using ResourceManager
var translated = message.Translate(resourceManager, CultureInfo.CurrentUICulture);
// "Order ORD-123 changed from Pending to Shipped"

WithArg is immutable — each call returns a new TranslatableMessage instance. TranslatableMessageSerializerContext provides AOT-compatible JSON serialization with WhenWritingNull to omit null Args.

TranslatableMessage equality compares Args the same way Error compares MessageArgs: structurally and order-independently, with null and an empty dictionary treated as equivalent. Values built through WithArg are safe as HashSet<TranslatableMessage> entries.

License

This project is licensed under the MIT License.

Product Compatible and additional computed target framework versions.
.NET 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.
  • net10.0

    • No dependencies.

NuGet packages (4)

Showing the top 4 NuGet packages that depend on Fluens.Kernel:

Package Downloads
Fluens.Web

ASP.NET Core base types for Fluens web libraries: Result-to-HTTP mapping, global exception handler, and paged responses.

Fluens.Messaging

Inbox/outbox messaging pattern for modular applications.

Fluens.Cqrs

CQRS pattern implementation with command/query dispatchers, handler interfaces, and logging/tracing decorators.

Fluens.Messaging.Sagas

Process Manager / Saga abstraction on top of Fluens.Messaging.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.7.9 65 9/11/2026
0.7.8 88 9/10/2026
0.7.7 91 9/10/2026
0.7.6 300 7/1/2026
0.7.5 281 6/22/2026
0.7.4 295 6/18/2026
0.7.2 271 6/18/2026
0.7.1 290 6/18/2026
0.6.6 349 3/11/2026
0.6.5 193 3/4/2026
0.6.4 183 3/4/2026
0.6.3 194 3/3/2026
0.6.2 198 3/2/2026
0.6.1 198 3/2/2026
0.6.0 191 3/1/2026
0.5.7 199 3/1/2026
0.5.6 191 3/1/2026
0.5.5 195 2/28/2026
0.5.4 195 2/28/2026
0.5.3 198 2/27/2026
Loading failed