- Add path-specific rules for commands/queries, dynamic queries, validation, and gRPC - Add /add-command, /add-query, /add-dynamic-query scaffolding skills - Add project settings with post-edit formatting, proto validation, and build-gate hooks - Add .editorconfig codifying existing code style conventions - Trim CLAUDE.md from 414 to 130 lines (domain details moved to rules) - Add .harness-version tracking for the shared claude-harness repo Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
126 lines
3.9 KiB
Markdown
126 lines
3.9 KiB
Markdown
---
|
|
name: add-dynamic-query
|
|
description: Scaffold a dynamic query with IQueryableProviderOverride for automatic pagination, filtering, and sorting. This is the DEFAULT choice for any list/collection query.
|
|
argument-hint: <entity name and optional description>
|
|
---
|
|
|
|
# Add Dynamic Query
|
|
|
|
Scaffold a dynamic query based on: $ARGUMENTS
|
|
|
|
**This is the default query pattern.** It gives pagination, filtering, sorting, grouping, and aggregation for free.
|
|
|
|
## Instructions
|
|
|
|
### 1. Identify the entity
|
|
|
|
Determine the source entity type from the user's description. If the project uses a DAL with EF Core, the entity should already exist in the DbContext.
|
|
|
|
### 2. Determine the feature folder
|
|
|
|
Place the file in: `Features/{DomainArea}/`
|
|
|
|
### 3. Create the queryable provider
|
|
|
|
Create `Features/{DomainArea}/{Entity}QueryableProvider.cs`:
|
|
|
|
```csharp
|
|
using Svrnty.CQRS.DynamicQuery.Abstractions;
|
|
|
|
namespace {ProjectNamespace}.Features.{DomainArea};
|
|
|
|
public class {Entity}QueryableProvider : IQueryableProviderOverride<{Entity}>
|
|
{
|
|
private readonly {DbContextType} _db;
|
|
public {Entity}QueryableProvider({DbContextType} db) => _db = db;
|
|
|
|
public Task<IQueryable<{Entity}>> GetQueryableAsync(object query, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult(_db.{EntityPlural}.AsQueryable());
|
|
}
|
|
```
|
|
|
|
If the entity needs a DTO projection (different shape for the API), note this for the registration step.
|
|
|
|
### 4. Create alter queryable service (if needed)
|
|
|
|
If the entity needs security filtering, tenant isolation, or default ordering, create `Features/{DomainArea}/{Entity}AlterQueryable.cs`:
|
|
|
|
```csharp
|
|
using Svrnty.CQRS.DynamicQuery.Abstractions;
|
|
|
|
namespace {ProjectNamespace}.Features.{DomainArea};
|
|
|
|
public class {Entity}AlterQueryable : IAlterQueryableService<{Entity}, {EntityOrDto}>
|
|
{
|
|
public Task<IQueryable<{Entity}>> AlterQueryableAsync(
|
|
IQueryable<{Entity}> query,
|
|
IDynamicQuery dynamicQuery,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
// Add default ordering, security filters, etc.
|
|
return Task.FromResult(query.OrderByDescending(x => x.CreatedAt));
|
|
}
|
|
}
|
|
```
|
|
|
|
### 5. Register in DI
|
|
|
|
Find the project's service registration and add:
|
|
|
|
```csharp
|
|
// Basic — entity returned as-is
|
|
builder.Services.AddDynamicQueryWithProvider<{Entity}, {Entity}QueryableProvider>();
|
|
|
|
// With DTO projection — entity mapped to DTO
|
|
builder.Services.AddDynamicQueryWithProvider<{Entity}, {EntityDto}, {Entity}QueryableProvider>();
|
|
|
|
// With alter service (add after provider registration)
|
|
builder.Services.AddAlterQueryable<{Entity}, {EntityOrDto}, {Entity}AlterQueryable>();
|
|
```
|
|
|
|
### 6. Ensure dynamic query dependencies are registered
|
|
|
|
Check that these are registered (typically once per project, before `AddSvrntyCqrs`):
|
|
|
|
```csharp
|
|
builder.Services.AddTransient<IAsyncQueryableService, SimpleAsyncQueryableService>();
|
|
builder.Services.AddTransient<IQueryHandlerAsync, QueryHandlerAsync>();
|
|
```
|
|
|
|
If they're already present, don't add duplicates.
|
|
|
|
### 7. Proto message (if project uses gRPC)
|
|
|
|
```protobuf
|
|
// In the DynamicQueryService definition
|
|
rpc Query{EntityPlural} (DynamicQuery{EntityPlural}Request) returns (DynamicQuery{EntityPlural}Response);
|
|
|
|
// Entity message (if not already defined)
|
|
message {Entity} {
|
|
// fields matching entity properties
|
|
}
|
|
|
|
// Request — uses standard dynamic query fields
|
|
message DynamicQuery{EntityPlural}Request {
|
|
int32 page = 1;
|
|
int32 page_size = 2;
|
|
repeated DynamicQueryFilter filters = 3;
|
|
repeated DynamicQuerySort sorts = 4;
|
|
repeated DynamicQueryGroup groups = 5;
|
|
repeated DynamicQueryAggregate aggregates = 6;
|
|
}
|
|
|
|
// Response
|
|
message DynamicQuery{EntityPlural}Response {
|
|
repeated {Entity} data = 1;
|
|
int64 total_records = 2;
|
|
int32 number_of_pages = 3;
|
|
}
|
|
```
|
|
|
|
The standard `DynamicQueryFilter`, `DynamicQuerySort`, `DynamicQueryGroup`, and `DynamicQueryAggregate` messages should already be defined — reuse them.
|
|
|
|
### 8. Summary
|
|
|
|
After creating everything, list what was created and where.
|