# @isa/common/decorators

A comprehensive collection of TypeScript decorators for enhancing method behavior in Angular applications. This library provides decorators for validation, caching, debouncing, rate limiting, and more.

## Installation

This library is already configured in the project's `tsconfig.base.json`. Import decorators using:

```typescript
import {
  // Validation decorators
  ValidateParams, ValidateParam, ZodValidationError,
  // Caching decorators
  Cache, CacheTimeToLive,
  // Rate limiting decorators
  Debounce, InFlight, InFlightWithKey, InFlightWithCache
} from '@isa/common/decorators';
```

## Available Decorators

### 🛡️ Validation Decorators

#### `ValidateParams`
Method decorator that validates method parameters using Zod schemas at runtime.

**Features:**
- Runtime type validation with Zod
- Custom parameter names for better error messages
- Schema transformations and default values
- Async method support
- Selective parameter validation

```typescript
import { ValidateParams, ZodValidationError } from '@isa/common/decorators';
import { z } from 'zod';

@Injectable()
class UserService {
  @ValidateParams([
    z.string().email(),
    z.object({
      name: z.string().min(1),
      age: z.number().int().min(0).max(120)
    })
  ], ['email', 'userData'])
  async createUser(email: string, userData: UserData): Promise<User> {
    // Method implementation - parameters are validated before execution
    return await this.apiClient.createUser(email, userData);
  }
}

// Usage with error handling
try {
  await userService.createUser('invalid-email', userData);
} catch (error) {
  if (error instanceof ZodValidationError) {
    console.log(`Validation failed: ${error.message}`);
    console.log('Parameter:', error.parameterName);
    console.log('Details:', error.zodError.issues);
  }
}
```

#### `ValidateParam`
Method decorator for validating a single parameter at a specific index.

```typescript
@Injectable()
class EmailService {
  @ValidateParam(0, z.string().email(), 'emailAddress')
  async sendEmail(email: string, subject: string): Promise<void> {
    // Only the first parameter (email) is validated
    return await this.emailClient.send(email, subject);
  }
}
```

**Advanced Validation Examples:**

```typescript
class AdvancedValidationService {
  // Complex object validation with transformations
  @ValidateParams([
    z.string().transform(s => s.trim().toLowerCase()),
    z.array(z.string().uuid()).min(1),
    z.object({
      priority: z.enum(['low', 'medium', 'high']).default('medium'),
      retries: z.number().int().min(0).max(5).default(3)
    }).optional()
  ])
  processRequest(name: string, ids: string[], options?: RequestOptions): void {
    // name is trimmed and lowercase, options have defaults applied
  }

  // Skip validation for specific parameters
  @ValidateParams([
    z.string().email(),  // Validate first parameter
    undefined,           // Skip second parameter
    z.number().positive() // Validate third parameter
  ])
  mixedValidation(email: string, metadata: any, count: number): void {
    // email and count validated, metadata can be anything
  }
}
```

### 📦 Caching Decorators

#### `Cache`
Method decorator that caches the results of both synchronous and asynchronous method calls.

**Features:**
- Automatic caching based on method arguments
- TTL (Time To Live) support with predefined constants
- Custom cache key generation
- Separate cache per instance
- Error handling (errors are not cached)

```typescript
import { Cache, CacheTimeToLive } from '@isa/common/decorators';

@Injectable()
class DataService {
  @Cache({
    ttl: CacheTimeToLive.fiveMinutes,
    keyGenerator: (query: string) => `search-${query}`
  })
  async searchData(query: string): Promise<SearchResult[]> {
    // Expensive API call - results cached for 5 minutes
    return await this.apiClient.search(query);
  }

  @Cache({ ttl: CacheTimeToLive.oneHour })
  calculateComplexResult(input: number): number {
    // Expensive calculation - cached for 1 hour
    return this.performHeavyCalculation(input);
  }
}
```

**Cache TTL Constants:**
- `CacheTimeToLive.oneMinute` (60,000ms)
- `CacheTimeToLive.fiveMinutes` (300,000ms)
- `CacheTimeToLive.tenMinutes` (600,000ms)
- `CacheTimeToLive.thirtyMinutes` (1,800,000ms)
- `CacheTimeToLive.oneHour` (3,600,000ms)

### ⏱️ Rate Limiting Decorators

#### `Debounce`
Method decorator that debounces method calls using lodash's debounce function.

**Features:**
- Configurable wait time
- Leading and trailing edge execution
- Maximum wait time limits
- Separate debounced functions per instance

```typescript
import { Debounce } from '@isa/common/decorators';

@Injectable()
class SearchService {
  @Debounce({ wait: 300 })
  performSearch(query: string): void {
    // Called only after 300ms of no additional calls
    console.log('Searching for:', query);
  }

  @Debounce({
    wait: 500,
    leading: true,
    trailing: false
  })
  saveData(data: any): void {
    // Executes immediately on first call, then debounces
    console.log('Saving:', data);
  }

  @Debounce({
    wait: 1000,
    maxWait: 5000
  })
  autoSave(): void {
    // Forces execution after 5 seconds even with continuous calls
    console.log('Auto-saving...');
  }
}
```

### 🚀 InFlight Decorators

Prevent multiple simultaneous calls to the same async method. All concurrent calls receive the same Promise result.

#### Basic Usage

```typescript
import { InFlight } from '@isa/common/decorators';

@Injectable()
class DataService {
  @InFlight()
  async fetchData(): Promise<Data> {
    // Even if called multiple times simultaneously, 
    // only one API call will be made
    return await this.http.get<Data>('/api/data').toPromise();
  }
}
```

**Benefits:**
- Prevents duplicate API calls
- Reduces server load
- Improves application performance
- All callers receive the same result

### 🔑 InFlightWithKey

Prevents duplicate calls while considering method arguments. Each unique set of arguments gets its own in-flight tracking.

```typescript
import { InFlightWithKey } from '@isa/common/decorators';

@Injectable()
class UserService {
  @InFlightWithKey({
    keyGenerator: (userId: string) => userId
  })
  async fetchUser(userId: string): Promise<User> {
    // Multiple calls with same userId share the same request
    // Different userIds can execute simultaneously
    return await this.http.get<User>(`/api/users/${userId}`).toPromise();
  }

  @InFlightWithKey() // Uses JSON.stringify by default
  async searchUsers(query: string, page: number): Promise<User[]> {
    return await this.http.get<User[]>(`/api/users/search`, {
      params: { query, page: page.toString() }
    }).toPromise();
  }
}
```

**Configuration Options:**
- `keyGenerator?: (...args) => string` - Custom key generation function
- If not provided, uses `JSON.stringify(args)` as the key

### 🗄️ InFlightWithCache

Combines in-flight request deduplication with result caching.

```typescript
import { InFlightWithCache } from '@isa/common/decorators';

@Injectable()
class ProductService {
  @InFlightWithCache({
    cacheTime: 5 * 60 * 1000, // Cache for 5 minutes
    keyGenerator: (productId: string) => productId
  })
  async getProduct(productId: string): Promise<Product> {
    // Results are cached for 5 minutes
    // Multiple calls within cache time return cached result
    return await this.http.get<Product>(`/api/products/${productId}`).toPromise();
  }
}
```

**Configuration Options:**
- `cacheTime?: number` - Cache duration in milliseconds
- `keyGenerator?: (...args) => string` - Custom key generation function

## How It Works

### Memory Management

All decorators use `WeakMap` for memory efficiency:
- Automatic garbage collection when instances are destroyed
- No memory leaks
- Per-instance state isolation

### Error Handling

- Failed requests are not cached
- In-flight tracking is cleaned up on both success and error
- All concurrent callers receive the same error

### Thread Safety

- Decorators are instance-aware
- Each service instance has its own in-flight tracking
- No shared state between instances

## Real-World Examples

### Solving Your Original Problem

```typescript
// Before: Multiple simultaneous calls
@Injectable({ providedIn: 'root' })
export class RemissionProductGroupService {
  async fetchProductGroups(): Promise<KeyValueStringAndString[]> {
    // Multiple calls = multiple API requests
    return await this.apiCall();
  }
}

// After: Using InFlight decorator
@Injectable({ providedIn: 'root' })
export class RemissionProductGroupService {
  @InFlight()
  async fetchProductGroups(): Promise<KeyValueStringAndString[]> {
    // Multiple simultaneous calls = single API request
    return await this.apiCall();
  }
}
```

### Advanced Scenarios

```typescript
@Injectable()
class OrderService {
  // Different cache times for different data types
  @InFlightWithCache({ cacheTime: 30 * 1000 }) // 30 seconds
  async getOrderStatus(orderId: string): Promise<OrderStatus> {
    return await this.http.get<OrderStatus>(`/api/orders/${orderId}/status`).toPromise();
  }

  @InFlightWithCache({ cacheTime: 10 * 60 * 1000 }) // 10 minutes
  async getOrderHistory(customerId: string): Promise<Order[]> {
    return await this.http.get<Order[]>(`/api/customers/${customerId}/orders`).toPromise();
  }

  // Custom key generation for complex parameters
  @InFlightWithKey({
    keyGenerator: (filter: OrderFilter) => 
      `${filter.status}-${filter.dateFrom}-${filter.dateTo}`
  })
  async searchOrders(filter: OrderFilter): Promise<Order[]> {
    return await this.http.post<Order[]>('/api/orders/search', filter).toPromise();
  }
}
```

## Advanced Usage Examples

### Combining Decorators

Decorators can be combined for powerful behavior:

```typescript
@Injectable()
class AdvancedService {
  @Cache({ ttl: CacheTimeToLive.tenMinutes })
  @Debounce({ wait: 500 })
  @ValidateParams([z.string().min(1)], ['query'])
  async searchWithCacheAndDebounce(query: string): Promise<SearchResult[]> {
    // 1. Parameter is validated
    // 2. Call is debounced by 500ms
    // 3. Result is cached for 10 minutes
    return await this.performSearch(query);
  }

  @InFlight()
  @ValidateParams([
    z.string().uuid(),
    z.object({
      retries: z.number().int().min(0).max(5).default(3),
      timeout: z.number().positive().default(5000)
    }).optional()
  ], ['operationId', 'options'])
  async executeOperation(
    operationId: string,
    options?: { retries?: number; timeout?: number }
  ): Promise<void> {
    // 1. Parameters are validated with defaults applied
    // 2. Only one execution per instance allowed
    await this.performOperation(operationId, options);
  }
}
```

### Complex Real-World Examples

```typescript
@Injectable()
class ProductService {
  // Comprehensive product search with validation, debouncing, and caching
  @Cache({ ttl: CacheTimeToLive.fiveMinutes })
  @Debounce({ wait: 300 })
  @ValidateParams([
    z.object({
      query: z.string().min(1).transform(s => s.trim()),
      category: z.string().optional(),
      priceRange: z.object({
        min: z.number().min(0),
        max: z.number().positive()
      }).optional(),
      sortBy: z.enum(['price', 'name', 'rating']).default('name'),
      page: z.number().int().min(1).default(1)
    })
  ], ['searchParams'])
  async searchProducts(params: ProductSearchParams): Promise<ProductResults> {
    return await this.apiClient.searchProducts(params);
  }

  // Bulk operations with validation and in-flight protection
  @InFlightWithKey({
    keyGenerator: (operation: string) => `bulk-${operation}`
  })
  @ValidateParams([
    z.enum(['update', 'delete', 'activate', 'deactivate']),
    z.array(z.string().uuid()).min(1).max(100),
    z.object({
      batchSize: z.number().int().positive().max(50).default(10),
      delayMs: z.number().min(0).default(100)
    }).optional()
  ], ['operation', 'productIds', 'options'])
  async bulkOperation(
    operation: BulkOperationType,
    productIds: string[],
    options?: BulkOptions
  ): Promise<BulkOperationResult> {
    return await this.performBulkOperation(operation, productIds, options);
  }
}

@Injectable()
class UserManagementService {
  // User creation with comprehensive validation
  @ValidateParams([
    z.object({
      email: z.string().email().transform(e => e.toLowerCase()),
      name: z.string().min(1).max(100).transform(n => n.trim()),
      role: z.enum(['admin', 'user', 'manager']).default('user'),
      permissions: z.array(z.string()).default([]),
      metadata: z.record(z.string()).optional()
    }),
    z.object({
      sendWelcomeEmail: z.boolean().default(true),
      skipValidation: z.boolean().default(false),
      auditLog: z.boolean().default(true)
    }).optional()
  ], ['userData', 'options'])
  async createUser(
    userData: CreateUserData,
    options?: CreateUserOptions
  ): Promise<User> {
    // All parameters are validated and transformed
    return await this.userRepository.create(userData, options);
  }

  // Password reset with rate limiting and caching
  @Cache({ ttl: CacheTimeToLive.oneMinute }) // Prevent spam
  @Debounce({ wait: 2000, leading: true, trailing: false })
  @ValidateParams([z.string().email()], ['email'])
  async initiatePasswordReset(email: string): Promise<void> {
    await this.authService.sendPasswordResetEmail(email);
  }
}
```

### Error Handling Patterns

```typescript
@Injectable()
class RobustService {
  @Cache({ ttl: CacheTimeToLive.fiveMinutes })
  @InFlight()
  @ValidateParams([z.string().url()], ['endpoint'])
  async fetchWithRetry(endpoint: string): Promise<ApiResponse> {
    try {
      const response = await this.httpClient.get(endpoint);
      return response.data;
    } catch (error) {
      // Errors are not cached, in-flight tracking is cleaned up
      console.error('API call failed:', error);
      throw new Error(`Failed to fetch data from ${endpoint}: ${error.message}`);
    }
  }

  // Graceful error handling in validation
  async processUserInput(input: unknown): Promise<ProcessResult> {
    try {
      // Manual validation for dynamic scenarios
      const validatedInput = z.object({
        action: z.enum(['create', 'update', 'delete']),
        data: z.record(z.any())
      }).parse(input);

      return await this.processValidatedInput(validatedInput);
    } catch (error) {
      if (error instanceof ZodValidationError) {
        return {
          success: false,
          error: 'Invalid input format',
          details: error.zodError.issues
        };
      }
      throw error;
    }
  }

  @ValidateParams([
    z.object({
      action: z.enum(['create', 'update', 'delete']),
      data: z.record(z.any())
    })
  ])
  async processValidatedInput(input: ProcessInput): Promise<ProcessResult> {
    // Input is guaranteed to be valid
    return { success: true, result: await this.execute(input) };
  }
}
```

## Best Practices

### ✅ Validation Best Practices

- **Use descriptive parameter names** for better error messages
- **Define schemas separately** for complex validations and reuse them
- **Use transformations** to normalize input data (trim, lowercase, etc.)
- **Provide defaults** for optional parameters using Zod's `.default()`
- **Validate at service boundaries** (API endpoints, user input handlers)

```typescript
// ✅ Good: Reusable schema with descriptive names
const UserSchema = z.object({
  email: z.string().email().transform(e => e.toLowerCase()),
  name: z.string().min(1).transform(n => n.trim())
});

@ValidateParams([UserSchema], ['userData'])
createUser(userData: UserData): Promise<User> { }

// ❌ Avoid: Inline complex schemas without parameter names
@ValidateParams([z.object({ /* complex schema */ })])
createUser(userData: any): Promise<User> { }
```

### ✅ Caching Best Practices

- **Use appropriate TTL** based on data volatility
- **Custom key generators** for complex parameters
- **Cache expensive operations** only
- **Don't cache operations with side effects**

```typescript
// ✅ Good: Appropriate TTL and custom key generation
@Cache({
  ttl: CacheTimeToLive.fiveMinutes,
  keyGenerator: (filter: SearchFilter) =>
    `search-${filter.category}-${filter.sortBy}-${filter.page}`
})
searchProducts(filter: SearchFilter): Promise<Product[]> { }

// ❌ Avoid: Caching operations with side effects
@Cache()
async sendEmail(to: string): Promise<void> { } // Don't cache this!
```

### ✅ Debouncing Best Practices

- **Debounce user input handlers** (search, form validation)
- **Use leading edge** for immediate feedback actions
- **Set maxWait** for critical operations that must eventually execute
- **Consider user experience** - don't make interactions feel sluggish

```typescript
// ✅ Good: User input debouncing
@Debounce({ wait: 300 })
onSearchInputChange(query: string): void { }

// ✅ Good: Button click with immediate feedback
@Debounce({ wait: 1000, leading: true, trailing: false })
onSaveButtonClick(): void { }
```

### ✅ InFlight Best Practices

- **Use for expensive async operations** to prevent duplicates
- **Use InFlightWithKey** when parameters affect the result
- **Use InFlightWithCache** for data that doesn't change frequently
- **Don't use on methods with side effects** unless intentional

### ✅ Do

- Use `@InFlight()` for simple methods without parameters
- Use `@InFlightWithKey()` for methods with parameters
- Use `@InFlightWithCache()` for expensive operations with stable results
- Provide custom `keyGenerator` for complex parameter objects
- Set appropriate cache times based on data volatility

### ❌ Don't

- Use on methods that return different results for the same input
- Use excessively long cache times for dynamic data
- Use on methods that have side effects (POST, PUT, DELETE)
- Rely on argument order for default key generation

## Performance Considerations

### Memory Usage

- `InFlight`: Minimal memory overhead (one Promise per instance)
- `InFlightWithKey`: Memory usage scales with unique parameter combinations
- `InFlightWithCache`: Additional memory for cached results

### Cleanup

- In-flight requests are automatically cleaned up on completion
- Cache entries are cleaned up on expiry
- WeakMap ensures instances can be garbage collected

## Testing

The decorators are fully tested with comprehensive unit tests. Key test scenarios include:

- Multiple simultaneous calls deduplication
- Error handling and cleanup
- Cache expiration
- Instance isolation
- Key generation

Run tests with:
```bash
npx nx test common-decorators
```

## Migration Guide

### From Manual Implementation

```typescript
// Before: Manual in-flight tracking
class MyService {
  private inFlight: Promise<Data> | null = null;

  async fetchData(): Promise<Data> {
    if (this.inFlight) {
      return this.inFlight;
    }
    
    this.inFlight = this.doFetch();
    try {
      return await this.inFlight;
    } finally {
      this.inFlight = null;
    }
  }
}

// After: Using decorator
class MyService {
  @InFlight()
  async fetchData(): Promise<Data> {
    return await this.doFetch();
  }
}
```

### From RxJS shareReplay

```typescript
// Before: RxJS approach
class MyService {
  private data$ = this.http.get<Data>('/api/data').pipe(
    shareReplay({ bufferSize: 1, refCount: true })
  );

  getData(): Observable<Data> {
    return this.data$;
  }
}

// After: Promise-based with decorator
class MyService {
  @InFlightWithCache({ cacheTime: 5 * 60 * 1000 })
  async getData(): Promise<Data> {
    return await this.http.get<Data>('/api/data').toPromise();
  }
}
```

## Testing Decorators

When testing methods with decorators, consider the decorator behavior:

```typescript
import { ZodValidationError } from '@isa/common/decorators';

describe('UserService', () => {
  let service: UserService;

  beforeEach(() => {
    service = new UserService();
    jest.clearAllMocks();
  });

  describe('validation', () => {
    it('should validate email parameter', () => {
      // Test valid input
      expect(() => service.createUser('valid@email.com', userData))
        .not.toThrow();

      // Test invalid input
      expect(() => service.createUser('invalid-email', userData))
        .toThrow(ZodValidationError);
    });

    it('should provide detailed error information', () => {
      try {
        service.createUser('invalid-email', userData);
      } catch (error) {
        expect(error).toBeInstanceOf(ZodValidationError);
        expect(error.parameterName).toBe('email');
        expect(error.parameterIndex).toBe(0);
      }
    });
  });

  describe('caching', () => {
    it('should cache expensive operations', async () => {
      const expensiveOperationSpy = jest.spyOn(service, 'expensiveOperation');

      // First call
      await service.cachedMethod('param');
      expect(expensiveOperationSpy).toHaveBeenCalledTimes(1);

      // Second call with same parameters (should use cache)
      await service.cachedMethod('param');
      expect(expensiveOperationSpy).toHaveBeenCalledTimes(1);

      // Third call with different parameters (should not use cache)
      await service.cachedMethod('different');
      expect(expensiveOperationSpy).toHaveBeenCalledTimes(2);
    });
  });

  describe('debouncing', () => {
    it('should debounce method calls', (done) => {
      const debouncedSpy = jest.spyOn(service, 'actualMethod');

      // Call multiple times rapidly
      service.debouncedMethod('param1');
      service.debouncedMethod('param2');
      service.debouncedMethod('param3');

      // Should not have been called yet
      expect(debouncedSpy).not.toHaveBeenCalled();

      // Wait for debounce period
      setTimeout(() => {
        expect(debouncedSpy).toHaveBeenCalledTimes(1);
        expect(debouncedSpy).toHaveBeenCalledWith('param3');
        done();
      }, 350); // Assuming 300ms debounce
    });
  });

  describe('in-flight protection', () => {
    it('should prevent duplicate async calls', async () => {
      const apiCallSpy = jest.spyOn(service, 'apiCall');

      // Start multiple calls simultaneously
      const promises = [
        service.inFlightMethod(),
        service.inFlightMethod(),
        service.inFlightMethod()
      ];

      await Promise.all(promises);

      // Should only have made one actual API call
      expect(apiCallSpy).toHaveBeenCalledTimes(1);
    });
  });
});
```

### Testing with Vitest

For projects using Vitest (new standard):

```typescript
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { ZodValidationError } from '@isa/common/decorators';

describe('UserService', () => {
  let service: UserService;

  beforeEach(() => {
    service = new UserService();
    vi.clearAllMocks();
  });

  it('should validate parameters', () => {
    expect(() => service.validateMethod('valid-input')).not.toThrow();
    expect(() => service.validateMethod('')).toThrow(ZodValidationError);
  });
});
```

## TypeScript Configuration

Ensure your `tsconfig.json` has experimental decorators enabled:

```json
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "lib": ["ES2022", "DOM"]
  }
}
```

## Dependencies

This library depends on:

- **Zod** (v3.24.2) - Schema validation library for the validation decorators
- **Lodash** - Specifically the `debounce` function for the Debounce decorator
- **TypeScript** - Experimental decorators support required

All dependencies are already configured in the project's package.json.

## Performance Considerations

### Memory Usage
- **Validation**: Adds runtime overhead for parameter validation
- **Cache**: Memory usage scales with cached results - set appropriate TTLs
- **Debounce**: Minimal memory overhead, one timer per method instance
- **InFlight**: Memory usage scales with concurrent operations

### Execution Overhead
- **Validation**: Schema validation adds ~0.1-1ms per call depending on complexity
- **Cache**: Cache lookups are generally < 0.1ms
- **Debounce**: Timer management adds minimal overhead
- **InFlight**: Promise management adds ~0.1ms per call

### Optimization Tips
1. **Use specific Zod schemas** - avoid overly complex validations in hot paths
2. **Set reasonable cache TTLs** - balance memory usage vs. performance gains
3. **Profile critical paths** - measure the impact of decorators in performance-critical code
4. **Consider decorator order** - validation → debouncing → caching → in-flight typically works best

## Quick Reference

### Decorator Comparison Table

| Decorator | Purpose | Use Case | Key Features | Example |
|-----------|---------|----------|--------------|---------|
| `ValidateParams` | Parameter validation | API inputs, user data | Zod schemas, transformations, custom errors | `@ValidateParams([z.string().email()])` |
| `ValidateParam` | Single parameter validation | Simple validation | Index-based, custom names | `@ValidateParam(0, z.string())` |
| `Cache` | Result caching | Expensive operations | TTL, custom keys, per-instance | `@Cache({ ttl: CacheTimeToLive.fiveMinutes })` |
| `Debounce` | Rate limiting | User input, auto-save | Wait time, leading/trailing edge | `@Debounce({ wait: 300 })` |
| `InFlight` | Duplicate prevention | API calls | Promise sharing, per-instance | `@InFlight()` |
| `InFlightWithKey` | Keyed duplicate prevention | Parameterized API calls | Argument-based keys | `@InFlightWithKey()` |
| `InFlightWithCache` | Cache + duplicate prevention | Expensive + stable data | Combined caching and deduplication | `@InFlightWithCache({ cacheTime: 60000 })` |

### Common Patterns

```typescript
// Form validation with debouncing
@Debounce({ wait: 300 })
@ValidateParams([z.string().min(1)], ['query'])
onSearchInput(query: string) { }

// API call with caching and duplicate prevention
@Cache({ ttl: CacheTimeToLive.fiveMinutes })
@InFlight()
@ValidateParams([z.string().uuid()], ['id'])
async fetchUser(id: string): Promise<User> { }

// Complex validation with transformation
@ValidateParams([
  z.object({
    email: z.string().email().transform(e => e.toLowerCase()),
    data: z.record(z.any()).transform(d => sanitizeData(d))
  })
])
processUserData(input: UserInput) { }
```

## Migration Guide

### Migrating from Manual Validation

```typescript
// Before: Manual validation
class OldService {
  async createUser(email: string, userData: any): Promise<User> {
    if (!email || !isValidEmail(email)) {
      throw new Error('Invalid email');
    }
    if (!userData || !userData.name) {
      throw new Error('Name is required');
    }
    // ... rest of method
  }
}

// After: Decorator validation
class NewService {
  @ValidateParams([
    z.string().email(),
    z.object({ name: z.string().min(1) })
  ], ['email', 'userData'])
  async createUser(email: string, userData: UserData): Promise<User> {
    // Parameters are guaranteed to be valid
    // ... rest of method
  }
}
```

### Migrating from RxJS Patterns

```typescript
// Before: RxJS shareReplay pattern
class OldService {
  private userCache$ = new BehaviorSubject<User[]>([]);

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>('/users').pipe(
      tap(users => this.userCache$.next(users)),
      shareReplay(1)
    );
  }
}

// After: Decorator pattern
class NewService {
  @Cache({ ttl: CacheTimeToLive.fiveMinutes })
  @InFlight()
  async getUsers(): Promise<User[]> {
    const response = await this.http.get<User[]>('/users').toPromise();
    return response;
  }
}
```

## Troubleshooting

### Common Issues

**Decorators not working:**
- Ensure `experimentalDecorators: true` in tsconfig.json
- Check decorator import statements
- Verify TypeScript version supports decorators

**Validation errors:**
- Check Zod schema definitions
- Verify parameter names match expectations
- Use `ZodValidationError` for proper error handling

**Caching not working:**
- Verify TTL settings are appropriate
- Check if custom key generators are working correctly
- Ensure methods are not throwing errors (errors are not cached)

**Performance issues:**
- Profile decorator overhead in critical paths
- Consider simpler validation schemas for hot code
- Review cache TTL settings for memory usage

### Debug Mode

Enable debug logging for decorators:

```typescript
// In development environment
if (environment.development) {
  // Enable Zod error details
  z.setErrorMap((issue, ctx) => ({
    message: `${ctx.defaultError} (Path: ${issue.path.join('.')})`
  }));
}
```

## License

This library is part of the ISA Frontend project and follows the project's licensing terms.

---

## Contributing

When adding new decorators:

1. **Implementation**: Add in `src/lib/[decorator-name].decorator.ts`
2. **Tests**: Include comprehensive unit tests with edge cases
3. **Documentation**: Update this README with examples and usage
4. **Exports**: Export from `src/index.ts`
5. **Examples**: Add real-world usage examples
6. **Performance**: Consider memory and execution overhead

For more detailed examples and usage patterns, see [USAGE.md](./USAGE.md).

---

*This comprehensive decorator library enhances Angular applications with powerful cross-cutting concerns while maintaining clean, readable code. Each decorator is designed to be composable, performant, and easy to test.*