Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:20:21 +08:00
commit bbbaf7acad
63 changed files with 38552 additions and 0 deletions

502
commands/feature/README.md Normal file
View File

@@ -0,0 +1,502 @@
# Feature Implementation Skill
Production-ready feature implementation across database, backend, and frontend layers with incremental phased approach and comprehensive quality standards.
## Overview
This skill provides a complete workflow for implementing full-stack features from database schema to frontend components. It follows industry best practices including layered architecture, comprehensive testing, security hardening, and performance optimization.
## Available Operations
### `implement` - Complete Full-Stack Implementation
Implement a feature across all layers (database, backend, frontend, integration) with production-ready code, tests, and documentation.
**Usage:**
```bash
/10x-fullstack-engineer:feature implement description:"user authentication with OAuth and 2FA" tests:"comprehensive"
```
**Parameters:**
- `description` (required) - Detailed feature description
- `scope` (optional) - Specific area to focus on
- `priority` (optional) - high|medium|low
- `tests` (optional) - Coverage level
- `framework` (optional) - react|vue|angular
**What it does:**
1. **Requirements Understanding** - Clarifies functional and non-functional requirements
2. **Codebase Analysis** - Examines existing patterns and conventions
3. **Implementation Design** - Designs database schema, API endpoints, and UI components
4. **Incremental Implementation** - Implements in phases (data → backend → frontend → integration)
5. **Quality Assurance** - Tests, security, performance, and documentation
### `database` - Database Layer Only
Implement database migrations, models, schemas, indexes, and validation for a feature.
**Usage:**
```bash
/10x-fullstack-engineer:feature database description:"user profiles table with indexes" migration:"add_user_profiles"
```
**Parameters:**
- `description` (required) - Database changes needed
- `migration` (optional) - Migration name
- `orm` (optional) - prisma|typeorm|sequelize
**What it does:**
- Schema design with proper types and constraints
- Index strategy for query optimization
- Migration scripts (up and down)
- ORM models/entities
- Database operation tests
**Supports:**
- SQL databases (PostgreSQL, MySQL, SQLite)
- NoSQL databases (MongoDB)
- ORMs (Prisma, TypeORM, Sequelize, Mongoose)
### `backend` - Backend Layer Only
Implement repositories, services, API endpoints, validation, and tests for a feature.
**Usage:**
```bash
/10x-fullstack-engineer:feature backend description:"REST API for product search with filters" validation:"strict"
```
**Parameters:**
- `description` (required) - Backend functionality needed
- `api` (optional) - REST|GraphQL
- `validation` (optional) - strict|standard
- `auth` (optional) - required|optional
**What it does:**
- **Data Access Layer** - Repositories with query builders
- **Business Logic Layer** - Services with validation and error handling
- **API Layer** - Controllers and routes
- **Validation** - Request/response schemas
- **Testing** - Unit and integration tests
**Supports:**
- Express, Fastify, NestJS, Koa frameworks
- Zod, Joi, class-validator validation
- JWT authentication
- RBAC authorization
### `frontend` - Frontend Layer Only
Implement components, state management, API integration, and tests for a feature.
**Usage:**
```bash
/10x-fullstack-engineer:feature frontend description:"product catalog with infinite scroll and filters" framework:"react"
```
**Parameters:**
- `description` (required) - UI functionality needed
- `framework` (optional) - react|vue|angular
- `state` (optional) - redux|zustand|context
- `tests` (optional) - unit|integration|e2e
**What it does:**
- **Components** - Reusable, accessible UI components
- **State Management** - Zustand, Redux, Context API
- **API Integration** - HTTP client with interceptors
- **Custom Hooks** - Reusable logic
- **Testing** - Component and hook tests
**Supports:**
- React, Vue, Angular, Svelte
- TypeScript
- React Hook Form, Formik for forms
- React Query, SWR for server state
- TailwindCSS, CSS-in-JS
### `integrate` - Integration & Polish
Complete integration testing, performance optimization, security hardening, and documentation.
**Usage:**
```bash
/10x-fullstack-engineer:feature integrate feature:"authentication flow" scope:"E2E tests and performance"
```
**Parameters:**
- `feature` (required) - Feature name
- `scope` (optional) - e2e|performance|security|documentation
- `priority` (optional) - high|medium|low
**What it does:**
- **E2E Testing** - Playwright/Cypress tests for user workflows
- **Performance** - Frontend (lazy loading, memoization) and backend (caching, indexes) optimization
- **Security** - Input validation, XSS/CSRF protection, rate limiting, security headers
- **Documentation** - API docs (OpenAPI), user guides, developer documentation
### `scaffold` - Generate Boilerplate
Scaffold feature structure and boilerplate across all layers.
**Usage:**
```bash
/10x-fullstack-engineer:feature scaffold name:"notification-system" layers:"database,backend,frontend"
```
**Parameters:**
- `name` (required) - Feature name (kebab-case)
- `layers` (optional) - database,backend,frontend (default: all)
- `pattern` (optional) - crud|workflow|custom
**What it does:**
Generates complete boilerplate structure:
- Database migrations and entities
- Repository, service, controller, routes
- API client and types
- React components and hooks
- Test files
## Feature Types Supported
### Authentication & Authorization
- User registration/login
- OAuth/SSO integration
- 2FA/MFA
- Session management
- JWT token handling
- RBAC/ABAC
### Data Management (CRUD)
- Resource listing with pagination
- Filtering and sorting
- Search functionality
- Create/update/delete operations
- Soft delete support
- Audit logging
### Real-time Features
- WebSocket connections
- Server-Sent Events (SSE)
- Live updates
- Presence tracking
- Collaborative editing
### Payment Integration
- Stripe/PayPal checkout
- Subscription management
- Invoice generation
- Payment webhooks
- Refund processing
### File Management
- Upload with progress
- Image optimization
- S3/GCS integration
- Virus scanning
- File validation
### Search Features
- Full-text search
- Faceted search
- Autocomplete
- Advanced filtering
- Relevance scoring
## Implementation Phases
### Phase 1: Requirements Understanding
- Functional requirements clarification
- Non-functional requirements (performance, security, scalability)
- Acceptance criteria definition
- Edge case identification
### Phase 2: Codebase Analysis
- Project structure discovery
- Tech stack identification
- Existing patterns examination
- Convention adoption
### Phase 3: Implementation Design
- **Database Design** - Schema, relationships, indexes
- **Backend Design** - API endpoints, request/response models, service architecture
- **Frontend Design** - Component structure, state management, API integration
### Phase 4: Incremental Implementation
#### Phase 4.1 - Data Layer
1. Create migration scripts
2. Create/update models
3. Test database operations
#### Phase 4.2 - Backend Layer
1. Create repository layer
2. Create service layer
3. Create API controllers
4. Create routes
5. Write tests
#### Phase 4.3 - Frontend Layer
1. Create API client
2. Create React hooks
3. Create components
4. Write component tests
#### Phase 4.4 - Integration & Polish
1. End-to-end tests
2. Performance optimization
3. Security hardening
4. Documentation
## Quality Standards
### Code Quality
- [x] Single Responsibility Principle
- [x] DRY (Don't Repeat Yourself)
- [x] Proper error handling
- [x] Input validation
- [x] Type safety (TypeScript)
- [x] Consistent naming conventions
### Testing
- [x] Unit tests (>80% coverage)
- [x] Integration tests for APIs
- [x] Component tests for UI
- [x] E2E tests for critical flows
- [x] Edge case coverage
### Security
- [x] Input validation and sanitization
- [x] SQL injection prevention (parameterized queries)
- [x] XSS prevention (DOMPurify)
- [x] CSRF protection
- [x] Authentication/authorization
- [x] Rate limiting
- [x] Security headers (Helmet)
- [x] No hardcoded secrets
### Performance
- [x] Database indexes on frequently queried columns
- [x] Query optimization (eager loading, no N+1)
- [x] Response caching
- [x] Connection pooling
- [x] Frontend code splitting
- [x] Lazy loading images
- [x] Memoization
- [x] Virtualization for long lists
### Accessibility
- [x] Semantic HTML
- [x] ARIA labels
- [x] Keyboard navigation
- [x] Alt text for images
- [x] Color contrast (WCAG 2.1 AA)
- [x] Screen reader support
### Documentation
- [x] API documentation (OpenAPI/Swagger)
- [x] Code comments for complex logic
- [x] Usage examples
- [x] Deployment instructions
- [x] Environment variables documented
## Common Workflows
### 1. Implement Complete CRUD Feature
```bash
# Full-stack implementation
/10x-fullstack-engineer:feature implement description:"blog post management with rich text editor, categories, tags, and draft/publish workflow"
# What you get:
# - Database: posts, categories, tags tables with relationships
# - Backend: REST API with CRUD endpoints, validation, search
# - Frontend: Post list, detail, create/edit forms, rich text editor
# - Tests: Unit, integration, E2E
# - Docs: API documentation
```
### 2. Add New API Endpoints to Existing Feature
```bash
# Backend only
/10x-fullstack-engineer:feature backend description:"Add bulk operations API for products (bulk delete, bulk update status, bulk export)"
```
### 3. Build New UI Screen
```bash
# Frontend only
/10x-fullstack-engineer:feature frontend description:"Admin dashboard with charts showing sales, users, and revenue metrics" framework:"react" state:"zustand"
```
### 4. Optimize Existing Feature
```bash
# Integration & polish
/10x-fullstack-engineer:feature integrate feature:"product catalog" scope:"performance and E2E tests"
```
### 5. Quick Feature Scaffolding
```bash
# Generate boilerplate
/10x-fullstack-engineer:feature scaffold name:"email-notifications" layers:"database,backend"
# Then customize the generated files
```
## Architecture Patterns
### Layered Architecture
```
┌─────────────────────────────────────┐
│ Presentation Layer │ React/Vue/Angular Components
│ (Components, Hooks, State) │
└──────────────┬──────────────────────┘
│ API Client
┌──────────────▼──────────────────────┐
│ API Layer │ Controllers, Routes, Middleware
│ (Request/Response Handling) │
└──────────────┬──────────────────────┘
┌──────────────▼──────────────────────┐
│ Business Logic Layer │ Services, Validation, Rules
│ (Domain Logic) │
└──────────────┬──────────────────────┘
┌──────────────▼──────────────────────┐
│ Data Access Layer │ Repositories, Query Builders
│ (Database Operations) │
└──────────────┬──────────────────────┘
┌──────────────▼──────────────────────┐
│ Database Layer │ PostgreSQL, MongoDB, etc.
│ (Schema, Migrations, Indexes) │
└─────────────────────────────────────┘
```
### Repository Pattern
- Abstracts data access
- Enables testability
- Centralizes query logic
### Service Pattern
- Contains business logic
- Orchestrates repositories
- Handles validation
### Controller Pattern
- HTTP request/response handling
- Delegates to services
- Thin layer
## Example Output
For a feature like "user authentication", the implementation includes:
### Database Layer
```sql
-- Migration: users and sessions tables
CREATE TABLE users (
id UUID PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
email_verified BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_users_email ON users(email);
```
### Backend Layer
```typescript
// Service with business logic
async register(input: RegisterInput): Promise<{ user: User; tokens: AuthTokens }> {
this.validateEmail(input.email);
this.validatePassword(input.password);
const passwordHash = await bcrypt.hash(input.password, 12);
const user = await this.userRepository.create({ email: input.email, passwordHash });
const tokens = await this.generateTokens(user.id);
return { user, tokens };
}
```
### Frontend Layer
```typescript
// React component with state management
export const LoginForm: React.FC = () => {
const { login, isLoading, error } = useAuth();
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = async (data) => {
await login(data.email, data.password);
};
return <form onSubmit={handleSubmit(onSubmit)}>...</form>;
};
```
## Error Handling
The skill handles various scenarios:
### Unclear Requirements
- Asks specific questions about acceptance criteria
- Requests clarification on edge cases
- Provides examples to confirm understanding
- Suggests sensible defaults
### Missing Context
- Lists needed information (tech stack, patterns)
- Attempts to discover from codebase
- Documents assumptions made
- Provides alternatives if context unclear
### Implementation Blockers
- Clearly identifies the blocker
- Suggests alternative approaches
- Provides workarounds if available
- Documents issue for resolution
- Continues with unblocked portions
## Dependencies
This skill works with common tech stacks:
**Backend:**
- Node.js with Express, Fastify, NestJS
- TypeScript
- TypeORM, Prisma, Sequelize (ORMs)
- PostgreSQL, MySQL, MongoDB
- Jest, Vitest (testing)
**Frontend:**
- React, Vue, Angular
- TypeScript
- Zustand, Redux, Context API (state)
- React Hook Form, Zod (forms/validation)
- React Testing Library (testing)
- Playwright, Cypress (E2E)
## Tips for Best Results
1. **Be specific in descriptions** - More detail leads to better implementations
2. **Specify framework/ORM** - Helps generate appropriate code
3. **Start with scaffold** - Use `scaffold` for quick boilerplate, then customize
4. **Layer-by-layer approach** - Implement database → backend → frontend for complex features
5. **Use integrate for polish** - Don't skip the integration phase for production features
## Related Skills
This skill is part of the 10x Fullstack Engineer plugin:
- `/api` - API design and implementation
- `/database` - Database design and optimization
- `/test` - Test generation and coverage
- `/deploy` - Deployment and CI/CD
## License
MIT

779
commands/feature/backend.md Normal file
View File

@@ -0,0 +1,779 @@
# Backend Layer Operation
Implement backend layer only: repositories, services, API endpoints, validation, and tests for a feature.
## Parameters
**Received**: `$ARGUMENTS` (after removing 'backend' operation name)
Expected format: `description:"backend functionality needed" [api:"REST|GraphQL"] [validation:"strict|standard"] [auth:"required|optional"]`
## Workflow
### 1. Understand Backend Requirements
Clarify:
- What business logic needs to be implemented?
- What API endpoints are needed (methods, paths, parameters)?
- What validation rules apply?
- What authentication/authorization is required?
- What external services need integration?
### 2. Analyze Existing Backend Structure
```bash
# Find backend structure
find . -path "*/src/server/*" -o -path "*/api/*" -o -path "*/backend/*"
# Identify framework
cat package.json | grep -E "(express|fastify|nest|koa|hapi)"
# Find existing patterns
find . -path "*/controllers/*" -o -path "*/services/*" -o -path "*/routes/*"
```
**Identify:**
- Framework (Express, Fastify, NestJS, etc.)
- Architecture pattern (MVC, Clean Architecture, Layered)
- Error handling approach
- Validation library (class-validator, Joi, Zod)
- Testing framework (Jest, Mocha, Vitest)
### 3. Implement Layers
#### Layer 1: Data Access (Repository Pattern)
```typescript
// repositories/ProductRepository.ts
import { Repository } from 'typeorm';
import { Product } from '../entities/Product.entity';
import { AppDataSource } from '../config/database';
export interface ProductFilters {
categoryId?: string;
minPrice?: number;
maxPrice?: number;
inStock?: boolean;
search?: string;
}
export interface PaginationOptions {
page: number;
limit: number;
sortBy?: string;
sortOrder?: 'ASC' | 'DESC';
}
export class ProductRepository {
private repository: Repository<Product>;
constructor() {
this.repository = AppDataSource.getRepository(Product);
}
async findById(id: string): Promise<Product | null> {
return this.repository.findOne({
where: { id },
relations: ['category', 'images', 'tags'],
});
}
async findAll(
filters: ProductFilters,
pagination: PaginationOptions
): Promise<{ products: Product[]; total: number }> {
const query = this.repository
.createQueryBuilder('product')
.leftJoinAndSelect('product.category', 'category')
.leftJoinAndSelect('product.images', 'images')
.leftJoinAndSelect('product.tags', 'tags');
// Apply filters
if (filters.categoryId) {
query.andWhere('product.categoryId = :categoryId', {
categoryId: filters.categoryId,
});
}
if (filters.minPrice !== undefined) {
query.andWhere('product.price >= :minPrice', { minPrice: filters.minPrice });
}
if (filters.maxPrice !== undefined) {
query.andWhere('product.price <= :maxPrice', { maxPrice: filters.maxPrice });
}
if (filters.inStock) {
query.andWhere('product.stockQuantity > 0');
}
if (filters.search) {
query.andWhere(
'(product.name ILIKE :search OR product.description ILIKE :search)',
{ search: `%${filters.search}%` }
);
}
// Apply sorting
const sortBy = pagination.sortBy || 'createdAt';
const sortOrder = pagination.sortOrder || 'DESC';
query.orderBy(`product.${sortBy}`, sortOrder);
// Apply pagination
const skip = (pagination.page - 1) * pagination.limit;
query.skip(skip).take(pagination.limit);
const [products, total] = await query.getManyAndCount();
return { products, total };
}
async create(data: Partial<Product>): Promise<Product> {
const product = this.repository.create(data);
return this.repository.save(product);
}
async update(id: string, data: Partial<Product>): Promise<Product> {
await this.repository.update(id, data);
const updated = await this.findById(id);
if (!updated) {
throw new Error('Product not found after update');
}
return updated;
}
async delete(id: string): Promise<void> {
await this.repository.softDelete(id);
}
}
```
#### Layer 2: Business Logic (Service Layer)
```typescript
// services/ProductService.ts
import { ProductRepository, ProductFilters, PaginationOptions } from '../repositories/ProductRepository';
import { Product } from '../entities/Product.entity';
import { NotFoundError, ValidationError, ConflictError } from '../errors';
import { slugify } from '../utils/slugify';
export interface CreateProductInput {
name: string;
description?: string;
price: number;
currency?: string;
stockQuantity: number;
categoryId?: string;
images?: Array<{ url: string; altText?: string }>;
tags?: string[];
}
export interface UpdateProductInput {
name?: string;
description?: string;
price?: number;
stockQuantity?: number;
categoryId?: string;
}
export class ProductService {
constructor(private productRepository: ProductRepository) {}
async getProduct(id: string): Promise<Product> {
const product = await this.productRepository.findById(id);
if (!product) {
throw new NotFoundError(`Product with ID ${id} not found`);
}
return product;
}
async listProducts(
filters: ProductFilters,
pagination: PaginationOptions
): Promise<{ products: Product[]; total: number; page: number; totalPages: number }> {
const { products, total } = await this.productRepository.findAll(filters, pagination);
return {
products,
total,
page: pagination.page,
totalPages: Math.ceil(total / pagination.limit),
};
}
async createProduct(input: CreateProductInput): Promise<Product> {
// Validate input
this.validateProductInput(input);
// Generate slug from name
const slug = slugify(input.name);
// Check if slug already exists
const existing = await this.productRepository.findBySlug(slug);
if (existing) {
throw new ConflictError('Product with this name already exists');
}
// Create product
const product = await this.productRepository.create({
...input,
slug,
});
return product;
}
async updateProduct(id: string, input: UpdateProductInput): Promise<Product> {
// Check if product exists
await this.getProduct(id);
// Validate input
if (input.price !== undefined && input.price < 0) {
throw new ValidationError('Price must be non-negative');
}
if (input.stockQuantity !== undefined && input.stockQuantity < 0) {
throw new ValidationError('Stock quantity must be non-negative');
}
// Update product
const updated = await this.productRepository.update(id, input);
return updated;
}
async deleteProduct(id: string): Promise<void> {
// Check if product exists
await this.getProduct(id);
// Soft delete
await this.productRepository.delete(id);
}
async adjustStock(id: string, quantity: number): Promise<Product> {
const product = await this.getProduct(id);
const newQuantity = product.stockQuantity + quantity;
if (newQuantity < 0) {
throw new ValidationError('Insufficient stock');
}
return this.productRepository.update(id, { stockQuantity: newQuantity });
}
private validateProductInput(input: CreateProductInput): void {
if (!input.name || input.name.trim().length === 0) {
throw new ValidationError('Product name is required');
}
if (input.name.length > 255) {
throw new ValidationError('Product name must not exceed 255 characters');
}
if (input.price < 0) {
throw new ValidationError('Price must be non-negative');
}
if (input.stockQuantity < 0) {
throw new ValidationError('Stock quantity must be non-negative');
}
}
}
```
#### Layer 3: API Layer (Controllers & Routes)
```typescript
// controllers/ProductController.ts
import { Request, Response, NextFunction } from 'express';
import { ProductService } from '../services/ProductService';
export class ProductController {
constructor(private productService: ProductService) {}
getProduct = async (req: Request, res: Response, next: NextFunction) => {
try {
const { id } = req.params;
const product = await this.productService.getProduct(id);
res.json({
success: true,
data: product,
});
} catch (error) {
next(error);
}
};
listProducts = async (req: Request, res: Response, next: NextFunction) => {
try {
const filters = {
categoryId: req.query.categoryId as string,
minPrice: req.query.minPrice ? parseFloat(req.query.minPrice as string) : undefined,
maxPrice: req.query.maxPrice ? parseFloat(req.query.maxPrice as string) : undefined,
inStock: req.query.inStock === 'true',
search: req.query.search as string,
};
const pagination = {
page: parseInt(req.query.page as string) || 1,
limit: parseInt(req.query.limit as string) || 20,
sortBy: (req.query.sortBy as string) || 'createdAt',
sortOrder: (req.query.sortOrder as 'ASC' | 'DESC') || 'DESC',
};
const result = await this.productService.listProducts(filters, pagination);
res.json({
success: true,
data: result.products,
meta: {
total: result.total,
page: result.page,
totalPages: result.totalPages,
limit: pagination.limit,
},
});
} catch (error) {
next(error);
}
};
createProduct = async (req: Request, res: Response, next: NextFunction) => {
try {
const product = await this.productService.createProduct(req.body);
res.status(201).json({
success: true,
data: product,
});
} catch (error) {
next(error);
}
};
updateProduct = async (req: Request, res: Response, next: NextFunction) => {
try {
const { id } = req.params;
const product = await this.productService.updateProduct(id, req.body);
res.json({
success: true,
data: product,
});
} catch (error) {
next(error);
}
};
deleteProduct = async (req: Request, res: Response, next: NextFunction) => {
try {
const { id } = req.params;
await this.productService.deleteProduct(id);
res.status(204).send();
} catch (error) {
next(error);
}
};
}
```
```typescript
// routes/product.routes.ts
import { Router } from 'express';
import { ProductController } from '../controllers/ProductController';
import { ProductService } from '../services/ProductService';
import { ProductRepository } from '../repositories/ProductRepository';
import { authenticate } from '../middlewares/auth.middleware';
import { validate } from '../middlewares/validation.middleware';
import { createProductSchema, updateProductSchema } from '../schemas/product.schemas';
const router = Router();
// Initialize dependencies
const productRepository = new ProductRepository();
const productService = new ProductService(productRepository);
const productController = new ProductController(productService);
// Public routes
router.get('/', productController.listProducts);
router.get('/:id', productController.getProduct);
// Protected routes (require authentication)
router.post(
'/',
authenticate,
validate(createProductSchema),
productController.createProduct
);
router.put(
'/:id',
authenticate,
validate(updateProductSchema),
productController.updateProduct
);
router.delete('/:id', authenticate, productController.deleteProduct);
export default router;
```
#### Validation Schemas
```typescript
// schemas/product.schemas.ts
import { z } from 'zod';
export const createProductSchema = z.object({
body: z.object({
name: z.string().min(1).max(255),
description: z.string().optional(),
price: z.number().min(0),
currency: z.string().length(3).optional(),
stockQuantity: z.number().int().min(0),
categoryId: z.string().uuid().optional(),
images: z.array(
z.object({
url: z.string().url(),
altText: z.string().optional(),
})
).optional(),
tags: z.array(z.string()).optional(),
}),
});
export const updateProductSchema = z.object({
body: z.object({
name: z.string().min(1).max(255).optional(),
description: z.string().optional(),
price: z.number().min(0).optional(),
stockQuantity: z.number().int().min(0).optional(),
categoryId: z.string().uuid().optional(),
}),
params: z.object({
id: z.string().uuid(),
}),
});
```
### 4. Write Tests
```typescript
// services/__tests__/ProductService.test.ts
import { ProductService } from '../ProductService';
import { ProductRepository } from '../../repositories/ProductRepository';
import { NotFoundError, ValidationError } from '../../errors';
describe('ProductService', () => {
let productService: ProductService;
let productRepository: jest.Mocked<ProductRepository>;
beforeEach(() => {
productRepository = {
findById: jest.fn(),
findAll: jest.fn(),
create: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
} as any;
productService = new ProductService(productRepository);
});
describe('createProduct', () => {
it('should create product with valid input', async () => {
const input = {
name: 'Test Product',
price: 99.99,
stockQuantity: 10,
};
productRepository.create.mockResolvedValue({
id: 'product-id',
...input,
slug: 'test-product',
} as any);
const result = await productService.createProduct(input);
expect(result.name).toBe('Test Product');
expect(productRepository.create).toHaveBeenCalled();
});
it('should throw ValidationError for negative price', async () => {
await expect(
productService.createProduct({
name: 'Test',
price: -10,
stockQuantity: 5,
})
).rejects.toThrow(ValidationError);
});
it('should throw ValidationError for empty name', async () => {
await expect(
productService.createProduct({
name: '',
price: 10,
stockQuantity: 5,
})
).rejects.toThrow(ValidationError);
});
});
describe('getProduct', () => {
it('should return product if found', async () => {
const product = { id: 'product-id', name: 'Test Product' };
productRepository.findById.mockResolvedValue(product as any);
const result = await productService.getProduct('product-id');
expect(result).toEqual(product);
});
it('should throw NotFoundError if product not found', async () => {
productRepository.findById.mockResolvedValue(null);
await expect(productService.getProduct('invalid-id')).rejects.toThrow(
NotFoundError
);
});
});
});
```
```typescript
// controllers/__tests__/ProductController.test.ts
import request from 'supertest';
import { app } from '../../app';
import { ProductRepository } from '../../repositories/ProductRepository';
describe('ProductController', () => {
let productRepository: ProductRepository;
beforeEach(async () => {
await clearDatabase();
productRepository = new ProductRepository();
});
describe('GET /api/products', () => {
it('should return list of products', async () => {
await productRepository.create({
name: 'Product 1',
slug: 'product-1',
price: 10,
stockQuantity: 5,
});
const response = await request(app).get('/api/products').expect(200);
expect(response.body.success).toBe(true);
expect(response.body.data).toHaveLength(1);
});
it('should filter by category', async () => {
const category = await createTestCategory();
await productRepository.create({
name: 'Product 1',
slug: 'product-1',
price: 10,
stockQuantity: 5,
categoryId: category.id,
});
const response = await request(app)
.get('/api/products')
.query({ categoryId: category.id })
.expect(200);
expect(response.body.data).toHaveLength(1);
});
it('should paginate results', async () => {
// Create 25 products
for (let i = 0; i < 25; i++) {
await productRepository.create({
name: `Product ${i}`,
slug: `product-${i}`,
price: 10,
stockQuantity: 5,
});
}
const response = await request(app)
.get('/api/products')
.query({ page: 2, limit: 10 })
.expect(200);
expect(response.body.data).toHaveLength(10);
expect(response.body.meta.page).toBe(2);
expect(response.body.meta.totalPages).toBe(3);
});
});
describe('POST /api/products', () => {
it('should create product with valid data', async () => {
const authToken = await getAuthToken();
const response = await request(app)
.post('/api/products')
.set('Authorization', `Bearer ${authToken}`)
.send({
name: 'New Product',
price: 99.99,
stockQuantity: 10,
})
.expect(201);
expect(response.body.data.name).toBe('New Product');
});
it('should return 401 without authentication', async () => {
await request(app)
.post('/api/products')
.send({
name: 'New Product',
price: 99.99,
stockQuantity: 10,
})
.expect(401);
});
it('should return 400 for invalid data', async () => {
const authToken = await getAuthToken();
await request(app)
.post('/api/products')
.set('Authorization', `Bearer ${authToken}`)
.send({
name: '',
price: -10,
})
.expect(400);
});
});
describe('PUT /api/products/:id', () => {
it('should update product', async () => {
const product = await productRepository.create({
name: 'Original Name',
slug: 'original',
price: 10,
stockQuantity: 5,
});
const authToken = await getAuthToken();
const response = await request(app)
.put(`/api/products/${product.id}`)
.set('Authorization', `Bearer ${authToken}`)
.send({
name: 'Updated Name',
price: 20,
})
.expect(200);
expect(response.body.data.name).toBe('Updated Name');
expect(response.body.data.price).toBe(20);
});
it('should return 404 for non-existent product', async () => {
const authToken = await getAuthToken();
await request(app)
.put('/api/products/non-existent-id')
.set('Authorization', `Bearer ${authToken}`)
.send({ name: 'Updated' })
.expect(404);
});
});
describe('DELETE /api/products/:id', () => {
it('should delete product', async () => {
const product = await productRepository.create({
name: 'To Delete',
slug: 'to-delete',
price: 10,
stockQuantity: 5,
});
const authToken = await getAuthToken();
await request(app)
.delete(`/api/products/${product.id}`)
.set('Authorization', `Bearer ${authToken}`)
.expect(204);
const deleted = await productRepository.findById(product.id);
expect(deleted).toBeNull();
});
});
});
```
## Output Format
```markdown
# Backend Layer: {Feature Name}
## API Endpoints
### {Method} {Path}
- Description: {description}
- Authentication: {required|optional|none}
- Request: {request_schema}
- Response: {response_schema}
- Status Codes: {codes}
## Architecture
### Repository Layer
\`\`\`typescript
{repository_code}
\`\`\`
### Service Layer
\`\`\`typescript
{service_code}
\`\`\`
### Controller Layer
\`\`\`typescript
{controller_code}
\`\`\`
### Routes
\`\`\`typescript
{routes_code}
\`\`\`
## Validation
### Schemas
\`\`\`typescript
{validation_schemas}
\`\`\`
## Testing
### Unit Tests
- {test_description}: {status}
### Integration Tests
- {test_description}: {status}
## Error Handling
- {error_type}: {handling_approach}
## Authentication
- {auth_details}
```
## Error Handling
- If framework unclear: Detect from package.json or ask
- If auth unclear: Suggest standard JWT approach
- If validation library unclear: Provide examples for common libraries

View File

@@ -0,0 +1,916 @@
# Database Layer Operation
Implement database layer only: migrations, models, schemas, indexes, and validation for a feature.
## Parameters
**Received**: `$ARGUMENTS` (after removing 'database' operation name)
Expected format: `description:"database changes needed" [migration:"migration_name"] [orm:"prisma|typeorm|sequelize"]`
## Workflow
### 1. Understand Database Requirements
Parse the requirements and clarify:
- What tables/collections need to be created or modified?
- What are the relationships between entities?
- What queries will be frequently executed (for index design)?
- What are the data validation requirements?
- Are there any data migration needs (existing data to transform)?
### 2. Analyze Existing Database Structure
Examine current database setup:
```bash
# Find existing migrations
find . -path "*/migrations/*" -o -path "*/prisma/migrations/*"
# Find existing models
find . -path "*/models/*" -o -path "*/entities/*" -o -name "schema.prisma"
# Check ORM configuration
find . -name "ormconfig.js" -o -name "datasource.ts" -o -name "schema.prisma"
```
**Identify:**
- ORM being used (Prisma, TypeORM, Sequelize, Mongoose, etc.)
- Database type (PostgreSQL, MySQL, MongoDB, etc.)
- Naming conventions for tables and columns
- Migration strategy and tooling
- Existing relationships and constraints
### 3. Design Database Schema
#### Schema Design Template
**For SQL Databases:**
```sql
-- Example: E-commerce Product Catalog
-- Main products table
CREATE TABLE products (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
slug VARCHAR(255) UNIQUE NOT NULL,
description TEXT,
price DECIMAL(10, 2) NOT NULL,
currency VARCHAR(3) DEFAULT 'USD',
stock_quantity INTEGER NOT NULL DEFAULT 0,
category_id UUID REFERENCES categories(id) ON DELETE SET NULL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
deleted_at TIMESTAMP, -- Soft delete
-- Constraints
CONSTRAINT price_positive CHECK (price >= 0),
CONSTRAINT stock_non_negative CHECK (stock_quantity >= 0),
CONSTRAINT slug_format CHECK (slug ~* '^[a-z0-9-]+$')
);
-- Categories table
CREATE TABLE categories (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL,
slug VARCHAR(100) UNIQUE NOT NULL,
parent_id UUID REFERENCES categories(id) ON DELETE CASCADE,
description TEXT,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Product images table (one-to-many)
CREATE TABLE product_images (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
product_id UUID NOT NULL REFERENCES products(id) ON DELETE CASCADE,
url VARCHAR(500) NOT NULL,
alt_text VARCHAR(255),
display_order INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP DEFAULT NOW()
);
-- Product tags (many-to-many)
CREATE TABLE tags (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(50) UNIQUE NOT NULL,
slug VARCHAR(50) UNIQUE NOT NULL
);
CREATE TABLE product_tags (
product_id UUID NOT NULL REFERENCES products(id) ON DELETE CASCADE,
tag_id UUID NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
PRIMARY KEY (product_id, tag_id)
);
-- Indexes for performance
CREATE INDEX idx_products_category_id ON products(category_id);
CREATE INDEX idx_products_slug ON products(slug);
CREATE INDEX idx_products_created_at ON products(created_at DESC);
CREATE INDEX idx_products_price ON products(price);
CREATE INDEX idx_products_stock ON products(stock_quantity) WHERE stock_quantity > 0;
CREATE INDEX idx_products_deleted_at ON products(deleted_at) WHERE deleted_at IS NULL;
CREATE INDEX idx_categories_parent_id ON categories(parent_id);
CREATE INDEX idx_categories_slug ON categories(slug);
CREATE INDEX idx_product_images_product_id ON product_images(product_id);
CREATE INDEX idx_product_tags_product_id ON product_tags(product_id);
CREATE INDEX idx_product_tags_tag_id ON product_tags(tag_id);
-- Full-text search index
CREATE INDEX idx_products_search ON products USING GIN(to_tsvector('english', name || ' ' || COALESCE(description, '')));
```
**For NoSQL (MongoDB):**
```javascript
// Product schema
{
_id: ObjectId,
name: String,
slug: String, // indexed, unique
description: String,
price: {
amount: Number,
currency: String
},
stockQuantity: Number,
category: {
id: ObjectId,
name: String, // denormalized for performance
slug: String
},
images: [
{
url: String,
altText: String,
displayOrder: Number
}
],
tags: [String], // indexed for queries
createdAt: Date,
updatedAt: Date,
deletedAt: Date // soft delete
}
// Indexes
db.products.createIndex({ slug: 1 }, { unique: true })
db.products.createIndex({ "category.id": 1 })
db.products.createIndex({ price.amount: 1 })
db.products.createIndex({ tags: 1 })
db.products.createIndex({ createdAt: -1 })
db.products.createIndex({ name: "text", description: "text" }) // Full-text search
```
#### Index Strategy
**When to add indexes:**
- Primary keys (always)
- Foreign keys (for JOIN performance)
- Columns used in WHERE clauses
- Columns used in ORDER BY
- Columns used in GROUP BY
- Columns used for full-text search
**Composite indexes** for queries with multiple conditions:
```sql
-- Query: SELECT * FROM products WHERE category_id = ? AND price > ? ORDER BY created_at DESC
CREATE INDEX idx_products_category_price_created ON products(category_id, price, created_at DESC);
```
**Partial indexes** for specific conditions:
```sql
-- Only index active (non-deleted) products
CREATE INDEX idx_active_products ON products(created_at) WHERE deleted_at IS NULL;
```
### 4. Create Migration Scripts
#### Example: TypeORM Migration
```typescript
// migrations/1704124800000-AddProductCatalog.ts
import { MigrationInterface, QueryRunner, Table, TableForeignKey, TableIndex } from 'typeorm';
export class AddProductCatalog1704124800000 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
// Create categories table
await queryRunner.createTable(
new Table({
name: 'categories',
columns: [
{
name: 'id',
type: 'uuid',
isPrimary: true,
default: 'gen_random_uuid()',
},
{
name: 'name',
type: 'varchar',
length: '100',
isNullable: false,
},
{
name: 'slug',
type: 'varchar',
length: '100',
isUnique: true,
isNullable: false,
},
{
name: 'parent_id',
type: 'uuid',
isNullable: true,
},
{
name: 'description',
type: 'text',
isNullable: true,
},
{
name: 'created_at',
type: 'timestamp',
default: 'now()',
},
{
name: 'updated_at',
type: 'timestamp',
default: 'now()',
},
],
}),
true
);
// Create products table
await queryRunner.createTable(
new Table({
name: 'products',
columns: [
{
name: 'id',
type: 'uuid',
isPrimary: true,
default: 'gen_random_uuid()',
},
{
name: 'name',
type: 'varchar',
length: '255',
isNullable: false,
},
{
name: 'slug',
type: 'varchar',
length: '255',
isUnique: true,
isNullable: false,
},
{
name: 'description',
type: 'text',
isNullable: true,
},
{
name: 'price',
type: 'decimal',
precision: 10,
scale: 2,
isNullable: false,
},
{
name: 'currency',
type: 'varchar',
length: '3',
default: "'USD'",
},
{
name: 'stock_quantity',
type: 'integer',
default: 0,
},
{
name: 'category_id',
type: 'uuid',
isNullable: true,
},
{
name: 'created_at',
type: 'timestamp',
default: 'now()',
},
{
name: 'updated_at',
type: 'timestamp',
default: 'now()',
},
{
name: 'deleted_at',
type: 'timestamp',
isNullable: true,
},
],
}),
true
);
// Add foreign keys
await queryRunner.createForeignKey(
'categories',
new TableForeignKey({
columnNames: ['parent_id'],
referencedColumnNames: ['id'],
referencedTableName: 'categories',
onDelete: 'CASCADE',
})
);
await queryRunner.createForeignKey(
'products',
new TableForeignKey({
columnNames: ['category_id'],
referencedColumnNames: ['id'],
referencedTableName: 'categories',
onDelete: 'SET NULL',
})
);
// Create indexes
await queryRunner.createIndex(
'products',
new TableIndex({
name: 'idx_products_category_id',
columnNames: ['category_id'],
})
);
await queryRunner.createIndex(
'products',
new TableIndex({
name: 'idx_products_slug',
columnNames: ['slug'],
})
);
await queryRunner.createIndex(
'products',
new TableIndex({
name: 'idx_products_price',
columnNames: ['price'],
})
);
await queryRunner.createIndex(
'categories',
new TableIndex({
name: 'idx_categories_parent_id',
columnNames: ['parent_id'],
})
);
// Add check constraints
await queryRunner.query(
`ALTER TABLE products ADD CONSTRAINT price_positive CHECK (price >= 0)`
);
await queryRunner.query(
`ALTER TABLE products ADD CONSTRAINT stock_non_negative CHECK (stock_quantity >= 0)`
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
// Drop in reverse order
await queryRunner.dropTable('products');
await queryRunner.dropTable('categories');
}
}
```
#### Example: Prisma Migration
```prisma
// prisma/schema.prisma
model Category {
id String @id @default(uuid()) @db.Uuid
name String @db.VarChar(100)
slug String @unique @db.VarChar(100)
parentId String? @map("parent_id") @db.Uuid
description String? @db.Text
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
parent Category? @relation("CategoryHierarchy", fields: [parentId], references: [id], onDelete: Cascade)
children Category[] @relation("CategoryHierarchy")
products Product[]
@@index([parentId])
@@index([slug])
@@map("categories")
}
model Product {
id String @id @default(uuid()) @db.Uuid
name String @db.VarChar(255)
slug String @unique @db.VarChar(255)
description String? @db.Text
price Decimal @db.Decimal(10, 2)
currency String @default("USD") @db.VarChar(3)
stockQuantity Int @default(0) @map("stock_quantity")
categoryId String? @map("category_id") @db.Uuid
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull)
images ProductImage[]
tags ProductTag[]
@@index([categoryId])
@@index([slug])
@@index([price])
@@index([createdAt(sort: Desc)])
@@index([stockQuantity], where: stockQuantity > 0)
@@map("products")
}
model ProductImage {
id String @id @default(uuid()) @db.Uuid
productId String @map("product_id") @db.Uuid
url String @db.VarChar(500)
altText String? @map("alt_text") @db.VarChar(255)
displayOrder Int @default(0) @map("display_order")
createdAt DateTime @default(now()) @map("created_at")
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
@@index([productId])
@@map("product_images")
}
model Tag {
id String @id @default(uuid()) @db.Uuid
name String @unique @db.VarChar(50)
slug String @unique @db.VarChar(50)
products ProductTag[]
@@map("tags")
}
model ProductTag {
productId String @map("product_id") @db.Uuid
tagId String @map("tag_id") @db.Uuid
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)
@@id([productId, tagId])
@@index([productId])
@@index([tagId])
@@map("product_tags")
}
```
```bash
# Generate migration
npx prisma migrate dev --name add_product_catalog
# Apply migration to production
npx prisma migrate deploy
```
### 5. Create/Update Models
#### TypeORM Models
```typescript
// entities/Product.entity.ts
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
DeleteDateColumn,
ManyToOne,
OneToMany,
ManyToMany,
JoinTable,
JoinColumn,
Index,
Check,
} from 'typeorm';
import { Category } from './Category.entity';
import { ProductImage } from './ProductImage.entity';
import { Tag } from './Tag.entity';
@Entity('products')
@Check('"price" >= 0')
@Check('"stock_quantity" >= 0')
export class Product {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'varchar', length: 255 })
name: string;
@Column({ type: 'varchar', length: 255, unique: true })
@Index()
slug: string;
@Column({ type: 'text', nullable: true })
description: string | null;
@Column({ type: 'decimal', precision: 10, scale: 2 })
@Index()
price: number;
@Column({ type: 'varchar', length: 3, default: 'USD' })
currency: string;
@Column({ type: 'integer', default: 0, name: 'stock_quantity' })
stockQuantity: number;
@Column({ type: 'uuid', name: 'category_id', nullable: true })
@Index()
categoryId: string | null;
@CreateDateColumn({ name: 'created_at' })
@Index()
createdAt: Date;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;
@DeleteDateColumn({ name: 'deleted_at' })
deletedAt: Date | null;
// Relations
@ManyToOne(() => Category, (category) => category.products, {
onDelete: 'SET NULL',
})
@JoinColumn({ name: 'category_id' })
category: Category;
@OneToMany(() => ProductImage, (image) => image.product, {
cascade: true,
})
images: ProductImage[];
@ManyToMany(() => Tag, (tag) => tag.products)
@JoinTable({
name: 'product_tags',
joinColumn: { name: 'product_id', referencedColumnName: 'id' },
inverseJoinColumn: { name: 'tag_id', referencedColumnName: 'id' },
})
tags: Tag[];
}
```
```typescript
// entities/Category.entity.ts
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
ManyToOne,
OneToMany,
JoinColumn,
Index,
} from 'typeorm';
import { Product } from './Product.entity';
@Entity('categories')
export class Category {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'varchar', length: 100 })
name: string;
@Column({ type: 'varchar', length: 100, unique: true })
@Index()
slug: string;
@Column({ type: 'uuid', name: 'parent_id', nullable: true })
@Index()
parentId: string | null;
@Column({ type: 'text', nullable: true })
description: string | null;
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;
// Relations
@ManyToOne(() => Category, (category) => category.children, {
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'parent_id' })
parent: Category | null;
@OneToMany(() => Category, (category) => category.parent)
children: Category[];
@OneToMany(() => Product, (product) => product.category)
products: Product[];
}
```
#### Validation
Add validation decorators if using class-validator:
```typescript
import { IsString, IsNumber, Min, IsOptional, IsUUID, MaxLength, Matches } from 'class-validator';
export class CreateProductDto {
@IsString()
@MaxLength(255)
name: string;
@IsString()
@MaxLength(255)
@Matches(/^[a-z0-9-]+$/, { message: 'Slug must contain only lowercase letters, numbers, and hyphens' })
slug: string;
@IsString()
@IsOptional()
description?: string;
@IsNumber()
@Min(0)
price: number;
@IsString()
@MaxLength(3)
@IsOptional()
currency?: string;
@IsNumber()
@Min(0)
stockQuantity: number;
@IsUUID()
@IsOptional()
categoryId?: string;
}
```
### 6. Test Database Operations
```typescript
// entities/__tests__/Product.entity.test.ts
import { DataSource } from 'typeorm';
import { Product } from '../Product.entity';
import { Category } from '../Category.entity';
import { createTestDataSource } from '../../test/utils';
describe('Product Entity', () => {
let dataSource: DataSource;
let productRepository: ReturnType<typeof dataSource.getRepository<Product>>;
let categoryRepository: ReturnType<typeof dataSource.getRepository<Category>>;
beforeAll(async () => {
dataSource = await createTestDataSource();
productRepository = dataSource.getRepository(Product);
categoryRepository = dataSource.getRepository(Category);
});
afterAll(async () => {
await dataSource.destroy();
});
beforeEach(async () => {
await productRepository.delete({});
await categoryRepository.delete({});
});
describe('Creation', () => {
it('should create product with valid data', async () => {
const product = productRepository.create({
name: 'Test Product',
slug: 'test-product',
price: 99.99,
stockQuantity: 10,
});
await productRepository.save(product);
expect(product.id).toBeDefined();
expect(product.name).toBe('Test Product');
expect(product.price).toBe(99.99);
});
it('should enforce unique slug constraint', async () => {
await productRepository.save({
name: 'Product 1',
slug: 'duplicate-slug',
price: 10,
stockQuantity: 1,
});
await expect(
productRepository.save({
name: 'Product 2',
slug: 'duplicate-slug',
price: 20,
stockQuantity: 2,
})
).rejects.toThrow();
});
it('should enforce price check constraint', async () => {
await expect(
productRepository.save({
name: 'Invalid Product',
slug: 'invalid-price',
price: -10,
stockQuantity: 1,
})
).rejects.toThrow(/price_positive/);
});
});
describe('Relations', () => {
it('should set category relationship', async () => {
const category = await categoryRepository.save({
name: 'Electronics',
slug: 'electronics',
});
const product = await productRepository.save({
name: 'Laptop',
slug: 'laptop',
price: 999,
stockQuantity: 5,
categoryId: category.id,
});
const loaded = await productRepository.findOne({
where: { id: product.id },
relations: ['category'],
});
expect(loaded?.category?.name).toBe('Electronics');
});
it('should cascade delete images', async () => {
const product = await productRepository.save({
name: 'Product with Images',
slug: 'product-images',
price: 50,
stockQuantity: 1,
images: [
{ url: 'https://example.com/image1.jpg', displayOrder: 0 },
{ url: 'https://example.com/image2.jpg', displayOrder: 1 },
],
});
await productRepository.delete(product.id);
// Images should be deleted automatically
// Verify by checking the images table is empty
});
});
describe('Soft Delete', () => {
it('should soft delete product', async () => {
const product = await productRepository.save({
name: 'Product to Delete',
slug: 'product-delete',
price: 10,
stockQuantity: 1,
});
await productRepository.softDelete(product.id);
const found = await productRepository.findOne({
where: { id: product.id },
});
expect(found).toBeNull();
// Can still find with withDeleted
const deleted = await productRepository.findOne({
where: { id: product.id },
withDeleted: true,
});
expect(deleted).toBeDefined();
expect(deleted?.deletedAt).toBeDefined();
});
});
describe('Queries', () => {
beforeEach(async () => {
// Seed test data
await productRepository.save([
{ name: 'Product A', slug: 'product-a', price: 10, stockQuantity: 5 },
{ name: 'Product B', slug: 'product-b', price: 20, stockQuantity: 0 },
{ name: 'Product C', slug: 'product-c', price: 30, stockQuantity: 10 },
]);
});
it('should find products by price range', async () => {
const products = await productRepository.find({
where: {
price: Between(15, 35),
},
});
expect(products).toHaveLength(2);
});
it('should find in-stock products', async () => {
const products = await productRepository
.createQueryBuilder('product')
.where('product.stock_quantity > 0')
.getMany();
expect(products).toHaveLength(2);
});
it('should order by created date', async () => {
const products = await productRepository.find({
order: { createdAt: 'DESC' },
});
expect(products[0].name).toBe('Product C');
});
});
});
```
## Output Format
```markdown
# Database Layer: {Feature Name}
## Schema Design
### Tables Created/Modified
- {table_name}: {description}
### Relationships
- {relationship_description}
### Indexes
- {index_name}: {purpose}
## Migration Scripts
### Up Migration
\`\`\`sql
{migration_sql}
\`\`\`
### Down Migration
\`\`\`sql
{rollback_sql}
\`\`\`
## Models/Entities
### {ModelName}
\`\`\`typescript
{model_code}
\`\`\`
## Validation
### DTOs
\`\`\`typescript
{validation_code}
\`\`\`
## Testing
### Test Results
- {test_description}: {status}
## Migration Commands
\`\`\`bash
# Run migration
{migration_command}
# Rollback migration
{rollback_command}
\`\`\`
## Performance Considerations
- {performance_note}
```
## Error Handling
- If ORM unclear: Ask which ORM is used or detect from codebase
- If database type unclear: Suggest common options or auto-detect
- If migration fails: Provide rollback instructions
- If constraints fail: Explain the constraint and suggest fixes

View File

@@ -0,0 +1,649 @@
# Frontend Layer Operation
Implement frontend layer only: components, state management, API integration, and tests for a feature.
## Parameters
**Received**: `$ARGUMENTS` (after removing 'frontend' operation name)
Expected format: `description:"UI functionality needed" [framework:"react|vue|angular"] [state:"redux|zustand|context"] [tests:"unit|integration|e2e"]`
## Workflow
### 1. Understand Frontend Requirements
Clarify:
- What UI components are needed?
- What user interactions are supported?
- What state management is required?
- What API endpoints to consume?
- What responsive/accessibility requirements?
### 2. Analyze Existing Frontend Structure
```bash
# Find frontend framework
cat package.json | grep -E "(react|vue|angular|svelte)"
# Find component structure
find . -path "*/components/*" -o -path "*/src/app/*"
# Find state management
cat package.json | grep -E "(redux|zustand|mobx|pinia|ngrx)"
```
### 3. Implement Components
#### Component Structure Example (React + TypeScript)
```typescript
// features/products/components/ProductCard.tsx
import React from 'react';
import { Product } from '../types';
interface ProductCardProps {
product: Product;
onAddToCart?: (productId: string) => void;
onViewDetails?: (productId: string) => void;
}
export const ProductCard: React.FC<ProductCardProps> = ({
product,
onAddToCart,
onViewDetails,
}) => {
const [imageError, setImageError] = React.useState(false);
const handleAddToCart = () => {
if (onAddToCart) {
onAddToCart(product.id);
}
};
const handleViewDetails = () => {
if (onViewDetails) {
onViewDetails(product.id);
}
};
return (
<div className="product-card" role="article" aria-label={`Product: ${product.name}`}>
<div className="product-card__image-container">
{!imageError && product.images[0] ? (
<img
src={product.images[0].url}
alt={product.images[0].altText || product.name}
className="product-card__image"
onError={() => setImageError(true)}
loading="lazy"
/>
) : (
<div className="product-card__placeholder">No image</div>
)}
{product.stockQuantity === 0 && (
<div className="product-card__badge product-card__badge--out-of-stock">
Out of Stock
</div>
)}
</div>
<div className="product-card__content">
<h3 className="product-card__title">{product.name}</h3>
{product.description && (
<p className="product-card__description">
{product.description.slice(0, 100)}
{product.description.length > 100 && '...'}
</p>
)}
<div className="product-card__footer">
<div className="product-card__price">
{product.currency} {product.price.toFixed(2)}
</div>
<div className="product-card__actions">
<button
onClick={handleViewDetails}
className="button button--secondary"
aria-label={`View details for ${product.name}`}
>
Details
</button>
<button
onClick={handleAddToCart}
disabled={product.stockQuantity === 0}
className="button button--primary"
aria-label={`Add ${product.name} to cart`}
>
Add to Cart
</button>
</div>
</div>
</div>
</div>
);
};
```
```typescript
// features/products/components/ProductList.tsx
import React from 'react';
import { ProductCard } from './ProductCard';
import { useProducts } from '../hooks/useProducts';
import { Pagination } from '@/components/Pagination';
import { LoadingSpinner } from '@/components/LoadingSpinner';
import { ErrorMessage } from '@/components/ErrorMessage';
interface ProductListProps {
categoryId?: string;
searchQuery?: string;
}
export const ProductList: React.FC<ProductListProps> = ({
categoryId,
searchQuery,
}) => {
const {
products,
isLoading,
error,
pagination,
onPageChange,
onAddToCart,
} = useProducts({ categoryId, searchQuery });
if (isLoading) {
return (
<div className="flex justify-center items-center h-64">
<LoadingSpinner />
</div>
);
}
if (error) {
return (
<ErrorMessage
message={error.message}
onRetry={() => window.location.reload()}
/>
);
}
if (products.length === 0) {
return (
<div className="text-center py-12">
<p className="text-gray-500">No products found.</p>
</div>
);
}
return (
<div className="product-list">
<div className="product-list__grid">
{products.map((product) => (
<ProductCard
key={product.id}
product={product}
onAddToCart={onAddToCart}
onViewDetails={(id) => console.log('View', id)}
/>
))}
</div>
{pagination && (
<Pagination
currentPage={pagination.page}
totalPages={pagination.totalPages}
onPageChange={onPageChange}
/>
)}
</div>
);
};
```
### 4. Implement State Management
#### Using Zustand
```typescript
// features/products/store/productStore.ts
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { productApi } from '../api/productApi';
import { Product } from '../types';
interface ProductState {
products: Product[];
selectedProduct: Product | null;
isLoading: boolean;
error: Error | null;
fetchProducts: (filters?: any) => Promise<void>;
fetchProduct: (id: string) => Promise<void>;
createProduct: (data: any) => Promise<void>;
updateProduct: (id: string, data: any) => Promise<void>;
deleteProduct: (id: string) => Promise<void>;
clearError: () => void;
}
export const useProductStore = create<ProductState>()(
devtools(
persist(
(set, get) => ({
products: [],
selectedProduct: null,
isLoading: false,
error: null,
fetchProducts: async (filters = {}) => {
set({ isLoading: true, error: null });
try {
const response = await productApi.list(filters);
set({ products: response.data, isLoading: false });
} catch (error: any) {
set({ error, isLoading: false });
}
},
fetchProduct: async (id: string) => {
set({ isLoading: true, error: null });
try {
const product = await productApi.getById(id);
set({ selectedProduct: product, isLoading: false });
} catch (error: any) {
set({ error, isLoading: false });
}
},
createProduct: async (data) => {
set({ isLoading: true, error: null });
try {
const product = await productApi.create(data);
set((state) => ({
products: [...state.products, product],
isLoading: false,
}));
} catch (error: any) {
set({ error, isLoading: false });
throw error;
}
},
updateProduct: async (id, data) => {
set({ isLoading: true, error: null });
try {
const product = await productApi.update(id, data);
set((state) => ({
products: state.products.map((p) =>
p.id === id ? product : p
),
selectedProduct:
state.selectedProduct?.id === id
? product
: state.selectedProduct,
isLoading: false,
}));
} catch (error: any) {
set({ error, isLoading: false });
throw error;
}
},
deleteProduct: async (id) => {
set({ isLoading: true, error: null });
try {
await productApi.delete(id);
set((state) => ({
products: state.products.filter((p) => p.id !== id),
isLoading: false,
}));
} catch (error: any) {
set({ error, isLoading: false });
throw error;
}
},
clearError: () => set({ error: null }),
}),
{
name: 'product-storage',
partialize: (state) => ({ products: state.products }),
}
)
)
);
```
### 5. Implement API Integration
```typescript
// features/products/api/productApi.ts
import axios from 'axios';
import { Product, ProductFilters, PaginatedResponse } from '../types';
const API_BASE_URL = import.meta.env.VITE_API_URL || '/api';
const apiClient = axios.create({
baseURL: API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
// Request interceptor
apiClient.interceptors.request.use((config) => {
const token = localStorage.getItem('accessToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Response interceptor
apiClient.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
// Handle unauthorized
localStorage.removeItem('accessToken');
window.location.href = '/login';
}
throw error;
}
);
export const productApi = {
list: async (filters: ProductFilters): Promise<PaginatedResponse<Product>> => {
const response = await apiClient.get('/products', { params: filters });
return response.data;
},
getById: async (id: string): Promise<Product> => {
const response = await apiClient.get(`/products/${id}`);
return response.data.data;
},
create: async (data: Partial<Product>): Promise<Product> => {
const response = await apiClient.post('/products', data);
return response.data.data;
},
update: async (id: string, data: Partial<Product>): Promise<Product> => {
const response = await apiClient.put(`/products/${id}`, data);
return response.data.data;
},
delete: async (id: string): Promise<void> => {
await apiClient.delete(`/products/${id}`);
},
};
```
### 6. Create Custom Hooks
```typescript
// features/products/hooks/useProducts.ts
import { useState, useEffect, useCallback } from 'react';
import { productApi } from '../api/productApi';
import { Product, ProductFilters } from '../types';
interface UseProductsOptions {
categoryId?: string;
searchQuery?: string;
autoFetch?: boolean;
}
export const useProducts = (options: UseProductsOptions = {}) => {
const [products, setProducts] = useState<Product[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const [pagination, setPagination] = useState({
page: 1,
totalPages: 1,
total: 0,
});
const fetchProducts = useCallback(
async (page: number = 1) => {
setIsLoading(true);
setError(null);
try {
const filters: ProductFilters = {
page,
limit: 20,
categoryId: options.categoryId,
search: options.searchQuery,
};
const response = await productApi.list(filters);
setProducts(response.data);
setPagination({
page: response.meta.page,
totalPages: response.meta.totalPages,
total: response.meta.total,
});
} catch (err: any) {
setError(err);
} finally {
setIsLoading(false);
}
},
[options.categoryId, options.searchQuery]
);
useEffect(() => {
if (options.autoFetch !== false) {
fetchProducts();
}
}, [fetchProducts, options.autoFetch]);
const onPageChange = useCallback(
(page: number) => {
fetchProducts(page);
},
[fetchProducts]
);
const onAddToCart = useCallback((productId: string) => {
// Implement add to cart logic
console.log('Add to cart:', productId);
}, []);
return {
products,
isLoading,
error,
pagination,
fetchProducts,
onPageChange,
onAddToCart,
};
};
```
### 7. Write Tests
```typescript
// features/products/components/__tests__/ProductCard.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { ProductCard } from '../ProductCard';
const mockProduct = {
id: '1',
name: 'Test Product',
description: 'Test description',
price: 99.99,
currency: 'USD',
stockQuantity: 10,
images: [{ url: 'https://example.com/image.jpg', altText: 'Product image' }],
};
describe('ProductCard', () => {
it('should render product information', () => {
render(<ProductCard product={mockProduct} />);
expect(screen.getByText('Test Product')).toBeInTheDocument();
expect(screen.getByText(/Test description/)).toBeInTheDocument();
expect(screen.getByText('USD 99.99')).toBeInTheDocument();
});
it('should call onAddToCart when button clicked', () => {
const onAddToCart = jest.fn();
render(<ProductCard product={mockProduct} onAddToCart={onAddToCart} />);
const addButton = screen.getByRole('button', { name: /add to cart/i });
fireEvent.click(addButton);
expect(onAddToCart).toHaveBeenCalledWith('1');
});
it('should disable add to cart button when out of stock', () => {
const outOfStockProduct = { ...mockProduct, stockQuantity: 0 };
render(<ProductCard product={outOfStockProduct} />);
const addButton = screen.getByRole('button', { name: /add to cart/i });
expect(addButton).toBeDisabled();
expect(screen.getByText('Out of Stock')).toBeInTheDocument();
});
it('should handle image load error', () => {
render(<ProductCard product={mockProduct} />);
const image = screen.getByRole('img');
fireEvent.error(image);
expect(screen.getByText('No image')).toBeInTheDocument();
});
});
```
```typescript
// features/products/hooks/__tests__/useProducts.test.ts
import { renderHook, act, waitFor } from '@testing-library/react';
import { useProducts } from '../useProducts';
import { productApi } from '../../api/productApi';
jest.mock('../../api/productApi');
const mockProductApi = productApi as jest.Mocked<typeof productApi>;
describe('useProducts', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should fetch products on mount', async () => {
mockProductApi.list.mockResolvedValue({
data: [{ id: '1', name: 'Product 1' }],
meta: { page: 1, totalPages: 1, total: 1 },
} as any);
const { result } = renderHook(() => useProducts());
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
expect(result.current.products).toHaveLength(1);
expect(mockProductApi.list).toHaveBeenCalled();
});
it('should handle fetch error', async () => {
const error = new Error('Fetch failed');
mockProductApi.list.mockRejectedValue(error);
const { result } = renderHook(() => useProducts());
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
expect(result.current.error).toEqual(error);
});
it('should refetch on page change', async () => {
mockProductApi.list.mockResolvedValue({
data: [],
meta: { page: 1, totalPages: 2, total: 20 },
} as any);
const { result } = renderHook(() => useProducts());
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
act(() => {
result.current.onPageChange(2);
});
await waitFor(() => {
expect(mockProductApi.list).toHaveBeenCalledWith(
expect.objectContaining({ page: 2 })
);
});
});
});
```
## Output Format
```markdown
# Frontend Layer: {Feature Name}
## Components
### {ComponentName}
- Purpose: {description}
- Props: {props_list}
- State: {state_description}
- Code: {component_code}
## State Management
### Store/Context
\`\`\`typescript
{state_management_code}
\`\`\`
## API Integration
### API Client
\`\`\`typescript
{api_client_code}
\`\`\`
## Custom Hooks
### {HookName}
\`\`\`typescript
{hook_code}
\`\`\`
## Testing
### Component Tests
- {test_description}: {status}
### Hook Tests
- {test_description}: {status}
## Accessibility
- {a11y_considerations}
## Performance
- {performance_optimizations}
```
## Error Handling
- If framework unclear: Detect from package.json or ask
- If state management unclear: Suggest options
- Provide examples for detected framework

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,722 @@
# Integration & Polish Operation
Complete integration testing, performance optimization, security hardening, and documentation for a feature.
## Parameters
**Received**: `$ARGUMENTS` (after removing 'integrate' operation name)
Expected format: `feature:"feature name" [scope:"e2e|performance|security|documentation"] [priority:"high|medium|low"]`
## Workflow
### 1. End-to-End Testing
Create comprehensive E2E tests covering critical user workflows.
#### Using Playwright
```typescript
// e2e/products.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Product Management', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3000');
});
test('should complete full product browsing flow', async ({ page }) => {
// Navigate to products page
await page.click('text=Products');
await expect(page).toHaveURL(/\/products/);
// Verify products are loaded
await expect(page.locator('.product-card')).toHaveCount(20, { timeout: 10000 });
// Filter by category
await page.click('text=Electronics');
await expect(page.locator('.product-card')).toHaveCount(5);
// Search for product
await page.fill('input[placeholder="Search products"]', 'laptop');
await page.keyboard.press('Enter');
await expect(page.locator('.product-card')).toHaveCount(2);
// Click on first product
await page.click('.product-card:first-child');
await expect(page).toHaveURL(/\/products\/[a-z0-9-]+/);
// Verify product details
await expect(page.locator('h1')).toContainText('Laptop');
await expect(page.locator('.product-price')).toBeVisible();
// Add to cart
await page.click('button:has-text("Add to Cart")');
await expect(page.locator('.cart-badge')).toContainText('1');
});
test('should handle error states gracefully', async ({ page }) => {
// Simulate network error
await page.route('**/api/products', (route) => route.abort());
await page.goto('http://localhost:3000/products');
// Should show error message
await expect(page.locator('text=Failed to load products')).toBeVisible();
// Should have retry button
await expect(page.locator('button:has-text("Retry")')).toBeVisible();
});
test('should handle authentication flow', async ({ page }) => {
// Try to create product without auth
await page.goto('http://localhost:3000/products/new');
// Should redirect to login
await expect(page).toHaveURL(/\/login/);
// Login
await page.fill('input[name="email"]', 'admin@example.com');
await page.fill('input[name="password"]', 'Password123');
await page.click('button:has-text("Login")');
// Should redirect back to product creation
await expect(page).toHaveURL(/\/products\/new/);
// Create product
await page.fill('input[name="name"]', 'New Test Product');
await page.fill('textarea[name="description"]', 'Test description');
await page.fill('input[name="price"]', '99.99');
await page.fill('input[name="stockQuantity"]', '10');
await page.click('button:has-text("Create Product")');
// Should show success message
await expect(page.locator('text=Product created successfully')).toBeVisible();
});
test('should be accessible', async ({ page }) => {
await page.goto('http://localhost:3000/products');
// Check for proper heading hierarchy
const h1 = await page.locator('h1').count();
expect(h1).toBeGreaterThan(0);
// Check for alt text on images
const images = page.locator('img');
const count = await images.count();
for (let i = 0; i < count; i++) {
const alt = await images.nth(i).getAttribute('alt');
expect(alt).toBeTruthy();
}
// Check for keyboard navigation
await page.keyboard.press('Tab');
const focusedElement = await page.evaluate(() => document.activeElement?.tagName);
expect(focusedElement).toBeTruthy();
});
test('should work on mobile devices', async ({ page, viewport }) => {
// Set mobile viewport
await page.setViewportSize({ width: 375, height: 667 });
await page.goto('http://localhost:3000/products');
// Mobile menu should be visible
await expect(page.locator('[aria-label="Menu"]')).toBeVisible();
// Products should be in single column
const gridColumns = await page.locator('.product-grid').evaluate((el) => {
return window.getComputedStyle(el).gridTemplateColumns.split(' ').length;
});
expect(gridColumns).toBe(1);
});
});
```
### 2. Performance Optimization
#### Frontend Performance
```typescript
// Performance monitoring
import { onCLS, onFID, onLCP, onFCP, onTTFB } from 'web-vitals';
function sendToAnalytics(metric) {
console.log(metric);
// Send to analytics service
}
onCLS(sendToAnalytics);
onFID(sendToAnalytics);
onLCP(sendToAnalytics);
onFCP(sendToAnalytics);
onTTFB(sendToAnalytics);
// Code splitting
const ProductList = React.lazy(() => import('./features/products/components/ProductList'));
const ProductDetail = React.lazy(() => import('./features/products/components/ProductDetail'));
// Image optimization
<img
src={product.images[0].url}
srcSet={`
${product.images[0].url}?w=320 320w,
${product.images[0].url}?w=640 640w,
${product.images[0].url}?w=1024 1024w
`}
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
loading="lazy"
decoding="async"
/>
// Memoization
const MemoizedProductCard = React.memo(ProductCard, (prevProps, nextProps) => {
return prevProps.product.id === nextProps.product.id &&
prevProps.product.stockQuantity === nextProps.product.stockQuantity;
});
// Virtualization for long lists
import { FixedSizeList } from 'react-window';
const ProductVirtualList = ({ products }) => (
<FixedSizeList
height={600}
itemCount={products.length}
itemSize={200}
width="100%"
>
{({ index, style }) => (
<div style={style}>
<ProductCard product={products[index]} />
</div>
)}
</FixedSizeList>
);
```
#### Backend Performance
```typescript
// Database query optimization
// Add indexes (already in database.md)
// Query result caching
import { Redis } from 'ioredis';
const redis = new Redis();
async function getCachedProducts(filters: ProductFilters) {
const cacheKey = `products:${JSON.stringify(filters)}`;
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
const products = await productRepository.findAll(filters, pagination);
await redis.setex(cacheKey, 300, JSON.stringify(products)); // 5 minutes
return products;
}
// N+1 query prevention
const products = await productRepository.find({
relations: ['category', 'images', 'tags'], // Eager load
});
// Response compression
import compression from 'compression';
app.use(compression());
// Connection pooling (already configured in database setup)
// API response caching
import apicache from 'apicache';
app.use('/api/products', apicache.middleware('5 minutes'));
```
### 3. Security Hardening
#### Input Validation & Sanitization
```typescript
// Backend validation (already in backend.md with Zod)
// SQL Injection prevention (using parameterized queries with TypeORM)
// XSS Prevention
import DOMPurify from 'dompurify';
function sanitizeHtml(dirty: string): string {
return DOMPurify.sanitize(dirty);
}
// In component
<div dangerouslySetInnerHTML={{ __html: sanitizeHtml(product.description) }} />
```
#### Security Headers
```typescript
// helmet middleware
import helmet from 'helmet';
app.use(helmet());
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'", process.env.API_URL],
},
}));
// CORS configuration
import cors from 'cors';
app.use(cors({
origin: process.env.FRONTEND_URL,
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
}));
```
#### Rate Limiting
```typescript
import rateLimit from 'express-rate-limit';
// General API rate limit
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
message: 'Too many requests from this IP',
});
app.use('/api/', apiLimiter);
// Stricter rate limit for mutations
const createLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 10, // 10 creates per hour
});
app.use('/api/products', createLimiter);
```
#### Authentication & Authorization
```typescript
// JWT validation middleware (already in backend.md)
// RBAC (Role-Based Access Control)
function authorize(...allowedRoles: string[]) {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ error: 'Unauthorized' });
}
if (!allowedRoles.includes(req.user.role)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}
// Usage
router.post('/products', authenticate, authorize('admin', 'editor'), createProduct);
```
### 4. Error Handling & Logging
```typescript
// Centralized error handler
class AppError extends Error {
constructor(
public statusCode: number,
public message: string,
public isOperational = true
) {
super(message);
Object.setPrototypeOf(this, AppError.prototype);
}
}
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
if (err instanceof AppError) {
return res.status(err.statusCode).json({
error: {
message: err.message,
statusCode: err.statusCode,
},
});
}
// Log unexpected errors
console.error('Unexpected error:', err);
res.status(500).json({
error: {
message: 'Internal server error',
statusCode: 500,
},
});
});
// Structured logging
import winston from 'winston';
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
],
});
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple(),
}));
}
// Request logging
import morgan from 'morgan';
app.use(morgan('combined', { stream: { write: (msg) => logger.info(msg) } }));
```
### 5. Documentation
#### API Documentation
```yaml
# openapi.yaml
openapi: 3.0.0
info:
title: Product API
version: 1.0.0
description: API for managing products
servers:
- url: http://localhost:3000/api
description: Local server
- url: https://api.example.com
description: Production server
paths:
/products:
get:
summary: List products
description: Retrieve a paginated list of products with optional filters
parameters:
- name: page
in: query
schema:
type: integer
default: 1
- name: limit
in: query
schema:
type: integer
default: 20
- name: categoryId
in: query
schema:
type: string
format: uuid
- name: search
in: query
schema:
type: string
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
data:
type: array
items:
$ref: '#/components/schemas/Product'
meta:
type: object
properties:
page:
type: integer
totalPages:
type: integer
total:
type: integer
post:
summary: Create product
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateProductInput'
responses:
'201':
description: Product created
content:
application/json:
schema:
$ref: '#/components/schemas/Product'
'401':
description: Unauthorized
'400':
description: Invalid input
/products/{id}:
get:
summary: Get product by ID
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/Product'
'404':
description: Product not found
components:
schemas:
Product:
type: object
properties:
id:
type: string
format: uuid
name:
type: string
slug:
type: string
description:
type: string
price:
type: number
format: decimal
currency:
type: string
stockQuantity:
type: integer
createdAt:
type: string
format: date-time
CreateProductInput:
type: object
required:
- name
- price
- stockQuantity
properties:
name:
type: string
maxLength: 255
description:
type: string
price:
type: number
minimum: 0
stockQuantity:
type: integer
minimum: 0
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
```
#### Feature Documentation
```markdown
# Product Management Feature
## Overview
Complete product catalog management with support for categories, images, tags, search, and filtering.
## Features
- Product listing with pagination
- Product search and filtering
- Category hierarchy
- Multiple product images
- Tag management
- Stock tracking
- Soft delete support
## User Flows
### Browsing Products
1. User navigates to products page
2. Products are loaded with pagination (20 per page)
3. User can filter by category
4. User can search by name/description
5. User clicks on product to view details
### Creating Product (Admin)
1. Admin logs in
2. Admin navigates to "Create Product"
3. Admin fills in product details
4. Admin uploads product images
5. Admin selects category and tags
6. Admin submits form
7. Product is created and admin is redirected to product page
## API Usage Examples
### List Products
\`\`\`bash
curl -X GET "http://localhost:3000/api/products?page=1&limit=20&categoryId=abc123"
\`\`\`
### Create Product
\`\`\`bash
curl -X POST "http://localhost:3000/api/products" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "New Product",
"description": "Product description",
"price": 99.99,
"stockQuantity": 10,
"categoryId": "abc123"
}'
\`\`\`
## Performance Characteristics
- API response time: <100ms (p95)
- Page load time: <2s (p95)
- Database queries: Optimized with indexes
- Image loading: Lazy loaded with srcSet
- List rendering: Virtualized for 1000+ items
## Security Measures
- JWT authentication for mutations
- Role-based access control (RBAC)
- Input validation on backend
- XSS protection with DOMPurify
- SQL injection prevention
- Rate limiting (100 req/15min)
- CORS configured
- Security headers with Helmet
## Known Limitations
- Maximum 10 images per product
- Product names limited to 255 characters
- Search limited to name and description
- Bulk operations not yet supported
## Future Enhancements
- [ ] Bulk product import/export
- [ ] Product variants (size, color)
- [ ] Advanced inventory management
- [ ] Product recommendations
- [ ] Analytics dashboard
```
## Output Format
```markdown
# Integration & Polish: {Feature Name}
## E2E Test Results
### Test Suites
- {suite_name}: {passed/failed} ({count} tests)
### Coverage
- User flows covered: {percentage}%
- Edge cases tested: {count}
## Performance Metrics
### Frontend
- LCP: {time}ms
- FID: {time}ms
- CLS: {score}
### Backend
- API response time (p95): {time}ms
- Database query time (p95): {time}ms
- Memory usage: {mb}MB
### Optimizations Applied
- {optimization_description}
## Security Audit
### Vulnerabilities Fixed
- {vulnerability}: {fix_description}
### Security Measures
- {measure_description}
## Documentation
### API Documentation
- {documentation_location}
### User Guide
- {guide_location}
### Developer Documentation
- {docs_location}
## Deployment Checklist
- [ ] All tests passing
- [ ] Performance benchmarks met
- [ ] Security audit completed
- [ ] Documentation updated
- [ ] Environment variables documented
- [ ] Monitoring configured
- [ ] Backup strategy in place
## Known Issues
- {issue_description}: {workaround}
## Next Steps
- {future_enhancement}
```
## Error Handling
- If tests fail: Provide failure details and suggested fixes
- If performance targets not met: Suggest optimizations
- If security issues found: Provide remediation steps

View File

@@ -0,0 +1,798 @@
# Scaffold Feature Operation
Generate boilerplate code structure for a new feature across database, backend, and frontend layers.
## Parameters
**Received**: `$ARGUMENTS` (after removing 'scaffold' operation name)
Expected format: `name:"feature-name" [layers:"database,backend,frontend"] [pattern:"crud|workflow|custom"]`
## Workflow
### 1. Understand Scaffolding Requirements
Clarify:
- What is the feature name?
- Which layers need scaffolding?
- What pattern does it follow (CRUD, workflow, custom)?
- What entity/resource is being managed?
### 2. Analyze Project Structure
```bash
# Detect project structure
ls -la src/
# Detect ORM
cat package.json | grep -E "(prisma|typeorm|sequelize|mongoose)"
# Detect frontend framework
cat package.json | grep -E "(react|vue|angular|svelte)"
# Detect backend framework
cat package.json | grep -E "(express|fastify|nest|koa)"
```
### 3. Generate Database Layer
#### Migration Scaffold
```typescript
// migrations/TIMESTAMP_add_{feature_name}.ts
import { MigrationInterface, QueryRunner, Table, TableIndex, TableForeignKey } from 'typeorm';
export class Add{FeatureName}{Timestamp} implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: '{table_name}',
columns: [
{
name: 'id',
type: 'uuid',
isPrimary: true,
default: 'gen_random_uuid()',
},
{
name: 'name',
type: 'varchar',
length: '255',
isNullable: false,
},
{
name: 'created_at',
type: 'timestamp',
default: 'now()',
},
{
name: 'updated_at',
type: 'timestamp',
default: 'now()',
},
],
}),
true
);
// Add indexes
await queryRunner.createIndex(
'{table_name}',
new TableIndex({
name: 'idx_{table_name}_created_at',
columnNames: ['created_at'],
})
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('{table_name}');
}
}
```
#### Entity/Model Scaffold
```typescript
// entities/{FeatureName}.entity.ts
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
Index,
} from 'typeorm';
@Entity('{table_name}')
export class {FeatureName} {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'varchar', length: 255 })
name: string;
@CreateDateColumn({ name: 'created_at' })
@Index()
createdAt: Date;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;
}
```
### 4. Generate Backend Layer
#### Repository Scaffold
```typescript
// repositories/{FeatureName}Repository.ts
import { Repository } from 'typeorm';
import { {FeatureName} } from '../entities/{FeatureName}.entity';
import { AppDataSource } from '../config/database';
export class {FeatureName}Repository {
private repository: Repository<{FeatureName}>;
constructor() {
this.repository = AppDataSource.getRepository({FeatureName});
}
async findById(id: string): Promise<{FeatureName} | null> {
return this.repository.findOne({ where: { id } });
}
async findAll(page: number = 1, limit: number = 20): Promise<[{FeatureName}[], number]> {
const skip = (page - 1) * limit;
return this.repository.findAndCount({
skip,
take: limit,
order: { createdAt: 'DESC' },
});
}
async create(data: Partial<{FeatureName}>): Promise<{FeatureName}> {
const entity = this.repository.create(data);
return this.repository.save(entity);
}
async update(id: string, data: Partial<{FeatureName}>): Promise<{FeatureName}> {
await this.repository.update(id, data);
const updated = await this.findById(id);
if (!updated) {
throw new Error('{FeatureName} not found after update');
}
return updated;
}
async delete(id: string): Promise<void> {
await this.repository.delete(id);
}
}
```
#### Service Scaffold
```typescript
// services/{FeatureName}Service.ts
import { {FeatureName}Repository } from '../repositories/{FeatureName}Repository';
import { {FeatureName} } from '../entities/{FeatureName}.entity';
import { NotFoundError, ValidationError } from '../errors';
export interface Create{FeatureName}Input {
name: string;
}
export interface Update{FeatureName}Input {
name?: string;
}
export class {FeatureName}Service {
constructor(private repository: {FeatureName}Repository) {}
async get{FeatureName}(id: string): Promise<{FeatureName}> {
const entity = await this.repository.findById(id);
if (!entity) {
throw new NotFoundError(`{FeatureName} with ID ${id} not found`);
}
return entity;
}
async list{FeatureName}s(
page: number = 1,
limit: number = 20
): Promise<{ data: {FeatureName}[]; total: number; page: number; totalPages: number }> {
const [data, total] = await this.repository.findAll(page, limit);
return {
data,
total,
page,
totalPages: Math.ceil(total / limit),
};
}
async create{FeatureName}(input: Create{FeatureName}Input): Promise<{FeatureName}> {
this.validateInput(input);
return this.repository.create(input);
}
async update{FeatureName}(id: string, input: Update{FeatureName}Input): Promise<{FeatureName}> {
await this.get{FeatureName}(id); // Verify exists
return this.repository.update(id, input);
}
async delete{FeatureName}(id: string): Promise<void> {
await this.get{FeatureName}(id); // Verify exists
await this.repository.delete(id);
}
private validateInput(input: Create{FeatureName}Input): void {
if (!input.name || input.name.trim().length === 0) {
throw new ValidationError('Name is required');
}
if (input.name.length > 255) {
throw new ValidationError('Name must not exceed 255 characters');
}
}
}
```
#### Controller Scaffold
```typescript
// controllers/{FeatureName}Controller.ts
import { Request, Response, NextFunction } from 'express';
import { {FeatureName}Service } from '../services/{FeatureName}Service';
export class {FeatureName}Controller {
constructor(private service: {FeatureName}Service) {}
get{FeatureName} = async (req: Request, res: Response, next: NextFunction) => {
try {
const { id } = req.params;
const entity = await this.service.get{FeatureName}(id);
res.json({
success: true,
data: entity,
});
} catch (error) {
next(error);
}
};
list{FeatureName}s = async (req: Request, res: Response, next: NextFunction) => {
try {
const page = parseInt(req.query.page as string) || 1;
const limit = parseInt(req.query.limit as string) || 20;
const result = await this.service.list{FeatureName}s(page, limit);
res.json({
success: true,
data: result.data,
meta: {
total: result.total,
page: result.page,
totalPages: result.totalPages,
limit,
},
});
} catch (error) {
next(error);
}
};
create{FeatureName} = async (req: Request, res: Response, next: NextFunction) => {
try {
const entity = await this.service.create{FeatureName}(req.body);
res.status(201).json({
success: true,
data: entity,
});
} catch (error) {
next(error);
}
};
update{FeatureName} = async (req: Request, res: Response, next: NextFunction) => {
try {
const { id } = req.params;
const entity = await this.service.update{FeatureName}(id, req.body);
res.json({
success: true,
data: entity,
});
} catch (error) {
next(error);
}
};
delete{FeatureName} = async (req: Request, res: Response, next: NextFunction) => {
try {
const { id } = req.params;
await this.service.delete{FeatureName}(id);
res.status(204).send();
} catch (error) {
next(error);
}
};
}
```
#### Routes Scaffold
```typescript
// routes/{feature-name}.routes.ts
import { Router } from 'express';
import { {FeatureName}Controller } from '../controllers/{FeatureName}Controller';
import { {FeatureName}Service } from '../services/{FeatureName}Service';
import { {FeatureName}Repository } from '../repositories/{FeatureName}Repository';
import { authenticate } from '../middlewares/auth.middleware';
import { validate } from '../middlewares/validation.middleware';
import { create{FeatureName}Schema, update{FeatureName}Schema } from '../schemas/{feature-name}.schemas';
const router = Router();
// Initialize dependencies
const repository = new {FeatureName}Repository();
const service = new {FeatureName}Service(repository);
const controller = new {FeatureName}Controller(service);
// Public routes
router.get('/', controller.list{FeatureName}s);
router.get('/:id', controller.get{FeatureName});
// Protected routes
router.post(
'/',
authenticate,
validate(create{FeatureName}Schema),
controller.create{FeatureName}
);
router.put(
'/:id',
authenticate,
validate(update{FeatureName}Schema),
controller.update{FeatureName}
);
router.delete('/:id', authenticate, controller.delete{FeatureName});
export default router;
```
#### Validation Schema Scaffold
```typescript
// schemas/{feature-name}.schemas.ts
import { z } from 'zod';
export const create{FeatureName}Schema = z.object({
body: z.object({
name: z.string().min(1).max(255),
// Add more fields as needed
}),
});
export const update{FeatureName}Schema = z.object({
body: z.object({
name: z.string().min(1).max(255).optional(),
// Add more fields as needed
}),
params: z.object({
id: z.string().uuid(),
}),
});
```
### 5. Generate Frontend Layer
#### Types Scaffold
```typescript
// features/{feature-name}/types/index.ts
export interface {FeatureName} {
id: string;
name: string;
createdAt: string;
updatedAt: string;
}
export interface Create{FeatureName}Input {
name: string;
}
export interface Update{FeatureName}Input {
name?: string;
}
export interface {FeatureName}Filters {
page?: number;
limit?: number;
}
export interface PaginatedResponse<T> {
success: boolean;
data: T[];
meta: {
total: number;
page: number;
totalPages: number;
limit: number;
};
}
```
#### API Client Scaffold
```typescript
// features/{feature-name}/api/{feature-name}Api.ts
import axios, { AxiosInstance } from 'axios';
import { {FeatureName}, Create{FeatureName}Input, Update{FeatureName}Input, {FeatureName}Filters, PaginatedResponse } from '../types';
class {FeatureName}Api {
private client: AxiosInstance;
constructor() {
this.client = axios.create({
baseURL: import.meta.env.VITE_API_URL || '/api',
timeout: 10000,
});
this.client.interceptors.request.use((config) => {
const token = localStorage.getItem('accessToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
}
async list(filters: {FeatureName}Filters = {}): Promise<PaginatedResponse<{FeatureName}>> {
const response = await this.client.get('/{feature-name}', { params: filters });
return response.data;
}
async getById(id: string): Promise<{FeatureName}> {
const response = await this.client.get(`/{feature-name}/${id}`);
return response.data.data;
}
async create(data: Create{FeatureName}Input): Promise<{FeatureName}> {
const response = await this.client.post('/{feature-name}', data);
return response.data.data;
}
async update(id: string, data: Update{FeatureName}Input): Promise<{FeatureName}> {
const response = await this.client.put(`/{feature-name}/${id}`, data);
return response.data.data;
}
async delete(id: string): Promise<void> {
await this.client.delete(`/{feature-name}/${id}`);
}
}
export const {featureName}Api = new {FeatureName}Api();
```
#### Component Scaffolds
```typescript
// features/{feature-name}/components/{FeatureName}List.tsx
import React from 'react';
import { use{FeatureName}s } from '../hooks/use{FeatureName}s';
import { {FeatureName}Card } from './{FeatureName}Card';
import { LoadingSpinner } from '@/components/LoadingSpinner';
import { ErrorMessage } from '@/components/ErrorMessage';
export const {FeatureName}List: React.FC = () => {
const { items, isLoading, error, refetch } = use{FeatureName}s();
if (isLoading) {
return <LoadingSpinner />;
}
if (error) {
return <ErrorMessage message={error.message} onRetry={refetch} />;
}
if (items.length === 0) {
return <div>No items found.</div>;
}
return (
<div className="{feature-name}-list">
{items.map((item) => (
<{FeatureName}Card key={item.id} item={item} />
))}
</div>
);
};
```
```typescript
// features/{feature-name}/components/{FeatureName}Card.tsx
import React from 'react';
import { {FeatureName} } from '../types';
interface {FeatureName}CardProps {
item: {FeatureName};
onEdit?: (id: string) => void;
onDelete?: (id: string) => void;
}
export const {FeatureName}Card: React.FC<{FeatureName}CardProps> = ({
item,
onEdit,
onDelete,
}) => {
return (
<div className="{feature-name}-card">
<h3>{item.name}</h3>
<div className="{feature-name}-card__actions">
{onEdit && (
<button onClick={() => onEdit(item.id)}>Edit</button>
)}
{onDelete && (
<button onClick={() => onDelete(item.id)}>Delete</button>
)}
</div>
</div>
);
};
```
```typescript
// features/{feature-name}/components/{FeatureName}Form.tsx
import React from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const {featureName}Schema = z.object({
name: z.string().min(1, 'Name is required').max(255),
});
type {FeatureName}FormData = z.infer<typeof {featureName}Schema>;
interface {FeatureName}FormProps {
initialData?: {FeatureName}FormData;
onSubmit: (data: {FeatureName}FormData) => Promise<void>;
onCancel?: () => void;
}
export const {FeatureName}Form: React.FC<{FeatureName}FormProps> = ({
initialData,
onSubmit,
onCancel,
}) => {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<{FeatureName}FormData>({
resolver: zodResolver({featureName}Schema),
defaultValues: initialData,
});
return (
<form onSubmit={handleSubmit(onSubmit)} className="{feature-name}-form">
<div className="form-group">
<label htmlFor="name">Name</label>
<input
id="name"
type="text"
{...register('name')}
disabled={isSubmitting}
/>
{errors.name && (
<span className="error">{errors.name.message}</span>
)}
</div>
<div className="form-actions">
{onCancel && (
<button type="button" onClick={onCancel} disabled={isSubmitting}>
Cancel
</button>
)}
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</div>
</form>
);
};
```
#### Custom Hook Scaffold
```typescript
// features/{feature-name}/hooks/use{FeatureName}s.ts
import { useState, useEffect, useCallback } from 'react';
import { {featureName}Api } from '../api/{feature-name}Api';
import { {FeatureName} } from '../types';
export const use{FeatureName}s = () => {
const [items, setItems] = useState<{FeatureName}[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const fetch{FeatureName}s = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
const response = await {featureName}Api.list();
setItems(response.data);
} catch (err: any) {
setError(err);
} finally {
setIsLoading(false);
}
}, []);
useEffect(() => {
fetch{FeatureName}s();
}, [fetch{FeatureName}s]);
const create = useCallback(async (data: any) => {
const newItem = await {featureName}Api.create(data);
setItems((prev) => [...prev, newItem]);
}, []);
const update = useCallback(async (id: string, data: any) => {
const updated = await {featureName}Api.update(id, data);
setItems((prev) => prev.map((item) => (item.id === id ? updated : item)));
}, []);
const remove = useCallback(async (id: string) => {
await {featureName}Api.delete(id);
setItems((prev) => prev.filter((item) => item.id !== id));
}, []);
return {
items,
isLoading,
error,
refetch: fetch{FeatureName}s,
create,
update,
remove,
};
};
```
### 6. Generate Test Scaffolds
```typescript
// Backend test scaffold
// repositories/__tests__/{FeatureName}Repository.test.ts
import { {FeatureName}Repository } from '../{FeatureName}Repository';
import { createTestDataSource } from '../../test/utils';
describe('{FeatureName}Repository', () => {
let repository: {FeatureName}Repository;
beforeAll(async () => {
await createTestDataSource();
repository = new {FeatureName}Repository();
});
it('should create {feature-name}', async () => {
const entity = await repository.create({ name: 'Test' });
expect(entity.id).toBeDefined();
expect(entity.name).toBe('Test');
});
it('should find {feature-name} by id', async () => {
const created = await repository.create({ name: 'Test' });
const found = await repository.findById(created.id);
expect(found?.name).toBe('Test');
});
// Add more tests
});
```
```typescript
// Frontend test scaffold
// features/{feature-name}/components/__tests__/{FeatureName}List.test.tsx
import { render, screen } from '@testing-library/react';
import { {FeatureName}List } from '../{FeatureName}List';
import { use{FeatureName}s } from '../../hooks/use{FeatureName}s';
jest.mock('../../hooks/use{FeatureName}s');
describe('{FeatureName}List', () => {
it('should render list of items', () => {
(use{FeatureName}s as jest.Mock).mockReturnValue({
items: [{ id: '1', name: 'Test Item' }],
isLoading: false,
error: null,
});
render(<{FeatureName}List />);
expect(screen.getByText('Test Item')).toBeInTheDocument();
});
it('should show loading state', () => {
(use{FeatureName}s as jest.Mock).mockReturnValue({
items: [],
isLoading: true,
error: null,
});
render(<{FeatureName}List />);
expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
});
// Add more tests
});
```
## Output Format
```markdown
# Scaffolded Feature: {Feature Name}
## Generated Files
### Database Layer
- migrations/TIMESTAMP_add_{feature_name}.ts
- entities/{FeatureName}.entity.ts
### Backend Layer
- repositories/{FeatureName}Repository.ts
- services/{FeatureName}Service.ts
- controllers/{FeatureName}Controller.ts
- routes/{feature-name}.routes.ts
- schemas/{feature-name}.schemas.ts
### Frontend Layer
- features/{feature-name}/types/index.ts
- features/{feature-name}/api/{feature-name}Api.ts
- features/{feature-name}/components/{FeatureName}List.tsx
- features/{feature-name}/components/{FeatureName}Card.tsx
- features/{feature-name}/components/{FeatureName}Form.tsx
- features/{feature-name}/hooks/use{FeatureName}s.ts
### Test Files
- repositories/__tests__/{FeatureName}Repository.test.ts
- services/__tests__/{FeatureName}Service.test.ts
- components/__tests__/{FeatureName}List.test.tsx
## Next Steps
1. Run database migration
2. Register routes in main app
3. Implement custom business logic
4. Add additional validations
5. Customize UI components
6. Write comprehensive tests
7. Add documentation
## Customization Points
- Add custom fields to entity
- Implement complex queries in repository
- Add business logic to service
- Customize UI components
- Add additional API endpoints
```
## Error Handling
- If project structure unclear: Ask for clarification or detect automatically
- If naming conflicts: Suggest alternative names
- Generate placeholders for unknown patterns

75
commands/feature/skill.md Normal file
View File

@@ -0,0 +1,75 @@
---
description: Implement production-ready features across database, backend, and frontend layers with incremental phased approach
argument-hint: <operation> [parameters...]
---
# Feature Implementation Router
Comprehensive feature implementation across the full stack with phased, incremental development approach. Routes feature implementation requests to specialized operations for different layers or full-stack implementation.
## Operations
- **implement** - Complete full-stack feature implementation across all layers (database, backend, frontend, integration)
- **database** - Database layer only (migrations, models, schemas, indexes)
- **backend** - Backend layer only (services, API endpoints, validation, tests)
- **frontend** - Frontend layer only (components, state, API integration, tests)
- **integrate** - Integration and polish phase (E2E tests, performance, security, documentation)
- **scaffold** - Scaffold feature structure and boilerplate across all layers
## Usage Examples
```bash
# Complete full-stack feature
/feature implement description:"user authentication with OAuth and 2FA" tests:"comprehensive"
# Database layer only
/feature database description:"user profiles table with indexes" migration:"add_user_profiles"
# Backend API only
/feature backend description:"REST API for product search with filters" validation:"strict"
# Frontend components only
/feature frontend description:"product catalog with infinite scroll and filters" framework:"react"
# Integration and polish
/feature integrate feature:"authentication flow" scope:"E2E tests and performance"
# Scaffold feature structure
/feature scaffold name:"notification-system" layers:"database,backend,frontend"
```
## Router Logic
Parse the first word of $ARGUMENTS to determine operation:
1. Extract operation from first word of $ARGUMENTS
2. Extract remaining arguments as operation parameters
3. Route to instruction file:
- "implement" → Read `.claude/commands/fullstack/feature/implement.md` and execute
- "database" → Read `.claude/commands/fullstack/feature/database.md` and execute
- "backend" → Read `.claude/commands/fullstack/feature/backend.md` and execute
- "frontend" → Read `.claude/commands/fullstack/feature/frontend.md` and execute
- "integrate" → Read `.claude/commands/fullstack/feature/integrate.md` and execute
- "scaffold" → Read `.claude/commands/fullstack/feature/scaffold.md` and execute
4. Pass extracted parameters to the instruction file
5. Return structured implementation
**Error Handling:**
- If operation is unrecognized, list available operations with examples
- If parameters are missing, request clarification with expected format
- If requirements are unclear, ask specific questions about scope and acceptance criteria
- Provide clear error messages with usage examples
**Security:**
- Validate all input parameters
- Ensure no hardcoded secrets in generated code
- Follow security best practices for each layer
- Include validation and sanitization in generated code
---
**Base directory:** `.claude/commands/fullstack/feature`
**Current Request:** $ARGUMENTS
Parse operation and route to appropriate instruction file now.