Skip to main content

shiny-mediator

Generate Shiny Mediator handlers, contracts, middleware, and scaffold projects for .NET applications

الانتقال إلى التثبيت

معلومات المصدر

المستودع
shinyorg/skills
آخر نشاط في المصدر
١٥ أغسطس ٢٠٢٦ في ١٧:١٣
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٤
التفرعات
٠

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

مستكشف الملفات
5 ملفات

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
shiny-mediator
description
Generate Shiny Mediator handlers, contracts, middleware, and scaffold projects for .NET applications
auto_invoke
true
triggers
["mediator","handler","request handler","command handler","event handler","stream handler","middleware","IRequest","ICommand","IEvent","CQRS","Shiny.Mediator","server sent events","SSE","EventStream","WaitForSingleEvent","IAsyncEnumerable","IStreamRequest","IServerSentEventsStream","OpenAPI","HTTP client","MediatorHttp","swagger","contract-first","strongly typed HTTP","AI tools","AITool","Microsoft.Extensions.AI","AddGeneratedAITools","ShinyMediatorGenerateAITools","ISerializer","ISerializerService","[Truncated]"]
# Shiny Mediator Skill You are an expert in Shiny Mediator, a mediator pattern library for .NET applications. ## When to Use This Skill Invoke this skill when the user wants to: - Create request handlers, command handlers, event handlers, or stream handlers - Generate contracts (IRequest, ICommand, IEvent, IStreamRequest) - Add middleware (caching, resilience, validation, offline) - Scaffold ASP.NET, MAUI, or Blazor projects with Shiny Mediator - Configure Shiny Mediator in their application - Set up ASP.NET Server-Sent Events (SSE) endpoints with stream handlers - Use event subscriptions (WaitForSingleEvent, EventStream, Subscribe) - Generate strongly-typed HTTP clients from OpenAPI/Swagger specs - Create contract-first HTTP request handlers with [Get], [Post], etc. - Expose mediator contracts as AI tools via Microsoft.Extensions.AI - Migrate from MediatR to Shiny Mediator ## Library Overview **Documentation**: https://shinylib.net/mediator Shiny Mediator is AOT & trimming friendly, using source generators for automatic DI registration. ### Core Patterns | Pattern | Contract | Handler | Usage | |---------|----------|---------|-------| | Request | `IRequest<TResult>` | `IRequestHandler<TRequest, TResult>` | Queries returning data | | Command | `ICommand` | `ICommandHandler<TCommand>` | Void state changes | | Event | `IEvent` | `IEventHandler<TEvent>` | Pub/sub notifications | | Stream | `IStreamRequest<TResult>` | `IStreamRequestHandler<TRequest, TResult>` | IAsyncEnumerable | ### Handler Registration Always use registration attributes: ```csharp [MediatorSingleton] // Stateless handlers [MediatorScoped] // Handlers needing per-request services (DbContext) ``` **Critical: Partial Class Requirement** When using **any middleware attribute** (`[Cache]`, `[OfflineAvailable]`, `[Resilient]`, `[MainThread]`, `[TimerRefresh]`, `[Sample]`, `[Throttle]`), the handler class **must be declared as `partial`**: ```csharp [MediatorSingleton] public partial class MyHandler : IRequestHandler<MyRequest, MyResult> // partial required! { [Cache(AbsoluteExpirationSeconds = 60)] public Task<MyResult> Handle(...) { } } ``` This enables the source generator to create the `IHandlerAttributeMarker` implementation. Without `partial`, you'll get error `SHINY001`. ### Basic Setup **ASP.NET:** ```csharp builder.Services.AddShinyMediator(x => x .AddMediatorRegistry() ); app.MapGeneratedMediatorEndpoints(); ``` **MAUI:** ```csharp builder.AddShinyMediator(x => x .AddMediatorRegistry() .UseMaui() .AddMauiPersistentCache() .PreventEventExceptions() ); ``` **Blazor:** ```csharp builder.Services.AddShinyMediator(x => x .AddMediatorRegistry() .UseBlazor() .PreventEventExceptions() ); ``` **Logging & Configuration are optional (v6.8+)** - do NOT tell users they must call `AddLogging()` or register an `IConfiguration` to use the mediator. Every built-in service and middleware injects `ILogger` / `ILoggerFactory` / `IConfiguration` as an optional constructor argument defaulting to `null`. With no logging registered, log statements are skipped. With no `IConfiguration` registered, every configuration section (`Cache`, `Offline`, `ReplayStream`, `Resilience`, `TimerRefresh`, `PerformanceLogging`, `UserErrorNotifications`, `Http`) resolves to "not configured" and the middleware passes through - use the attribute equivalents (`[Cache]`, `[OfflineAvailable]`, `[TimerRefresh]`, `[Resilient]`) when there is no configuration source. Application-level handlers and middleware can still inject `ILogger` / `IConfiguration` as required dependencies - the app controls its own container. Follow the optional convention (`ILogger<T>? logger = null` last in the constructor, `logger?.LogDebug(...)`) when writing middleware or infrastructure meant to ship in a reusable library. ## JSON Serialization (v6.6+) Mediator uses `Shiny.ISerializer` from `Shiny.Extensions.Serialization`. The default chain is **AOT-strict** — no reflection fallback — so every contract that touches JSON (HTTP transport, storage cache, offline service, TickerQ scheduled commands, ASP.NET endpoints) must be in a registered `JsonSerializerContext`. Missing registrations throw `InvalidOperationException: No JsonTypeInfo registered for type 'T'` at runtime; the compiler won't catch it. **Default pattern when generating contracts.** Always emit a `[ShinyJsonContext]`-tagged partial `JsonSerializerContext` alongside the contracts and list every request, response, event, and scheduled-command type: ```csharp using System.Text.Json.Serialization; using Shiny; [ShinyJsonContext] [JsonSerializable(typeof(GetCustomerRequest))] [JsonSerializable(typeof(CustomerResponse))] [JsonSerializable(typeof(OrderPlacedEvent))] internal partial class AppJsonContext : JsonSerializerContext; ``` The `[ModuleInitializer]` emitted by the extensions generator registers this context with `Shiny.Json` before `Main` runs — no `services.AddJsonContext(...)` call needed. **Opt-in auto-generation (default off).** Setting `<ShinyMediatorGenerateJsonContext>true</ShinyMediatorGenerateJsonContext>` in the project file makes the mediator source generator emit a per-assembly `__ShinyMediatorContractsJsonResolver` covering every registered handler's request, response, command, event, and stream contract types (transitively, including their public property types), plus a `[ModuleInitializer]` that registers it. When enabled you don't need to hand-declare a `[ShinyJsonContext]` for in-assembly handler contracts. It is **opt-in** so there's always an escape hatch if the generated resolver misbehaves — when generating contracts, keep emitting an explicit `[ShinyJsonContext]` as the default unless the consumer has turned this property on. Cross-assembly contract types still need registration in their owning assembly. **Collections (`List<T>`, `T[]`, `IAsyncEnumerable<T>`, etc.) of a contract type.** Mark the element type with `[ShinyJsonInclude]`: ```csharp [ShinyJsonInclude] public partial class Customer { /* ... */ } ``` **OpenAPI HTTP clients with `GenerateJsonConverters="true"`:** the OpenAPI generator emits a custom `IJsonTypeInfoResolver` covering every generated model + contract + enum (including `Nullable<TEnum>` / `List<T>` / `T[]` shapes) plus a `[ModuleInitializer]`. Users don't need `[ShinyJsonContext]` for OpenAPI-generated types. **Attribute-driven HTTP clients (`[Get]` / `[Post]` / `[Body]`):** user-written types; require explicit `[ShinyJsonContext]` registration. **Migration from v5/early-v6:** - `ISerializerService` was removed — replace with `Shiny.ISerializer` (Shiny namespace, from `Shiny.Extensions.Serialization`). - `SysTextJsonSerializerService` was removed — DI registration is automatic via `AddShinyMediator`. - `ShinyMediatorBuilder.SetSerializer<T>()` was removed — replace by either registering a different `Shiny.ISerializer` in DI before `AddShinyMediator`, or calling `Shiny.Json.AddContext` / `Shiny.Json.AddResolver`. - The legacy `[SourceGenerateJsonConverter]` attribute still works for backward compatibility but new code should use `[ShinyJsonContext]` + `[JsonSerializable]` instead — it gives first-class AOT coverage of collection shapes. **Tests / development scenarios that need reflection fallback** (ad-hoc / anonymous types): ```csharp // In an [assembly: ...] or a [ModuleInitializer] Shiny.Json.AddResolver(new System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver()); ``` This is not AOT-safe — use only in test fixtures or non-production code. ## Code Generation Instructions When generating Shiny Mediator code: ### 1. Contracts Always use records for immutability: ```csharp public record GetUserRequest(int UserId) : IRequest<UserDto>; public record CreateUserCommand(string Name, string Email) : ICommand; public record UserCreatedEvent(int UserId, string Name) : IEvent; ``` ### 2. Handlers Include all three parameters in Handle method: ```csharp [MediatorScoped] public class GetUserRequestHandler : IRequestHandler<GetUserRequest, UserDto> { public Task<UserDto> Handle( GetUserRequest request, IMediatorContext context, CancellationToken cancellationToken) { // Implementation } } ``` ### 3. Middleware Attributes Apply to handler methods as needed: - `[Cache(AbsoluteExpirationSeconds = N)]` - Cacheable queries - `[OfflineAvailable]` - Offline storage for mobile - `[Resilient("policyName")]` - Retry/timeout policies - `[MainThread]` - MAUI main thread execution - `[TimerRefresh(milliseconds)]` - Auto-refresh streams - `[Sample(milliseconds)]` - Fixed-window sampling (last event in window executes) - `[Throttle(milliseconds)]` - True throttle (first event executes, cooldown discards rest) - `[Validate]` - Data annotation validation **When using ANY of these attributes, the handler class MUST be `partial`:** ```csharp [MediatorSingleton] public partial class CachedHandler : IRequestHandler<MyRequest, MyData> { [Cache(AbsoluteExpirationSeconds = 60)] [OfflineAvailable] public Task<MyData> Handle(...) { } } ``` ### 4. Middleware Ordering Use `[MiddlewareOrder(int)]` on custom middleware classes to control execution order. Lower values run first (outermost). Default is 0. ```csharp [MiddlewareOrder(-100)] // Runs before middleware with higher order values [MediatorSingleton] public class EarlyMiddleware<TRequest, TResult> : IRequestMiddleware<TRequest, TResult> where TRequest : IRequest<TResult> { ... } ``` ### 5. File Organization Place files in appropriate folders: - Contracts: `Contracts/{Name}Request.cs`, `Contracts/{Name}Command.cs` - Handlers: `Handlers/{Name}Handler.cs` - Middleware: `Middleware/{Name}Middleware.cs` ## Usage Examples **Request:** ```csharp var response = await mediator.Request(new GetUserRequest(1)); var user = response.Result; ``` **Command:** ```csharp await mediator.Send(new CreateUserCommand("John", "john@example.com")); ``` **Event:** ```csharp await mediator.Publish(new UserCreatedEvent(1, "John")); ``` **Chaining via Context:** ```csharp public async Task<UserDto> Handle(GetUserRequest request, IMediatorContext context, CancellationToken ct) { // Use context to chain operations (shares scope) await context.Publish(new UserAccessedEvent(request.UserId)); return new UserDto(...); } ``` ### Event Subscriptions & Streaming **WaitForSingleEvent** - Await a single event occurrence (with optional filter): ```csharp // Wait for a specific event (blocks until event fires or cancellation) var evt = await mediator.WaitForSingleEvent<OrderCompletedEvent>( filter: e => e.OrderId == orderId, cancellationToken: ct ); ``` **EventStream** - Continuous IAsyncEnumerable stream of events (uses Channels internally): ```csharp // Consume events as an async stream await foreach (var evt in mediator.EventStream<PriceUpdatedEvent>(cancellationToken: ct)) { Console.WriteLine($"New price: {evt.Price}"); } ``` **Subscribe** - Manual subscription returning IDisposable: ```csharp var sub = mediator.Subscribe<MyEvent>((ev, ctx, ct) => { Console.WriteLine($"Event received: {ev}"); return Task.CompletedTask; }); // Later: sub.Dispose() to unsubscribe ``` ### ASP.NET Server-Sent Events (SSE) Stream handlers decorated with `[MediatorHttpGet]` or `[MediatorHttpPost]` on an `IStreamRequestHandler` are **automatically generated as SSE endpoints** by the source generator via `MapGeneratedMediatorEndpoints()`. **Manual SSE endpoint with EventStream:** ```csharp app.MapGet("/events", ([FromServices] IMediator mediator) => TypedResults.ServerSentEvents(mediator.EventStream<MyEvent>()) ); ``` **Stream handler as auto-generated SSE endpoint:** ```csharp public record TickerStreamRequest : IStreamRequest<int>; [MediatorScoped] public class TickerStreamHandler : IStreamRequestHandler<TickerStreamRequest, int> { [MediatorHttpGet("/ticker")] public async IAsyncEnumerable<int> Handle( TickerStreamRequest request, IMediatorContext context, [EnumeratorCancellation] CancellationToken cancellationToken) { var i = 0; while (!cancellationToken.IsCancellationRequested) { yield return i++; await Task.Delay(1000, cancellationToken); } } } ``` **HTTP client-side SSE consumption:** Implement `IServerSentEventsStream` marker on the contract to indicate the server returns SSE format. The generated HTTP handler will use `ReadServerSentEvents<T>()` to parse the `data:` prefixed SSE lines. ```csharp public record TickerStreamRequest : IStreamRequest<int>, IServerSentEventsStream; ``` ## Contract-First HTTP Clients Shiny Mediator generates strongly-typed HTTP client handlers from contract classes decorated with HTTP method attributes. No manual `HttpClient` code needed. ### Manual HTTP Contracts Decorate request classes with `[Get]`, `[Post]`, `[Put]`, `[Delete]`, `[Patch]` and use `[Query]`, `[Header]`, `[Body]` on properties: ```csharp [Get("/api/orders/{OrderId}")] public class GetOrderRequest : IRequest<OrderDto> { public int OrderId { get; set; } // Route parameter (matches {OrderId}) [Query("status")] public string? Status { get; set; } // ?status=value [Header("Authorization")] public string? AuthToken { get; set; } // HTTP header } [Post("/api/orders")] public class CreateOrderRequest : IRequest<OrderDto> { [Body] public CreateOrderBody? Body { get; set; } // JSON request body } ``` The source generator creates handler classes inheriting `BaseHttpRequestHandler` that build routes, add query/header parameters, serialize bodies, and call `IHttpClientFactory`.
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub