@mohantn/gate-keeper 2.2.3 → 2.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-entry.js +5 -2
- package/dist/cli-entry.js.map +1 -1
- package/package.json +7 -6
- package/.github/instructions/dotnet-api-integration.instructions.md +0 -416
- package/.github/instructions/dotnet-development.instructions.md +0 -353
- package/.github/instructions/dotnet-testing.instructions.md +0 -406
- package/.github/instructions/react-development.instructions.md +0 -315
- package/.github/instructions/react-testing-optimization.instructions.md +0 -373
- package/.github/instructions/uiux.instructions.md +0 -261
|
@@ -1,353 +0,0 @@
|
|
|
1
|
-
# .NET Core 8 Development Instructions
|
|
2
|
-
|
|
3
|
-
**Scope**: All `.cs` files | **Version**: 2.0 | **Tags**: `csharp`, `.net`, `dotnet-core-8`, `cqrs`, `clean-architecture`
|
|
4
|
-
|
|
5
|
-
**Related Files**: See [dotnet-api-integration.instructions.md](./dotnet-api-integration.instructions.md) for DI/API patterns, [dotnet-testing.instructions.md](./dotnet-testing.instructions.md) for testing
|
|
6
|
-
|
|
7
|
-
## 🔴 MANDATORY: Using Statements Organization
|
|
8
|
-
Group at top: **System** → **Third-party** (Microsoft, AutoMapper) → **Application** (RetailApp.*). Never fully-qualify.
|
|
9
|
-
|
|
10
|
-
```csharp
|
|
11
|
-
using System;
|
|
12
|
-
using System.Collections.Generic;
|
|
13
|
-
using Microsoft.AspNetCore.Mvc;
|
|
14
|
-
using AutoMapper;
|
|
15
|
-
using RetailApp.Application.Products.Services;
|
|
16
|
-
|
|
17
|
-
namespace RetailApp.Presentation.Controllers;
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Architecture Patterns
|
|
21
|
-
|
|
22
|
-
| Pattern | Rules |
|
|
23
|
-
|---------|-------|
|
|
24
|
-
| **CQRS** | Commands (write) → `ICommand<TResult>` handlers; Queries (read) separate |
|
|
25
|
-
| **Clean Architecture** | Core → Application → Infrastructure → Presentation |
|
|
26
|
-
| **Single Class Per File** | One public type per file |
|
|
27
|
-
| **Repository Pattern** | Generic `IRepository<T>` + `IUnitOfWork` |
|
|
28
|
-
| **Service Layer** | Business logic, validation, DI via constructor |
|
|
29
|
-
| **Async/Await** | All I/O async; support CancellationToken |
|
|
30
|
-
|
|
31
|
-
## Database Conventions
|
|
32
|
-
|
|
33
|
-
- **C# Properties**: PascalCase (ProductName, RetailerId)
|
|
34
|
-
- **Database**: snake_case (product_name, retailer_id)
|
|
35
|
-
- **Foreign Keys**: {EntityName}_id (retailer_id, product_id)
|
|
36
|
-
- **Indexes**: idx_table_column (idx_products_retailer_id)
|
|
37
|
-
- **Audit Fields**: CreatedAt, UpdatedAt, CreatedBy, UpdatedBy
|
|
38
|
-
|
|
39
|
-
## Core Code Patterns
|
|
40
|
-
|
|
41
|
-
### BaseEntity
|
|
42
|
-
```csharp
|
|
43
|
-
public abstract class BaseEntity {
|
|
44
|
-
public Guid Id { get; set; }
|
|
45
|
-
public DateTime CreatedAt { get; set; }
|
|
46
|
-
public DateTime? UpdatedAt { get; set; }
|
|
47
|
-
public string? CreatedBy { get; set; }
|
|
48
|
-
public string? UpdatedBy { get; set; }
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
### Command Handler (CQRS)
|
|
53
|
-
```csharp
|
|
54
|
-
public class CreateProductCommandHandler : ICommandHandler<CreateProductCommand, ProductDto> {
|
|
55
|
-
private readonly IUnitOfWork _unitOfWork;
|
|
56
|
-
private readonly ProductMapper _mapper;
|
|
57
|
-
private readonly ILogger<CreateProductCommandHandler> _logger;
|
|
58
|
-
|
|
59
|
-
public async Task<ProductDto> HandleAsync(CreateProductCommand command, CancellationToken ct) {
|
|
60
|
-
var product = _mapper.MapToEntity(command);
|
|
61
|
-
await _unitOfWork.Products.AddAsync(product, ct);
|
|
62
|
-
await _unitOfWork.SaveChangesAsync(ct);
|
|
63
|
-
return _mapper.MapToDto(product);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### Query Handler (CQRS)
|
|
69
|
-
```csharp
|
|
70
|
-
public class GetProductQueryHandler : IQueryHandler<GetProductQuery, ProductDto> {
|
|
71
|
-
private readonly IRepository<Product> _repository;
|
|
72
|
-
private readonly ProductMapper _mapper;
|
|
73
|
-
|
|
74
|
-
public async Task<ProductDto> HandleAsync(GetProductQuery query, CancellationToken ct) {
|
|
75
|
-
var product = await _repository.GetByIdAsync(query.ProductId, ct);
|
|
76
|
-
if (product == null) throw new NotFoundException("Product not found");
|
|
77
|
-
return _mapper.MapToDto(product);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### Service Layer
|
|
83
|
-
```csharp
|
|
84
|
-
public class ProductService : IProductService {
|
|
85
|
-
private readonly IUnitOfWork _unitOfWork;
|
|
86
|
-
private readonly ILogger<ProductService> _logger;
|
|
87
|
-
|
|
88
|
-
public async Task<ProductDto> CreateProductAsync(CreateProductCommand cmd, CancellationToken ct) {
|
|
89
|
-
var product = Product.Create(cmd.Name, cmd.Price, cmd.RetailerId);
|
|
90
|
-
await _unitOfWork.Products.AddAsync(product, ct);
|
|
91
|
-
await _unitOfWork.SaveChangesAsync(ct);
|
|
92
|
-
return new ProductDto { Id = product.Id, Name = product.Name };
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
### Entity
|
|
98
|
-
```csharp
|
|
99
|
-
public class Product : BaseEntity {
|
|
100
|
-
public string Name { get; set; } = string.Empty;
|
|
101
|
-
public decimal Price { get; set; }
|
|
102
|
-
public int StockQuantity { get; set; }
|
|
103
|
-
public string RetailerId { get; set; } = string.Empty;
|
|
104
|
-
|
|
105
|
-
public static Product Create(string name, decimal price, string retailerId) {
|
|
106
|
-
if (price < 0) throw new ArgumentOutOfRangeException(nameof(price));
|
|
107
|
-
return new Product { Id = Guid.NewGuid(), Name = name, Price = price, RetailerId = retailerId };
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### Entity Configuration (Fluent API)
|
|
113
|
-
```csharp
|
|
114
|
-
public class ProductConfiguration : IEntityTypeConfiguration<Product> {
|
|
115
|
-
public void Configure(EntityTypeBuilder<Product> builder) {
|
|
116
|
-
builder.ToTable("products");
|
|
117
|
-
builder.HasKey(p => p.Id);
|
|
118
|
-
builder.Property(p => p.Name).HasMaxLength(255).IsRequired();
|
|
119
|
-
builder.Property(p => p.Price).HasPrecision(10, 2);
|
|
120
|
-
builder.HasIndex(p => p.RetailerId).HasDatabaseName("idx_products_retailer_id");
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
### Repository Pattern
|
|
126
|
-
```csharp
|
|
127
|
-
public interface IRepository<T> where T : BaseEntity {
|
|
128
|
-
Task<T?> GetByIdAsync(Guid id, CancellationToken ct);
|
|
129
|
-
Task<IEnumerable<T>> ListAsync(CancellationToken ct);
|
|
130
|
-
Task AddAsync(T entity, CancellationToken ct);
|
|
131
|
-
void Update(T entity);
|
|
132
|
-
void Delete(T entity);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
public class Repository<T> : IRepository<T> where T : BaseEntity {
|
|
136
|
-
protected readonly DbContext _dbContext;
|
|
137
|
-
|
|
138
|
-
public async Task<T?> GetByIdAsync(Guid id, CancellationToken ct) =>
|
|
139
|
-
await _dbContext.Set<T>().FirstOrDefaultAsync(x => x.Id == id, ct);
|
|
140
|
-
public async Task<IEnumerable<T>> ListAsync(CancellationToken ct) =>
|
|
141
|
-
await _dbContext.Set<T>().ToListAsync(ct);
|
|
142
|
-
public async Task AddAsync(T entity, CancellationToken ct) {
|
|
143
|
-
await _dbContext.Set<T>().AddAsync(entity, ct);
|
|
144
|
-
}
|
|
145
|
-
public void Update(T entity) => _dbContext.Set<T>().Update(entity);
|
|
146
|
-
public void Delete(T entity) => _dbContext.Set<T>().Remove(entity);
|
|
147
|
-
}
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
### Unit of Work Pattern
|
|
151
|
-
```csharp
|
|
152
|
-
public interface IUnitOfWork : IAsyncDisposable {
|
|
153
|
-
IRepository<Product> Products { get; }
|
|
154
|
-
IRepository<Order> Orders { get; }
|
|
155
|
-
Task<int> SaveChangesAsync(CancellationToken ct = default);
|
|
156
|
-
Task BeginTransactionAsync(CancellationToken ct = default);
|
|
157
|
-
Task CommitTransactionAsync(CancellationToken ct = default);
|
|
158
|
-
Task RollbackTransactionAsync(CancellationToken ct = default);
|
|
159
|
-
}
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
### Multi-Table Transactions
|
|
163
|
-
```csharp
|
|
164
|
-
await _unitOfWork.BeginTransactionAsync(ct);
|
|
165
|
-
try {
|
|
166
|
-
await _unitOfWork.Products.AddAsync(product, ct);
|
|
167
|
-
await _unitOfWork.Orders.AddAsync(order, ct);
|
|
168
|
-
await _unitOfWork.SaveChangesAsync(ct);
|
|
169
|
-
await _unitOfWork.CommitTransactionAsync(ct);
|
|
170
|
-
} catch {
|
|
171
|
-
await _unitOfWork.RollbackTransactionAsync(ct);
|
|
172
|
-
throw;
|
|
173
|
-
}
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### Mapperly Configuration
|
|
177
|
-
```bash
|
|
178
|
-
dotnet add package Mapperly
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
```csharp
|
|
182
|
-
[Mapper]
|
|
183
|
-
public partial class ProductMapper {
|
|
184
|
-
public partial ProductDto MapToDto(Product product);
|
|
185
|
-
public partial Product MapToEntity(CreateProductCommand command);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Advanced: Collections & nested objects
|
|
189
|
-
[Mapper]
|
|
190
|
-
public partial class OrderMapper {
|
|
191
|
-
public partial OrderDto MapToDto(Order order);
|
|
192
|
-
|
|
193
|
-
[MapProperty(nameof(Order.Items), nameof(OrderDto.LineItems))]
|
|
194
|
-
public partial OrderDto CustomMapping(Order order);
|
|
195
|
-
|
|
196
|
-
private string MapStatus(OrderStatus status) => status.ToString();
|
|
197
|
-
}
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
## Async Best Practices
|
|
201
|
-
|
|
202
|
-
### Async/Await Patterns
|
|
203
|
-
```csharp
|
|
204
|
-
// ✅ GOOD: Async method returns Task
|
|
205
|
-
public async Task<ProductDto> GetProductAsync(string id, CancellationToken ct) {
|
|
206
|
-
return await _repository.GetByIdAsync(id, ct);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// ❌ BAD: Async void - cannot be awaited, hides exceptions
|
|
210
|
-
public async void ProcessProductAsync(string id) { }
|
|
211
|
-
|
|
212
|
-
// ✅ GOOD: Properly wait in background jobs
|
|
213
|
-
[DisableConcurrentExecution(10, "Background-Job")]
|
|
214
|
-
public class BackgroundPublishJobHandler {
|
|
215
|
-
public async Task ExecuteAsync(CancellationToken ct) {
|
|
216
|
-
await ProcessBatchAsync(ct);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
## Code Style & Formatting
|
|
222
|
-
|
|
223
|
-
### Naming Conventions
|
|
224
|
-
```csharp
|
|
225
|
-
// Naming conventions
|
|
226
|
-
public class ProductService { // PascalCase for class names
|
|
227
|
-
private readonly ILogger _logger; // camelCase with underscore for private fields
|
|
228
|
-
public string ProductName { get; set; } // PascalCase for properties
|
|
229
|
-
|
|
230
|
-
public async Task<Product> GetProductAsync(string id) { // Async suffix
|
|
231
|
-
var product = await _repository.GetAsync(id); // camelCase for local variables
|
|
232
|
-
return product;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Interface naming
|
|
237
|
-
public interface IProductRepository { } // I prefix
|
|
238
|
-
public interface IProductService { } // I prefix
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
### Run formatting
|
|
242
|
-
```bash
|
|
243
|
-
dotnet format # Auto-fix style issues
|
|
244
|
-
dotnet format --verify-no-changes # Check only
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
## Performance Best Practices
|
|
248
|
-
|
|
249
|
-
### Query Optimization
|
|
250
|
-
```csharp
|
|
251
|
-
// ✅ GOOD: Async streaming for large datasets
|
|
252
|
-
public async IAsyncEnumerable<ProductDto> GetProductsStreamAsync(CancellationToken ct) {
|
|
253
|
-
await foreach (var product in _dbContext.Products.AsAsyncEnumerable().WithCancellation(ct)) {
|
|
254
|
-
yield return _mapper.MapToDto(product);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// ✅ GOOD: Pagination
|
|
259
|
-
public async Task<PagedResult<ProductDto>> GetProductsPaginatedAsync(
|
|
260
|
-
int pageNumber, int pageSize, CancellationToken ct) {
|
|
261
|
-
var total = await _dbContext.Products.CountAsync(ct);
|
|
262
|
-
var items = await _dbContext.Products
|
|
263
|
-
.Skip((pageNumber - 1) * pageSize)
|
|
264
|
-
.Take(pageSize)
|
|
265
|
-
.ToListAsync(ct);
|
|
266
|
-
return new PagedResult<ProductDto> {
|
|
267
|
-
Items = items.Select(_mapper.MapToDto),
|
|
268
|
-
Total = total
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// ✅ GOOD: Explicit includes, no lazy loading
|
|
273
|
-
public async Task<Product> GetProductWithDetailsAsync(string id, CancellationToken ct) {
|
|
274
|
-
return await _dbContext.Products
|
|
275
|
-
.Include(p => p.Category)
|
|
276
|
-
.Include(p => p.Supplier)
|
|
277
|
-
.FirstOrDefaultAsync(p => p.Id == id, ct);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// ✅ GOOD: Memory caching with TTL
|
|
281
|
-
public async Task<ProductDto> GetProductCachedAsync(string id, CancellationToken ct) {
|
|
282
|
-
return await _cache.GetOrCreateAsync($"product:{id}", async entry => {
|
|
283
|
-
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
|
|
284
|
-
return await GetProductAsync(id, ct);
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
## Essential CLI Commands
|
|
290
|
-
|
|
291
|
-
```bash
|
|
292
|
-
# Package Management
|
|
293
|
-
dotnet add package PackageName
|
|
294
|
-
dotnet restore
|
|
295
|
-
dotnet list package --outdated
|
|
296
|
-
|
|
297
|
-
# Build & Run
|
|
298
|
-
dotnet build
|
|
299
|
-
dotnet build --configuration Release
|
|
300
|
-
dotnet run
|
|
301
|
-
dotnet watch run
|
|
302
|
-
dotnet clean
|
|
303
|
-
|
|
304
|
-
# EF Migrations
|
|
305
|
-
dotnet ef migrations add InitialCreate --project Infrastructure
|
|
306
|
-
dotnet ef migrations list
|
|
307
|
-
dotnet ef database update
|
|
308
|
-
dotnet ef database drop --force
|
|
309
|
-
dotnet ef migrations script > migration.sql
|
|
310
|
-
|
|
311
|
-
# Publishing
|
|
312
|
-
dotnet publish --configuration Release --output ./publish
|
|
313
|
-
dotnet publish --self-contained -r linux-x64
|
|
314
|
-
|
|
315
|
-
# Code Quality
|
|
316
|
-
dotnet format
|
|
317
|
-
dotnet build --verbosity diagnostic
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
## Quick Checklist ✅
|
|
321
|
-
|
|
322
|
-
- Using statements: System → Third-party → Application
|
|
323
|
-
- One public type per file
|
|
324
|
-
- CQRS: Commands & Queries in separate handlers
|
|
325
|
-
- Repository + Unit of Work for data access
|
|
326
|
-
- Service layer for business logic
|
|
327
|
-
- Mapperly for DTO mapping
|
|
328
|
-
- Entity properties: PascalCase; DB columns: snake_case
|
|
329
|
-
- Audit fields in BaseEntity
|
|
330
|
-
- Foreign keys follow {EntityName}_id pattern
|
|
331
|
-
- All async methods support CancellationToken
|
|
332
|
-
- Transactions for multi-table operations
|
|
333
|
-
- No fully-qualified namespace references
|
|
334
|
-
|
|
335
|
-
## Common Mistakes ❌
|
|
336
|
-
|
|
337
|
-
- Using fully-qualified names instead of `using` statements
|
|
338
|
-
- Multiple public types in one file
|
|
339
|
-
- Missing CancellationToken in async methods
|
|
340
|
-
- Business logic in controllers/endpoints
|
|
341
|
-
- Missing audit field updates
|
|
342
|
-
- Skipped transaction handling for multi-table ops
|
|
343
|
-
- No logging of important operations
|
|
344
|
-
- Missing error handling
|
|
345
|
-
- Ignoring test failures
|
|
346
|
-
|
|
347
|
-
## Resources
|
|
348
|
-
|
|
349
|
-
- [Microsoft .NET Docs](https://docs.microsoft.com/dotnet/)
|
|
350
|
-
- [Entity Framework Core](https://docs.microsoft.com/ef/core/)
|
|
351
|
-
- [CQRS Pattern](https://martinfowler.com/bliki/CQRS.html)
|
|
352
|
-
- [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)
|
|
353
|
-
- [Mapperly](https://mapperly.riok.app/)
|