Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "arbiter",
"owner": {
"name": "LoreSoft",
"url": "https://github.com/loresoft"
},
"metadata": {
"description": "Claude Code plugins for the Arbiter .NET libraries — mediation, CQRS, mapping, Blazor dispatcher, communication, and more.",
"version": "1.0.0"
},
"plugins": [
{
"name": "arbiter-skills",
"source": "./plugins/arbiter-skills",
"description": "Skills that teach Claude how to install, register, and use the Arbiter family of .NET libraries (Mediation, CommandQuery + EF/Mongo, Mapping, Endpoints/Mvc, Dispatcher, Communication, Services, OpenTelemetry, Messaging.ServiceBus).",
"version": "1.0.0",
"category": "dotnet",
"tags": ["dotnet", "cqrs", "mediator", "blazor", "arbiter"]
}
]
}
13 changes: 13 additions & 0 deletions plugins/arbiter-skills/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "arbiter-skills",
"version": "1.0.0",
"description": "Skills that teach Claude how to install, register, and use the Arbiter family of .NET libraries (Mediation, CommandQuery + EF/Mongo, Mapping, Endpoints/Mvc, Dispatcher, Communication, Services, OpenTelemetry, Messaging.ServiceBus).",
"author": {
"name": "LoreSoft",
"url": "https://github.com/loresoft"
},
"homepage": "https://github.com/loresoft/Arbiter",
"repository": "https://github.com/loresoft/Arbiter",
"license": "MIT",
"keywords": ["dotnet", "cqrs", "mediator", "blazor", "arbiter"]
}
37 changes: 37 additions & 0 deletions plugins/arbiter-skills/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Arbiter Skills

A Claude Code plugin that teaches Claude how to use the [Arbiter](https://github.com/loresoft/Arbiter) family of .NET libraries.

When installed, Claude auto-loads the relevant skill based on what you're asking about — e.g. asking *"how do I register an EF Core handler for my Product entity?"* loads `arbiter-commandquery-ef` and Claude responds with the canonical `AddEntityQueries` / `AddEntityCommands` pattern.

## Install

```bash
# Add this repo as a marketplace
/plugin marketplace add loresoft/arbiter

# Install the skills bundle
/plugin install arbiter-skills@arbiter
```

## Skills included

| Skill | When it loads |
| --- | --- |
| `arbiter-overview` | Routing skill — explains the Arbiter package landscape and points to the right specialist skill |
| `arbiter-mediation` | `IMediator`, `IRequest`, `IRequestHandler`, `INotification`, `IPipelineBehavior`, `AddMediator` |
| `arbiter-commandquery` | `EntityQuery`, `EntityFilter`, `FilterOperators`, `EntityPagedQuery`, `EntityIdentifierQuery`, behaviors |
| `arbiter-commandquery-ef` | Entity Framework Core handler registration (`AddEntityQueries`, `AddEntityCommands`) |
| `arbiter-commandquery-mongo` | MongoDB handler registration |
| `arbiter-endpoints` | Minimal API integration (`AddEndpointRoutes`, `MapEndpointRoutes`, `EntityCommandEndpointBase`) |
| `arbiter-mvc` | MVC controller integration |
| `arbiter-mapping` | Source-generated mapper (`[GenerateMapper]`, `MapperProfile`, `ProjectTo`) |
| `arbiter-dispatcher` | Blazor `IDispatcher`, WASM/Server/Auto setup, `ModelStateEditor` |
| `arbiter-communication` | Email/SMS templates + Azure / Graph / Twilio / SendGrid / SMTP providers |
| `arbiter-services` | CSV, AES encryption, cache tagging, token service, URL builder |
| `arbiter-opentelemetry` | Tracing/metrics with `MediatorTelemetry`, server + monitor packages |
| `arbiter-messaging-servicebus` | Azure Service Bus integration |

## Authoring conventions

Each skill is cheat-sheet style: a `description` frontmatter line that drives auto-loading, then a tight body with **When to use → Install → Register → Canonical pattern → Variations → Reference**. Keep skills under ~200 lines; link to `docs/guide/*.md` in the Arbiter repo for depth.
97 changes: 97 additions & 0 deletions plugins/arbiter-skills/skills/arbiter-commandquery-ef/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
---
name: arbiter-commandquery-ef
description: Use when wiring Arbiter.CommandQuery with Entity Framework Core — registering AddEntityQueries / AddEntityCommands / AddQueryPipeline for an entity, defining DbContext-backed handlers, or implementing audited / soft-delete / tenant entities for EF Core.
---

# Arbiter.CommandQuery.EntityFramework

EF Core implementations of the generic `Arbiter.CommandQuery` handlers. Provides ready-made handlers for `EntityIdentifierQuery`, `EntityPagedQuery`, and the create/update/patch/delete commands against a `DbContext`.

## Install

```bash
dotnet add package Arbiter.CommandQuery.EntityFramework
```

Pairs with `Arbiter.CommandQuery` (base) and `Arbiter.Mapping` (read/update model mapping).

## Register

```csharp
using Arbiter.CommandQuery;
using Arbiter.CommandQuery.EntityFramework;

// EF Core DbContext
services.AddDbContext<TrackerContext>(o => o.UseSqlServer(connectionString));

// CQRS + mapping
services.AddCommandQuery();
services.AddSingleton<IMapper, ServiceProviderMapper>();
services.AddSingleton<IMapper<Product, ProductReadModel>, ProductToReadModelMapper>();
services.AddSingleton<IMapper<ProductCreateModel, Product>, ProductCreateModelToProductMapper>();
services.AddSingleton<IMapper<ProductUpdateModel, Product>, ProductUpdateModelToProductMapper>();

// Per-entity registration
services.AddEntityQueries <TrackerContext, Product, int, ProductReadModel>();
services.AddEntityCommands<TrackerContext, Product, int, ProductReadModel, ProductCreateModel, ProductUpdateModel>();
```

`AddEntityQueries` registers identifier, identifiers, and paged-query handlers.
`AddEntityCommands` registers create, update, patch, and delete handlers.

## Entity requirements

```csharp
public class Product : IHaveIdentifier<int>,
ITrackCreated, ITrackUpdated, // audit fields
ITrackDeleted // soft delete
{
public int Id { get; set; }
public string Name { get; set; } = "";
public decimal Price { get; set; }

public DateTimeOffset Created { get; set; }
public string? CreatedBy { get; set; }
public DateTimeOffset Updated { get; set; }
public string? UpdatedBy { get; set; }

public bool IsDeleted { get; set; }
}
```

Implementing `IHaveIdentifier<TKey>` is required. The other marker interfaces opt the entity into audit and soft-delete behaviors automatically — paged queries filter out soft-deleted rows; commands stamp audit fields from the `ClaimsPrincipal`.

## Finer-grained registration

Use these if you don't want every CRUD operation, or you want a custom handler for one:

```csharp
// Queries
services.AddEntityIdentifierQuery <int, ProductReadModel, MyHandler>();
services.AddEntityIdentifiersQuery<int, ProductReadModel, MyHandler>();
services.AddEntityPagedQuery <ProductReadModel, MyHandler>();

// Commands (DbContext-backed)
services.AddEntityCreateCommand<TrackerContext, Product, int, ProductReadModel, ProductCreateModel>();
services.AddEntityUpdateCommand<TrackerContext, Product, int, ProductReadModel, ProductUpdateModel>();
services.AddEntityPatchCommand <TrackerContext, Product, int, ProductReadModel>();
services.AddEntityDeleteCommand<TrackerContext, Product, int, ProductReadModel>();

// Commands (custom handler)
services.AddEntityCreateCommand<int, ProductReadModel, ProductCreateModel, MyCreateHandler>();
```

`AddQueryPipeline()` registers EF-specific pipeline pieces (call once; included automatically by the helpers above).

## Key-based lookups (for string/natural keys)

```csharp
// Entity implements IHaveKey + IHaveIdentifier<TKey>
services.AddEntityKeyQuery<TrackerContext, Product, ProductReadModel>();
```

## Reference

- EF handler guide: https://github.com/loresoft/Arbiter/blob/main/docs/guide/handlers/entityFramework.md
- Behaviors: https://github.com/loresoft/Arbiter/tree/main/docs/guide/behaviors
- Sample: https://github.com/loresoft/Arbiter/tree/main/samples/EntityFramework
87 changes: 87 additions & 0 deletions plugins/arbiter-skills/skills/arbiter-commandquery-mongo/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
name: arbiter-commandquery-mongo
description: Use when wiring Arbiter.CommandQuery with MongoDB — AddMongoRepository plus AddEntityQueries / AddEntityCommands for an IMongoEntityRepository<TEntity>. Trigger on IMongoRepository, IMongoEntityRepository, MongoDB document handlers.
---

# Arbiter.CommandQuery.MongoDB

MongoDB implementations of the generic `Arbiter.CommandQuery` handlers. Uses `IMongoRepository<TEntity, TKey>` (typically resolved as `IMongoEntityRepository<TEntity>`) as the data gateway.

## Install

```bash
dotnet add package Arbiter.CommandQuery.MongoDB
```

Pairs with `Arbiter.CommandQuery` (base) and `Arbiter.Mapping`.

## Register

```csharp
using Arbiter.CommandQuery;
using Arbiter.CommandQuery.MongoDB;

// MongoDB repository services (connection-string name)
services.AddMongoRepository("Tracker");

// CQRS + mapping
services.AddCommandQuery();
services.AddSingleton<IMapper, ServiceProviderMapper>();
services.AddSingleton<IMapper<Product, ProductReadModel>, ProductToReadModelMapper>();
services.AddSingleton<IMapper<ProductCreateModel, Product>, ProductCreateModelToProductMapper>();
services.AddSingleton<IMapper<ProductUpdateModel, Product>, ProductUpdateModelToProductMapper>();

// Per-entity registration (TRepository is the concrete repo interface)
services.AddEntityQueries <IMongoEntityRepository<Product>, Product, string, ProductReadModel>();
services.AddEntityCommands<IMongoEntityRepository<Product>, Product, string, ProductReadModel, ProductCreateModel, ProductUpdateModel>();
```

## Entity requirements

```csharp
public class Product : IHaveIdentifier<string>,
ITrackCreated, ITrackUpdated
{
public string Id { get; set; } = "";
public string Name { get; set; } = "";

public DateTimeOffset Created { get; set; }
public string? CreatedBy { get; set; }
public DateTimeOffset Updated { get; set; }
public string? UpdatedBy { get; set; }
}
```

Mongo entities typically use `string` keys. Marker interfaces (`ITrackCreated`, `ITrackUpdated`, `ITrackDeleted`, `IHaveTenant<TKey>`) opt into the standard behaviors just like the EF flavor — see `arbiter-commandquery`.

## Finer-grained registration

```csharp
services.AddEntityIdentifierQuery <string, ProductReadModel, MyHandler>();
services.AddEntityPagedQuery <ProductReadModel, MyHandler>();

services.AddEntityCreateCommand<IMongoEntityRepository<Product>, Product, string, ProductReadModel, ProductCreateModel>();
services.AddEntityUpdateCommand<IMongoEntityRepository<Product>, Product, string, ProductReadModel, ProductUpdateModel>();
services.AddEntityPatchCommand <IMongoEntityRepository<Product>, Product, string, ProductReadModel>();
services.AddEntityDeleteCommand<IMongoEntityRepository<Product>, Product, string, ProductReadModel>();
```

Key-based (natural-key) lookup:

```csharp
services.AddEntityKeyQuery<IMongoEntityRepository<Product>, Product, string, ProductReadModel>();
```

## Sending commands

Same as the EF flavor — the handlers conform to the shared `Arbiter.CommandQuery` request types, so callers don't change:

```csharp
var product = await mediator.Send(
new EntityIdentifierQuery<string, ProductReadModel>(principal, id), ct);
```

## Reference

- Mongo handler guide: https://github.com/loresoft/Arbiter/blob/main/docs/guide/handlers/mongo.md
- Sample: https://github.com/loresoft/Arbiter/tree/main/samples/MongoDB
124 changes: 124 additions & 0 deletions plugins/arbiter-skills/skills/arbiter-commandquery/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
---
name: arbiter-commandquery
description: Use when working with Arbiter's CQRS base layer — EntityQuery, EntityFilter, FilterOperators, SortDirections, EntityPagedQuery, EntityIdentifierQuery, EntityCreateCommand / EntityUpdateCommand / EntityPatchCommand / EntityDeleteCommand, or pipeline behaviors like validation, hybrid cache, audit, tenant, soft-delete. Trigger on AddCommandQuery, AddCommandValidation, AddEntityHybridCache.
---

# Arbiter.CommandQuery

CQRS layer on top of `Arbiter.Mediation`: pre-built generic commands/queries for entity CRUD, plus filter/sort/page modeling and opt-in behaviors.

## Install

```bash
dotnet add package Arbiter.CommandQuery
```

A data provider package supplies the handlers — pick one:
- `Arbiter.CommandQuery.EntityFramework` → see `arbiter-commandquery-ef`
- `Arbiter.CommandQuery.MongoDB` → see `arbiter-commandquery-mongo`

## Register

```csharp
using Arbiter.CommandQuery;

services.AddCommandQuery(); // core CQRS pipeline + mediator
services.AddCommandValidation(); // optional: FluentValidation pipeline behavior
services.AddEntityHybridCache(); // optional: HybridCache-backed query caching
services.AddMessagePackOptions(); // optional: configure MessagePack for dispatcher
```

## Built-in commands and queries

| Type | Purpose |
| --- | --- |
| `EntityIdentifierQuery<TKey, TReadModel>` | Get one by id |
| `EntityIdentifiersQuery<TKey, TReadModel>` | Get many by ids |
| `EntityPagedQuery<TReadModel>` | Filter + sort + (optional) page |
| `EntityCreateCommand<TCreateModel, TReadModel>` | Insert |
| `EntityUpdateCommand<TKey, TUpdateModel, TReadModel>` | Update; pass `upsert: true` for upsert |
| `EntityPatchCommand<TKey, TReadModel>` | JSON Patch partial update |
| `EntityDeleteCommand<TKey, TReadModel>` | Delete |

All commands/queries take a `ClaimsPrincipal` as the first ctor arg.

## Canonical pattern — paged query with filter + sort

```csharp
using Arbiter.CommandQuery.Queries;

var entityQuery = new EntityQuery
{
Filter = new EntityFilter
{
Logic = FilterLogic.And,
Filters = new List<EntityFilter>
{
new() { Name = "Category", Operator = FilterOperators.Equal, Value = "Electronics" },
new() { Name = "Price", Operator = FilterOperators.GreaterThan, Value = 100m },
}
},
Sort = new List<EntitySort>
{
new() { Name = "Name", Direction = SortDirections.Ascending }
},
Page = 1,
PageSize = 20, // omit Page/PageSize to return all matches
};

var query = new EntityPagedQuery<ProductReadModel>(principal, entityQuery);
var result = await mediator.Send(query, cancellationToken); // EntityPagedResult<ProductReadModel>
```

## Variations

```csharp
// Get by id
var product = await mediator.Send(
new EntityIdentifierQuery<int, ProductReadModel>(principal, id), ct);

// Create
var created = await mediator.Send(
new EntityCreateCommand<ProductCreateModel, ProductReadModel>(principal, createModel), ct);

// Update (upsert with last bool)
var updated = await mediator.Send(
new EntityUpdateCommand<int, ProductUpdateModel, ProductReadModel>(principal, id, model, upsert: true), ct);

// Delete
var deleted = await mediator.Send(
new EntityDeleteCommand<int, ProductReadModel>(principal, id), ct);
```

## Filter operators (enum, v2.0+)

`FilterOperators`: `Equal`, `NotEqual`, `LessThan`, `LessThanOrEqual`, `GreaterThan`, `GreaterThanOrEqual`, `Contains`, `StartsWith`, `EndsWith`, `In`, `NotIn`, `IsNull`, `IsNotNull`.
`FilterLogic`: `And`, `Or`.
`SortDirections`: `Ascending`, `Descending`.

## Behaviors (opt-in via marker interfaces on the entity / model)

| Behavior | Triggered by |
| --- | --- |
| Validation | `services.AddCommandValidation()` + a `FluentValidation.IValidator<TRequest>` |
| Hybrid cache | `services.AddEntityHybridCache()` + `[Cacheable]` on the query/read-model |
| Audit fields | Entity implements `ITrackCreated` / `ITrackUpdated` |
| Soft delete | Entity implements `ITrackDeleted` — paged queries auto-filter |
| Tenant isolation | Entity implements `IHaveTenant<TKey>` |

Add per-entity behavior registrations only if you need extra pipeline stages beyond what `AddEntityQueries` / `AddEntityCommands` already wires:

```csharp
services.AddEntityQueryBehaviors<int, ProductReadModel>();
services.AddEntityCreateBehaviors<int, ProductReadModel, ProductCreateModel>();
services.AddEntityUpdateBehaviors<int, ProductReadModel, ProductUpdateModel>();
services.AddEntityPatchBehaviors<int, Product, ProductReadModel>();
services.AddEntityDeleteBehaviors<int, Product, ProductReadModel>();
```

## Reference

- Guide: https://github.com/loresoft/Arbiter/blob/main/docs/guide/commandQuery.md
- Queries: https://github.com/loresoft/Arbiter/tree/main/docs/guide/queries
- Commands: https://github.com/loresoft/Arbiter/tree/main/docs/guide/commands
- Behaviors: https://github.com/loresoft/Arbiter/tree/main/docs/guide/behaviors
Loading
Loading