--- url: /guide/scope-context/anchors-api.md --- # Anchors API While the [Owner API](/guide/scope-context/owner-api) gives a scope a single identity, you often need to associate *additional* context objects — a database connection, a cache, an environment context. Anchors let you attach multiple named or typed objects to the scope and compose capabilities onto them. Two kinds exist: **typed anchors** (keyed by type, one per type) and **named anchors** (keyed by string, for multiple instances of the same type). ## Typed Anchors ### Setting ```csharp var db = new DatabaseConnection("connstr"); scope.Anchors.Set(db); ``` ### Getting ```csharp // Get — returns null if not set or collected var db = scope.Anchors.Get(); // GetOrThrow — throws if not set or collected var db = scope.Anchors.GetOrThrow(); // TryGet — out-parameter pattern if (scope.Anchors.TryGet(out var db)) { db.Query("SELECT 1"); } ``` ## Named Anchors ### Setting ```csharp scope.Anchors.Set("primary-db", primaryDb); scope.Anchors.Set("replica-db", replicaDb); ``` ### Getting ```csharp // Get — returns null if not set or collected var db = scope.Anchors.Get("primary-db"); // GetOrThrow — throws if not set or collected var db = scope.Anchors.GetOrThrow("primary-db"); // TryGet — out-parameter pattern if (scope.Anchors.TryGet("primary-db", out var db)) { // use db } ``` ## Composing via Anchors ### Typed ```csharp scope.Anchors.Set(db); scope.Anchors.ComposeFor() .Add(new PoolingCapability { MaxSize = 10 }) .Build(); ``` ### Named ```csharp scope.Anchors.Set("cache", cache); scope.Anchors.Compose("cache") .Add(new ExpirationCapability { Ttl = TimeSpan.FromMinutes(5) }) .Build(); ``` ## Retrieving Compositions ### Typed ```csharp Composition? comp = scope.Anchors.GetCompositionFor(); Composition required = scope.Anchors.GetRequiredCompositionFor(); if (scope.Anchors.TryGetCompositionFor(out var comp)) { // use composition } ``` ### Named ```csharp Composition? comp = scope.Anchors.GetComposition("cache"); Composition required = scope.Anchors.GetRequiredComposition("cache"); if (scope.Anchors.TryGetComposition("cache", out var comp)) { // use composition } ``` ## Retrieving Composers ### Typed ```csharp Composer? c = scope.Anchors.GetComposerFor(); Composer required = scope.Anchors.GetRequiredComposerFor(); if (scope.Anchors.TryGetComposerFor(out var composer)) { // composer still in registry } ``` ### Named ```csharp Composer? c = scope.Anchors.GetComposer("cache"); Composer required = scope.Anchors.GetRequiredComposer("cache"); if (scope.Anchors.TryGetComposer("cache", out var composer)) { // use composer } ``` ## Fluent Chaining Anchors methods return `ScopeAnchorsApi`: ```csharp scope.Anchors .Set(db) .Set("cache", cache) .Scope // return to scope .Compose("something") .Build(); ``` ## Weak References Like the Owner API, anchors are stored as weak references. Anchored objects can be garbage collected without the scope preventing it. ## Typed vs Named — When to Use | Use Case | Typed | Named | |----------|-------|-------| | One instance per type | Yes | — | | Multiple instances of same type | — | Yes | | Compile-time type safety | Yes | — | | Dynamic/runtime keys | — | Yes | --- --- url: /reference/api.md --- # API Overview Complete method reference for all public types in `Cocoar.Capabilities`. ## CapabilityScope ```csharp public class CapabilityScope : IDisposable ``` | Member | Type | Description | |--------|------|-------------| | `CapabilityScope(options?)` | Constructor | Create scope with optional configuration | | `Compose(subject, useRegistry?)` | Method | Create a `Composer` for the subject | | `Recompose(composition, useRegistry?)` | Method | Create a `Composer` from an existing composition | | `Composers` | Property | `ComposerRegistryApi` — access the composer registry | | `Compositions` | Property | `CompositionRegistryApi` — access the composition registry | | `Owner` | Property | `ScopeOwnerApi` — owner context management | | `Anchors` | Property | `ScopeAnchorsApi` — anchors context management | | `Dispose()` | Method | Clean up registry resources | ## CapabilityScope\ ```csharp public class CapabilityScope : CapabilityScope where TOwner : class ``` | Member | Type | Description | |--------|------|-------------| | `CapabilityScope(owner)` | Constructor | Create scope with typed owner | | `CapabilityScope(owner, options)` | Constructor | Create scope with typed owner and options | | `Owner` | Property | `ScopeOwnerApi` — typed owner API | ## Composer ```csharp public sealed class Composer ``` | Member | Signature | Description | |--------|-----------|-------------| | `Subject` | `object` | The subject being composed | | `Add` | `Add(object, int?)` | Add capability with optional order | | `Add` | `Add(object, Func)` | Add with order selector | | `AddAs` | `AddAs(object, int?)` | Add under contract type | | `AddAs` | `AddAs(object, Func)` | Add under contract with order selector | | `TryAdd` | `TryAdd(T, int?)` | Add if type not already present | | `TryAdd` | `TryAdd(T, Func)` | Try-add with order selector | | `TryAddAs` | `TryAddAs(object, int?)` | Try-add under contract | | `TryAddAs` | `TryAddAs(object, Func)` | Try-add under contract with order selector | | `WithPrimary` | `WithPrimary(IPrimaryCapability?)` | Set or clear primary capability | | `Has` | `bool` | Check if capability type exists | | `HasPrimary` | `bool` | Check if primary is set | | `RemoveWhere` | `RemoveWhere(Func)` | Remove matching capabilities | | `Build` | `Build(bool?)` | Create immutable composition | All mutation methods return `Composer` for fluent chaining. ## IComposition ```csharp public interface IComposition ``` ### Properties | Property | Type | Description | |----------|------|-------------| | `Subject` | `object` | The subject this composition belongs to | | `TotalCapabilityCount` | `int` | Total capabilities across all types | ### Query Methods | Method | Returns | Throws | Description | |--------|---------|--------|-------------| | `GetAll()` | `IReadOnlyList` | — | All capabilities of type | | `GetAll()` | `IReadOnlyList` | — | All capabilities | | `GetFirstOrDefault()` | `T?` | — | First or null | | `GetLastOrDefault()` | `T?` | — | Last or null | | `GetRequiredFirst()` | `T` | `InvalidOperationException` | First or throw | | `GetRequiredLast()` | `T` | `InvalidOperationException` | Last or throw | | `TryGetFirst(out T)` | `bool` | — | Try-pattern first | | `TryGetLast(out T)` | `bool` | — | Try-pattern last | | `Has()` | `bool` | — | Has any of type | | `Count()` | `int` | — | Count of type | ### Primary Capability Methods | Method | Returns | Throws | Description | |--------|---------|--------|-------------| | `HasPrimary()` | `bool` | — | Has any primary | | `HasPrimary()` | `bool` | — | Has primary of type | | `GetPrimary()` | `IPrimaryCapability` | `InvalidOperationException` | Get primary or throw | | `GetPrimaryOrDefault()` | `IPrimaryCapability?` | — | Get primary or null | | `TryGetPrimary(out)` | `bool` | — | Try-pattern | | `GetRequiredPrimaryAs()` | `T` | `InvalidOperationException` | Typed primary or throw | | `GetPrimaryOrDefaultAs()` | `T?` | — | Typed primary or null | | `TryGetPrimaryAs(out T)` | `bool` | — | Typed try-pattern | ## Using\* Extensions Extension methods on `IComposition` (namespace: `Cocoar.Capabilities`). | Method | Returns | Description | |--------|---------|-------------| | `UsingFirst(Action)` | `IComposition` | Use first capability (throws if missing) | | `UsingFirst(Func)` | `R` | Transform first capability | | `UsingFirstOrDefault(Action)` | `IComposition` | Use first if exists | | `UsingLast(Action)` | `IComposition` | Use last capability (throws if missing) | | `UsingLast(Func)` | `R` | Transform last capability | | `UsingLastOrDefault(Action)` | `IComposition` | Use last if exists | | `UsingEach(Action)` | `IComposition` | Apply to each capability | | `UsingEach(Func)` | `IReadOnlyList` | Transform each capability | | `UsingAll(Action>)` | `IComposition` | Use full collection | | `UsingAll(Func, R>)` | `R` | Transform full collection | ## ScopeOwnerApi | Method | Returns | Description | |--------|---------|-------------| | `Set(object)` | `ScopeOwnerApi` | Set owner (throws if already set) | | `Replace(object)` | `ScopeOwnerApi` | Replace owner | | `Get()` | `T?` | Get owner or null if not set, collected, or wrong type | | `GetOrThrow()` | `T` | Get owner or throw (`InvalidOperationException` / `InvalidCastException`) | | `TryGet(out T?)` | `bool` | Try-pattern get | | `Compose(useRegistry?)` | `Composer` | Compose on owner | | `ComposeFor(useRegistry?)` | `Composer` | Typed compose on owner | | `GetComposition()` | `Composition?` | Get owner's composition | | `GetRequiredComposition()` | `Composition` | Get required or throw | | `TryGetComposition(out)` | `bool` | Try-pattern composition | | `GetCompositionFor()` | `Composition?` | Typed composition | | `GetRequiredCompositionFor()` | `Composition` | Typed required or throw | | `GetComposer()` | `Composer?` | Get owner's composer | | `GetRequiredComposer()` | `Composer` | Get required or throw | | `TryGetComposer(out)` | `bool` | Try-pattern composer | | `GetComposerFor()` | `Composer?` | Typed composer | | `GetRequiredComposerFor()` | `Composer` | Typed required or throw | | `Scope` | `CapabilityScope` | Return to scope | ## ScopeOwnerApi\ | Method | Returns | Description | |--------|---------|-------------| | `Get()` | `TOwner` | Get typed owner | | `TryGet(out TOwner?)` | `bool` | Try-pattern get | | `Compose(useRegistry?)` | `Composer` | Compose on owner | | `GetComposition()` | `Composition?` | Get composition | | `GetRequiredComposition()` | `Composition` | Required or throw | | `TryGetComposition(out)` | `bool` | Try-pattern composition | | `GetComposer()` | `Composer?` | Get composer | | `GetRequiredComposer()` | `Composer` | Required or throw | | `TryGetComposer(out)` | `bool` | Try-pattern composer | | `Scope` | `CapabilityScope` | Return to scope | ## ScopeAnchorsApi ### Anchor Management | Method | Returns | Description | |--------|---------|-------------| | `Set(T)` | `ScopeAnchorsApi` | Set typed anchor | | `Set(string, object)` | `ScopeAnchorsApi` | Set named anchor | | `Get()` | `T?` | Get typed anchor or null | | `GetOrThrow()` | `T` | Get typed anchor or throw | | `TryGet(out T?)` | `bool` | Typed try-pattern | | `Get(string)` | `object?` | Get named anchor or null | | `GetOrThrow(string)` | `object` | Get named anchor or throw | | `TryGet(string, out object?)` | `bool` | Named try-pattern | | `Scope` | `CapabilityScope` | Return to scope | ### Composition/Composer Access (typed) | Method | Returns | Description | |--------|---------|-------------| | `ComposeFor(useRegistry?)` | `Composer` | Compose on typed anchor | | `GetCompositionFor()` | `Composition?` | Get composition | | `GetRequiredCompositionFor()` | `Composition` | Required or throw | | `TryGetCompositionFor(out)` | `bool` | Try-pattern | | `GetComposerFor()` | `Composer?` | Get composer | | `GetRequiredComposerFor()` | `Composer` | Required or throw | | `TryGetComposerFor(out)` | `bool` | Try-pattern | ### Composition/Composer Access (named) | Method | Returns | Description | |--------|---------|-------------| | `Compose(string, useRegistry?)` | `Composer` | Compose on named anchor | | `GetComposition(string)` | `Composition?` | Get composition | | `GetRequiredComposition(string)` | `Composition` | Required or throw | | `TryGetComposition(string, out)` | `bool` | Try-pattern | | `GetComposer(string)` | `Composer?` | Get composer | | `GetRequiredComposer(string)` | `Composer` | Required or throw | | `TryGetComposer(string, out)` | `bool` | Try-pattern | ## CompositionRegistryApi | Method | Returns | Description | |--------|---------|-------------| | `TryGet(T, out IComposition)` | `bool` | Try-get by typed subject | | `TryGet(object, out IComposition)` | `bool` | Try-get by subject | | `GetOrDefault(T)` | `IComposition?` | Get or null | | `GetOrDefault(object)` | `IComposition?` | Get or null | | `GetRequired(T)` | `IComposition` | Get or throw | | `GetRequired(object)` | `IComposition` | Get or throw | | `Remove(T)` | `bool` | Remove by typed subject | | `Remove(object)` | `bool` | Remove by subject | ## ComposerRegistryApi | Method | Returns | Description | |--------|---------|-------------| | `TryGet(T, out Composer?)` | `bool` | Try-get composer | | `GetOrDefault(T)` | `Composer?` | Get or null | | `GetRequired(T)` | `Composer` | Get or throw | | `Register(T, Composer, bool)` | `void` | Register composer | | `Remove(T)` | `bool` | Remove composer | ## Other Types ### IPrimaryCapability ```csharp public interface IPrimaryCapability { } ``` Marker interface for primary capabilities. ### ISubjectKeyMapper ```csharp public interface ISubjectKeyMapper { bool CanHandle(Type subjectType); object Map(object subject); } ``` ### CapabilityScopeOptions ```csharp public record CapabilityScopeOptions { public bool UseComposerRegistry { get; init; } = true; public bool UseCompositionRegistry { get; init; } = true; public IEnumerable? SubjectKeyMappers { get; init; } } ``` ### ReadOnlyListExtensions ```csharp public static void ForEach(this IReadOnlyList list, Action action) ``` Zero-allocation iteration helper. ## Thread Safety | Type | Thread-Safe? | Notes | |------|-------------|-------| | `CapabilityScope` | Yes | Registries use concurrent collections | | `Composer` | No | Use within a single thread, then `Build()` | | `IComposition` | Yes | Fully immutable after creation | | `ScopeOwnerApi` | Yes | Weak reference access is thread-safe | | `ScopeAnchorsApi` | Yes | Dictionary access is thread-safe | --- --- url: /guide/core/capability-scope.md --- # CapabilityScope ## What Is a Scope? A `CapabilityScope` is the container that manages all capability compositions. It holds two registries (composers and compositions) and provides the entry points for composing and querying capabilities. ## Creating a Scope ```csharp // Default — both registries enabled var scope = new CapabilityScope(); // With options var scope = new CapabilityScope(new CapabilityScopeOptions { UseComposerRegistry = false, UseCompositionRegistry = true }); ``` ## Composing Capabilities `Compose()` creates a `Composer` for a given subject: ```csharp var composer = scope.Compose("my-subject"); composer.Add(new SomeCapability()).Build(); ``` This registers the subject in the composer registry (if enabled), then `Build()` stores the resulting composition in the composition registry. If you call `Compose()` on a subject that already has a composition, the new composition **replaces** the existing one in the registry. Use `Recompose()` instead when you want to build on existing capabilities. ::: warning Subject lifetime When using a reference-type subject, the scope stores it via `ConditionalWeakTable` — if the subject is garbage collected, the composition silently disappears. Always keep a reference to your subject: ```csharp // Bad — temporary object, no reference held scope.Compose(new MyService()).Add(cap).Build(); // composition may vanish after GC // Good — reference kept alive var service = new MyService(); scope.Compose(service).Add(cap).Build(); // composition lives as long as service ``` See [Registries](/guide/advanced/registries) for details on reference vs value type storage. ::: ## Recomposing Use `Recompose()` to create a new composition based on an existing one — it inherits all capabilities from the original: ```csharp var existing = scope.Compositions.GetRequired("my-subject"); scope.Recompose(existing) .Add(new AnotherCapability()) .Build(); ``` The new composition replaces the old one in the registry. The difference from calling `Compose()` again: `Recompose()` carries over all existing capabilities, while `Compose()` starts from scratch. ## Context Isolation Different scopes are completely independent worlds: ```csharp var scopeA = new CapabilityScope(); var scopeB = new CapabilityScope(); scopeA.Compose("user").Add(new CapA()).Build(); scopeB.Compose("user").Add(new CapB()).Build(); // scopeA only has CapA for "user" // scopeB only has CapB for "user" ``` ## Scope Lifetime Scopes are typically long-lived — created once and shared. Common patterns: * **Application-wide**: One scope for the entire app * **Per-tenant**: Isolated scope per tenant * **Per-test**: Fresh scope per test for isolation `CapabilityScope` implements `IDisposable` to clean up registry resources. ## Properties | Property | Type | Description | |----------|------|-------------| | `Composers` | `ComposerRegistryApi` | Access the composer registry | | `Compositions` | `CompositionRegistryApi` | Access the composition registry | | `Owner` | `ScopeOwnerApi` | Associate a single owner with the scope | | `Anchors` | `ScopeAnchorsApi` | Associate typed or named anchors with the scope | ## Scope Flow ```mermaid flowchart LR S[CapabilityScope] -->|Compose| C[Composer] C -->|Add| C C -->|Build| CO[Composition] CO -->|stored in| R[Registry] R -->|GetRequired| CO ``` --- --- url: /guide/core/composer.md --- # Composer The `Composer` is the fluent builder that attaches capabilities to a subject. You get one from `scope.Compose(subject)`. ## Adding Capabilities ```csharp scope.Compose(myService) .Add(new LoggingCapability()) .Add(new MetricsCapability()) .Build(); ``` Any object can be a capability — no base class or interface required. ## Adding with Order ```csharp scope.Compose(myService) .Add(new ValidationStep(), order: 100) .Add(new AuthStep(), order: 200) .Add(new LoggingStep(), order: 300) .Build(); ``` ## Adding with Order Selector ```csharp scope.Compose(pipeline) .Add(new Step { Priority = 10 }, c => ((Step)c).Priority) .Build(); ``` ## Primary Capability Set the "identity" of a composition. Only one primary is allowed: ```csharp scope.Compose(myService) .WithPrimary(new ServiceIdentity("Auth", "1.0")) .Add(new LoggingCapability()) .Build(); ``` The primary must implement `IPrimaryCapability`. Call `WithPrimary(null)` to clear. ## AddAs — Contract Registration Register a capability under a specific contract type: ```csharp // Single contract scope.Compose(host) .AddAs(new EmailPlugin()) .Build(); // Multiple contracts with tuple syntax scope.Compose(host) .AddAs<(IPlugin, IHealthCheck)>(new EmailPlugin()) .Build(); ``` ## TryAdd — Conditional Registration Only adds if no capability of that type exists yet: ```csharp scope.Compose(myService) .TryAdd(new DefaultLogger()) // Added .TryAdd(new CustomLogger()) // Skipped — DefaultLogger already present .Build(); ``` `TryAddAs` checks against the contract type instead. ## Pre-Build Checks ```csharp var composer = scope.Compose(myService) .Add(new LoggingCapability()); if (composer.Has()) // true Console.WriteLine("Logging configured"); if (composer.HasPrimary()) // false Console.WriteLine("Has identity"); ``` ## RemoveWhere Selectively remove capabilities before building: ```csharp scope.Compose(myService) .Add(new CapA()) .Add(new CapB()) .RemoveWhere(c => c is CapB) .Build(); // Only CapA in the result ``` ## Build `Build()` creates an immutable `IComposition` and optionally stores it in the registry: ```csharp var composition = scope.Compose(subject) .Add(new SomeCapability()) .Build(); // uses scope default registry setting var composition = scope.Compose(subject) .Add(new SomeCapability()) .Build(useRegistry: false); // skip registry — use the returned composition directly ``` Use `useRegistry: false` when you need a one-off composition that you pass directly to a consumer, without polluting the scope's registry. --- --- url: /guide/core/composition.md --- # Composition An `IComposition` is the immutable, thread-safe result of building capabilities. Once created, it cannot be modified — only replaced via recomposition. ## Querying Capabilities ### GetAll ```csharp IReadOnlyList plugins = composition.GetAll(); IReadOnlyList everything = composition.GetAll(); ``` ### First and Last ```csharp var first = composition.GetFirstOrDefault(); // null if none var last = composition.GetLastOrDefault(); // null if none ``` ### Required Queries (throw if missing) ```csharp var first = composition.GetRequiredFirst(); // throws InvalidOperationException var last = composition.GetRequiredLast(); // throws InvalidOperationException ``` ### Try-Pattern ```csharp if (composition.TryGetFirst(out var plugin)) { plugin.Initialize(); } if (composition.TryGetLast(out var logger)) { logger.Log("ready"); } ``` ### Has and Count ```csharp bool hasPlugins = composition.Has(); int pluginCount = composition.Count(); ``` ## Primary Capability Queries ```csharp // Check existence bool hasPrimary = composition.HasPrimary(); bool hasTyped = composition.HasPrimary(); // Get IPrimaryCapability? primary = composition.GetPrimaryOrDefault(); IPrimaryCapability required = composition.GetPrimary(); // throws if none // Typed access if (composition.TryGetPrimaryAs(out var identity)) { Console.WriteLine(identity.Name); } var id = composition.GetRequiredPrimaryAs(); // throws if missing/wrong type var idOrNull = composition.GetPrimaryOrDefaultAs(); ``` ## Properties | Property | Type | Description | |----------|------|-------------| | `Subject` | `object` | The subject this composition belongs to | | `TotalCapabilityCount` | `int` | Total number of capabilities (across all types) | ## Thread Safety Compositions are fully immutable after `Build()`. They can be safely shared across threads, cached, passed between components, and stored in concurrent collections without synchronization. --- --- url: /reference/options.md --- # Configuration Options ## CapabilityScopeOptions Configuration for `CapabilityScope` behavior, passed at construction time. ```csharp var scope = new CapabilityScope(new CapabilityScopeOptions { UseComposerRegistry = true, UseCompositionRegistry = true, SubjectKeyMappers = null }); ``` ## Properties ### UseComposerRegistry | | | |---|---| | Type | `bool` | | Default | `true` | | Description | When `true`, `Compose()` automatically stores the `Composer` in the registry for later retrieval via `scope.Composers`. | ### UseCompositionRegistry | | | |---|---| | Type | `bool` | | Default | `true` | | Description | When `true`, `Build()` automatically stores the `IComposition` in the registry for later retrieval via `scope.Compositions`. | ### SubjectKeyMappers | | | |---|---| | Type | `IEnumerable?` | | Default | `null` | | Description | Custom key mappers for subject canonicalization. When set, subjects are passed through matching mappers before being used as registry keys. | ## Configuration Examples ### Default (all registries on) ```csharp var scope = new CapabilityScope(); ``` ### Composition-only (no composer caching) ```csharp var scope = new CapabilityScope(new CapabilityScopeOptions { UseComposerRegistry = false }); ``` ### No registries (fire-and-forget) ```csharp var scope = new CapabilityScope(new CapabilityScopeOptions { UseComposerRegistry = false, UseCompositionRegistry = false }); ``` ### Custom key mapping ```csharp var scope = new CapabilityScope(new CapabilityScopeOptions { SubjectKeyMappers = new ISubjectKeyMapper[] { new CaseInsensitiveEmailMapper() } }); ``` --- --- url: /guide/advanced/custom-key-mapping.md --- # Custom Key Mapping By default, subjects are used directly as registry keys. String subjects are mapped to a `StringSubjectKey` for consistent handling. You can customize this mapping for specific subject types. ## ISubjectKeyMapper Interface ```csharp public interface ISubjectKeyMapper { bool CanHandle(Type subjectType); object Map(object subject); } ``` ## Registering Mappers ```csharp var scope = new CapabilityScope(new CapabilityScopeOptions { SubjectKeyMappers = new[] { new CaseInsensitiveEmailMapper() } }); ``` ## Example: Case-Insensitive Email ```csharp public class CaseInsensitiveEmailMapper : ISubjectKeyMapper { public bool CanHandle(Type subjectType) => subjectType == typeof(string); public object Map(object subject) { var email = (string)subject; return email.ToLowerInvariant(); } } ``` With this mapper, `"User@Example.COM"` and `"user@example.com"` map to the same registry entry. ## Default Behavior Without custom mappers, string subjects are wrapped in `StringSubjectKey` for consistent dictionary key behavior. All other types use their default `GetHashCode()` and `Equals()` implementations. ## When to Use Custom key mapping is rarely needed. Consider it when: * You need case-insensitive or normalized key matching * Your subjects have custom equality semantics that differ from their default implementation * You're using domain objects as subjects and need a stable key across instances --- --- url: /reference/examples.md --- # Examples Real-world patterns demonstrating capability composition. ## Plugin Architecture A plugin host that discovers and manages plugins through capabilities: ```csharp public interface IPlugin { string Id { get; } void Initialize(); } public record PluginMetadata(string Id, string Author, string Version) : IPrimaryCapability; public class PluginHost { private readonly CapabilityScope _scope = new(); public void RegisterPlugin(IPlugin plugin) { _scope.Compose(plugin) .WithPrimary(new PluginMetadata(plugin.Id, "unknown", "1.0")) .AddAs(plugin) .Build(); } public void RegisterPlugin(IPlugin plugin, PluginMetadata metadata) { _scope.Compose(plugin) .WithPrimary(metadata) .AddAs(plugin) .Build(); } public PluginMetadata GetMetadata(IPlugin plugin) { return _scope.Compositions.GetRequired(plugin) .GetRequiredPrimaryAs(); } } ``` ## Role-Based Access Control (RBAC) ```csharp public record Permission(string Resource, string Action); public record RoleIdentity(string Name) : IPrimaryCapability; var scope = new CapabilityScope(); // Define roles scope.Compose("admin") .WithPrimary(new RoleIdentity("Administrator")) .Add(new Permission("users", "read")) .Add(new Permission("users", "write")) .Add(new Permission("settings", "read")) .Add(new Permission("settings", "write")) .Build(); scope.Compose("viewer") .WithPrimary(new RoleIdentity("Viewer")) .Add(new Permission("users", "read")) .Add(new Permission("settings", "read")) .Build(); // Check permissions bool canWrite = scope.Compositions.GetRequired("admin") .GetAll() .Any(p => p.Resource == "users" && p.Action == "write"); // true ``` ## Event Processing Pipeline ```csharp public record PipelineIdentity(string Name) : IPrimaryCapability; public class PipelineStep { public string Name { get; init; } public int Priority { get; init; } public Func Handler { get; init; } } var scope = new CapabilityScope(); scope.Compose("event-pipeline") .WithPrimary(new PipelineIdentity("EventProcessor")) .Add(new PipelineStep { Name = "Validate", Priority = 100, Handler = e => Task.FromResult(e.IsValid) }, order: 100) .Add(new PipelineStep { Name = "Enrich", Priority = 200, Handler = e => Task.CompletedTask }, order: 200) .Add(new PipelineStep { Name = "Store", Priority = 300, Handler = e => Task.CompletedTask }, order: 300) .Build(); // Execute pipeline var composition = scope.Compositions.GetRequired("event-pipeline"); foreach (var step in composition.GetAll()) { await step.Handler(myEvent); } ``` ## Enum Enrichment Attach display metadata to enum values: ```csharp public enum ErrorCode { NotFound, Unauthorized, RateLimit, Internal } public record DisplayCapability(string Label, string Description); public record SeverityCapability(string Level); var scope = new CapabilityScope(); scope.Compose(ErrorCode.NotFound) .Add(new DisplayCapability("Not Found", "The requested resource was not found")) .Add(new SeverityCapability("Warning")) .Build(); scope.Compose(ErrorCode.Unauthorized) .Add(new DisplayCapability("Unauthorized", "Authentication required")) .Add(new SeverityCapability("Error")) .Build(); scope.Compose(ErrorCode.RateLimit) .Add(new DisplayCapability("Rate Limited", "Too many requests")) .Add(new SeverityCapability("Warning")) .Build(); // Usage var display = scope.Compositions.GetRequired(ErrorCode.NotFound) .GetRequiredFirst(); Console.WriteLine(display.Label); // "Not Found" ``` ::: warning Enum values are value types — they persist in the registry for the scope's lifetime. See [Registries](/guide/advanced/registries) for details. ::: ## Configuration Builder Pattern How [Cocoar.Configuration](https://github.com/cocoar-dev/Cocoar.Configuration) uses capabilities in production: ```csharp // Core assembly public class ConfigBuilder { public CapabilityScope Scope { get; } public ConfigBuilder(CapabilityScope scope) { Scope = scope; scope.Compose(this).Build(); } } // DI extension assembly public static class DiExtensions { public static ConfigBuilder AsSingleton(this ConfigBuilder builder) { var existing = builder.Scope.Compositions.GetRequired(builder); builder.Scope.Recompose(existing) .Add(new ServiceLifetimeCapability(ServiceLifetime.Singleton)) .Build(); return builder; } } // Host assembly foreach (var builder in builders) { var composition = scope.Compositions.GetRequired(builder); if (composition.TryGetFirst(out var lifetime)) { services.Add(new ServiceDescriptor(typeof(T), sp => /* ... */, lifetime.Lifetime)); } } ``` ## Domain-Specific Typed Scope ```csharp public class PipelineHost { public string Name { get; } public PipelineHost(string name) => Name = name; } public class PipelineScope : CapabilityScope { public PipelineScope(PipelineHost host) : base(host) { } public void AddStep(IPipelineStep step, int order) { var existing = Owner.GetComposition(); if (existing != null) Recompose(existing).Add(step, order).Build(); else Owner.Compose().Add(step, order).Build(); } public IReadOnlyList GetOrderedSteps() => Owner.GetRequiredComposition().GetAll(); } // Usage var scope = new PipelineScope(new PipelineHost("data-ingestion")); scope.AddStep(new ValidateStep(), 100); scope.AddStep(new TransformStep(), 200); scope.AddStep(new PersistStep(), 300); var steps = scope.GetOrderedSteps(); Console.WriteLine(scope.Owner.Get().Name); // "data-ingestion" ``` --- --- url: /guide/getting-started.md --- # Getting Started Install: ```bash dotnet add package Cocoar.Capabilities ``` All types are in the `Cocoar.Capabilities` namespace. ## Quick Start: Compose and Query A **subject** is any object you compose capabilities onto — it's the key that identifies a composition in the registry. Subjects can be strings, class instances, enum values, or any other object. ```csharp using Cocoar.Capabilities; // 1. Create a scope var scope = new CapabilityScope(); // 2. Compose capabilities onto a subject (here, the string "user-service") scope.Compose("user-service") .Add(new LoggingCapability { Level = LogLevel.Debug }) .Add(new RetryCapability { MaxAttempts = 3 }) .Build(); // 3. Query the composition var composition = scope.Compositions.GetRequired("user-service"); var logging = composition.GetFirstOrDefault(); Console.WriteLine(logging?.Level); // Debug var all = composition.GetAll(); Console.WriteLine(composition.Has()); // True ``` ## Quick Start: Primary Capability ```csharp public record ServiceIdentity(string Name, string Version) : IPrimaryCapability; scope.Compose("user-service") .WithPrimary(new ServiceIdentity("UserService", "2.1.0")) .Add(new LoggingCapability { Level = LogLevel.Debug }) .Build(); var composition = scope.Compositions.GetRequired("user-service"); var identity = composition.GetRequiredPrimaryAs(); Console.WriteLine(identity.Name); // UserService ``` ## Quick Start: Multiple Contracts ```csharp public class EmailNotifier : INotifier, IHealthCheck { public string Name => "Email"; public bool IsHealthy => true; } scope.Compose("notifications") .AddAs<(INotifier, IHealthCheck)>(new EmailNotifier()) // tuple = register under both interfaces .Build(); var composition = scope.Compositions.GetRequired("notifications"); var notifiers = composition.GetAll(); // [EmailNotifier] var checks = composition.GetAll(); // [EmailNotifier] ``` ## Key Types | Type | Role | |------|------| | `CapabilityScope` | Container that manages composers and compositions | | `Composer` | Fluent builder for attaching capabilities to a subject | | `IComposition` | Immutable, thread-safe result of a composition | | `IPrimaryCapability` | Marker interface for the "identity" capability | | `CapabilityScopeOptions` | Configuration for scope behavior | ## Next Steps * [Why Capabilities?](/guide/why-capabilities) — The problem this solves * [CapabilityScope](/guide/core/capability-scope) — Deep dive into scopes * [Composer](/guide/core/composer) — Full builder API * [Composition](/guide/core/composition) — Query API reference * [Examples](/reference/examples) — Real-world patterns --- --- url: /guide/composition/multiple-contracts.md --- # Multiple Contracts A single capability instance can be registered under multiple contract types, making it discoverable through different interfaces. ## Single Contract Use `AddAs()` to register under a specific type: ```csharp scope.Compose(host) .AddAs(new EmailPlugin()) .Build(); var plugins = composition.GetAll(); // [EmailPlugin] ``` The capability is only discoverable as `IPlugin`, not as `EmailPlugin`. ## Tuple Syntax Register under multiple contracts at once: ```csharp public class EmailNotifier : INotifier, IHealthCheck, IDisposable { public string Name => "Email"; public bool IsHealthy => true; public void Dispose() { } } scope.Compose(host) .AddAs<(INotifier, IHealthCheck, IDisposable)>(new EmailNotifier()) .Build(); var notifiers = composition.GetAll(); // [EmailNotifier] var checks = composition.GetAll(); // [EmailNotifier] var disposables = composition.GetAll(); // [EmailNotifier] ``` The same instance appears in each contract's collection. ## Grouping Multiple Instances ```csharp scope.Compose(host) .AddAs(new EmailPlugin()) .AddAs(new SmsPlugin()) .AddAs(new SlackPlugin()) .Build(); var plugins = composition.GetAll(); // [Email, Sms, Slack] ``` ## When to Use Use `AddAs` when: * A capability implements multiple interfaces and should be discoverable through each * You want to group heterogeneous types under a common contract * Consumer code should work with abstractions, not concrete types Use regular `Add()` when the capability's own type is how it should be discovered. --- --- url: /guide/composition/ordering.md --- # Ordering When a composition contains multiple capabilities of the same type, their order can matter — for example, in pipelines where steps must execute in a specific sequence. ## Explicit Order Pass an `order` parameter to `Add()`: ```csharp scope.Compose(pipeline) .Add(new AuthStep(), order: 100) .Add(new ValidationStep(), order: 200) .Add(new LoggingStep(), order: 300) .Build(); var steps = composition.GetAll(); // AuthStep → ValidationStep → LoggingStep ``` Lower values execute first. ## Order Selector Use a lambda to extract the order from the capability itself: ```csharp public class PipelineStep : IPipelineStep { public string Name { get; init; } public int Priority { get; init; } } scope.Compose(pipeline) .Add(new PipelineStep { Name = "Log", Priority = 30 }, c => ((PipelineStep)c).Priority) .Add(new PipelineStep { Name = "Auth", Priority = 10 }, c => ((PipelineStep)c).Priority) .Add(new PipelineStep { Name = "Validate", Priority = 20 }, c => ((PipelineStep)c).Priority) .Build(); // Result: Auth(10) → Validate(20) → Log(30) ``` ## Default Behavior Capabilities without an explicit order are sorted after all ordered capabilities, in insertion order. The sort is stable — capabilities with the same order retain their insertion sequence. ## Practical Example ```csharp // Priority tiers for a request pipeline scope.Compose("api-pipeline") .Add(new CorsMiddleware(), order: 100) // Infrastructure .Add(new AuthMiddleware(), order: 200) // Security .Add(new RateLimitMiddleware(), order: 300) // Protection .Add(new ValidationMiddleware(), order: 400) // Input .Add(new BusinessLogicHandler(), order: 500) // Core .Add(new AuditMiddleware(), order: 900) // Cross-cutting .Build(); ``` --- --- url: /guide/scope-context/owner-api.md --- # Owner API When you pass a scope between components — to extension methods, helper classes, or across layers — receiving code often needs to know *who* created or manages the scope. The Owner API solves this by associating a single distinguished object with the scope. This is the object that "owns" or manages the scope — for example, a service host, pipeline runner, or application root. It also serves as a convenient subject for composing capabilities onto the owner itself. ## Setting an Owner ```csharp var host = new ServiceHost("MyApp"); var scope = new CapabilityScope(); scope.Owner.Set(host); ``` `Set()` throws if an owner is already set. Use `Replace()` for explicit replacement: ```csharp scope.Owner.Replace(newHost); ``` ## Fluent Chaining Owner methods return `ScopeOwnerApi`, allowing chaining. Use `.Scope` to return to the scope: ```csharp var scope = new CapabilityScope(); scope.Owner.Set(host).Scope.Compose("something").Add(cap).Build(); ``` ## Getting the Owner ```csharp // Get — returns null if not set, collected, or wrong type var host = scope.Owner.Get(); // GetOrThrow — throws if not set, collected, or wrong type var host = scope.Owner.GetOrThrow(); // TryGet — out-parameter pattern if (scope.Owner.TryGet(out var host)) { Console.WriteLine(host.Name); } ``` ## Composing via Owner Compose capabilities directly on the owner: ```csharp scope.Owner.Set(host); scope.Owner.Compose() .Add(new HostCapability()) .Build(); ``` For typed composition (uses the owner's type as the subject key): ```csharp scope.Owner.ComposeFor() .Add(new HostCapability()) .Build(); ``` ## Retrieving Compositions via Owner ```csharp // Direct Composition? comp = scope.Owner.GetComposition(); Composition required = scope.Owner.GetRequiredComposition(); // Try-pattern if (scope.Owner.TryGetComposition(out var composition)) { var caps = composition.GetAll(); } // Typed Composition? comp = scope.Owner.GetCompositionFor(); Composition required = scope.Owner.GetRequiredCompositionFor(); ``` ## Retrieving Composers via Owner ```csharp Composer? composer = scope.Owner.GetComposer(); Composer required = scope.Owner.GetRequiredComposer(); if (scope.Owner.TryGetComposer(out var c)) { // Composer is still in registry } // Typed Composer? composer = scope.Owner.GetComposerFor(); Composer required = scope.Owner.GetRequiredComposerFor(); ``` ## Weak References The owner is stored as a weak reference. If the owner object is garbage collected, `Get()` returns `null` and `GetOrThrow()` throws. This prevents the scope from keeping objects alive unnecessarily. --- --- url: /guide/patterns.md --- # Patterns & Inspiration This page explores architectural possibilities that capability composition enables. These are **thought experiments and explorations**, not prescriptive best practices. ::: info Production vs Exploration The production use case that drove the creation of this library is [Cocoar.Configuration](https://github.com/cocoar-dev/Cocoar.Configuration) — attaching DI metadata to configuration builders across assembly boundaries (see [Why Capabilities?](/guide/why-capabilities) and the [Configuration Builder example](/reference/examples#configuration-builder-pattern)). Everything below explores what *else* becomes possible with the same mechanism. Some patterns might be perfect for your scenario. Others might be overkill. Many have simpler alternatives depending on your constraints. **Don't cargo-cult these patterns.** Understand them, then use what makes sense for your context. ::: ## Functions as Capabilities The most non-obvious insight: **functions and delegates are objects**, so they can be capabilities too. Combined with ordering, you get framework-free pipeline orchestration. ```csharp public class RequestPipeline { private readonly CapabilityScope _scope = new(); private readonly object _definition = new(); public void Initialize() { _scope.Compose(_definition) .Add>( async ctx => await AuthenticateUser(ctx), order: 100) .Add>( async ctx => await ValidateInput(ctx), order: 200) .Add>( async ctx => await ExecuteBusinessLogic(ctx), order: 300) .Add>( async ctx => await AuditAction(ctx), order: 400) .Build(); } public async Task ProcessRequest(RequestContext context) { var steps = _scope.Compositions.GetRequired(_definition) .GetAll>(); foreach (var step in steps) // guaranteed order { await step(context); } } } ``` With [Using\* Extensions](/guide/composition/using-extensions), the execution becomes even cleaner: ```csharp public async Task ProcessRequest(RequestContext context) { _scope.Compositions.GetRequired(_definition) .UsingEach>(async step => await step(context)); } ``` Or when the pipeline has different step types at different stages: ```csharp _scope.Compositions.GetRequired(_definition) .UsingFirst>(validate => validate(context)) // gate .UsingEach>(async step => await step(context)) // pipeline .UsingFirstOrDefault>(cleanup => cleanup(context)); // optional ``` **Why this works:** The pipeline definition object is created once and reused. It acts as the stable key for the registry. Since it's a reference type, the composition is automatically cleaned up when the pipeline object is garbage collected. **When to consider:** You want middleware-style pipelines in a class library or background service without taking a dependency on ASP.NET Core or any other framework. **Why not just a `List>`?** You could. But capabilities give you ordering guarantees, type-safe querying across multiple delegate types, recomposition (adding steps later from other assemblies), and registry-based lookup by subject. ## Self-Registering Plugins Instead of a host that registers plugins (see [Plugin example](/reference/examples#plugin-architecture)), let plugins describe *themselves*: ```csharp public interface IPlugin { void RegisterCapabilities(CapabilityScope scope); } public class EmailPlugin : IPlugin { public void RegisterCapabilities(CapabilityScope scope) { scope.Compose(this) .WithPrimary(new PluginMetadata("email", "Acme Corp", "1.2.0")) .AddAs<(IEmailSender, INotificationProvider)>(new EmailService()) .Add(new FeatureCapability("Templates")) .Add(new FeatureCapability("Attachments")) .Add(new DependencyCapability("SMTP", "1.0+")) .Build(); } } ``` The host just loads plugins and queries their capabilities: ```csharp public class PluginHost { private readonly CapabilityScope _scope = new(); private readonly List _plugins = new(); public void Load(IPlugin plugin) { plugin.RegisterCapabilities(_scope); _plugins.Add(plugin); } public IEnumerable WithFeature(string feature) { return _plugins.Where(p => { var comp = _scope.Compositions.GetOrDefault(p); return comp?.GetAll().Any(f => f.Name == feature) ?? false; }); } public bool ValidateDependencies(IPlugin plugin) { var deps = _scope.Compositions.GetRequired(plugin) .GetAll(); return deps.All(d => IsSatisfied(d)); } } ``` **The difference from host-driven registration:** The plugin decides what capabilities it has. The host doesn't need to know about `FeatureCapability` or `DependencyCapability` at compile time — it just queries by type. A second assembly could define entirely new capability types that the original host never heard of. **When to consider:** You're building an extensibility system where plugins come from different assemblies and need to self-describe their features, dependencies, or metadata. **Simpler alternative:** If all plugins are known at compile time, a simple `IPlugin` interface with typed properties (e.g., `Features`, `Dependencies`) is more straightforward. Capabilities shine when plugin types are unknown to the host at compile time. ## Multi-Tenant Context Isolation Different `CapabilityScope` instances are completely independent worlds. This maps naturally to multi-tenant architectures: ```csharp public class TenantService { private readonly Dictionary _tenantScopes = new(); public void OnboardTenant(string tenantId, TenantConfig config) { var scope = new CapabilityScope(); _tenantScopes[tenantId] = scope; // Same enum values, completely different metadata per tenant scope.Compose(ErrorCode.InvalidInput) .Add(new DisplayCapability(config.ErrorMessages[ErrorCode.InvalidInput])) .Add(new SeverityCapability(config.SeverityOverrides[ErrorCode.InvalidInput])) .Build(); scope.Compose(ErrorCode.Unauthorized) .Add(new DisplayCapability(config.ErrorMessages[ErrorCode.Unauthorized])) .Add(new SeverityCapability(LogLevel.Error)) .Build(); } public void HandleError(string tenantId, ErrorCode error) { var scope = _tenantScopes[tenantId]; var composition = scope.Compositions.GetOrDefault(error); if (composition != null) { var display = composition.GetFirstOrDefault(); Console.WriteLine(display?.Message); // tenant-specific message } } } ``` **The key insight:** You don't need any multi-tenant abstractions in your business logic. The scope *is* the isolation boundary. Tenant A's capabilities can never leak into Tenant B's scope. **When to consider:** You need per-tenant customization of metadata, error messages, feature flags, or configuration — and you want complete isolation without tenant-ID checks scattered through your code. **Simpler alternative:** A `Dictionary` works fine for static per-tenant configuration. Scopes add value when tenants need different *capability compositions* — different pipelines, different behaviors attached to the same identifiers. ## Attaching Behavior to Values The [Enum Enrichment example](/reference/examples#enum-enrichment) shows attaching *data* to enum values. But capabilities can also be *actions* — turning values into dispatch targets: ```csharp scope.Compose(ErrorCode.Timeout) .Add(new DisplayCapability("Request timed out")) .Add>(ctx => { ctx.Response.StatusCode = 504; ctx.Response.Headers["Retry-After"] = "30"; }) .Build(); scope.Compose(ErrorCode.NotFound) .Add(new DisplayCapability("Not found")) .Add>(ctx => { ctx.Response.StatusCode = 404; }) .Build(); // Dispatch — no switch statement var composition = scope.Compositions.GetOrDefault(errorCode); composition?.UsingFirstOrDefault>(handler => handler(httpContext)); ``` This eliminates switch statements over enums entirely. Each value carries its own behavior. **When to consider:** You have enum values or status codes that map to distinct behavior, and you want to avoid growing switch statements. Especially useful when the behavior comes from a different assembly than the enum definition. **Simpler alternative:** A `Dictionary>` achieves the same dispatch. Capabilities add value when the behavior is attached from multiple assemblies, or when you need multiple capability types per value (display + severity + handler). ## Choosing the Right Pattern | Pattern | Core Mechanism | Best For | |---------|---------------|----------| | Functions as Capabilities | `Add>()` + ordering | Framework-free pipelines | | Self-Registering Plugins | `AddAs<(I1, I2)>()` + primary | Extensibility systems | | Multi-Tenant Isolation | Separate `CapabilityScope` per tenant | SaaS, per-context customization | | Behavior on Values | `Add>()` on enums | Eliminating switch statements | All of these build on the same three concepts: [Scope](/guide/core/capability-scope), [Composer](/guide/core/composer), and [Composition](/guide/core/composition). The flexibility comes from what you choose to compose — data, functions, or both — and how you structure your scopes. --- --- url: /guide/core/primary-capabilities.md --- # Primary Capabilities A primary capability represents the "identity" or "self-description" of a composition. While a composition can hold many capabilities, at most one can be primary. ## Defining a Primary Capability Implement the `IPrimaryCapability` marker interface: ```csharp public record ServiceIdentity(string Name, string Version) : IPrimaryCapability; public record PluginMetadata(string Id, string Author) : IPrimaryCapability; ``` ## Setting a Primary Use `WithPrimary()` on the `Composer`: ```csharp scope.Compose("auth-service") .WithPrimary(new ServiceIdentity("AuthService", "2.0.0")) .Add(new LoggingCapability()) .Add(new MetricsCapability()) .Build(); ``` Rules: * Only one primary per composition * Calling `WithPrimary()` again replaces the previous one * Pass `null` to clear the primary ## Querying the Primary ```csharp var composition = scope.Compositions.GetRequired("auth-service"); // Check if (composition.HasPrimary()) { var primary = composition.GetPrimary(); } // Typed access if (composition.HasPrimary()) { var identity = composition.GetRequiredPrimaryAs(); Console.WriteLine($"{identity.Name} v{identity.Version}"); } // Try-pattern if (composition.TryGetPrimaryAs(out var id)) { Console.WriteLine(id.Name); } // Default var idOrNull = composition.GetPrimaryOrDefaultAs(); ``` ## Replacing via Recomposition ```csharp var existing = scope.Compositions.GetRequired("auth-service"); scope.Recompose(existing) .WithPrimary(new ServiceIdentity("AuthService", "3.0.0")) .Build(); ``` ## When to Use Use primary capabilities when a composition needs an identity: * **Service metadata**: Name, version, description * **Plugin self-description**: ID, author, dependencies * **Entity classification**: Type, category, role If you just need to query capabilities by type, regular capabilities (via `Add()`) are sufficient. --- --- url: /guide/composition/recomposition.md --- # Recomposition Since compositions are immutable, you can't modify them after `Build()`. Recomposition creates a new composition based on an existing one, inheriting its capabilities while allowing additions, removals, and primary replacement. ## Basic Recomposition ```csharp // Original composition scope.Compose("service") .Add(new LoggingCapability()) .Build(); // Recompose — add more capabilities var existing = scope.Compositions.GetRequired("service"); scope.Recompose(existing) .Add(new MetricsCapability()) .Build(); // New composition has both LoggingCapability and MetricsCapability ``` The new composition replaces the old one in the registry. ## Removing Capabilities Use `RemoveWhere()` to selectively exclude capabilities: ```csharp var existing = scope.Compositions.GetRequired("service"); scope.Recompose(existing) .RemoveWhere(c => c is LoggingCapability) .Add(new BetterLoggingCapability()) .Build(); ``` ## Replacing the Primary ```csharp var existing = scope.Compositions.GetRequired("service"); scope.Recompose(existing) .WithPrimary(new ServiceIdentity("Service", "2.0.0")) .Build(); ``` ## Conditional Enrichment A common pattern is enriching compositions from extension methods: ```csharp public static void EnrichWithDiagnostics(this CapabilityScope scope, object subject) { if (scope.Compositions.TryGet(subject, out var composition)) { scope.Recompose(composition) .Add(new DiagnosticsCapability()) .Build(); } } ``` --- --- url: /guide/advanced/registries.md --- # Registries `CapabilityScope` maintains two optional registries that cache composers and compositions for later retrieval. ## Two Registries | Registry | Stores | Purpose | |----------|--------|---------| | `Composers` | `Composer` instances | Retrieve a builder after creation (e.g., for recomposition) | | `Compositions` | `IComposition` results | Retrieve completed compositions by subject | Both are enabled by default. ## Configuring Registries ```csharp var scope = new CapabilityScope(new CapabilityScopeOptions { UseComposerRegistry = true, // default: true UseCompositionRegistry = true, // default: true }); ``` ## CompositionRegistryApi ```csharp // Try-pattern if (scope.Compositions.TryGet("my-subject", out var composition)) { var caps = composition.GetAll(); } // Get or default (returns null) var comp = scope.Compositions.GetOrDefault("my-subject"); // Get required (throws) var comp = scope.Compositions.GetRequired("my-subject"); // Generic overloads var comp = scope.Compositions.GetRequired("my-subject"); // Remove scope.Compositions.Remove("my-subject"); ``` ## ComposerRegistryApi ```csharp // Try-pattern if (scope.Composers.TryGet("my-subject", out var composer)) { // Composer still available } // Get or default var c = scope.Composers.GetOrDefault("my-subject"); // Get required (throws) var c = scope.Composers.GetRequired("my-subject"); // Register manually scope.Composers.Register("key", composer, forceRegister: false); // Remove scope.Composers.Remove("my-subject"); ``` ## Per-Operation Override Override the default registry behavior for individual operations: ```csharp // Compose but don't register the composer var composer = scope.Compose("temp-subject", useRegistry: false); // Build but don't register the composition var composition = composer.Build(useRegistry: false); ``` ## Reference Types vs Value Types Registry storage differs by subject type: | Subject Type | Storage | Lifetime | Cleanup | |-------------|---------|----------|---------| | Reference types (classes) | `ConditionalWeakTable` | Weak — GC cleans up | Automatic when subject is collected | | Value types (structs, enums) | `ConcurrentDictionary` | Strong — persists | Manual via `Remove()` | ::: warning Value-type subjects (e.g., enums, structs) persist in the registry for the scope's lifetime. If you register many value-type subjects dynamically, call `Remove()` when they're no longer needed. ::: ```mermaid flowchart TD S[Subject] -->|reference type?| WT[ConditionalWeakTable] S -->|value type?| CD[ConcurrentDictionary] WT -->|auto-cleanup| GC[GC collects subject] CD -->|manual cleanup| RM[Remove] ``` ## Registry Decision Matrix | Scenario | Composer Registry | Composition Registry | |----------|-------------------|---------------------| | Standard usage | On | On | | Fire-and-forget compositions | Off | On | | Temporary compositions | Off | Off | | Need to recompose later | On | On | --- --- url: /guide/scope-context/typed-scopes.md --- # Strongly-Typed Scopes `CapabilityScope` is a generic scope that sets the owner at construction time. The owner is immutable — it can't be replaced. ## Creating a Typed Scope ```csharp var host = new PipelineHost("ingestion"); var scope = new CapabilityScope(host); // With options var scope = new CapabilityScope(host, new CapabilityScopeOptions { UseComposerRegistry = false }); ``` ## Typed Owner API The scope exposes `ScopeOwnerApi` — no generic parameters needed on queries: ```csharp // No generic parameter needed — TOwner is already known PipelineHost owner = scope.Owner.Get(); if (scope.Owner.TryGet(out var host)) { Console.WriteLine(host.Name); } ``` Compare with the non-generic scope: ```csharp // Non-generic — must specify type each time var host = scope.Owner.Get(); // returns null if wrong type var host = scope.Owner.GetOrThrow(); // throws if wrong type ``` ## Composition via Typed Owner ```csharp scope.Owner.Compose() .Add(new PipelineMetrics()) .Build(); Composition? comp = scope.Owner.GetComposition(); Composition required = scope.Owner.GetRequiredComposition(); if (scope.Owner.TryGetComposition(out var composition)) { // use composition } ``` ## Composer via Typed Owner ```csharp Composer? c = scope.Owner.GetComposer(); Composer required = scope.Owner.GetRequiredComposer(); if (scope.Owner.TryGetComposer(out var composer)) { // use composer } ``` ## Inheritance `CapabilityScope` inherits from `CapabilityScope`, so all non-generic methods (`Compose()`, `Recompose()`, `Anchors`, `Compositions`, `Composers`) work exactly the same. ## Domain-Specific Scopes Create purpose-built scope classes by inheriting: ```csharp public class PipelineScope : CapabilityScope { public PipelineScope(PipelineHost host) : base(host) { } public void AddStep(IPipelineStep step, int order) { var existing = Owner.GetComposition(); if (existing != null) Recompose(existing).Add(step, order).Build(); else Owner.Compose().Add(step, order).Build(); } public IReadOnlyList GetSteps() { return Owner.GetRequiredComposition().GetAll(); } } ``` Usage: ```csharp var scope = new PipelineScope(new PipelineHost("ingestion")); scope.AddStep(new ValidateStep(), order: 100); scope.AddStep(new TransformStep(), order: 200); scope.AddStep(new LoadStep(), order: 300); var steps = scope.GetSteps(); // Ordered: Validate → Transform → Load ``` --- --- url: /guide/composition/using-extensions.md --- # Using\* Extensions The `Using*` extension methods provide fluent inline access to capabilities without extracting them first. They are defined on `IComposition`. ## UsingFirst / UsingLast Execute an action on the first or last capability of a type: ```csharp // Action — returns IComposition for chaining composition .UsingFirst(logger => logger.Log("Starting")) .UsingFirst(metrics => metrics.Increment("requests")); // Function — returns the result string name = composition.UsingFirst(id => id.Name); ``` `UsingFirst` throws if no capability of that type exists. Use `UsingFirstOrDefault` for a safe version that does nothing if the capability is missing: ```csharp composition.UsingFirstOrDefault(logger => logger.Log("optional")); ``` `UsingLast` and `UsingLastOrDefault` work the same way but use the last capability. ## UsingEach Execute an action for every capability of a type: ```csharp // Action — returns IComposition for chaining composition.UsingEach(plugin => plugin.Initialize()); // Function — returns results as a list IReadOnlyList names = composition.UsingEach(p => p.Name); ``` ## UsingAll Work with the full collection at once: ```csharp // Action composition.UsingAll(plugins => { foreach (var plugin in plugins) plugin.Initialize(); }); // Function int count = composition.UsingAll(plugins => plugins.Count); ``` ## Fluent Chaining Action-based overloads return `IComposition`, enabling chains: ```csharp composition .UsingFirst(l => l.Log("init")) .UsingEach(p => p.Start()) .UsingFirstOrDefault(m => m.Record("started")); ``` ## When Using\* vs Get\* | Goal | Use | |------|-----| | Extract a value to use later | `GetFirstOrDefault()`, `GetAll()` | | Inline side effect (logging, init) | `UsingFirst()`, `UsingEach()` | | Transform and return | `UsingFirst()` | | Chain multiple operations | `UsingFirst` / `UsingEach` (action overloads) | ## Delegation Mapping | Using\* Method | Delegates to | |---------------|-------------| | `UsingFirst(action)` | `GetRequiredFirst()` | | `UsingFirst(func)` | `GetRequiredFirst()` | | `UsingFirstOrDefault(action)` | `GetFirstOrDefault()` | | `UsingLast(action)` | `GetRequiredLast()` | | `UsingLast(func)` | `GetRequiredLast()` | | `UsingLastOrDefault(action)` | `GetLastOrDefault()` | | `UsingEach(action)` | `GetAll()` | | `UsingEach(func)` | `GetAll()` | | `UsingAll(action)` | `GetAll()` | | `UsingAll(func)` | `GetAll()` | --- --- url: /guide/why-capabilities.md --- # Why Capabilities? Cocoar.Capabilities is a **composition layer for typed metadata**. It is not a DI container, plugin framework, or ECS — it solves a specific problem: attaching and querying typed metadata across assembly boundaries without coupling. ## The Cross-Assembly Metadata Problem When building fluent APIs that span multiple assemblies, attaching metadata to builder objects becomes a fundamental challenge. A core assembly defines a builder, extension assemblies need to attach metadata to it, and a consuming assembly needs to read all attached metadata — without circular dependencies. ```csharp // Core assembly — defines the builder public class ConfigBuilder { /* ... */ } // DI assembly — wants to tag builders with DI metadata public static ConfigBuilder AsSingleton(this ConfigBuilder b) { /* ??? */ } // Host assembly — needs to read DI metadata from builders foreach (var builder in builders) { // How do we get the DI metadata that was attached? } ``` ## Why Standard Solutions Fail | Approach | Problem | |----------|---------| | `Dictionary` | No type safety, runtime key collisions, no discoverability | | `[Attribute]` | Static, compile-time only — can't attach dynamically from extensions | | Method parameters | API explosion, breaks every caller when adding new metadata | | Builder internal state | Core assembly must know about all possible metadata — defeats extensibility | ## The Capability Composition Approach Cocoar.Capabilities introduces a **composition layer** that sits alongside your objects. Any assembly can attach typed metadata ("capabilities") to any object through a shared `CapabilityScope`, and any assembly can read them back — all without coupling. ```csharp // Core assembly — builder uses a shared scope public class ConfigBuilder { internal CapabilityScope Scope { get; } public ConfigBuilder(CapabilityScope scope) { Scope = scope; scope.Compose(this).Build(); // Create initial composition } } // DI assembly — attaches DI metadata via extension method public static ConfigBuilder AsSingleton(this ConfigBuilder b) { b.Scope.Recompose(b.Scope.Compositions.GetRequired(b)) .Add(new ServiceLifetimeCapability(ServiceLifetime.Singleton)) .Build(); return b; } // Host assembly — reads metadata from any builder foreach (var builder in builders) { var composition = scope.Compositions.GetRequired(builder); if (composition.TryGetFirst(out var lifetime)) { // Register with the appropriate lifetime } } ``` This is exactly how [Cocoar.Configuration](https://github.com/cocoar-dev/Cocoar.Configuration) works in production — capability composition solves the cross-assembly metadata problem that every fluent API framework eventually faces. ## What You Get | Feature | Benefit | |---------|---------| | Type-safe composition | No string keys, no casting — compile-time checked | | Cross-assembly extensibility | Any assembly can attach capabilities without knowing about others | | Immutable results | Thread-safe, shareable compositions | | Fluent API | Chain `.Add()`, `.WithPrimary()`, `.Build()` naturally | | Ordering | Control execution order when multiple capabilities exist | | Recomposition | Modify compositions without mutation | | No circular dependencies | Extensions don't need to reference each other |