Cocoar.Capabilities 0.4.0-alpha.0

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

Cocoar.Capabilities

A general-purpose capabilities system for .NET that enables type-safe, composable capability attachment to any object. Perfect for cross-project extensibility without circular dependencies.

.NET 9.0 Tests Coverage

๐ŸŽฏ What is it?

Cocoar.Capabilities allows you to attach typed capabilities to any object and retrieve them later in a type-safe manner. Think of it as a strongly-typed, high-performance property bag that enables cross-project extensibility patterns.

This library implements Capability Composition: a component/extension-object style where a subject carries a typed, immutable bag of capabilities (policies/behaviors). It's composition-over-inheritance, with exact-type lookups and cross-project contributions.

Architecture Pattern

The design maps to well-established patterns:

  • Subject โ†” Host object (any class)
  • Capability โ†” Component/extension/role (behavior or policy)
  • CapabilityBag โ†” Extension registry on the host
  • AddAs<TContract>() โ†” Register under an extension interface
  • Primary marker โ†” Ensures exactly one "role of kind Primary" is present

Related Patterns: Extension Object, Role Object, Component-Based Design, Strategy/Policy, Composition over Inheritance

Key Benefits

  • ๐Ÿ”’ Type Safe: Compile-time guarantees for capability-subject relationships
  • โšก Zero Allocation: Optimized hot paths with Array.Empty<T>() and type-safe casting
  • ๐Ÿงต Thread Safe: Immutable by design - no locks needed
  • ๐Ÿ”Œ Extensible: Any project can define capabilities for any subject type
  • ๐Ÿ“ฆ Lightweight: Zero dependencies, AOT-friendly

๐Ÿš€ Quick Start

Installation

# When published to NuGet
dotnet add package Cocoar.Capabilities

# For now, reference the project directly
<ProjectReference Include="path/to/Cocoar.Capabilities/Cocoar.Capabilities.csproj" />

Basic Usage

using Cocoar.Capabilities;

// 1. Define capabilities for your domain
public record LogLevelCapability<T>(LogLevel Level) : ICapability<T>;
public record CacheCapability<T>(TimeSpan Ttl) : ICapability<T>;

// 2. Create a capability bag
var userService = new UserService();
var bag = Composer.For(userService)
    .Add(new LogLevelCapability<UserService>(LogLevel.Debug))
    .Add(new CacheCapability<UserService>(TimeSpan.FromMinutes(5)))
    .Build();

// 3. Use capabilities later
if (bag.TryGet<LogLevelCapability<UserService>>(out var logLevel))
{
    logger.SetLevel(logLevel.Level);
}

if (bag.TryGet<CacheCapability<UserService>>(out var cache))
{
    ConfigureCache(cache.Ttl);
}

๐Ÿ“š Core Concepts

1. Capabilities

A capability represents a piece of functionality or configuration that can be attached to a subject:

// Generic capability - works with any subject type T
public record MyCapability<T>(string Value) : ICapability<T>;

// Specific capability - only works with UserService
public record UserCapability(int UserId) : ICapability<UserService>;

2. Subjects

A subject is any object that can have capabilities attached. No special interfaces or base classes required:

// Any class can be a subject
public class UserService { }
public class DatabaseConfig { }
public class PaymentProcessor { }

3. Capability Bags

A capability bag is an immutable container that stores capabilities for a specific subject:

var bag = Composer.For(myObject)
    .Add(new SomeCapability<MyObject>("value"))
    .Add(new AnotherCapability<MyObject>(42))
    .Build(); // Immutable from this point

๐Ÿ”ง API Reference

Building Capability Bags

// Create a builder for any object
var builder = Composer.For(myObject);

// Add capabilities by concrete type
builder.Add(new MyCapability<MyObject>("value"));

// Add capabilities by interface/contract (for exact-type retrieval)
builder.AddAs<IMyCapability<MyObject>>(new ConcreteCapability<MyObject>());

// Build immutable bag (one-shot operation)
var bag = builder.Build();

// โŒ This throws - builder is unusable after Build()
var bag2 = builder.Build(); // InvalidOperationException

Retrieving Capabilities

var bag = /* ... built bag ... */;

// Try to get a capability (safe)
if (bag.TryGet<MyCapability<MyObject>>(out var capability))
{
    Console.WriteLine(capability.Value);
}

// Get required capability (throws if missing)
var required = bag.GetRequired<MyCapability<MyObject>>();

// Get all capabilities of a type
var allCapabilities = bag.GetAll<MyCapability<MyObject>>();

// Check if capability exists
bool exists = bag.Contains<MyCapability<MyObject>>();

// Count capabilities of a type
int count = bag.Count<MyCapability<MyObject>>();

// Total capability count across all types
int total = bag.TotalCapabilityCount;

Convenience Extensions

// Execute action only if capability exists
bag.Use<MyObject, MyCapability<MyObject>>(cap => 
{
    Console.WriteLine($"Found: {cap.Value}");
});

// Transform capability value (returns default if missing)
var result = bag.Transform<MyObject, MyCapability<MyObject>, string>(cap => 
    cap.Value.ToUpper());

๐ŸŽฏ Real-World Examples

Configuration System

// Define configuration-specific capabilities
public record ExposeAsCapability<T>(Type ContractType) : ICapability<T>;
public record SingletonLifetimeCapability<T> : ICapability<T>;
public record HealthCheckCapability<T>(string Name) : ICapability<T>;

// Create configuration with capabilities
var dbConfig = new DatabaseConfig { ConnectionString = "..." };
var configBag = Composer.For(dbConfig)
    .Add(new ExposeAsCapability<DatabaseConfig>(typeof(IDbConfig)))
    .Add(new SingletonLifetimeCapability<DatabaseConfig>())
    .Add(new HealthCheckCapability<DatabaseConfig>("database"))
    .Build();

// Process capabilities in your DI registration code
if (configBag.Contains<SingletonLifetimeCapability<DatabaseConfig>>())
{
    services.AddSingleton(configBag.Subject);
}

foreach (var expose in configBag.GetAll<ExposeAsCapability<DatabaseConfig>>())
{
    services.AddSingleton(expose.ContractType, _ => configBag.Subject);
}

Plugin Architecture

// Plugin can automatically contribute capabilities
public class SecurityPlugin<T> : ICapabilityPlugin<T>
{
    public void ContributeCapabilities(CapabilityBagBuilder<T> builder)
    {
        builder.Add(new AuthenticationCapability<T>());
        builder.Add(new AuthorizationCapability<T>("DefaultPolicy"));
    }
}

// Host discovers and applies plugins
var bag = PluginSystem.CreateWithPlugins(myObject)
    .Add(new CustomCapability<MyObject>()) // Your own capabilities
    .Build();

Web Framework Integration

// Declarative endpoint configuration
var controllerBag = Composer.For(new UsersController())
    .Add(new RouteCapability<UsersController>("/api/users"))
    .Add(new AuthorizeCapability<UsersController>("AdminPolicy"))
    .Add(new RateLimitCapability<UsersController>(100))
    .Build();

// Framework processes capabilities automatically
ProcessWebCapabilities(controllerBag);

๐Ÿ“– See Advanced Examples for complete implementations including:

  • Primary Capability Pattern - Extensible "exactly one" constraint system
  • Cross-Assembly Plugin Architecture - Automatic capability discovery
  • Complete Configuration System - Full DI integration with validation and health checks
  • Web Framework Integration - Declarative routing, auth, caching, and rate limiting
  • Event-Driven Architecture - Handler orchestration with ordering and error handling

โšก Advanced Features

Ordered Capabilities

Capabilities can implement IOrderedCapability for predictable ordering:

public record PriorityCapability<T>(int Priority, string Name) : ICapability<T>, IOrderedCapability
{
    public int Order => Priority; // Lower values run first
}

var bag = Composer.For(myObject)
    .Add(new PriorityCapability<MyObject>(100, "Last"))
    .Add(new PriorityCapability<MyObject>(1, "First"))  
    .Add(new PriorityCapability<MyObject>(50, "Middle"))
    .Build();

var ordered = bag.GetAll<PriorityCapability<MyObject>>();
// Returns: ["First", "Middle", "Last"]

Contract-Based Retrieval

Use AddAs<T>() when you need to retrieve capabilities by interface:

public interface IValidationCapability<T> : ICapability<T>
{
    bool IsValid { get; }
}

public record EmailValidationCapability<T> : IValidationCapability<T>
{
    public bool IsValid => true;
}

var bag = Composer.For(myObject)
    // Register concrete type under interface contract
    .AddAs<IValidationCapability<MyObject>>(new EmailValidationCapability<MyObject>())
    .Build();

// Retrieve by interface
var validator = bag.GetRequired<IValidationCapability<MyObject>>();

Cross-Project Extensibility

Different projects can add capabilities to the same subject without dependencies:

// Core.dll - Defines the subject
namespace MyApp.Core
{
    public class UserService { }
}

// DI.dll - Adds DI capabilities  
namespace MyApp.DI
{
    public record SingletonCapability<T> : ICapability<T>;
}

// Web.dll - Adds web capabilities
namespace MyApp.Web  
{
    public record RouteCapability<T>(string Template) : ICapability<T>;
}

// Composition.dll - Composes everything
var userService = new UserService();
var bag = Composer.For(userService)
    .Add(new SingletonCapability<UserService>())  // From DI.dll
    .Add(new RouteCapability<UserService>("/api/users"))  // From Web.dll
    .Build();

๐Ÿšจ Important Notes

Exact Type Matching

The system uses exact type matching - it only finds capabilities registered under the exact same type:

// โŒ This won't work
builder.Add(new ConcreteCapability<T>());
bag.TryGet<ICapability<T>>(out _); // Returns false!

// โœ… Use AddAs<T> for interface retrieval
builder.AddAs<ICapability<T>>(new ConcreteCapability<T>());
bag.TryGet<ICapability<T>>(out _); // Returns true!

Builder Lifecycle

Builders are single-use - they become unusable after Build():

var builder = Composer.For(myObject);
var bag1 = builder.Build();  // โœ… Works
var bag2 = builder.Build();  // โŒ Throws InvalidOperationException

Thread Safety

  • Capability Bags: Thread-safe (immutable)
  • Builders: NOT thread-safe (single-threaded use only)
var bag = builder.Build(); // Thread-safe from this point

// โœ… Safe to use from multiple threads
Task.Run(() => bag.TryGet<MyCapability<T>>(out _));
Task.Run(() => bag.GetAll<MyCapability<T>>());

๐Ÿ”ง Integration Patterns

For Library Authors

When creating a library that uses Cocoar.Capabilities:

  1. Define your domain capabilities:
namespace MyLibrary.Capabilities
{
    public record MyLibraryCapability<T>(string Setting) : ICapability<T>;
    public record AnotherCapability<T>(int Value) : ICapability<T>;
}
  1. Accept capability bags in your APIs:
public void Configure<T>(ICapabilityBag<T> capabilityBag)
{
    // Process capabilities to configure behavior
    if (capabilityBag.TryGet<MyLibraryCapability<T>>(out var cap))
    {
        // Apply configuration
    }
}
  1. Provide fluent builders (optional):
public static class MyLibraryExtensions
{
    public static CapabilityBagBuilder<T> WithMyFeature<T>(
        this CapabilityBagBuilder<T> builder, string setting)
    {
        return builder.Add(new MyLibraryCapability<T>(setting));
    }
}

// Usage:
var bag = Composer.For(myObject)
    .WithMyFeature("custom-setting")  // Your extension method
    .Build();

For Consumers

When using a library that accepts capability bags:

  1. Create capability bags for your objects:
var myObject = new MyClass();
var bag = Composer.For(myObject)
    .Add(new RequiredCapability<MyClass>("value"))
    .Add(new OptionalCapability<MyClass>(42))
    .Build();
  1. Pass to library APIs:
library.Configure(bag);

๐Ÿงช Testing

The library includes comprehensive tests covering all scenarios:

  • Comprehensive unit tests covering core functionality, edge cases, and performance
  • 100% coverage on core types
  • Thread safety tests for concurrent access
  • Performance tests validating zero-allocation claims

Run tests:

dotnet test

๐Ÿ“ฆ Project Structure

Cocoar.Capabilities/
โ”œโ”€โ”€ ICapability.cs              # Core capability interfaces
โ”œโ”€โ”€ ICapabilityBag.cs           # Capability bag contract
โ”œโ”€โ”€ CapabilityBag.cs            # Implementation with Dictionary<Type, Array>
โ”œโ”€โ”€ CapabilityBagBuilder.cs     # Fluent builder with one-shot lifecycle
โ”œโ”€โ”€ IOrderedCapability.cs       # Ordering support
โ”œโ”€โ”€ Composer.cs                 # Helper factory methods
โ””โ”€โ”€ Extensions/
    โ””โ”€โ”€ CapabilityBagExtensions.cs # Convenience extension methods

๐Ÿค Contributing

Contributions welcome! The library is designed to be:

  • Stable: Core APIs are locked and won't change
  • Extensible: New features can be added without breaking existing code
  • Well-tested: All changes require comprehensive tests

๐Ÿ“„ License

Apache-2.0 License - Use it anywhere, commercial or personal.


๐ŸŽฏ What's Next?

This library provides the foundation for capability-driven architectures. Build your own domain-specific wrapper APIs on top for the best developer experience!

Example integration: Check out how Cocoar.Configuration uses this library to provide type-safe configuration management with capabilities.

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 was computed.  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.
  • net9.0

    • No dependencies.

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
0.10.0 58 10/3/2025
0.7.0 114 9/30/2025
0.6.0 157 9/29/2025
0.5.0 157 9/29/2025
0.4.0 154 9/29/2025
0.4.0-alpha.0 113 9/28/2025
0.2.0-alpha.0 90 9/28/2025

See CHANGELOG.md for detailed release notes.