[
  {
    "path": ".gitignore",
    "content": "# Dependencies\nnode_modules/\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Build\ndist/\nbuild/\n*.tsbuildinfo\n\n# Environment\n.env\n.env.local\n.env.*.local\n\n# IDE\n.vscode/\n.idea/\n*.swp\n*.swo\n*~\n.DS_Store\n\n# Testing\ncoverage/\n.nyc_output/\n*.lcov\n\n# Logs\nlogs/\n*.log\n\n# Database\n*.sqlite\n*.db\n\n# OS\nThumbs.db\n"
  },
  {
    "path": ".npmrc",
    "content": "shamefully-hoist=true\nstrict-peer-dependencies=false\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2025 A3S Lab\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Nestify - Production-Ready NestJS Monorepo Template\n\nA production-ready NestJS monorepo template with pnpm workspace, implementing Domain-Driven Design (DDD), Clean Architecture, and comprehensive infrastructure for enterprise applications.\n\n## Features\n\n### Core Architecture\n- **Monorepo Architecture**: pnpm workspace for managing multiple packages and applications\n- **Clean Architecture**: Clear separation of concerns with Domain, Application, Infrastructure, and Presentation layers\n- **Domain-Driven Design**: Rich domain models with entities, value objects, aggregates, and domain events\n- **CQRS Pattern**: Separate command and query handlers using @nestjs/cqrs\n- **Event-Driven**: Domain events for decoupled communication\n\n### Infrastructure Packages\n- **Type-Safe SQL**: Kysely query builder with full TypeScript support\n- **Distributed Caching**: Redis with Redisson for locks, caching, and rate limiting\n- **Structured Logging**: Pino-based JSON logging with request tracing\n- **Message Queue**: BullMQ for distributed task processing\n- **Event Streaming**: NATS with JetStream support\n- **Object Storage**: S3-compatible RustFS storage\n- **Distributed Config**: etcd for configuration management with hot-reload\n\n### Application Features\n- **Authentication**: JWT with access/refresh tokens, RBAC permission system\n- **API Metrics**: Prometheus metrics with request tracking\n- **Circuit Breaker**: Fault tolerance with automatic failover\n- **Retry Logic**: Exponential backoff with jitter\n- **Rate Limiting**: Redis-based sliding window rate limiting\n- **Multi-tenancy**: Tenant isolation support\n- **Audit Logging**: Comprehensive audit trail\n- **Feature Flags**: Rollout management\n- **API Versioning**: Header-based API versioning\n- **File Upload**: Multipart file handling\n\n### Quality Assurance\n- **Type Safety**: Full TypeScript with strict mode\n- **API Documentation**: Swagger/OpenAPI integration\n- **Validation**: class-validator with custom decorators\n- **Testing**: Unit, integration, and E2E test setup\n- **Code Quality**: Biome linting and formatting\n\n## Architecture Overview\n\n```\n┌─────────────────────────────────────────────────────────────────────────────┐\n│                              API Application                                  │\n├─────────────────────────────────────────────────────────────────────────────┤\n│  Presentation    │  Application    │  Domain      │  Infrastructure         │\n│  - Controllers   │  - Commands     │  - Entities  │  - Kysely (PostgreSQL) │\n│  - DTOs          │  - Queries      │  - Value Obj │  - Redisson (Redis)    │\n│  - Guards        │  - Event Hand. │  - Aggreg.   │  - BullMQ (Tasks)       │\n│  - Interceptors  │  - DTOs        │  - Events    │  - NATS (Messaging)     │\n│                  │                 │  - Services   │  - RustFS (Storage)     │\n│                  │                 │              │  - etcd (Config)        │\n├─────────────────────────────────────────────────────────────────────────────┤\n│                           Shared Infrastructure                               │\n│  Auth │ Metrics │ Cache │ CircuitBreaker │ Retry │ RateLimit │ Health    │\n└─────────────────────────────────────────────────────────────────────────────┘\n```\n\n## Monorepo Structure\n\n```\nnestify/\n├── pnpm-workspace.yaml              # Workspace configuration\n├── package.json                     # Root package.json with workspace scripts\n├── tsconfig.json                   # Base TypeScript configuration\n├── biome.json                      # Biome linting/formatting config\n├── apps/\n│   └── api/                       # Main NestJS API application\n│       ├── src/\n│       │   ├── app.module.ts      # Root application module\n│       │   ├── main.ts            # Application entry point\n│       │   ├── modules/          # Business modules (DDD)\n│       │   └── shared/            # Shared infrastructure modules\n│       └── package.json\n└── packages/\n    ├── kysely/                    # @a3s-lab/kysely - Type-safe SQL\n    ├── redisson/                  # @a3s-lab/redisson - Redis client\n    ├── logger/                    # @a3s-lab/logger - Structured logging\n    ├── bullmq/                    # @a3s-lab/bullmq - Task queue\n    ├── nats/                      # @a3s-lab/nats - Message broker\n    ├── rustfs/                    # @a3s-lab/rustfs - S3 storage\n    └── etcd/                      # @a3s-lab/etcd - Config center\n```\n\n## Packages\n\n### @a3s-lab/kysely\n\nType-safe SQL query builder module for NestJS.\n\n```typescript\nKyselyModule.register({\n  config: {\n    dialect: new PostgresDialect({ pool: new Pool({ connectionString }) }),\n  },\n})\n```\n\n### @a3s-lab/redisson\n\nRedis distributed locks, caching, and rate limiting.\n\n```typescript\n// Distributed lock\nawait redisson.withLock('resource-key', async () => {\n  // Critical section\n});\n\n// Cache with TTL\nawait redisson.setJSON('cache-key', data, 3600);\n\n// Rate limiting\nconst limited = await rateLimiter.tryAcquire('endpoint-limit');\n```\n\n### @a3s-lab/logger\n\nStructured JSON logging with request tracing.\n\n```typescript\nLoggerModule.register({\n  level: 'info',\n  name: 'api',\n  json: true,  // JSON format for K8s\n});\n\n// In services\nlogger.logRequest({ method, url, statusCode, responseTime });\n```\n\n### @a3s-lab/bullmq\n\nDistributed task queue with retry and delayed jobs.\n\n```typescript\n// Add job\nawait bullmq.addJob('notifications', 'send-email', { to: 'user@example.com' });\n\n// Create worker\nbullmq.createWorker('notifications', async (job) => {\n  await sendEmail(job.data);\n  return { success: true };\n});\n```\n\n### @a3s-lab/nats\n\nHigh-performance message broker with JetStream.\n\n```typescript\n// Publish\nawait nats.publish({ subject: 'orders.created', data: orderEvent });\n\n// Subscribe\nawait nats.subscribe$('orders.created', async (data) => {\n  await handleOrderCreated(data);\n});\n\n// JetStream\nawait nats.jsPublish({ stream: 'ORDERS', subject: 'created', data });\n```\n\n### @a3s-lab/rustfs\n\nS3-compatible object storage.\n\n```typescript\n// Upload file\nconst result = await rustfs.putObject('bucket', {\n  key: 'uploads/file.pdf',\n  body: fileBuffer,\n  contentType: 'application/pdf',\n});\n\n// Get presigned URL\nconst url = await rustfs.getPresignedUrl('bucket', {\n  key: 'uploads/file.pdf',\n  expiresIn: 3600,\n});\n```\n\n### @a3s-lab/etcd\n\nDistributed configuration with hot-reload.\n\n```typescript\n// Get config\nconst value = await etcd.get('config/feature-flags');\n\n// Watch for changes\netcd.watch('config/feature-flags', (event) => {\n  if (event.value) reloadFeatures(event.value);\n});\n```\n\n## Shared Modules\n\n### Authentication (auth)\n\nJWT-based authentication with RBAC.\n\n```typescript\n// JWT Token Generation\nconst tokens = jwtService.generateTokenPair({ sub: userId, roles: ['admin'] });\n\n// Protect Routes\n@UseGuards(JwtAuthGuard)\n\n// Role-based Access\n@Roles('admin')\n@UseGuards(RolesGuard)\n\n// Permission Check\n@Permissions('users', 'create')\n@UseGuards(PermissionsGuard)\n```\n\n### Metrics (metrics)\n\nPrometheus metrics collection.\n\n```typescript\n// Automatic HTTP metrics\nGET /metrics  // Prometheus format\n\n// Custom metrics\nmetricsService.incGauge('active_users');\nmetricsService.observeHistogram('request_duration', duration);\n```\n\n### Circuit Breaker (circuit-breaker)\n\nFault tolerance pattern.\n\n```typescript\n@CircuitBreaker({ timeout: 5000, maxFailures: 5 })\nasync callExternalService() {\n  return await externalService.get();\n}\n```\n\n### Retry (retry)\n\nAutomatic retry with exponential backoff.\n\n```typescript\nconst result = await retryService.execute(fn, {\n  maxAttempts: 3,\n  initialDelay: 100,\n  backoffMultiplier: 2,\n  retryableErrors: [NetworkError, TimeoutError],\n});\n```\n\n### Rate Limiting (rate-limiting)\n\nRedis-based sliding window rate limiting.\n\n```typescript\n@RateLimit({ limit: 100, window: '1m' })\nasync endpoint() { }\n```\n\n### Health (health)\n\nHealth check endpoints.\n\n```typescript\nGET /health      // Full health check\nGET /health/live // Liveness probe\nGET /health/ready // Readiness probe\n```\n\n### Validation (validation)\n\nCustom validators beyond class-validator.\n\n```typescript\n@IsPassword()              // Strong password\n@IsStrongPassword()        // Very strong password\n@IsUsername()              // Alphanumeric with underscores\n@IsSlug()                  // URL-safe slug\n@IsFutureDate()           // Future date only\n@IsInRange(0, 100)        // Number in range\n```\n\n### Serialization (serialization)\n\nclass-transformer integration with groups.\n\n```typescript\nclass UserEntity { }\nclass UserDto { }\n\n@Serialize(UserDto, { groups: ['user:read'] })\ngetUser(): UserEntity { }\n```\n\n## Project Structure\n\n```\napps/api/src/\n├── app.module.ts                    # Root module\n├── main.ts                         # Bootstrap\n├── modules/                         # Business modules\n│   └── order/                      # Order bounded context\n│       ├── domain/\n│       │   ├── entities/          # Order, OrderItem\n│       │   ├── value-objects/    # Money, Quantity\n│       │   ├── events/           # OrderCreated, OrderConfirmed\n│       │   ├── repositories/     # IOrderRepository\n│       │   └── exceptions/      # Domain exceptions\n│       ├── application/\n│       │   ├── commands/        # CreateOrder, CancelOrder\n│       │   ├── queries/        # GetOrder, ListOrders\n│       │   └── event-handlers/ # HandleOrderCreated\n│       ├── infrastructure/\n│       │   └── persistence/     # KyselyOrderRepository\n│       └── presentation/\n│           └── order.controller.ts\n└── shared/                         # Shared kernel\n    ├── auth/                      # JWT, RBAC, Guards\n    ├── metrics/                   # Prometheus\n    ├── cache/                     # Caching\n    ├── circuit-breaker/           # Fault tolerance\n    ├── retry/                    # Retry logic\n    ├── rate-limiting/            # Rate limit\n    ├── health/                    # Health checks\n    ├── validation/                # Custom validators\n    ├── serialization/             # DTO transformation\n    ├── base/                      # Base service, entity\n    ├── domain/                    # Core DDD\n    ├── errors/                    # Error handling\n    ├── utils/                     # Utilities\n    └── ...\n```\n\n## Getting Started\n\n### Prerequisites\n\n- Node.js 20+\n- pnpm 8+\n- Docker and Docker Compose\n- PostgreSQL 15+\n- Redis 7+\n\n### Installation\n\n```bash\n# Clone and install\ngit clone https://github.com/A3S-Lab/nestify.git\ncd nestify\npnpm install\n\n# Start infrastructure\ncd docker && docker-compose up -d\n\n# Build\npnpm build\n\n# Run\npnpm start:dev\n```\n\nAccess:\n- API: http://localhost:3000\n- Swagger: http://localhost:3000/api/docs\n- Metrics: http://localhost:3000/metrics\n\n## Scripts\n\n```bash\npnpm install              # Install dependencies\npnpm build               # Build all\npnpm start:dev           # Development mode\npnpm test                # Run tests\npnpm lint                # Lint code\npnpm format             # Format code\n```\n\n## Key Design Patterns\n\n### Domain-Driven Design\n\n```\nDomain Layer (innermost, no dependencies)\n    │\n    ▼\nApplication Layer (depends on Domain)\n    │\n    ▼\nInfrastructure Layer (implements interfaces)\n    │\n    ▼\nPresentation Layer (depends on all)\n```\n\n### CQRS\n\n- **Commands**: `CreateOrder`, `ConfirmOrder` - Write operations\n- **Queries**: `GetOrder`, `ListOrders` - Read operations\n- **Events**: `OrderCreated`, `OrderConfirmed` - Decoupled communication\n\n### Fault Tolerance\n\n```\nCircuit Breaker States:\n┌─────────┐     5 failures      ┌──────┐     timeout     ┌───────────┐\n│ CLOSED │ ─────────────────▶  │ OPEN │ ──────────────▶ │ HALF_OPEN │\n└─────────┘                     └──────┘                 └───────────┘\n     ▲                                                        │\n     │            success                                     │\n     └────────────────────────────────────────────────────────┘\n```\n\n## Environment Variables\n\n```env\n# Database\nDATABASE_URL=postgresql://user:pass@localhost:5432/nestify\n\n# Redis\nREDIS_HOST=localhost\nREDIS_PORT=6379\n\n# JWT\nJWT_ACCESS_SECRET=your-access-secret\nJWT_REFRESH_SECRET=your-refresh-secret\nJWT_ACCESS_EXPIRY=15m\nJWT_REFRESH_EXPIRY=7d\n\n# NATS (optional)\nNATS_SERVERS=nats://localhost:4222\n\n# RustFS (optional)\nRUSTFS_ENDPOINT=http://localhost:9000\nRUSTFS_ACCESS_KEY=rustfsadmin\nRUSTFS_SECRET_KEY=rustfsadmin\nRUSTFS_BUCKET=nestify\n\n# etcd (optional)\nETCD_ENDPOINTS=http://localhost:2379\n```\n\n## License\n\nMIT\n"
  },
  {
    "path": "apps/api/DATABASE.md",
    "content": "# Database Setup\n\nThis project uses PostgreSQL with Kysely for type-safe SQL queries.\n\n## Prerequisites\n\n- PostgreSQL 14+\n- pnpm 8+\n\n## Setup\n\n### 1. Start PostgreSQL\n\nUsing Docker:\n```bash\ncd docker\ndocker-compose up -d postgres\n```\n\nOr use your local PostgreSQL installation.\n\n### 2. Create Database\n\n```bash\ncreatedb nestify\n```\n\n### 3. Run Migrations\n\n```bash\npsql -d nestify -f apps/api/migrations/001_create_orders_tables.sql\n```\n\n### 4. Configure Environment\n\nCreate `.env` file in the root:\n\n```env\n# Database\nDB_HOST=localhost\nDB_PORT=5432\nDB_USERNAME=postgres\nDB_PASSWORD=postgres\nDB_DATABASE=nestify\n\n# Application\nNODE_ENV=development\nPORT=3000\n```\n\n## Running the Application\n\n```bash\n# Install dependencies\npnpm install\n\n# Build packages\npnpm build:packages\n\n# Start development server\npnpm start:dev\n```\n\nThe API will be available at http://localhost:3000/api\n\n## API Endpoints\n\n### Create Order\n```bash\ncurl -X POST http://localhost:3000/api/orders \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"customerId\": \"customer-123\",\n    \"items\": [\n      {\n        \"productId\": \"product-456\",\n        \"quantity\": 2,\n        \"unitPrice\": 10.99\n      }\n    ]\n  }'\n```\n\n### Get Order\n```bash\ncurl http://localhost:3000/api/orders/{orderId}\n```\n\n### List Orders by Customer\n```bash\ncurl http://localhost:3000/api/orders?customerId=customer-123\n```\n\n### Confirm Order\n```bash\ncurl -X POST http://localhost:3000/api/orders/{orderId}/confirm\n```\n\n### Cancel Order\n```bash\ncurl -X POST http://localhost:3000/api/orders/{orderId}/cancel\n```\n\n## Database Schema\n\n### orders table\n- `id` (UUID, Primary Key)\n- `customer_id` (VARCHAR)\n- `status` (VARCHAR: 'pending', 'confirmed', 'cancelled')\n- `total_amount` (DECIMAL)\n- `created_at` (TIMESTAMP)\n- `updated_at` (TIMESTAMP)\n\n### order_items table\n- `id` (UUID, Primary Key)\n- `order_id` (UUID, Foreign Key)\n- `product_id` (VARCHAR)\n- `quantity` (INTEGER)\n- `unit_price` (DECIMAL)\n- `subtotal` (DECIMAL)\n- `created_at` (TIMESTAMP)\n\n## Using Kysely in Your Code\n\n```typescript\nimport { Injectable } from '@nestjs/common';\nimport { KyselyService } from '@a3s-lab/kysely';\nimport { Database } from '@/shared/database/database.types';\n\n@Injectable()\nexport class MyRepository {\n  constructor(private readonly db: KyselyService<Database>) {}\n\n  async findAll() {\n    return this.db\n      .selectFrom('orders')\n      .selectAll()\n      .execute();\n  }\n\n  async create(data: NewOrder) {\n    return this.db\n      .insertInto('orders')\n      .values(data)\n      .returningAll()\n      .executeTakeFirstOrThrow();\n  }\n\n  async update(id: string, data: OrderUpdate) {\n    return this.db\n      .updateTable('orders')\n      .set(data)\n      .where('id', '=', id)\n      .returningAll()\n      .executeTakeFirstOrThrow();\n  }\n\n  async delete(id: string) {\n    await this.db\n      .deleteFrom('orders')\n      .where('id', '=', id)\n      .execute();\n  }\n}\n```\n\n## Transactions\n\n```typescript\nasync saveWithTransaction(order: Order) {\n  return await this.db.transaction().execute(async (trx) => {\n    // Insert order\n    await trx\n      .insertInto('orders')\n      .values(orderData)\n      .execute();\n\n    // Insert order items\n    await trx\n      .insertInto('order_items')\n      .values(itemsData)\n      .execute();\n\n    return order;\n  });\n}\n```\n\n## Query Logging\n\nKysely logger is enabled in development mode and will show:\n- SQL queries with syntax highlighting\n- Query parameters\n- Execution time with color coding:\n  - Green: < 1ms (excellent)\n  - Yellow: 1-100ms (acceptable)\n  - Red: > 100ms (needs optimization)\n"
  },
  {
    "path": "apps/api/REDIS_USAGE.md",
    "content": "# Redis & Redisson Usage Guide\n\nThis guide demonstrates how to use the `@a3s-lab/redisson` package for caching, distributed locks, and other Redis operations in the NestJS application.\n\n## Installation\n\nThe package is already installed as a workspace dependency:\n\n```json\n{\n  \"dependencies\": {\n    \"@a3s-lab/redisson\": \"workspace:*\"\n  }\n}\n```\n\n## Configuration\n\n### Environment Variables\n\nAdd Redis configuration to your `.env` file:\n\n```env\n# Redis Configuration\nREDIS_HOST=localhost\nREDIS_PORT=6379\nREDIS_PASSWORD=\nREDIS_DB=0\n```\n\n### Module Setup\n\nThe `RedisModule` is configured globally in `src/shared/redis/redis.module.ts`:\n\n```typescript\nimport { Module, Global } from '@nestjs/common';\nimport { ConfigModule, ConfigService } from '@nestjs/config';\nimport { RedissonModule } from '@a3s-lab/redisson';\n\n@Global()\n@Module({\n    imports: [\n        RedissonModule.registerAsync({\n            imports: [ConfigModule],\n            useFactory: (configService: ConfigService) => ({\n                host: configService.get('REDIS_HOST', 'localhost'),\n                port: configService.get('REDIS_PORT', 6379),\n                password: configService.get('REDIS_PASSWORD'),\n                db: configService.get('REDIS_DB', 0),\n            }),\n            inject: [ConfigService],\n        }),\n    ],\n    exports: [RedissonModule],\n})\nexport class RedisModule {}\n```\n\n## Basic Usage\n\n### 1. Inject RedissonService\n\n```typescript\nimport { Injectable } from '@nestjs/common';\nimport { RedissonService } from '@a3s-lab/redisson';\n\n@Injectable()\nexport class MyService {\n    constructor(private readonly redisson: RedissonService) {}\n\n    async example() {\n        // Your Redis operations here\n    }\n}\n```\n\n### 2. Simple Key-Value Operations\n\n```typescript\n// Set a value\nawait this.redisson.set('key', 'value');\n\n// Set with TTL (time to live in seconds)\nawait this.redisson.set('key', 'value', 3600); // 1 hour\n\n// Get a value\nconst value = await this.redisson.get('key');\n\n// Delete a key\nawait this.redisson.delete('key');\n\n// Check if key exists\nconst exists = await this.redisson.exists('key');\n```\n\n### 3. JSON Operations\n\n```typescript\n// Store JSON data\nconst user = { id: '123', name: 'John', email: 'john@example.com' };\nawait this.redisson.setJSON('user:123', user, 3600);\n\n// Retrieve JSON data\nconst cachedUser = await this.redisson.getJSON<User>('user:123');\n\n// Update JSON data\nawait this.redisson.setJSON('user:123', { ...user, name: 'Jane' });\n```\n\n### 4. Cache with Get-or-Set Pattern\n\n```typescript\n// Get from cache or execute factory function\nconst order = await this.redisson.getOrSet(\n    'order:123',\n    async () => {\n        // This function only runs if cache miss\n        return await this.orderRepository.findById('123');\n    },\n    3600, // TTL in seconds\n);\n```\n\n### 5. Distributed Locks\n\nPrevent race conditions with distributed locks:\n\n```typescript\n// Execute operation with lock\nconst result = await this.redisson.withLock(\n    'lock:order:123',\n    async () => {\n        // Critical section - only one process can execute this at a time\n        const order = await this.orderRepository.findById('123');\n        order.confirm();\n        return await this.orderRepository.save(order);\n    },\n    5000,  // Wait time (ms) - how long to wait for lock\n    10000, // Lease time (ms) - how long to hold lock\n);\n```\n\n### 6. Counters\n\n```typescript\n// Increment counter\nconst views = await this.redisson.increment('page:views');\n\n// Increment by specific amount\nconst score = await this.redisson.increment('user:score', 10);\n\n// Decrement counter\nconst remaining = await this.redisson.decrement('stock:product:123');\n```\n\n### 7. Hash Operations\n\n```typescript\n// Set hash field\nawait this.redisson.hset('user:123', 'name', 'John');\nawait this.redisson.hset('user:123', 'email', 'john@example.com');\n\n// Get hash field\nconst name = await this.redisson.hget('user:123', 'name');\n\n// Get all hash fields\nconst user = await this.redisson.hgetall('user:123');\n// Returns: { name: 'John', email: 'john@example.com' }\n\n// Delete hash field\nawait this.redisson.hdel('user:123', 'email');\n```\n\n### 8. Pattern-Based Deletion\n\n```typescript\n// Delete all keys matching pattern\nconst deletedCount = await this.redisson.deleteByPattern('cache:order:*');\nconsole.log(`Deleted ${deletedCount} keys`);\n```\n\n### 9. Expiration\n\n```typescript\n// Set expiration on existing key\nawait this.redisson.expire('key', 3600); // 1 hour\n```\n\n## Real-World Example: Order Cache Service\n\nSee `src/modules/order/infrastructure/cache/order-cache.service.ts` for a complete implementation:\n\n```typescript\nimport { Injectable, Logger } from '@nestjs/common';\nimport { RedissonService } from '@a3s-lab/redisson';\n\n@Injectable()\nexport class OrderCacheService {\n    private readonly logger = new Logger(OrderCacheService.name);\n\n    constructor(private readonly redisson: RedissonService) {}\n\n    // Cache an order\n    async cacheOrder(order: Order, ttl: number = 3600): Promise<void> {\n        const key = `order:${order.id}`;\n        await this.redisson.setJSON(key, this.serializeOrder(order), ttl);\n    }\n\n    // Get cached order\n    async getCachedOrder(orderId: string): Promise<SerializedOrder | null> {\n        const key = `order:${orderId}`;\n        return this.redisson.getJSON<SerializedOrder>(key);\n    }\n\n    // Get or fetch order with cache\n    async getOrSetOrder(\n        orderId: string,\n        factory: () => Promise<Order | null>,\n        ttl: number = 3600,\n    ): Promise<SerializedOrder | null> {\n        const key = `order:${orderId}`;\n        return this.redisson.getOrSet<SerializedOrder | null>(\n            key,\n            async () => {\n                const order = await factory();\n                return order ? this.serializeOrder(order) : null;\n            },\n            ttl,\n        );\n    }\n\n    // Execute with distributed lock\n    async withOrderLock<T>(\n        orderId: string,\n        operation: () => Promise<T>,\n    ): Promise<T> {\n        const lockKey = `lock:order:${orderId}`;\n        return this.redisson.withLock(lockKey, operation, 5000, 10000);\n    }\n\n    // Increment view count\n    async incrementOrderViewCount(orderId: string): Promise<number> {\n        const key = `order:views:${orderId}`;\n        return this.redisson.increment(key);\n    }\n}\n```\n\n## Usage in Query Handlers\n\n### Example: Get Order with Cache\n\n```typescript\nimport { Injectable } from '@nestjs/common';\nimport { IQueryHandler, QueryHandler } from '@nestjs/cqrs';\nimport { OrderCacheService } from '../../infrastructure/cache/order-cache.service';\nimport { OrderRepository } from '../../infrastructure/persistence/kysely-order.repository';\n\n@QueryHandler(GetOrderQuery)\nexport class GetOrderHandler implements IQueryHandler<GetOrderQuery> {\n    constructor(\n        private readonly orderRepository: OrderRepository,\n        private readonly cacheService: OrderCacheService,\n    ) {}\n\n    async execute(query: GetOrderQuery): Promise<OrderResponseDto> {\n        // Try cache first\n        const cached = await this.cacheService.getCachedOrder(query.orderId);\n        if (cached) {\n            return this.toDto(cached);\n        }\n\n        // Cache miss - fetch from database\n        const order = await this.orderRepository.findById(query.orderId);\n        if (!order) {\n            throw new NotFoundException('Order not found');\n        }\n\n        // Cache for future requests\n        await this.cacheService.cacheOrder(order);\n\n        // Increment view count\n        await this.cacheService.incrementOrderViewCount(query.orderId);\n\n        return this.toDto(order);\n    }\n}\n```\n\n### Example: Update Order with Lock\n\n```typescript\n@CommandHandler(ConfirmOrderCommand)\nexport class ConfirmOrderHandler implements ICommandHandler<ConfirmOrderCommand> {\n    constructor(\n        private readonly orderRepository: OrderRepository,\n        private readonly cacheService: OrderCacheService,\n    ) {}\n\n    async execute(command: ConfirmOrderCommand): Promise<void> {\n        // Use distributed lock to prevent race conditions\n        await this.cacheService.withOrderLock(command.orderId, async () => {\n            const order = await this.orderRepository.findById(command.orderId);\n            if (!order) {\n                throw new NotFoundException('Order not found');\n            }\n\n            order.confirm();\n            await this.orderRepository.save(order);\n\n            // Invalidate cache after update\n            await this.cacheService.invalidateOrder(command.orderId);\n            await this.cacheService.invalidateCustomerOrders(order.customerId);\n        });\n    }\n}\n```\n\n## Advanced Features\n\n### Lua Scripts\n\nExecute Lua scripts for atomic operations:\n\n```typescript\nconst script = `\n    local current = redis.call('GET', KEYS[1])\n    if current and tonumber(current) > tonumber(ARGV[1]) then\n        return redis.call('DECRBY', KEYS[1], ARGV[1])\n    else\n        return -1\n    end\n`;\n\nconst result = await this.redisson.eval(script, ['stock:product:123'], ['5']);\n```\n\n### Script Caching\n\nLoad and cache scripts for better performance:\n\n```typescript\n// Load script once\nconst sha1 = await this.redisson.scriptLoad(script);\n\n// Execute cached script multiple times\nconst result = await this.redisson.evalsha(sha1, ['key'], ['arg']);\n```\n\n## Best Practices\n\n1. **Use Appropriate TTL**: Set reasonable expiration times to prevent stale data\n2. **Cache Invalidation**: Always invalidate cache after updates\n3. **Distributed Locks**: Use locks for critical sections to prevent race conditions\n4. **Key Naming**: Use consistent, hierarchical key naming (e.g., `entity:id:field`)\n5. **Error Handling**: Always handle Redis errors gracefully with fallbacks\n6. **Monitoring**: Log cache hits/misses for performance monitoring\n\n## Testing\n\n### Start Redis with Docker\n\n```bash\ndocker run -d -p 6379:6379 --name redis redis:7-alpine\n```\n\n### Test Connection\n\n```bash\nredis-cli ping\n# Should return: PONG\n```\n\n## Troubleshooting\n\n### Connection Issues\n\n```typescript\n// Check if Redis is connected\ntry {\n    await this.redisson.redis.ping();\n    console.log('Redis connected');\n} catch (error) {\n    console.error('Redis connection failed:', error);\n}\n```\n\n### Memory Issues\n\n```bash\n# Check Redis memory usage\nredis-cli INFO memory\n\n# Clear all keys (development only!)\nredis-cli FLUSHDB\n```\n\n## Performance Tips\n\n1. **Batch Operations**: Use pipelines for multiple operations\n2. **Compression**: Compress large JSON objects before caching\n3. **Lazy Loading**: Only cache frequently accessed data\n4. **TTL Strategy**: Use shorter TTL for frequently changing data\n5. **Connection Pooling**: Configure appropriate pool size in production\n\n## References\n\n- [Redisson Documentation](https://github.com/redisson/redisson)\n- [ioredis Documentation](https://github.com/redis/ioredis)\n- [Redis Commands](https://redis.io/commands)\n"
  },
  {
    "path": "apps/api/migrations/001_create_orders_tables.sql",
    "content": "-- Create orders table\nCREATE TABLE IF NOT EXISTS orders (\n    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n    customer_id VARCHAR(255) NOT NULL,\n    status VARCHAR(50) NOT NULL DEFAULT 'pending',\n    total_amount DECIMAL(10, 2) NOT NULL DEFAULT 0,\n    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP\n);\n\n-- Create order_items table\nCREATE TABLE IF NOT EXISTS order_items (\n    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n    order_id UUID NOT NULL REFERENCES orders(id) ON DELETE CASCADE,\n    product_id VARCHAR(255) NOT NULL,\n    quantity INTEGER NOT NULL,\n    unit_price DECIMAL(10, 2) NOT NULL,\n    subtotal DECIMAL(10, 2) NOT NULL,\n    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP\n);\n\n-- Create indexes\nCREATE INDEX IF NOT EXISTS idx_orders_customer_id ON orders(customer_id);\nCREATE INDEX IF NOT EXISTS idx_orders_status ON orders(status);\nCREATE INDEX IF NOT EXISTS idx_order_items_order_id ON order_items(order_id);\n"
  },
  {
    "path": "apps/api/nest-cli.json",
    "content": "{\n    \"$schema\": \"https://json.schemastore.org/nest-cli\",\n    \"collection\": \"@nestjs/schematics\",\n    \"sourceRoot\": \"src\",\n    \"compilerOptions\": {\n        \"builder\": \"swc\",\n        \"typeCheck\": true,\n        \"deleteOutDir\": true,\n        \"tsConfigPath\": \"tsconfig.build.json\"\n    }\n}\n"
  },
  {
    "path": "apps/api/package.json",
    "content": "{\n    \"name\": \"@a3s-lab/api\",\n    \"version\": \"1.0.0\",\n    \"description\": \"Production-ready NestJS API with Domain-Driven Design and Clean Architecture\",\n    \"author\": \"\",\n    \"private\": true,\n    \"license\": \"MIT\",\n    \"scripts\": {\n        \"build\": \"nest build\",\n        \"format\": \"biome format --write .\",\n        \"format:check\": \"biome format .\",\n        \"lint\": \"biome lint --write .\",\n        \"lint:check\": \"biome lint .\",\n        \"start\": \"nest start\",\n        \"start:dev\": \"nest start --watch\",\n        \"start:debug\": \"nest start --debug --watch\",\n        \"start:prod\": \"node dist/main\",\n        \"test\": \"jest\",\n        \"test:watch\": \"jest --watch\",\n        \"test:cov\": \"jest --coverage\",\n        \"test:debug\": \"node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand\",\n        \"test:e2e\": \"jest --config ./test/jest-e2e.json\"\n    },\n    \"dependencies\": {\n        \"@a3s-lab/kysely\": \"workspace:*\",\n        \"@a3s-lab/redisson\": \"workspace:*\",\n        \"@a3s-lab/logger\": \"workspace:*\",\n        \"@a3s-lab/bullmq\": \"workspace:*\",\n        \"@a3s-lab/nats\": \"workspace:*\",\n        \"@a3s-lab/rustfs\": \"workspace:*\",\n        \"@a3s-lab/etcd\": \"workspace:*\",\n        \"@nestjs/common\": \"^10.0.0\",\n        \"@nestjs/config\": \"^3.0.0\",\n        \"@nestjs/core\": \"^10.0.0\",\n        \"@nestjs/cqrs\": \"^10.0.0\",\n        \"@nestjs/platform-express\": \"^10.0.0\",\n        \"@nestjs/swagger\": \"^7.0.0\",\n        \"@nestjs/terminus\": \"^10.0.0\",\n        \"class-transformer\": \"^0.5.1\",\n        \"class-validator\": \"^0.14.0\",\n        \"kysely\": \"^0.28.11\",\n        \"pg\": \"^8.18.0\",\n        \"pino\": \"^9.0.0\",\n        \"pino-http\": \"^10.0.0\",\n        \"reflect-metadata\": \"^0.1.13\",\n        \"rxjs\": \"^7.8.1\",\n        \"uuid\": \"^9.0.0\"\n    },\n    \"devDependencies\": {\n        \"@nestjs/cli\": \"^10.0.0\",\n        \"@nestjs/schematics\": \"^10.0.0\",\n        \"@nestjs/testing\": \"^10.0.0\",\n        \"@swc/cli\": \"^0.7.10\",\n        \"@swc/core\": \"^1.15.11\",\n        \"@types/express\": \"^4.17.17\",\n        \"@types/jest\": \"^29.5.0\",\n        \"@types/node\": \"^20.0.0\",\n        \"@types/pg\": \"^8.16.0\",\n        \"@types/uuid\": \"^9.0.0\",\n        \"jest\": \"^29.5.0\",\n        \"source-map-support\": \"^0.5.21\",\n        \"supertest\": \"^6.3.3\",\n        \"ts-jest\": \"^29.1.0\",\n        \"ts-loader\": \"^9.4.3\",\n        \"ts-node\": \"^10.9.1\",\n        \"tsconfig-paths\": \"^4.2.0\",\n        \"typescript\": \"^5.1.3\"\n    },\n    \"jest\": {\n        \"moduleFileExtensions\": [\n            \"js\",\n            \"json\",\n            \"ts\"\n        ],\n        \"rootDir\": \"src\",\n        \"testRegex\": \".*\\\\.spec\\\\.ts$\",\n        \"transform\": {\n            \"^.+\\\\.(t|j)s$\": \"ts-jest\"\n        },\n        \"collectCoverageFrom\": [\n            \"**/*.(t|j)s\"\n        ],\n        \"coverageDirectory\": \"../coverage\",\n        \"testEnvironment\": \"node\",\n        \"moduleNameMapper\": {\n            \"^@/(.*)$\": \"<rootDir>/$1\"\n        }\n    }\n}\n"
  },
  {
    "path": "apps/api/src/app.module.ts",
    "content": "import { Module } from '@nestjs/common';\nimport { ConfigModule } from '@nestjs/config';\nimport { OrderModule } from './modules/order/order.module';\nimport { DatabaseModule } from './shared/database';\nimport { RedisModule } from './shared/redis';\n\n// Shared infrastructure modules\nimport { LoggerModule } from '@a3s-lab/logger';\nimport { AuthModule } from './shared/auth';\nimport { MetricsModule } from './shared/metrics';\nimport { CacheModule } from './shared/cache';\nimport { CircuitBreakerModule } from './shared/circuit-breaker';\nimport { RetryModule } from './shared/retry';\nimport { HealthModule } from './shared/health';\nimport { ValidationModule } from './shared/validation';\nimport { SerializationModule } from './shared/serialization';\nimport { RateLimitingModule } from './shared/rate-limiting';\nimport { TenantModule } from './shared/tenant';\nimport { AuditModule } from './shared/audit';\nimport { ApiResponseModule } from './shared/api-response';\nimport { ApiVersioningModule } from './shared/api-versioning';\nimport { FeatureFlagsModule } from './shared/feature-flags';\nimport { FileUploadModule } from './shared/file-upload';\nimport { TransformModule } from './shared/transform';\nimport { ErrorsModule } from './shared/errors';\nimport { OpenAPIModule } from './shared/openapi';\nimport { TrackingModule } from './shared/tracking';\n\n@Module({\n    imports: [\n        // NestJS Config\n        ConfigModule.forRoot({\n            isGlobal: true,\n            envFilePath: '.env',\n        }),\n\n        // Logger (global JSON logging with request tracing)\n        LoggerModule.register({\n            level: process.env.LOG_LEVEL as any || 'info',\n            name: 'nestify-api',\n            json: true,\n        }),\n\n        // Database (Kysely + PostgreSQL)\n        DatabaseModule,\n\n        // Redis (Redisson)\n        RedisModule,\n\n        // Auth (JWT + RBAC)\n        AuthModule,\n\n        // Metrics (Prometheus)\n        MetricsModule,\n\n        // Cache\n        CacheModule,\n\n        // Circuit Breaker\n        CircuitBreakerModule,\n\n        // Retry with exponential backoff\n        RetryModule,\n\n        // Health checks\n        HealthModule,\n\n        // Validation\n        ValidationModule,\n\n        // Serialization (class-transformer)\n        SerializationModule,\n\n        // Rate limiting\n        RateLimitingModule,\n\n        // Tenant isolation\n        TenantModule,\n\n        // Audit logging\n        AuditModule,\n\n        // API response wrapper\n        ApiResponseModule,\n\n        // API versioning\n        ApiVersioningModule,\n\n        // Feature flags\n        FeatureFlagsModule,\n\n        // File upload\n        FileUploadModule,\n\n        // Transform interceptor\n        TransformModule,\n\n        // Error handling\n        ErrorsModule,\n\n        // OpenAPI decorators\n        OpenAPIModule,\n\n        // Request tracking\n        TrackingModule,\n\n        // Business modules\n        OrderModule,\n    ],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "apps/api/src/main.ts",
    "content": "import { NestFactory } from '@nestjs/core';\nimport { ValidationPipe } from '@nestjs/common';\nimport { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';\nimport { AppModule } from './app.module';\nimport { HttpExceptionFilter } from './shared/presentation/filters/http-exception.filter';\nimport { DomainExceptionFilter } from './shared/presentation/filters/domain-exception.filter';\nimport { LoggingInterceptor } from './shared/presentation/interceptors/logging.interceptor';\n\nasync function bootstrap() {\n    const app = await NestFactory.create(AppModule);\n\n    app.setGlobalPrefix('api');\n\n    app.useGlobalPipes(\n        new ValidationPipe({\n            whitelist: true,\n            forbidNonWhitelisted: true,\n            transform: true,\n        }),\n    );\n\n    app.useGlobalFilters(new HttpExceptionFilter(), new DomainExceptionFilter());\n\n    app.useGlobalInterceptors(new LoggingInterceptor());\n\n    const config = new DocumentBuilder()\n        .setTitle('NestJS DDD Template')\n        .setDescription('Production-ready NestJS template with Domain-Driven Design')\n        .setVersion('1.0')\n        .addTag('orders')\n        .build();\n\n    const document = SwaggerModule.createDocument(app, config);\n    SwaggerModule.setup('api/docs', app, document);\n\n    const port = process.env.APP_PORT || 3000;\n    await app.listen(port);\n\n    console.log(`Application is running on: http://localhost:${port}`);\n    console.log(`Swagger documentation: http://localhost:${port}/api/docs`);\n}\n\nbootstrap();\n"
  },
  {
    "path": "apps/api/src/modules/order/application/commands/cancel-order/cancel-order.command.ts",
    "content": "export class CancelOrderCommand {\n    constructor(public readonly orderId: string) {}\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/application/commands/cancel-order/cancel-order.dto.ts",
    "content": "import { IsString } from 'class-validator';\nimport { ApiProperty } from '@nestjs/swagger';\n\nexport class CancelOrderDto {\n    @ApiProperty({ example: 'order-id-123' })\n    @IsString()\n    orderId: string;\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/application/commands/cancel-order/cancel-order.handler.spec.ts",
    "content": "import { Test, TestingModule } from '@nestjs/testing';\nimport { CancelOrderHandler } from './cancel-order.handler';\nimport { CancelOrderCommand } from './cancel-order.command';\nimport { IOrderRepository, ORDER_REPOSITORY } from '../../../domain/repositories/order.repository.interface';\nimport { IEventBus, EVENT_BUS } from '@/shared/infrastructure/messaging/event-bus.interface';\nimport { Order } from '../../../domain/entities/order.entity';\nimport { OrderItem } from '../../../domain/entities/order-item.entity';\nimport { Money } from '../../../domain/value-objects/money.vo';\nimport { Quantity } from '../../../domain/value-objects/quantity.vo';\nimport { OrderNotFoundException } from '../../../domain/exceptions/order-not-found.exception';\nimport { InvalidOrderStateException } from '../../../domain/exceptions/invalid-order-state.exception';\n\ndescribe('CancelOrderHandler', () => {\n    let handler: CancelOrderHandler;\n    let orderRepository: jest.Mocked<IOrderRepository>;\n    let eventBus: jest.Mocked<IEventBus>;\n\n    beforeEach(async () => {\n        const mockOrderRepository: Partial<IOrderRepository> = {\n            save: jest.fn(),\n            findById: jest.fn(),\n            findByCustomerId: jest.fn(),\n            delete: jest.fn(),\n        };\n\n        const mockEventBus: Partial<IEventBus> = {\n            publish: jest.fn(),\n            publishAll: jest.fn(),\n        };\n\n        const module: TestingModule = await Test.createTestingModule({\n            providers: [\n                CancelOrderHandler,\n                {\n                    provide: ORDER_REPOSITORY,\n                    useValue: mockOrderRepository,\n                },\n                {\n                    provide: EVENT_BUS,\n                    useValue: mockEventBus,\n                },\n            ],\n        }).compile();\n\n        handler = module.get<CancelOrderHandler>(CancelOrderHandler);\n        orderRepository = module.get(ORDER_REPOSITORY);\n        eventBus = module.get(EVENT_BUS);\n    });\n\n    it('should be defined', () => {\n        expect(handler).toBeDefined();\n    });\n\n    describe('execute', () => {\n        it('should cancel existing pending order', async () => {\n            const items = [\n                OrderItem.create({\n                    id: 'item-1',\n                    productId: 'product-1',\n                    quantity: Quantity.create(1),\n                    unitPrice: Money.create(10),\n                }),\n            ];\n            const order = Order.create('customer-1', items);\n            const command = new CancelOrderCommand(order.id);\n\n            orderRepository.findById.mockResolvedValue(order);\n            orderRepository.save.mockImplementation(async (o: Order) => o);\n\n            await handler.execute(command);\n\n            expect(orderRepository.findById).toHaveBeenCalledWith(order.id);\n            expect(order.status.isCancelled()).toBe(true);\n            expect(orderRepository.save).toHaveBeenCalledWith(order);\n        });\n\n        it('should cancel confirmed order', async () => {\n            const items = [\n                OrderItem.create({\n                    id: 'item-1',\n                    productId: 'product-1',\n                    quantity: Quantity.create(1),\n                    unitPrice: Money.create(10),\n                }),\n            ];\n            const order = Order.create('customer-1', items);\n            order.confirm();\n            order.clearEvents();\n            const command = new CancelOrderCommand(order.id);\n\n            orderRepository.findById.mockResolvedValue(order);\n            orderRepository.save.mockImplementation(async (o: Order) => o);\n\n            await handler.execute(command);\n\n            expect(order.status.isCancelled()).toBe(true);\n        });\n\n        it('should publish OrderCancelledEvent', async () => {\n            const items = [\n                OrderItem.create({\n                    id: 'item-1',\n                    productId: 'product-1',\n                    quantity: Quantity.create(1),\n                    unitPrice: Money.create(10),\n                }),\n            ];\n            const order = Order.create('customer-1', items);\n            const command = new CancelOrderCommand(order.id);\n\n            orderRepository.findById.mockResolvedValue(order);\n            orderRepository.save.mockImplementation(async (o: Order) => o);\n\n            await handler.execute(command);\n\n            expect(eventBus.publishAll).toHaveBeenCalledTimes(1);\n        });\n\n        it('should throw OrderNotFoundException when order not found', async () => {\n            const command = new CancelOrderCommand('non-existent-id');\n\n            orderRepository.findById.mockResolvedValue(null);\n\n            await expect(handler.execute(command)).rejects.toThrow(OrderNotFoundException);\n        });\n\n        it('should throw InvalidOrderStateException when order is already cancelled', async () => {\n            const items = [\n                OrderItem.create({\n                    id: 'item-1',\n                    productId: 'product-1',\n                    quantity: Quantity.create(1),\n                    unitPrice: Money.create(10),\n                }),\n            ];\n            const order = Order.create('customer-1', items);\n            order.cancel(); // Already cancelled\n            const command = new CancelOrderCommand(order.id);\n\n            orderRepository.findById.mockResolvedValue(order);\n\n            await expect(handler.execute(command)).rejects.toThrow(InvalidOrderStateException);\n        });\n    });\n});\n"
  },
  {
    "path": "apps/api/src/modules/order/application/commands/cancel-order/cancel-order.handler.ts",
    "content": "import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';\nimport { Inject } from '@nestjs/common';\nimport { CancelOrderCommand } from './cancel-order.command';\nimport { IOrderRepository, ORDER_REPOSITORY } from '../../../domain/repositories/order.repository.interface';\nimport { OrderNotFoundException } from '../../../domain/exceptions/order-not-found.exception';\nimport { EVENT_BUS, IEventBus } from '@/shared/infrastructure/messaging/event-bus.interface';\n\n@CommandHandler(CancelOrderCommand)\nexport class CancelOrderHandler implements ICommandHandler<CancelOrderCommand> {\n    constructor(\n        @Inject(ORDER_REPOSITORY)\n        private readonly orderRepository: IOrderRepository,\n        @Inject(EVENT_BUS)\n        private readonly eventBus: IEventBus,\n    ) {}\n\n    async execute(command: CancelOrderCommand): Promise<void> {\n        const order = await this.orderRepository.findById(command.orderId);\n\n        if (!order) {\n            throw new OrderNotFoundException(command.orderId);\n        }\n\n        order.cancel();\n\n        await this.orderRepository.save(order);\n\n        await this.eventBus.publishAll(order.domainEvents);\n        order.clearEvents();\n    }\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/application/commands/confirm-order/confirm-order.command.ts",
    "content": "export class ConfirmOrderCommand {\n    constructor(public readonly orderId: string) {}\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/application/commands/confirm-order/confirm-order.dto.ts",
    "content": "import { IsString } from 'class-validator';\nimport { ApiProperty } from '@nestjs/swagger';\n\nexport class ConfirmOrderDto {\n    @ApiProperty({ example: 'order-id-123' })\n    @IsString()\n    orderId: string;\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/application/commands/confirm-order/confirm-order.handler.spec.ts",
    "content": "import { Test, TestingModule } from '@nestjs/testing';\nimport { ConfirmOrderHandler } from './confirm-order.handler';\nimport { ConfirmOrderCommand } from './confirm-order.command';\nimport { IOrderRepository, ORDER_REPOSITORY } from '../../../domain/repositories/order.repository.interface';\nimport { IEventBus, EVENT_BUS } from '@/shared/infrastructure/messaging/event-bus.interface';\nimport { Order } from '../../../domain/entities/order.entity';\nimport { OrderItem } from '../../../domain/entities/order-item.entity';\nimport { Money } from '../../../domain/value-objects/money.vo';\nimport { Quantity } from '../../../domain/value-objects/quantity.vo';\nimport { OrderNotFoundException } from '../../../domain/exceptions/order-not-found.exception';\nimport { InvalidOrderStateException } from '../../../domain/exceptions/invalid-order-state.exception';\n\ndescribe('ConfirmOrderHandler', () => {\n    let handler: ConfirmOrderHandler;\n    let orderRepository: jest.Mocked<IOrderRepository>;\n    let eventBus: jest.Mocked<IEventBus>;\n\n    beforeEach(async () => {\n        const mockOrderRepository: Partial<IOrderRepository> = {\n            save: jest.fn(),\n            findById: jest.fn(),\n            findByCustomerId: jest.fn(),\n            delete: jest.fn(),\n        };\n\n        const mockEventBus: Partial<IEventBus> = {\n            publish: jest.fn(),\n            publishAll: jest.fn(),\n        };\n\n        const module: TestingModule = await Test.createTestingModule({\n            providers: [\n                ConfirmOrderHandler,\n                {\n                    provide: ORDER_REPOSITORY,\n                    useValue: mockOrderRepository,\n                },\n                {\n                    provide: EVENT_BUS,\n                    useValue: mockEventBus,\n                },\n            ],\n        }).compile();\n\n        handler = module.get<ConfirmOrderHandler>(ConfirmOrderHandler);\n        orderRepository = module.get(ORDER_REPOSITORY);\n        eventBus = module.get(EVENT_BUS);\n    });\n\n    it('should be defined', () => {\n        expect(handler).toBeDefined();\n    });\n\n    describe('execute', () => {\n        it('should confirm existing pending order', async () => {\n            const items = [\n                OrderItem.create({\n                    id: 'item-1',\n                    productId: 'product-1',\n                    quantity: Quantity.create(1),\n                    unitPrice: Money.create(10),\n                }),\n            ];\n            const order = Order.create('customer-1', items);\n            const command = new ConfirmOrderCommand(order.id);\n\n            orderRepository.findById.mockResolvedValue(order);\n            orderRepository.save.mockImplementation(async (o: Order) => o);\n\n            await handler.execute(command);\n\n            expect(orderRepository.findById).toHaveBeenCalledWith(order.id);\n            expect(order.status.isConfirmed()).toBe(true);\n            expect(orderRepository.save).toHaveBeenCalledWith(order);\n        });\n\n        it('should publish OrderConfirmedEvent', async () => {\n            const items = [\n                OrderItem.create({\n                    id: 'item-1',\n                    productId: 'product-1',\n                    quantity: Quantity.create(1),\n                    unitPrice: Money.create(10),\n                }),\n            ];\n            const order = Order.create('customer-1', items);\n            const command = new ConfirmOrderCommand(order.id);\n\n            orderRepository.findById.mockResolvedValue(order);\n            orderRepository.save.mockImplementation(async (o: Order) => o);\n\n            await handler.execute(command);\n\n            expect(eventBus.publishAll).toHaveBeenCalledTimes(1);\n            const publishedEvents = eventBus.publishAll.mock.calls[0][0];\n            expect(publishedEvents.length).toBeGreaterThan(0);\n        });\n\n        it('should throw OrderNotFoundException when order not found', async () => {\n            const command = new ConfirmOrderCommand('non-existent-id');\n\n            orderRepository.findById.mockResolvedValue(null);\n\n            await expect(handler.execute(command)).rejects.toThrow(OrderNotFoundException);\n            await expect(handler.execute(command)).rejects.toThrow('non-existent-id');\n        });\n\n        it('should throw InvalidOrderStateException when order is not pending', async () => {\n            const items = [\n                OrderItem.create({\n                    id: 'item-1',\n                    productId: 'product-1',\n                    quantity: Quantity.create(1),\n                    unitPrice: Money.create(10),\n                }),\n            ];\n            const order = Order.create('customer-1', items);\n            order.confirm(); // Already confirmed\n            const command = new ConfirmOrderCommand(order.id);\n\n            orderRepository.findById.mockResolvedValue(order);\n\n            await expect(handler.execute(command)).rejects.toThrow(InvalidOrderStateException);\n        });\n\n        it('should not save order if confirmation fails', async () => {\n            const items = [\n                OrderItem.create({\n                    id: 'item-1',\n                    productId: 'product-1',\n                    quantity: Quantity.create(1),\n                    unitPrice: Money.create(10),\n                }),\n            ];\n            const order = Order.create('customer-1', items);\n            order.confirm(); // Already confirmed\n            const command = new ConfirmOrderCommand(order.id);\n\n            orderRepository.findById.mockResolvedValue(order);\n\n            await expect(handler.execute(command)).rejects.toThrow();\n            expect(orderRepository.save).not.toHaveBeenCalled();\n        });\n    });\n});\n"
  },
  {
    "path": "apps/api/src/modules/order/application/commands/confirm-order/confirm-order.handler.ts",
    "content": "import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';\nimport { Inject } from '@nestjs/common';\nimport { ConfirmOrderCommand } from './confirm-order.command';\nimport { IOrderRepository, ORDER_REPOSITORY } from '../../../domain/repositories/order.repository.interface';\nimport { OrderNotFoundException } from '../../../domain/exceptions/order-not-found.exception';\nimport { EVENT_BUS, IEventBus } from '@/shared/infrastructure/messaging/event-bus.interface';\n\n@CommandHandler(ConfirmOrderCommand)\nexport class ConfirmOrderHandler implements ICommandHandler<ConfirmOrderCommand> {\n    constructor(\n        @Inject(ORDER_REPOSITORY)\n        private readonly orderRepository: IOrderRepository,\n        @Inject(EVENT_BUS)\n        private readonly eventBus: IEventBus,\n    ) {}\n\n    async execute(command: ConfirmOrderCommand): Promise<void> {\n        const order = await this.orderRepository.findById(command.orderId);\n\n        if (!order) {\n            throw new OrderNotFoundException(command.orderId);\n        }\n\n        order.confirm();\n\n        await this.orderRepository.save(order);\n\n        await this.eventBus.publishAll(order.domainEvents);\n        order.clearEvents();\n    }\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/application/commands/create-order/create-order.command.ts",
    "content": "export class CreateOrderCommand {\n    constructor(\n        public readonly customerId: string,\n        public readonly items: Array<{\n            productId: string;\n            quantity: number;\n            unitPrice: number;\n        }>,\n    ) {}\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/application/commands/create-order/create-order.dto.ts",
    "content": "import { IsString, IsArray, ValidateNested, IsNumber, Min } from 'class-validator';\nimport { Type } from 'class-transformer';\nimport { ApiProperty } from '@nestjs/swagger';\n\nexport class CreateOrderItemDto {\n    @ApiProperty({ example: 'product-123' })\n    @IsString()\n    productId: string;\n\n    @ApiProperty({ example: 2 })\n    @IsNumber()\n    @Min(1)\n    quantity: number;\n\n    @ApiProperty({ example: 10.99 })\n    @IsNumber()\n    @Min(0)\n    unitPrice: number;\n}\n\nexport class CreateOrderDto {\n    @ApiProperty({ example: 'customer-456' })\n    @IsString()\n    customerId: string;\n\n    @ApiProperty({ type: [CreateOrderItemDto] })\n    @IsArray()\n    @ValidateNested({ each: true })\n    @Type(() => CreateOrderItemDto)\n    items: CreateOrderItemDto[];\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/application/commands/create-order/create-order.handler.spec.ts",
    "content": "import { Test, TestingModule } from '@nestjs/testing';\nimport { CreateOrderHandler } from './create-order.handler';\nimport { CreateOrderCommand } from './create-order.command';\nimport { IOrderRepository, ORDER_REPOSITORY } from '../../../domain/repositories/order.repository.interface';\nimport { IEventBus, EVENT_BUS } from '@/shared/infrastructure/messaging/event-bus.interface';\nimport { Order } from '../../../domain/entities/order.entity';\n\ndescribe('CreateOrderHandler', () => {\n    let handler: CreateOrderHandler;\n    let orderRepository: jest.Mocked<IOrderRepository>;\n    let eventBus: jest.Mocked<IEventBus>;\n\n    beforeEach(async () => {\n        const mockOrderRepository: Partial<IOrderRepository> = {\n            save: jest.fn(),\n            findById: jest.fn(),\n            findByCustomerId: jest.fn(),\n            delete: jest.fn(),\n        };\n\n        const mockEventBus: Partial<IEventBus> = {\n            publish: jest.fn(),\n            publishAll: jest.fn(),\n        };\n\n        const module: TestingModule = await Test.createTestingModule({\n            providers: [\n                CreateOrderHandler,\n                {\n                    provide: ORDER_REPOSITORY,\n                    useValue: mockOrderRepository,\n                },\n                {\n                    provide: EVENT_BUS,\n                    useValue: mockEventBus,\n                },\n            ],\n        }).compile();\n\n        handler = module.get<CreateOrderHandler>(CreateOrderHandler);\n        orderRepository = module.get(ORDER_REPOSITORY);\n        eventBus = module.get(EVENT_BUS);\n    });\n\n    it('should be defined', () => {\n        expect(handler).toBeDefined();\n    });\n\n    describe('execute', () => {\n        it('should create order with valid command', async () => {\n            const command = new CreateOrderCommand('customer-1', [\n                { productId: 'product-1', quantity: 2, unitPrice: 10 },\n                { productId: 'product-2', quantity: 1, unitPrice: 20 },\n            ]);\n\n            orderRepository.save.mockImplementation(async (order: Order) => order);\n\n            const orderId = await handler.execute(command);\n\n            expect(orderId).toBeDefined();\n            expect(typeof orderId).toBe('string');\n            expect(orderRepository.save).toHaveBeenCalledTimes(1);\n        });\n\n        it('should save order with correct properties', async () => {\n            const command = new CreateOrderCommand('customer-1', [\n                { productId: 'product-1', quantity: 2, unitPrice: 10 },\n            ]);\n\n            orderRepository.save.mockImplementation(async (order: Order) => order);\n\n            await handler.execute(command);\n\n            expect(orderRepository.save).toHaveBeenCalledWith(\n                expect.objectContaining({\n                    customerId: 'customer-1',\n                }),\n            );\n\n            const savedOrder = orderRepository.save.mock.calls[0][0];\n            expect(savedOrder.items.length).toBe(1);\n            expect(savedOrder.items[0].productId).toBe('product-1');\n            expect(savedOrder.items[0].quantity.value).toBe(2);\n            expect(savedOrder.items[0].unitPrice.amount).toBe(10);\n        });\n\n        it('should publish domain events after saving', async () => {\n            const command = new CreateOrderCommand('customer-1', [\n                { productId: 'product-1', quantity: 1, unitPrice: 10 },\n            ]);\n\n            orderRepository.save.mockImplementation(async (order: Order) => order);\n\n            await handler.execute(command);\n\n            expect(eventBus.publishAll).toHaveBeenCalledTimes(1);\n            expect(eventBus.publishAll).toHaveBeenCalledWith(\n                expect.arrayContaining([\n                    expect.objectContaining({\n                        customerId: 'customer-1',\n                    }),\n                ]),\n            );\n        });\n\n        it('should clear events after publishing', async () => {\n            const command = new CreateOrderCommand('customer-1', [\n                { productId: 'product-1', quantity: 1, unitPrice: 10 },\n            ]);\n\n            let savedOrder: Order;\n            orderRepository.save.mockImplementation(async (order: Order) => {\n                savedOrder = order;\n                return order;\n            });\n\n            await handler.execute(command);\n\n            // Events should be cleared after publishing\n            expect(savedOrder!.domainEvents.length).toBe(0);\n        });\n\n        it('should create order with multiple items', async () => {\n            const command = new CreateOrderCommand('customer-1', [\n                { productId: 'product-1', quantity: 2, unitPrice: 10 },\n                { productId: 'product-2', quantity: 1, unitPrice: 20 },\n                { productId: 'product-3', quantity: 3, unitPrice: 5 },\n            ]);\n\n            orderRepository.save.mockImplementation(async (order: Order) => order);\n\n            await handler.execute(command);\n\n            const savedOrder = orderRepository.save.mock.calls[0][0];\n            expect(savedOrder.items.length).toBe(3);\n        });\n\n        it('should handle repository errors', async () => {\n            const command = new CreateOrderCommand('customer-1', [\n                { productId: 'product-1', quantity: 1, unitPrice: 10 },\n            ]);\n\n            orderRepository.save.mockRejectedValue(new Error('Database error'));\n\n            await expect(handler.execute(command)).rejects.toThrow('Database error');\n        });\n    });\n});\n"
  },
  {
    "path": "apps/api/src/modules/order/application/commands/create-order/create-order.handler.ts",
    "content": "import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';\nimport { Inject } from '@nestjs/common';\nimport { CreateOrderCommand } from './create-order.command';\nimport { Order } from '../../../domain/entities/order.entity';\nimport { OrderItem } from '../../../domain/entities/order-item.entity';\nimport { Money } from '../../../domain/value-objects/money.vo';\nimport { Quantity } from '../../../domain/value-objects/quantity.vo';\nimport { OrderId } from '../../../domain/value-objects/order-id.vo';\nimport { IOrderRepository, ORDER_REPOSITORY } from '../../../domain/repositories/order.repository.interface';\nimport { EVENT_BUS, IEventBus } from '@/shared/infrastructure/messaging/event-bus.interface';\n\n@CommandHandler(CreateOrderCommand)\nexport class CreateOrderHandler implements ICommandHandler<CreateOrderCommand> {\n    constructor(\n        @Inject(ORDER_REPOSITORY)\n        private readonly orderRepository: IOrderRepository,\n        @Inject(EVENT_BUS)\n        private readonly eventBus: IEventBus,\n    ) {}\n\n    async execute(command: CreateOrderCommand): Promise<string> {\n        const orderItems = command.items.map(item =>\n            OrderItem.create({\n                id: OrderId.create().value,\n                productId: item.productId,\n                quantity: Quantity.create(item.quantity),\n                unitPrice: Money.create(item.unitPrice),\n            }),\n        );\n\n        const order = Order.create(command.customerId, orderItems);\n\n        await this.orderRepository.save(order);\n\n        await this.eventBus.publishAll(order.domainEvents);\n        order.clearEvents();\n\n        return order.id;\n    }\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/application/event-handlers/order-confirmed.handler.ts",
    "content": "import { EventsHandler, IEventHandler } from '@nestjs/cqrs';\nimport { Logger } from '@nestjs/common';\nimport { OrderConfirmedEvent } from '../../domain/events/order-confirmed.event';\n\n@EventsHandler(OrderConfirmedEvent)\nexport class OrderConfirmedHandler implements IEventHandler<OrderConfirmedEvent> {\n    private readonly logger = new Logger(OrderConfirmedHandler.name);\n\n    async handle(event: OrderConfirmedEvent) {\n        this.logger.log(`Order confirmed: ${event.orderId}`);\n    }\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/application/event-handlers/order-created.handler.ts",
    "content": "import { EventsHandler, IEventHandler } from '@nestjs/cqrs';\nimport { Logger } from '@nestjs/common';\nimport { OrderCreatedEvent } from '../../domain/events/order-created.event';\n\n@EventsHandler(OrderCreatedEvent)\nexport class OrderCreatedHandler implements IEventHandler<OrderCreatedEvent> {\n    private readonly logger = new Logger(OrderCreatedHandler.name);\n\n    async handle(event: OrderCreatedEvent) {\n        this.logger.log(\n            `Order created: ${event.orderId} for customer ${event.customerId} with total ${event.totalAmount.amount}`,\n        );\n    }\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/application/queries/get-order/get-order.handler.spec.ts",
    "content": "import { Test, TestingModule } from '@nestjs/testing';\nimport { GetOrderHandler } from './get-order.handler';\nimport { GetOrderQuery } from './get-order.query';\nimport { IOrderRepository, ORDER_REPOSITORY } from '../../../domain/repositories/order.repository.interface';\nimport { Order } from '../../../domain/entities/order.entity';\nimport { OrderItem } from '../../../domain/entities/order-item.entity';\nimport { Money } from '../../../domain/value-objects/money.vo';\nimport { Quantity } from '../../../domain/value-objects/quantity.vo';\nimport { OrderNotFoundException } from '../../../domain/exceptions/order-not-found.exception';\n\ndescribe('GetOrderHandler', () => {\n    let handler: GetOrderHandler;\n    let orderRepository: jest.Mocked<IOrderRepository>;\n\n    beforeEach(async () => {\n        const mockOrderRepository: Partial<IOrderRepository> = {\n            save: jest.fn(),\n            findById: jest.fn(),\n            findByCustomerId: jest.fn(),\n            delete: jest.fn(),\n        };\n\n        const module: TestingModule = await Test.createTestingModule({\n            providers: [\n                GetOrderHandler,\n                {\n                    provide: ORDER_REPOSITORY,\n                    useValue: mockOrderRepository,\n                },\n            ],\n        }).compile();\n\n        handler = module.get<GetOrderHandler>(GetOrderHandler);\n        orderRepository = module.get(ORDER_REPOSITORY);\n    });\n\n    it('should be defined', () => {\n        expect(handler).toBeDefined();\n    });\n\n    describe('execute', () => {\n        it('should return order DTO for existing order', async () => {\n            const items = [\n                OrderItem.create({\n                    id: 'item-1',\n                    productId: 'product-1',\n                    quantity: Quantity.create(2),\n                    unitPrice: Money.create(10),\n                }),\n                OrderItem.create({\n                    id: 'item-2',\n                    productId: 'product-2',\n                    quantity: Quantity.create(1),\n                    unitPrice: Money.create(20),\n                }),\n            ];\n            const order = Order.create('customer-1', items);\n            const query = new GetOrderQuery(order.id);\n\n            orderRepository.findById.mockResolvedValue(order);\n\n            const result = await handler.execute(query);\n\n            expect(result).toBeDefined();\n            expect(result.id).toBe(order.id);\n            expect(result.customerId).toBe('customer-1');\n            expect(result.items.length).toBe(2);\n            expect(result.status).toBe('PENDING');\n            expect(result.totalAmount).toBe(40);\n        });\n\n        it('should map order items correctly', async () => {\n            const items = [\n                OrderItem.create({\n                    id: 'item-1',\n                    productId: 'product-1',\n                    quantity: Quantity.create(3),\n                    unitPrice: Money.create(15),\n                }),\n            ];\n            const order = Order.create('customer-1', items);\n            const query = new GetOrderQuery(order.id);\n\n            orderRepository.findById.mockResolvedValue(order);\n\n            const result = await handler.execute(query);\n\n            expect(result.items[0].id).toBe('item-1');\n            expect(result.items[0].productId).toBe('product-1');\n            expect(result.items[0].quantity).toBe(3);\n            expect(result.items[0].unitPrice).toBe(15);\n            expect(result.items[0].totalPrice).toBe(45);\n        });\n\n        it('should include timestamps in response', async () => {\n            const items = [\n                OrderItem.create({\n                    id: 'item-1',\n                    productId: 'product-1',\n                    quantity: Quantity.create(1),\n                    unitPrice: Money.create(10),\n                }),\n            ];\n            const order = Order.create('customer-1', items);\n            const query = new GetOrderQuery(order.id);\n\n            orderRepository.findById.mockResolvedValue(order);\n\n            const result = await handler.execute(query);\n\n            expect(result.createdAt).toBeInstanceOf(Date);\n            expect(result.updatedAt).toBeInstanceOf(Date);\n        });\n\n        it('should throw OrderNotFoundException when order not found', async () => {\n            const query = new GetOrderQuery('non-existent-id');\n\n            orderRepository.findById.mockResolvedValue(null);\n\n            await expect(handler.execute(query)).rejects.toThrow(OrderNotFoundException);\n            await expect(handler.execute(query)).rejects.toThrow('non-existent-id');\n        });\n\n        it('should handle confirmed order status', async () => {\n            const items = [\n                OrderItem.create({\n                    id: 'item-1',\n                    productId: 'product-1',\n                    quantity: Quantity.create(1),\n                    unitPrice: Money.create(10),\n                }),\n            ];\n            const order = Order.create('customer-1', items);\n            order.confirm();\n            const query = new GetOrderQuery(order.id);\n\n            orderRepository.findById.mockResolvedValue(order);\n\n            const result = await handler.execute(query);\n\n            expect(result.status).toBe('CONFIRMED');\n        });\n\n        it('should handle cancelled order status', async () => {\n            const items = [\n                OrderItem.create({\n                    id: 'item-1',\n                    productId: 'product-1',\n                    quantity: Quantity.create(1),\n                    unitPrice: Money.create(10),\n                }),\n            ];\n            const order = Order.create('customer-1', items);\n            order.cancel();\n            const query = new GetOrderQuery(order.id);\n\n            orderRepository.findById.mockResolvedValue(order);\n\n            const result = await handler.execute(query);\n\n            expect(result.status).toBe('CANCELLED');\n        });\n    });\n});\n"
  },
  {
    "path": "apps/api/src/modules/order/application/queries/get-order/get-order.handler.ts",
    "content": "import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';\nimport { Inject } from '@nestjs/common';\nimport { GetOrderQuery } from './get-order.query';\nimport { OrderResponseDto } from './order.response.dto';\nimport { IOrderRepository, ORDER_REPOSITORY } from '../../../domain/repositories/order.repository.interface';\nimport { OrderNotFoundException } from '../../../domain/exceptions/order-not-found.exception';\n\n@QueryHandler(GetOrderQuery)\nexport class GetOrderHandler implements IQueryHandler<GetOrderQuery> {\n    constructor(\n        @Inject(ORDER_REPOSITORY)\n        private readonly orderRepository: IOrderRepository,\n    ) {}\n\n    async execute(query: GetOrderQuery): Promise<OrderResponseDto> {\n        const order = await this.orderRepository.findById(query.orderId);\n\n        if (!order) {\n            throw new OrderNotFoundException(query.orderId);\n        }\n\n        return {\n            id: order.id,\n            customerId: order.customerId,\n            items: order.items.map(item => ({\n                id: item.id,\n                productId: item.productId,\n                quantity: item.quantity.value,\n                unitPrice: item.unitPrice.amount,\n                totalPrice: item.getTotalPrice().amount,\n            })),\n            status: order.status.value,\n            totalAmount: order.getTotalAmount().amount,\n            createdAt: order.createdAt,\n            updatedAt: order.updatedAt,\n        };\n    }\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/application/queries/get-order/get-order.query.ts",
    "content": "export class GetOrderQuery {\n    constructor(public readonly orderId: string) {}\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/application/queries/get-order/order.response.dto.ts",
    "content": "import { ApiProperty } from '@nestjs/swagger';\n\nexport class OrderItemResponseDto {\n    @ApiProperty()\n    id: string;\n\n    @ApiProperty()\n    productId: string;\n\n    @ApiProperty()\n    quantity: number;\n\n    @ApiProperty()\n    unitPrice: number;\n\n    @ApiProperty()\n    totalPrice: number;\n}\n\nexport class OrderResponseDto {\n    @ApiProperty()\n    id: string;\n\n    @ApiProperty()\n    customerId: string;\n\n    @ApiProperty({ type: [OrderItemResponseDto] })\n    items: OrderItemResponseDto[];\n\n    @ApiProperty()\n    status: string;\n\n    @ApiProperty()\n    totalAmount: number;\n\n    @ApiProperty()\n    createdAt: Date;\n\n    @ApiProperty()\n    updatedAt: Date;\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/application/queries/list-orders/list-orders.handler.spec.ts",
    "content": "import { Test, TestingModule } from '@nestjs/testing';\nimport { ListOrdersHandler } from './list-orders.handler';\nimport { ListOrdersQuery } from './list-orders.query';\nimport { IOrderRepository, ORDER_REPOSITORY } from '../../../domain/repositories/order.repository.interface';\nimport { Order } from '../../../domain/entities/order.entity';\nimport { OrderItem } from '../../../domain/entities/order-item.entity';\nimport { Money } from '../../../domain/value-objects/money.vo';\nimport { Quantity } from '../../../domain/value-objects/quantity.vo';\n\ndescribe('ListOrdersHandler', () => {\n    let handler: ListOrdersHandler;\n    let orderRepository: jest.Mocked<IOrderRepository>;\n\n    beforeEach(async () => {\n        const mockOrderRepository: Partial<IOrderRepository> = {\n            save: jest.fn(),\n            findById: jest.fn(),\n            findByCustomerId: jest.fn(),\n            delete: jest.fn(),\n        };\n\n        const module: TestingModule = await Test.createTestingModule({\n            providers: [\n                ListOrdersHandler,\n                {\n                    provide: ORDER_REPOSITORY,\n                    useValue: mockOrderRepository,\n                },\n            ],\n        }).compile();\n\n        handler = module.get<ListOrdersHandler>(ListOrdersHandler);\n        orderRepository = module.get(ORDER_REPOSITORY);\n    });\n\n    it('should be defined', () => {\n        expect(handler).toBeDefined();\n    });\n\n    describe('execute', () => {\n        it('should return list of orders for customer', async () => {\n            const items1 = [\n                OrderItem.create({\n                    id: 'item-1',\n                    productId: 'product-1',\n                    quantity: Quantity.create(1),\n                    unitPrice: Money.create(10),\n                }),\n            ];\n            const items2 = [\n                OrderItem.create({\n                    id: 'item-2',\n                    productId: 'product-2',\n                    quantity: Quantity.create(2),\n                    unitPrice: Money.create(20),\n                }),\n            ];\n\n            const order1 = Order.create('customer-1', items1);\n            const order2 = Order.create('customer-1', items2);\n\n            const query = new ListOrdersQuery('customer-1');\n\n            orderRepository.findByCustomerId.mockResolvedValue([order1, order2]);\n\n            const result = await handler.execute(query);\n\n            expect(result.orders.length).toBe(2);\n            expect(result.total).toBe(2);\n            expect(result.orders[0].customerId).toBe('customer-1');\n            expect(result.orders[1].customerId).toBe('customer-1');\n        });\n\n        it('should return empty list when no orders found', async () => {\n            const query = new ListOrdersQuery('customer-1');\n\n            orderRepository.findByCustomerId.mockResolvedValue([]);\n\n            const result = await handler.execute(query);\n\n            expect(result.orders.length).toBe(0);\n            expect(result.total).toBe(0);\n        });\n\n        it('should map all order properties correctly', async () => {\n            const items = [\n                OrderItem.create({\n                    id: 'item-1',\n                    productId: 'product-1',\n                    quantity: Quantity.create(2),\n                    unitPrice: Money.create(15),\n                }),\n            ];\n            const order = Order.create('customer-1', items);\n            const query = new ListOrdersQuery('customer-1');\n\n            orderRepository.findByCustomerId.mockResolvedValue([order]);\n\n            const result = await handler.execute(query);\n\n            expect(result.orders[0].id).toBe(order.id);\n            expect(result.orders[0].customerId).toBe('customer-1');\n            expect(result.orders[0].items.length).toBe(1);\n            expect(result.orders[0].status).toBe('PENDING');\n            expect(result.orders[0].totalAmount).toBe(30);\n        });\n\n        it('should handle orders with different statuses', async () => {\n            const items1 = [\n                OrderItem.create({\n                    id: 'item-1',\n                    productId: 'product-1',\n                    quantity: Quantity.create(1),\n                    unitPrice: Money.create(10),\n                }),\n            ];\n            const items2 = [\n                OrderItem.create({\n                    id: 'item-2',\n                    productId: 'product-2',\n                    quantity: Quantity.create(1),\n                    unitPrice: Money.create(20),\n                }),\n            ];\n\n            const order1 = Order.create('customer-1', items1);\n            const order2 = Order.create('customer-1', items2);\n            order2.confirm();\n\n            const query = new ListOrdersQuery('customer-1');\n\n            orderRepository.findByCustomerId.mockResolvedValue([order1, order2]);\n\n            const result = await handler.execute(query);\n\n            expect(result.orders[0].status).toBe('PENDING');\n            expect(result.orders[1].status).toBe('CONFIRMED');\n        });\n\n        it('should return empty list when customerId is not provided', async () => {\n            const query = new ListOrdersQuery();\n\n            const result = await handler.execute(query);\n\n            expect(result.orders.length).toBe(0);\n            expect(result.total).toBe(0);\n            expect(orderRepository.findByCustomerId).not.toHaveBeenCalled();\n        });\n    });\n});\n"
  },
  {
    "path": "apps/api/src/modules/order/application/queries/list-orders/list-orders.handler.ts",
    "content": "import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';\nimport { Inject } from '@nestjs/common';\nimport { ListOrdersQuery } from './list-orders.query';\nimport { OrderListResponseDto } from './order-list.response.dto';\nimport { IOrderRepository, ORDER_REPOSITORY } from '../../../domain/repositories/order.repository.interface';\n\n@QueryHandler(ListOrdersQuery)\nexport class ListOrdersHandler implements IQueryHandler<ListOrdersQuery> {\n    constructor(\n        @Inject(ORDER_REPOSITORY)\n        private readonly orderRepository: IOrderRepository,\n    ) {}\n\n    async execute(query: ListOrdersQuery): Promise<OrderListResponseDto> {\n        const orders = query.customerId ? await this.orderRepository.findByCustomerId(query.customerId) : [];\n\n        return {\n            orders: orders.map(order => ({\n                id: order.id,\n                customerId: order.customerId,\n                items: order.items.map(item => ({\n                    id: item.id,\n                    productId: item.productId,\n                    quantity: item.quantity.value,\n                    unitPrice: item.unitPrice.amount,\n                    totalPrice: item.getTotalPrice().amount,\n                })),\n                status: order.status.value,\n                totalAmount: order.getTotalAmount().amount,\n                createdAt: order.createdAt,\n                updatedAt: order.updatedAt,\n            })),\n            total: orders.length,\n        };\n    }\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/application/queries/list-orders/list-orders.query.ts",
    "content": "export class ListOrdersQuery {\n    constructor(public readonly customerId?: string) {}\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/application/queries/list-orders/order-list.response.dto.ts",
    "content": "import { ApiProperty } from '@nestjs/swagger';\nimport { OrderResponseDto } from '../get-order/order.response.dto';\n\nexport class OrderListResponseDto {\n    @ApiProperty({ type: [OrderResponseDto] })\n    orders: OrderResponseDto[];\n\n    @ApiProperty()\n    total: number;\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/domain/entities/order-item.entity.spec.ts",
    "content": "import { OrderItem } from './order-item.entity';\nimport { Money } from '../value-objects/money.vo';\nimport { Quantity } from '../value-objects/quantity.vo';\n\ndescribe('OrderItem Entity', () => {\n    describe('create', () => {\n        it('should create order item with valid properties', () => {\n            const orderItem = OrderItem.create({\n                id: 'item-1',\n                productId: 'product-1',\n                quantity: Quantity.create(2),\n                unitPrice: Money.create(10.99),\n            });\n\n            expect(orderItem.id).toBe('item-1');\n            expect(orderItem.productId).toBe('product-1');\n            expect(orderItem.quantity.value).toBe(2);\n            expect(orderItem.unitPrice.amount).toBe(10.99);\n        });\n    });\n\n    describe('getTotalPrice', () => {\n        it('should calculate total price correctly', () => {\n            const orderItem = OrderItem.create({\n                id: 'item-1',\n                productId: 'product-1',\n                quantity: Quantity.create(3),\n                unitPrice: Money.create(10),\n            });\n\n            const totalPrice = orderItem.getTotalPrice();\n\n            expect(totalPrice.amount).toBe(30);\n        });\n\n        it('should calculate total price for single item', () => {\n            const orderItem = OrderItem.create({\n                id: 'item-1',\n                productId: 'product-1',\n                quantity: Quantity.create(1),\n                unitPrice: Money.create(15.99),\n            });\n\n            const totalPrice = orderItem.getTotalPrice();\n\n            expect(totalPrice.amount).toBe(15.99);\n        });\n\n        it('should calculate total price with decimal values', () => {\n            const orderItem = OrderItem.create({\n                id: 'item-1',\n                productId: 'product-1',\n                quantity: Quantity.create(2),\n                unitPrice: Money.create(10.99),\n            });\n\n            const totalPrice = orderItem.getTotalPrice();\n\n            expect(totalPrice.amount).toBe(21.98);\n        });\n    });\n\n    describe('updateQuantity', () => {\n        it('should update quantity', () => {\n            const orderItem = OrderItem.create({\n                id: 'item-1',\n                productId: 'product-1',\n                quantity: Quantity.create(2),\n                unitPrice: Money.create(10),\n            });\n\n            orderItem.updateQuantity(Quantity.create(5));\n\n            expect(orderItem.quantity.value).toBe(5);\n        });\n\n        it('should affect total price after quantity update', () => {\n            const orderItem = OrderItem.create({\n                id: 'item-1',\n                productId: 'product-1',\n                quantity: Quantity.create(2),\n                unitPrice: Money.create(10),\n            });\n\n            orderItem.updateQuantity(Quantity.create(3));\n\n            expect(orderItem.getTotalPrice().amount).toBe(30);\n        });\n    });\n\n    describe('equals', () => {\n        it('should return true for same id', () => {\n            const item1 = OrderItem.create({\n                id: 'item-1',\n                productId: 'product-1',\n                quantity: Quantity.create(2),\n                unitPrice: Money.create(10),\n            });\n\n            const item2 = OrderItem.create({\n                id: 'item-1',\n                productId: 'product-2',\n                quantity: Quantity.create(3),\n                unitPrice: Money.create(20),\n            });\n\n            expect(item1.equals(item2)).toBe(true);\n        });\n\n        it('should return false for different ids', () => {\n            const item1 = OrderItem.create({\n                id: 'item-1',\n                productId: 'product-1',\n                quantity: Quantity.create(2),\n                unitPrice: Money.create(10),\n            });\n\n            const item2 = OrderItem.create({\n                id: 'item-2',\n                productId: 'product-1',\n                quantity: Quantity.create(2),\n                unitPrice: Money.create(10),\n            });\n\n            expect(item1.equals(item2)).toBe(false);\n        });\n    });\n});\n"
  },
  {
    "path": "apps/api/src/modules/order/domain/entities/order-item.entity.ts",
    "content": "import { Entity } from '@/shared/domain/entity';\nimport { Money } from '../value-objects/money.vo';\nimport { Quantity } from '../value-objects/quantity.vo';\n\nexport interface OrderItemProps {\n    id: string;\n    productId: string;\n    quantity: Quantity;\n    unitPrice: Money;\n}\n\nexport class OrderItem extends Entity<string> {\n    private _productId: string;\n    private _quantity: Quantity;\n    private _unitPrice: Money;\n\n    get productId(): string {\n        return this._productId;\n    }\n\n    get quantity(): Quantity {\n        return this._quantity;\n    }\n\n    get unitPrice(): Money {\n        return this._unitPrice;\n    }\n\n    private constructor(props: OrderItemProps) {\n        super(props.id);\n        this._productId = props.productId;\n        this._quantity = props.quantity;\n        this._unitPrice = props.unitPrice;\n    }\n\n    public static create(props: OrderItemProps): OrderItem {\n        return new OrderItem(props);\n    }\n\n    public getTotalPrice(): Money {\n        return this._unitPrice.multiply(this._quantity.value);\n    }\n\n    public updateQuantity(quantity: Quantity): void {\n        this._quantity = quantity;\n    }\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/domain/entities/order.entity.spec.ts",
    "content": "import { Order } from './order.entity';\nimport { OrderItem } from './order-item.entity';\nimport { Money } from '../value-objects/money.vo';\nimport { Quantity } from '../value-objects/quantity.vo';\nimport { OrderId } from '../value-objects/order-id.vo';\nimport { OrderStatus } from '../value-objects/order-status.vo';\nimport { OrderCreatedEvent } from '../events/order-created.event';\nimport { OrderConfirmedEvent } from '../events/order-confirmed.event';\nimport { OrderCancelledEvent } from '../events/order-cancelled.event';\nimport { InvalidOrderStateException } from '../exceptions/invalid-order-state.exception';\n\ndescribe('Order Aggregate Root', () => {\n    let orderItems: OrderItem[];\n\n    beforeEach(() => {\n        orderItems = [\n            OrderItem.create({\n                id: 'item-1',\n                productId: 'product-1',\n                quantity: Quantity.create(2),\n                unitPrice: Money.create(10),\n            }),\n            OrderItem.create({\n                id: 'item-2',\n                productId: 'product-2',\n                quantity: Quantity.create(1),\n                unitPrice: Money.create(20),\n            }),\n        ];\n    });\n\n    describe('create', () => {\n        it('should create order with valid properties', () => {\n            const order = Order.create('customer-1', orderItems);\n\n            expect(order.id).toBeDefined();\n            expect(order.customerId).toBe('customer-1');\n            expect(order.items.length).toBe(2);\n            expect(order.status.isPending()).toBe(true);\n            expect(order.createdAt).toBeInstanceOf(Date);\n            expect(order.updatedAt).toBeInstanceOf(Date);\n        });\n\n        it('should create order with provided id', () => {\n            const orderId = OrderId.create('custom-order-id');\n            const order = Order.create('customer-1', orderItems, orderId);\n\n            expect(order.id).toBe('custom-order-id');\n        });\n\n        it('should raise OrderCreatedEvent', () => {\n            const order = Order.create('customer-1', orderItems);\n\n            expect(order.domainEvents.length).toBe(1);\n            expect(order.domainEvents[0]).toBeInstanceOf(OrderCreatedEvent);\n            expect((order.domainEvents[0] as OrderCreatedEvent).orderId).toBe(order.id);\n            expect((order.domainEvents[0] as OrderCreatedEvent).customerId).toBe('customer-1');\n        });\n\n        it('should create order with empty items', () => {\n            const order = Order.create('customer-1', []);\n\n            expect(order.items.length).toBe(0);\n        });\n    });\n\n    describe('getTotalAmount', () => {\n        it('should calculate total amount correctly', () => {\n            const order = Order.create('customer-1', orderItems);\n\n            const total = order.getTotalAmount();\n\n            expect(total.amount).toBe(40); // (2 * 10) + (1 * 20)\n        });\n\n        it('should return zero for empty order', () => {\n            const order = Order.create('customer-1', []);\n\n            const total = order.getTotalAmount();\n\n            expect(total.amount).toBe(0);\n        });\n\n        it('should calculate total for single item', () => {\n            const singleItem = [\n                OrderItem.create({\n                    id: 'item-1',\n                    productId: 'product-1',\n                    quantity: Quantity.create(3),\n                    unitPrice: Money.create(15),\n                }),\n            ];\n\n            const order = Order.create('customer-1', singleItem);\n\n            expect(order.getTotalAmount().amount).toBe(45);\n        });\n    });\n\n    describe('confirm', () => {\n        it('should confirm pending order', () => {\n            const order = Order.create('customer-1', orderItems);\n\n            order.confirm();\n\n            expect(order.status.isConfirmed()).toBe(true);\n        });\n\n        it('should raise OrderConfirmedEvent', () => {\n            const order = Order.create('customer-1', orderItems);\n            order.clearEvents(); // Clear creation event\n\n            order.confirm();\n\n            expect(order.domainEvents.length).toBe(1);\n            expect(order.domainEvents[0]).toBeInstanceOf(OrderConfirmedEvent);\n            expect((order.domainEvents[0] as OrderConfirmedEvent).orderId).toBe(order.id);\n        });\n\n        it('should update updatedAt timestamp', () => {\n            const order = Order.create('customer-1', orderItems);\n            const originalUpdatedAt = order.updatedAt;\n\n            // Wait a bit to ensure timestamp difference\n            setTimeout(() => {\n                order.confirm();\n                expect(order.updatedAt.getTime()).toBeGreaterThanOrEqual(originalUpdatedAt.getTime());\n            }, 10);\n        });\n\n        it('should throw error when confirming non-pending order', () => {\n            const order = Order.create('customer-1', orderItems);\n            order.confirm();\n\n            expect(() => order.confirm()).toThrow(InvalidOrderStateException);\n            expect(() => order.confirm()).toThrow('Cannot confirm order');\n        });\n\n        it('should throw error when confirming cancelled order', () => {\n            const order = Order.create('customer-1', orderItems);\n            order.cancel();\n\n            expect(() => order.confirm()).toThrow(InvalidOrderStateException);\n        });\n    });\n\n    describe('cancel', () => {\n        it('should cancel pending order', () => {\n            const order = Order.create('customer-1', orderItems);\n\n            order.cancel();\n\n            expect(order.status.isCancelled()).toBe(true);\n        });\n\n        it('should cancel confirmed order', () => {\n            const order = Order.create('customer-1', orderItems);\n            order.confirm();\n\n            order.cancel();\n\n            expect(order.status.isCancelled()).toBe(true);\n        });\n\n        it('should raise OrderCancelledEvent', () => {\n            const order = Order.create('customer-1', orderItems);\n            order.clearEvents();\n\n            order.cancel();\n\n            expect(order.domainEvents.length).toBe(1);\n            expect(order.domainEvents[0]).toBeInstanceOf(OrderCancelledEvent);\n        });\n\n        it('should throw error when cancelling already cancelled order', () => {\n            const order = Order.create('customer-1', orderItems);\n            order.cancel();\n\n            expect(() => order.cancel()).toThrow(InvalidOrderStateException);\n            expect(() => order.cancel()).toThrow('Cannot cancel order');\n        });\n    });\n\n    describe('addItem', () => {\n        it('should add item to pending order', () => {\n            const order = Order.create('customer-1', orderItems);\n            const newItem = OrderItem.create({\n                id: 'item-3',\n                productId: 'product-3',\n                quantity: Quantity.create(1),\n                unitPrice: Money.create(30),\n            });\n\n            order.addItem(newItem);\n\n            expect(order.items.length).toBe(3);\n            expect(order.items[2].id).toBe('item-3');\n        });\n\n        it('should update total amount after adding item', () => {\n            const order = Order.create('customer-1', orderItems);\n            const newItem = OrderItem.create({\n                id: 'item-3',\n                productId: 'product-3',\n                quantity: Quantity.create(1),\n                unitPrice: Money.create(30),\n            });\n\n            order.addItem(newItem);\n\n            expect(order.getTotalAmount().amount).toBe(70); // 40 + 30\n        });\n\n        it('should throw error when adding item to non-pending order', () => {\n            const order = Order.create('customer-1', orderItems);\n            order.confirm();\n\n            const newItem = OrderItem.create({\n                id: 'item-3',\n                productId: 'product-3',\n                quantity: Quantity.create(1),\n                unitPrice: Money.create(30),\n            });\n\n            expect(() => order.addItem(newItem)).toThrow(InvalidOrderStateException);\n            expect(() => order.addItem(newItem)).toThrow('Cannot add items to a non-pending order');\n        });\n    });\n\n    describe('removeItem', () => {\n        it('should remove item from pending order', () => {\n            const order = Order.create('customer-1', orderItems);\n\n            order.removeItem('item-1');\n\n            expect(order.items.length).toBe(1);\n            expect(order.items[0].id).toBe('item-2');\n        });\n\n        it('should update total amount after removing item', () => {\n            const order = Order.create('customer-1', orderItems);\n\n            order.removeItem('item-1'); // Remove item worth 20\n\n            expect(order.getTotalAmount().amount).toBe(20);\n        });\n\n        it('should throw error when removing item from non-pending order', () => {\n            const order = Order.create('customer-1', orderItems);\n            order.confirm();\n\n            expect(() => order.removeItem('item-1')).toThrow(InvalidOrderStateException);\n            expect(() => order.removeItem('item-1')).toThrow('Cannot remove items from a non-pending order');\n        });\n\n        it('should handle removing non-existent item', () => {\n            const order = Order.create('customer-1', orderItems);\n\n            order.removeItem('non-existent-id');\n\n            expect(order.items.length).toBe(2); // No change\n        });\n    });\n\n    describe('clearEvents', () => {\n        it('should clear all domain events', () => {\n            const order = Order.create('customer-1', orderItems);\n\n            expect(order.domainEvents.length).toBeGreaterThan(0);\n\n            order.clearEvents();\n\n            expect(order.domainEvents.length).toBe(0);\n        });\n    });\n\n    describe('reconstitute', () => {\n        it('should reconstitute order from props without raising events', () => {\n            const orderId = OrderId.create('existing-order-id');\n            const order = Order.reconstitute({\n                id: orderId,\n                customerId: 'customer-1',\n                items: orderItems,\n                status: OrderStatus.confirmed(),\n                createdAt: new Date('2024-01-01'),\n                updatedAt: new Date('2024-01-02'),\n            });\n\n            expect(order.id).toBe('existing-order-id');\n            expect(order.status.isConfirmed()).toBe(true);\n            expect(order.domainEvents.length).toBe(0); // No events raised\n        });\n    });\n\n    describe('items immutability', () => {\n        it('should return copy of items array', () => {\n            const order = Order.create('customer-1', orderItems);\n\n            const items1 = order.items;\n            const items2 = order.items;\n\n            expect(items1).not.toBe(items2); // Different array instances\n            expect(items1).toEqual(items2); // Same content\n        });\n\n        it('should not allow external modification of items', () => {\n            const order = Order.create('customer-1', orderItems);\n\n            const items = order.items;\n            items.push(\n                OrderItem.create({\n                    id: 'item-3',\n                    productId: 'product-3',\n                    quantity: Quantity.create(1),\n                    unitPrice: Money.create(30),\n                }),\n            );\n\n            expect(order.items.length).toBe(2); // Original unchanged\n        });\n    });\n});\n"
  },
  {
    "path": "apps/api/src/modules/order/domain/entities/order.entity.ts",
    "content": "import { AggregateRoot } from '@/shared/domain/aggregate-root';\nimport { OrderId } from '../value-objects/order-id.vo';\nimport { OrderStatus } from '../value-objects/order-status.vo';\nimport { Money } from '../value-objects/money.vo';\nimport { OrderItem } from './order-item.entity';\nimport { OrderCreatedEvent } from '../events/order-created.event';\nimport { OrderConfirmedEvent } from '../events/order-confirmed.event';\nimport { OrderCancelledEvent } from '../events/order-cancelled.event';\nimport { InvalidOrderStateException } from '../exceptions/invalid-order-state.exception';\n\nexport interface OrderProps {\n    id: OrderId;\n    customerId: string;\n    items: OrderItem[];\n    status: OrderStatus;\n    createdAt: Date;\n    updatedAt: Date;\n}\n\nexport class Order extends AggregateRoot<string> {\n    private _customerId: string;\n    private _items: OrderItem[];\n    private _status: OrderStatus;\n    private _createdAt: Date;\n    private _updatedAt: Date;\n\n    get customerId(): string {\n        return this._customerId;\n    }\n\n    get items(): OrderItem[] {\n        return [...this._items];\n    }\n\n    get status(): OrderStatus {\n        return this._status;\n    }\n\n    get createdAt(): Date {\n        return this._createdAt;\n    }\n\n    get updatedAt(): Date {\n        return this._updatedAt;\n    }\n\n    private constructor(props: OrderProps) {\n        super(props.id.value);\n        this._customerId = props.customerId;\n        this._items = props.items;\n        this._status = props.status;\n        this._createdAt = props.createdAt;\n        this._updatedAt = props.updatedAt;\n    }\n\n    public static create(customerId: string, items: OrderItem[], id?: OrderId): Order {\n        const orderId = id || OrderId.create();\n        const now = new Date();\n\n        const order = new Order({\n            id: orderId,\n            customerId,\n            items,\n            status: OrderStatus.pending(),\n            createdAt: now,\n            updatedAt: now,\n        });\n\n        order.addDomainEvent(new OrderCreatedEvent(orderId.value, customerId, order.getTotalAmount()));\n\n        return order;\n    }\n\n    public static reconstitute(props: OrderProps): Order {\n        return new Order(props);\n    }\n\n    public getTotalAmount(): Money {\n        if (this._items.length === 0) {\n            return Money.create(0);\n        }\n\n        return this._items.reduce((total, item) => total.add(item.getTotalPrice()), Money.create(0));\n    }\n\n    public confirm(): void {\n        if (!this._status.isPending()) {\n            throw new InvalidOrderStateException(`Cannot confirm order. Current status: ${this._status.value}`);\n        }\n\n        this._status = OrderStatus.confirmed();\n        this._updatedAt = new Date();\n\n        this.addDomainEvent(new OrderConfirmedEvent(this.id));\n    }\n\n    public cancel(): void {\n        if (this._status.isCancelled() || this._status.isCompleted()) {\n            throw new InvalidOrderStateException(`Cannot cancel order. Current status: ${this._status.value}`);\n        }\n\n        this._status = OrderStatus.cancelled();\n        this._updatedAt = new Date();\n\n        this.addDomainEvent(new OrderCancelledEvent(this.id));\n    }\n\n    public addItem(item: OrderItem): void {\n        if (!this._status.isPending()) {\n            throw new InvalidOrderStateException('Cannot add items to a non-pending order');\n        }\n\n        this._items.push(item);\n        this._updatedAt = new Date();\n    }\n\n    public removeItem(itemId: string): void {\n        if (!this._status.isPending()) {\n            throw new InvalidOrderStateException('Cannot remove items from a non-pending order');\n        }\n\n        this._items = this._items.filter(item => item.id !== itemId);\n        this._updatedAt = new Date();\n    }\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/domain/events/order-cancelled.event.ts",
    "content": "import { DomainEvent } from '@/shared/domain/domain-event';\n\nexport class OrderCancelledEvent extends DomainEvent {\n    constructor(public readonly orderId: string) {\n        super();\n    }\n\n    getAggregateId(): string {\n        return this.orderId;\n    }\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/domain/events/order-confirmed.event.ts",
    "content": "import { DomainEvent } from '@/shared/domain/domain-event';\n\nexport class OrderConfirmedEvent extends DomainEvent {\n    constructor(public readonly orderId: string) {\n        super();\n    }\n\n    getAggregateId(): string {\n        return this.orderId;\n    }\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/domain/events/order-created.event.ts",
    "content": "import { DomainEvent } from '@/shared/domain/domain-event';\nimport { Money } from '../value-objects/money.vo';\n\nexport class OrderCreatedEvent extends DomainEvent {\n    constructor(\n        public readonly orderId: string,\n        public readonly customerId: string,\n        public readonly totalAmount: Money,\n    ) {\n        super();\n    }\n\n    getAggregateId(): string {\n        return this.orderId;\n    }\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/domain/exceptions/invalid-order-state.exception.ts",
    "content": "import { DomainException } from '@/shared/presentation/filters/domain-exception.filter';\n\nexport class InvalidOrderStateException extends DomainException {\n    constructor(message: string) {\n        super(message);\n    }\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/domain/exceptions/order-not-found.exception.ts",
    "content": "import { DomainException } from '@/shared/presentation/filters/domain-exception.filter';\n\nexport class OrderNotFoundException extends DomainException {\n    constructor(orderId: string) {\n        super(`Order with id ${orderId} not found`);\n    }\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/domain/repositories/order.repository.interface.ts",
    "content": "import { Order } from '../entities/order.entity';\n\nexport interface IOrderRepository {\n    findById(id: string): Promise<Order | null>;\n    findByCustomerId(customerId: string): Promise<Order[]>;\n    save(order: Order): Promise<Order>;\n    delete(id: string): Promise<void>;\n}\n\nexport const ORDER_REPOSITORY = Symbol('ORDER_REPOSITORY');\n"
  },
  {
    "path": "apps/api/src/modules/order/domain/services/order-pricing.service.spec.ts",
    "content": "import { OrderPricingService } from './order-pricing.service';\nimport { Order } from '../entities/order.entity';\nimport { OrderItem } from '../entities/order-item.entity';\nimport { Money } from '../value-objects/money.vo';\nimport { Quantity } from '../value-objects/quantity.vo';\n\ndescribe('OrderPricingService', () => {\n    let service: OrderPricingService;\n    let order: Order;\n\n    beforeEach(() => {\n        service = new OrderPricingService();\n\n        const items = [\n            OrderItem.create({\n                id: 'item-1',\n                productId: 'product-1',\n                quantity: Quantity.create(2),\n                unitPrice: Money.create(10),\n            }),\n            OrderItem.create({\n                id: 'item-2',\n                productId: 'product-2',\n                quantity: Quantity.create(1),\n                unitPrice: Money.create(20),\n            }),\n        ];\n\n        order = Order.create('customer-1', items);\n    });\n\n    describe('calculateTotal', () => {\n        it('should calculate total amount for order', () => {\n            const total = service.calculateTotal(order);\n\n            expect(total.amount).toBe(40); // (2 * 10) + (1 * 20)\n        });\n\n        it('should return zero for empty order', () => {\n            const emptyOrder = Order.create('customer-1', []);\n\n            const total = service.calculateTotal(emptyOrder);\n\n            expect(total.amount).toBe(0);\n        });\n    });\n\n    describe('applyDiscount', () => {\n        it('should apply discount percentage correctly', () => {\n            const total = Money.create(100);\n\n            const discounted = service.applyDiscount(total, 10);\n\n            expect(discounted.amount).toBe(90);\n        });\n\n        it('should apply 50% discount', () => {\n            const total = Money.create(100);\n\n            const discounted = service.applyDiscount(total, 50);\n\n            expect(discounted.amount).toBe(50);\n        });\n\n        it('should apply 100% discount', () => {\n            const total = Money.create(100);\n\n            const discounted = service.applyDiscount(total, 100);\n\n            expect(discounted.amount).toBe(0);\n        });\n\n        it('should apply 0% discount', () => {\n            const total = Money.create(100);\n\n            const discounted = service.applyDiscount(total, 0);\n\n            expect(discounted.amount).toBe(100);\n        });\n\n        it('should throw error for negative discount', () => {\n            const total = Money.create(100);\n\n            expect(() => service.applyDiscount(total, -10)).toThrow('Discount percent must be between 0 and 100');\n        });\n\n        it('should throw error for discount over 100%', () => {\n            const total = Money.create(100);\n\n            expect(() => service.applyDiscount(total, 101)).toThrow('Discount percent must be between 0 and 100');\n        });\n\n        it('should preserve currency after discount', () => {\n            const total = Money.create(100, 'EUR');\n\n            const discounted = service.applyDiscount(total, 10);\n\n            expect(discounted.currency).toBe('EUR');\n        });\n\n        it('should handle decimal discount percentages', () => {\n            const total = Money.create(100);\n\n            const discounted = service.applyDiscount(total, 12.5);\n\n            expect(discounted.amount).toBe(87.5);\n        });\n    });\n});\n"
  },
  {
    "path": "apps/api/src/modules/order/domain/services/order-pricing.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { Order } from '../entities/order.entity';\nimport { Money } from '../value-objects/money.vo';\n\n@Injectable()\nexport class OrderPricingService {\n    calculateTotal(order: Order): Money {\n        return order.getTotalAmount();\n    }\n\n    applyDiscount(total: Money, discountPercent: number): Money {\n        if (discountPercent < 0 || discountPercent > 100) {\n            throw new Error('Discount percent must be between 0 and 100');\n        }\n\n        const discountMultiplier = 1 - discountPercent / 100;\n        return Money.create(total.amount * discountMultiplier, total.currency);\n    }\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/domain/value-objects/money.vo.spec.ts",
    "content": "import { Money } from './money.vo';\n\ndescribe('Money Value Object', () => {\n    describe('create', () => {\n        it('should create money with valid amount', () => {\n            const money = Money.create(10.99, 'USD');\n\n            expect(money.amount).toBe(10.99);\n            expect(money.currency).toBe('USD');\n        });\n\n        it('should create money with default currency', () => {\n            const money = Money.create(10.99);\n\n            expect(money.amount).toBe(10.99);\n            expect(money.currency).toBe('USD');\n        });\n\n        it('should throw error for negative amount', () => {\n            expect(() => Money.create(-10)).toThrow('Money amount cannot be negative');\n        });\n\n        it('should allow zero amount', () => {\n            const money = Money.create(0);\n\n            expect(money.amount).toBe(0);\n        });\n    });\n\n    describe('add', () => {\n        it('should add money with same currency', () => {\n            const money1 = Money.create(10, 'USD');\n            const money2 = Money.create(5, 'USD');\n\n            const result = money1.add(money2);\n\n            expect(result.amount).toBe(15);\n            expect(result.currency).toBe('USD');\n        });\n\n        it('should throw error when adding different currencies', () => {\n            const money1 = Money.create(10, 'USD');\n            const money2 = Money.create(5, 'EUR');\n\n            expect(() => money1.add(money2)).toThrow('Cannot add money with different currencies');\n        });\n\n        it('should not mutate original money objects', () => {\n            const money1 = Money.create(10, 'USD');\n            const money2 = Money.create(5, 'USD');\n\n            money1.add(money2);\n\n            expect(money1.amount).toBe(10);\n            expect(money2.amount).toBe(5);\n        });\n    });\n\n    describe('multiply', () => {\n        it('should multiply money by positive number', () => {\n            const money = Money.create(10, 'USD');\n\n            const result = money.multiply(2);\n\n            expect(result.amount).toBe(20);\n            expect(result.currency).toBe('USD');\n        });\n\n        it('should multiply money by decimal', () => {\n            const money = Money.create(10, 'USD');\n\n            const result = money.multiply(1.5);\n\n            expect(result.amount).toBe(15);\n        });\n\n        it('should multiply money by zero', () => {\n            const money = Money.create(10, 'USD');\n\n            const result = money.multiply(0);\n\n            expect(result.amount).toBe(0);\n        });\n\n        it('should not mutate original money object', () => {\n            const money = Money.create(10, 'USD');\n\n            money.multiply(2);\n\n            expect(money.amount).toBe(10);\n        });\n    });\n\n    describe('equals', () => {\n        it('should return true for equal money objects', () => {\n            const money1 = Money.create(10, 'USD');\n            const money2 = Money.create(10, 'USD');\n\n            expect(money1.equals(money2)).toBe(true);\n        });\n\n        it('should return false for different amounts', () => {\n            const money1 = Money.create(10, 'USD');\n            const money2 = Money.create(20, 'USD');\n\n            expect(money1.equals(money2)).toBe(false);\n        });\n\n        it('should return false for different currencies', () => {\n            const money1 = Money.create(10, 'USD');\n            const money2 = Money.create(10, 'EUR');\n\n            expect(money1.equals(money2)).toBe(false);\n        });\n\n        it('should return false for null', () => {\n            const money = Money.create(10, 'USD');\n\n            expect(money.equals(null as any)).toBe(false);\n        });\n\n        it('should return false for undefined', () => {\n            const money = Money.create(10, 'USD');\n\n            expect(money.equals(undefined as any)).toBe(false);\n        });\n    });\n});\n"
  },
  {
    "path": "apps/api/src/modules/order/domain/value-objects/money.vo.ts",
    "content": "import { ValueObject } from '@/shared/domain/value-object';\nimport { Guard } from '@/shared/utils/guard';\n\ninterface MoneyProps {\n    amount: number;\n    currency: string;\n}\n\nexport class Money extends ValueObject<MoneyProps> {\n    get amount(): number {\n        return this.props.amount;\n    }\n\n    get currency(): string {\n        return this.props.currency;\n    }\n\n    private constructor(props: MoneyProps) {\n        super(props);\n    }\n\n    public static create(amount: number, currency: string = 'USD'): Money {\n        const guardResult = Guard.againstNullOrUndefined(amount, 'amount');\n        if (!guardResult.succeeded) {\n            throw new Error(guardResult.message);\n        }\n\n        if (amount < 0) {\n            throw new Error('Money amount cannot be negative');\n        }\n\n        return new Money({ amount, currency });\n    }\n\n    public add(money: Money): Money {\n        if (this.currency !== money.currency) {\n            throw new Error('Cannot add money with different currencies');\n        }\n        return Money.create(this.amount + money.amount, this.currency);\n    }\n\n    public multiply(multiplier: number): Money {\n        return Money.create(this.amount * multiplier, this.currency);\n    }\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/domain/value-objects/order-id.vo.spec.ts",
    "content": "import { OrderId } from './order-id.vo';\n\ndescribe('OrderId Value Object', () => {\n    describe('create', () => {\n        it('should create order id with provided value', () => {\n            const id = 'test-order-id';\n            const orderId = OrderId.create(id);\n\n            expect(orderId.value).toBe(id);\n        });\n\n        it('should generate UUID when no value provided', () => {\n            const orderId = OrderId.create();\n\n            expect(orderId.value).toBeDefined();\n            expect(orderId.value.length).toBeGreaterThan(0);\n            expect(orderId.value).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i);\n        });\n\n        it('should generate different UUIDs for each call', () => {\n            const orderId1 = OrderId.create();\n            const orderId2 = OrderId.create();\n\n            expect(orderId1.value).not.toBe(orderId2.value);\n        });\n    });\n\n    describe('toString', () => {\n        it('should return string representation', () => {\n            const id = 'test-order-id';\n            const orderId = OrderId.create(id);\n\n            expect(orderId.toString()).toBe(id);\n        });\n    });\n\n    describe('equals', () => {\n        it('should return true for equal order ids', () => {\n            const id = 'test-order-id';\n            const orderId1 = OrderId.create(id);\n            const orderId2 = OrderId.create(id);\n\n            expect(orderId1.equals(orderId2)).toBe(true);\n        });\n\n        it('should return false for different order ids', () => {\n            const orderId1 = OrderId.create('id-1');\n            const orderId2 = OrderId.create('id-2');\n\n            expect(orderId1.equals(orderId2)).toBe(false);\n        });\n    });\n});\n"
  },
  {
    "path": "apps/api/src/modules/order/domain/value-objects/order-id.vo.ts",
    "content": "import { ValueObject } from '@/shared/domain/value-object';\nimport { v4 as uuidv4 } from 'uuid';\n\ninterface OrderIdProps {\n    value: string;\n}\n\nexport class OrderId extends ValueObject<OrderIdProps> {\n    get value(): string {\n        return this.props.value;\n    }\n\n    private constructor(props: OrderIdProps) {\n        super(props);\n    }\n\n    public static create(id?: string): OrderId {\n        return new OrderId({ value: id || uuidv4() });\n    }\n\n    public toString(): string {\n        return this.value;\n    }\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/domain/value-objects/order-status.vo.spec.ts",
    "content": "import { OrderStatus, OrderStatusEnum } from './order-status.vo';\n\ndescribe('OrderStatus Value Object', () => {\n    describe('create', () => {\n        it('should create order status with valid enum value', () => {\n            const status = OrderStatus.create(OrderStatusEnum.PENDING);\n\n            expect(status.value).toBe(OrderStatusEnum.PENDING);\n        });\n    });\n\n    describe('factory methods', () => {\n        it('should create pending status', () => {\n            const status = OrderStatus.pending();\n\n            expect(status.value).toBe(OrderStatusEnum.PENDING);\n            expect(status.isPending()).toBe(true);\n        });\n\n        it('should create confirmed status', () => {\n            const status = OrderStatus.confirmed();\n\n            expect(status.value).toBe(OrderStatusEnum.CONFIRMED);\n            expect(status.isConfirmed()).toBe(true);\n        });\n\n        it('should create cancelled status', () => {\n            const status = OrderStatus.cancelled();\n\n            expect(status.value).toBe(OrderStatusEnum.CANCELLED);\n            expect(status.isCancelled()).toBe(true);\n        });\n\n        it('should create completed status', () => {\n            const status = OrderStatus.completed();\n\n            expect(status.value).toBe(OrderStatusEnum.COMPLETED);\n            expect(status.isCompleted()).toBe(true);\n        });\n    });\n\n    describe('status checks', () => {\n        it('isPending should return false for non-pending status', () => {\n            const status = OrderStatus.confirmed();\n\n            expect(status.isPending()).toBe(false);\n        });\n\n        it('isConfirmed should return false for non-confirmed status', () => {\n            const status = OrderStatus.pending();\n\n            expect(status.isConfirmed()).toBe(false);\n        });\n\n        it('isCancelled should return false for non-cancelled status', () => {\n            const status = OrderStatus.pending();\n\n            expect(status.isCancelled()).toBe(false);\n        });\n\n        it('isCompleted should return false for non-completed status', () => {\n            const status = OrderStatus.pending();\n\n            expect(status.isCompleted()).toBe(false);\n        });\n    });\n\n    describe('equals', () => {\n        it('should return true for equal statuses', () => {\n            const status1 = OrderStatus.pending();\n            const status2 = OrderStatus.pending();\n\n            expect(status1.equals(status2)).toBe(true);\n        });\n\n        it('should return false for different statuses', () => {\n            const status1 = OrderStatus.pending();\n            const status2 = OrderStatus.confirmed();\n\n            expect(status1.equals(status2)).toBe(false);\n        });\n    });\n});\n"
  },
  {
    "path": "apps/api/src/modules/order/domain/value-objects/order-status.vo.ts",
    "content": "import { ValueObject } from '@/shared/domain/value-object';\n\nexport enum OrderStatusEnum {\n    PENDING = 'PENDING',\n    CONFIRMED = 'CONFIRMED',\n    CANCELLED = 'CANCELLED',\n    COMPLETED = 'COMPLETED',\n}\n\ninterface OrderStatusProps {\n    value: OrderStatusEnum;\n}\n\nexport class OrderStatus extends ValueObject<OrderStatusProps> {\n    get value(): OrderStatusEnum {\n        return this.props.value;\n    }\n\n    private constructor(props: OrderStatusProps) {\n        super(props);\n    }\n\n    public static create(status: OrderStatusEnum): OrderStatus {\n        return new OrderStatus({ value: status });\n    }\n\n    public static pending(): OrderStatus {\n        return new OrderStatus({ value: OrderStatusEnum.PENDING });\n    }\n\n    public static confirmed(): OrderStatus {\n        return new OrderStatus({ value: OrderStatusEnum.CONFIRMED });\n    }\n\n    public static cancelled(): OrderStatus {\n        return new OrderStatus({ value: OrderStatusEnum.CANCELLED });\n    }\n\n    public static completed(): OrderStatus {\n        return new OrderStatus({ value: OrderStatusEnum.COMPLETED });\n    }\n\n    public isPending(): boolean {\n        return this.value === OrderStatusEnum.PENDING;\n    }\n\n    public isConfirmed(): boolean {\n        return this.value === OrderStatusEnum.CONFIRMED;\n    }\n\n    public isCancelled(): boolean {\n        return this.value === OrderStatusEnum.CANCELLED;\n    }\n\n    public isCompleted(): boolean {\n        return this.value === OrderStatusEnum.COMPLETED;\n    }\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/domain/value-objects/quantity.vo.spec.ts",
    "content": "import { Quantity } from './quantity.vo';\n\ndescribe('Quantity Value Object', () => {\n    describe('create', () => {\n        it('should create quantity with valid value', () => {\n            const quantity = Quantity.create(5);\n\n            expect(quantity.value).toBe(5);\n        });\n\n        it('should throw error for zero quantity', () => {\n            expect(() => Quantity.create(0)).toThrow('Quantity must be at least 1');\n        });\n\n        it('should throw error for negative quantity', () => {\n            expect(() => Quantity.create(-5)).toThrow('Quantity must be at least 1');\n        });\n\n        it('should throw error for non-integer quantity', () => {\n            expect(() => Quantity.create(5.5)).toThrow('Quantity must be an integer');\n        });\n\n        it('should allow large quantities', () => {\n            const quantity = Quantity.create(1000);\n\n            expect(quantity.value).toBe(1000);\n        });\n    });\n\n    describe('equals', () => {\n        it('should return true for equal quantities', () => {\n            const quantity1 = Quantity.create(5);\n            const quantity2 = Quantity.create(5);\n\n            expect(quantity1.equals(quantity2)).toBe(true);\n        });\n\n        it('should return false for different quantities', () => {\n            const quantity1 = Quantity.create(5);\n            const quantity2 = Quantity.create(10);\n\n            expect(quantity1.equals(quantity2)).toBe(false);\n        });\n\n        it('should return false for null', () => {\n            const quantity = Quantity.create(5);\n\n            expect(quantity.equals(null as any)).toBe(false);\n        });\n    });\n});\n"
  },
  {
    "path": "apps/api/src/modules/order/domain/value-objects/quantity.vo.ts",
    "content": "import { ValueObject } from '@/shared/domain/value-object';\n\ninterface QuantityProps {\n    value: number;\n}\n\nexport class Quantity extends ValueObject<QuantityProps> {\n    get value(): number {\n        return this.props.value;\n    }\n\n    private constructor(props: QuantityProps) {\n        super(props);\n    }\n\n    public static create(value: number): Quantity {\n        if (value < 1) {\n            throw new Error('Quantity must be at least 1');\n        }\n\n        if (!Number.isInteger(value)) {\n            throw new Error('Quantity must be an integer');\n        }\n\n        return new Quantity({ value });\n    }\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/infrastructure/cache/order-cache.service.ts",
    "content": "import { Injectable, Logger } from '@nestjs/common';\nimport { RedissonService } from '@a3s-lab/redisson';\nimport { Order } from '../../domain/entities/order.entity';\n\nconst ORDER_CACHE_PREFIX = 'order:';\nconst ORDER_LIST_CACHE_PREFIX = 'orders:customer:';\nconst DEFAULT_TTL = 3600; // 1 hour\n\n@Injectable()\nexport class OrderCacheService {\n    private readonly logger = new Logger(OrderCacheService.name);\n\n    constructor(private readonly redisson: RedissonService) {}\n\n    /**\n     * Cache an order by ID\n     */\n    async cacheOrder(order: Order, ttl: number = DEFAULT_TTL): Promise<void> {\n        const key = `${ORDER_CACHE_PREFIX}${order.id}`;\n        const data = this.serializeOrder(order);\n        await this.redisson.setJSON(key, data, ttl);\n        this.logger.debug(`Cached order: ${order.id}`);\n    }\n\n    /**\n     * Get cached order by ID\n     */\n    async getCachedOrder(orderId: string): Promise<SerializedOrder | null> {\n        const key = `${ORDER_CACHE_PREFIX}${orderId}`;\n        return this.redisson.getJSON<SerializedOrder>(key);\n    }\n\n    /**\n     * Invalidate order cache\n     */\n    async invalidateOrder(orderId: string): Promise<void> {\n        const key = `${ORDER_CACHE_PREFIX}${orderId}`;\n        await this.redisson.delete(key);\n        this.logger.debug(`Invalidated order cache: ${orderId}`);\n    }\n\n    /**\n     * Cache order list for a customer\n     */\n    async cacheCustomerOrders(\n        customerId: string,\n        orders: Order[],\n        ttl: number = DEFAULT_TTL,\n    ): Promise<void> {\n        const key = `${ORDER_LIST_CACHE_PREFIX}${customerId}`;\n        const data = orders.map((order) => this.serializeOrder(order));\n        await this.redisson.setJSON(key, data, ttl);\n        this.logger.debug(`Cached ${orders.length} orders for customer: ${customerId}`);\n    }\n\n    /**\n     * Get cached orders for a customer\n     */\n    async getCachedCustomerOrders(customerId: string): Promise<SerializedOrder[] | null> {\n        const key = `${ORDER_LIST_CACHE_PREFIX}${customerId}`;\n        return this.redisson.getJSON<SerializedOrder[]>(key);\n    }\n\n    /**\n     * Invalidate customer orders cache\n     */\n    async invalidateCustomerOrders(customerId: string): Promise<void> {\n        const key = `${ORDER_LIST_CACHE_PREFIX}${customerId}`;\n        await this.redisson.delete(key);\n        this.logger.debug(`Invalidated customer orders cache: ${customerId}`);\n    }\n\n    /**\n     * Get or set order with cache\n     */\n    async getOrSetOrder(\n        orderId: string,\n        factory: () => Promise<Order | null>,\n        ttl: number = DEFAULT_TTL,\n    ): Promise<SerializedOrder | null> {\n        const key = `${ORDER_CACHE_PREFIX}${orderId}`;\n\n        return this.redisson.getOrSet<SerializedOrder | null>(\n            key,\n            async () => {\n                const order = await factory();\n                return order ? this.serializeOrder(order) : null;\n            },\n            ttl,\n        );\n    }\n\n    /**\n     * Execute operation with distributed lock\n     * Prevents race conditions when updating orders\n     */\n    async withOrderLock<T>(\n        orderId: string,\n        operation: () => Promise<T>,\n        waitTime: number = 5000,\n        leaseTime: number = 10000,\n    ): Promise<T> {\n        const lockKey = `lock:order:${orderId}`;\n        return this.redisson.withLock(lockKey, operation, waitTime, leaseTime);\n    }\n\n    /**\n     * Increment order view count (for analytics)\n     */\n    async incrementOrderViewCount(orderId: string): Promise<number> {\n        const key = `order:views:${orderId}`;\n        return this.redisson.increment(key);\n    }\n\n    /**\n     * Get order view count\n     */\n    async getOrderViewCount(orderId: string): Promise<number> {\n        const key = `order:views:${orderId}`;\n        const count = await this.redisson.get(key);\n        return count ? parseInt(count, 10) : 0;\n    }\n\n    private serializeOrder(order: Order): SerializedOrder {\n        return {\n            id: order.id,\n            customerId: order.customerId,\n            status: order.status.value,\n            totalAmount: order.getTotalAmount().amount,\n            items: order.items.map((item) => ({\n                id: item.id,\n                productId: item.productId,\n                quantity: item.quantity.value,\n                unitPrice: item.unitPrice.amount,\n                subtotal: item.getTotalPrice().amount,\n            })),\n            createdAt: order.createdAt.toISOString(),\n            updatedAt: order.updatedAt.toISOString(),\n        };\n    }\n}\n\nexport interface SerializedOrder {\n    id: string;\n    customerId: string;\n    status: string;\n    totalAmount: number;\n    items: SerializedOrderItem[];\n    createdAt: string;\n    updatedAt: string;\n}\n\nexport interface SerializedOrderItem {\n    id: string;\n    productId: string;\n    quantity: number;\n    unitPrice: number;\n    subtotal: number;\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/infrastructure/persistence/kysely-order.repository.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { KyselyService } from '@a3s-lab/kysely';\nimport { Database, NewOrder, NewOrderItem } from '@/shared/database/database.types';\nimport { IOrderRepository } from '../../domain/repositories/order.repository.interface';\nimport { Order } from '../../domain/entities/order.entity';\nimport { OrderItem } from '../../domain/entities/order-item.entity';\nimport { OrderId } from '../../domain/value-objects/order-id.vo';\nimport { OrderStatus, OrderStatusEnum } from '../../domain/value-objects/order-status.vo';\nimport { Money } from '../../domain/value-objects/money.vo';\nimport { Quantity } from '../../domain/value-objects/quantity.vo';\n\n@Injectable()\nexport class OrderRepository implements IOrderRepository {\n    constructor(private readonly db: KyselyService<Database>) {}\n\n    async findById(id: string): Promise<Order | null> {\n        const orderRow = await this.db\n            .selectFrom('orders')\n            .where('id', '=', id)\n            .selectAll()\n            .executeTakeFirst();\n\n        if (!orderRow) {\n            return null;\n        }\n\n        const itemRows = await this.db\n            .selectFrom('order_items')\n            .where('order_id', '=', id)\n            .selectAll()\n            .execute();\n\n        return this.toDomain(orderRow, itemRows);\n    }\n\n    async findByCustomerId(customerId: string): Promise<Order[]> {\n        const orderRows = await this.db\n            .selectFrom('orders')\n            .where('customer_id', '=', customerId)\n            .selectAll()\n            .execute();\n\n        const orders: Order[] = [];\n\n        for (const orderRow of orderRows) {\n            const itemRows = await this.db\n                .selectFrom('order_items')\n                .where('order_id', '=', orderRow.id)\n                .selectAll()\n                .execute();\n\n            orders.push(this.toDomain(orderRow, itemRows));\n        }\n\n        return orders;\n    }\n\n    async save(order: Order): Promise<Order> {\n        return await this.db.transaction().execute(async (trx) => {\n            // Check if order exists\n            const existingOrder = await trx\n                .selectFrom('orders')\n                .where('id', '=', order.id)\n                .select('id')\n                .executeTakeFirst();\n\n            const statusValue = this.mapStatusToDb(order.status.value);\n\n            const orderData: NewOrder = {\n                id: order.id,\n                customer_id: order.customerId,\n                status: statusValue,\n                total_amount: order.getTotalAmount().amount,\n                created_at: order.createdAt.toISOString(),\n                updated_at: order.updatedAt.toISOString(),\n            };\n\n            if (existingOrder) {\n                // Update existing order\n                await trx\n                    .updateTable('orders')\n                    .set({\n                        status: orderData.status,\n                        total_amount: orderData.total_amount,\n                        updated_at: orderData.updated_at,\n                    })\n                    .where('id', '=', order.id)\n                    .execute();\n\n                // Delete existing items\n                await trx\n                    .deleteFrom('order_items')\n                    .where('order_id', '=', order.id)\n                    .execute();\n            } else {\n                // Insert new order\n                await trx\n                    .insertInto('orders')\n                    .values(orderData)\n                    .execute();\n            }\n\n            // Insert order items\n            if (order.items.length > 0) {\n                const itemsData: NewOrderItem[] = order.items.map((item) => ({\n                    id: item.id,\n                    order_id: order.id,\n                    product_id: item.productId,\n                    quantity: item.quantity.value,\n                    unit_price: item.unitPrice.amount,\n                    subtotal: item.getTotalPrice().amount,\n                    created_at: new Date().toISOString(),\n                }));\n\n                await trx\n                    .insertInto('order_items')\n                    .values(itemsData)\n                    .execute();\n            }\n\n            return order;\n        });\n    }\n\n    async delete(id: string): Promise<void> {\n        await this.db.transaction().execute(async (trx) => {\n            await trx\n                .deleteFrom('order_items')\n                .where('order_id', '=', id)\n                .execute();\n\n            await trx\n                .deleteFrom('orders')\n                .where('id', '=', id)\n                .execute();\n        });\n    }\n\n    private mapStatusToDb(status: OrderStatusEnum): 'pending' | 'confirmed' | 'cancelled' {\n        switch (status) {\n            case OrderStatusEnum.PENDING:\n                return 'pending';\n            case OrderStatusEnum.CONFIRMED:\n                return 'confirmed';\n            case OrderStatusEnum.CANCELLED:\n                return 'cancelled';\n            default:\n                return 'pending';\n        }\n    }\n\n    private mapStatusFromDb(status: string): OrderStatusEnum {\n        switch (status.toLowerCase()) {\n            case 'pending':\n                return OrderStatusEnum.PENDING;\n            case 'confirmed':\n                return OrderStatusEnum.CONFIRMED;\n            case 'cancelled':\n                return OrderStatusEnum.CANCELLED;\n            default:\n                return OrderStatusEnum.PENDING;\n        }\n    }\n\n    private toDomain(\n        orderRow: {\n            id: string;\n            customer_id: string;\n            status: string;\n            total_amount: number;\n            created_at: Date;\n            updated_at: Date;\n        },\n        itemRows: Array<{\n            id: string;\n            order_id: string;\n            product_id: string;\n            quantity: number;\n            unit_price: number;\n            subtotal: number;\n            created_at: Date;\n        }>,\n    ): Order {\n        const items = itemRows.map((itemRow) =>\n            OrderItem.create({\n                id: itemRow.id,\n                productId: itemRow.product_id,\n                quantity: Quantity.create(itemRow.quantity),\n                unitPrice: Money.create(itemRow.unit_price),\n            }),\n        );\n\n        return Order.reconstitute({\n            id: OrderId.create(orderRow.id),\n            customerId: orderRow.customer_id,\n            items,\n            status: OrderStatus.create(this.mapStatusFromDb(orderRow.status)),\n            createdAt: orderRow.created_at,\n            updatedAt: orderRow.updated_at,\n        });\n    }\n}\n"
  },
  {
    "path": "apps/api/src/modules/order/order.module.ts",
    "content": "import { Module } from '@nestjs/common';\nimport { CqrsModule } from '@nestjs/cqrs';\nimport { OrderController } from './presentation/order.controller';\nimport { OrderRepository } from './infrastructure/persistence/kysely-order.repository';\nimport { OrderCacheService } from './infrastructure/cache/order-cache.service';\nimport { ORDER_REPOSITORY } from './domain/repositories/order.repository.interface';\nimport { CreateOrderHandler } from './application/commands/create-order/create-order.handler';\nimport { ConfirmOrderHandler } from './application/commands/confirm-order/confirm-order.handler';\nimport { CancelOrderHandler } from './application/commands/cancel-order/cancel-order.handler';\nimport { GetOrderHandler } from './application/queries/get-order/get-order.handler';\nimport { ListOrdersHandler } from './application/queries/list-orders/list-orders.handler';\nimport { OrderCreatedHandler } from './application/event-handlers/order-created.handler';\nimport { OrderConfirmedHandler } from './application/event-handlers/order-confirmed.handler';\nimport { OrderPricingService } from './domain/services/order-pricing.service';\nimport { EventBusService } from '@/shared/infrastructure/messaging/event-bus.service';\nimport { EVENT_BUS } from '@/shared/infrastructure/messaging/event-bus.interface';\n\nconst CommandHandlers = [CreateOrderHandler, ConfirmOrderHandler, CancelOrderHandler];\nconst QueryHandlers = [GetOrderHandler, ListOrdersHandler];\nconst EventHandlers = [OrderCreatedHandler, OrderConfirmedHandler];\n\n@Module({\n    imports: [CqrsModule],\n    controllers: [OrderController],\n    providers: [\n        ...CommandHandlers,\n        ...QueryHandlers,\n        ...EventHandlers,\n        OrderPricingService,\n        OrderCacheService,\n        {\n            provide: ORDER_REPOSITORY,\n            useClass: OrderRepository,\n        },\n        {\n            provide: EVENT_BUS,\n            useClass: EventBusService,\n        },\n    ],\n    exports: [OrderCacheService],\n})\nexport class OrderModule {}\n"
  },
  {
    "path": "apps/api/src/modules/order/presentation/order.controller.ts",
    "content": "import { Controller, Get, Post, Body, Param, Query, HttpCode, HttpStatus } from '@nestjs/common';\nimport { CommandBus, QueryBus } from '@nestjs/cqrs';\nimport { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';\nimport { CreateOrderDto } from '../application/commands/create-order/create-order.dto';\nimport { CreateOrderCommand } from '../application/commands/create-order/create-order.command';\nimport { ConfirmOrderCommand } from '../application/commands/confirm-order/confirm-order.command';\nimport { CancelOrderCommand } from '../application/commands/cancel-order/cancel-order.command';\nimport { GetOrderQuery } from '../application/queries/get-order/get-order.query';\nimport { ListOrdersQuery } from '../application/queries/list-orders/list-orders.query';\nimport { OrderResponseDto } from '../application/queries/get-order/order.response.dto';\nimport { OrderListResponseDto } from '../application/queries/list-orders/order-list.response.dto';\n\n@ApiTags('orders')\n@Controller('orders')\nexport class OrderController {\n    constructor(\n        private readonly commandBus: CommandBus,\n        private readonly queryBus: QueryBus,\n    ) {}\n\n    @Post()\n    @ApiOperation({ summary: 'Create a new order' })\n    @ApiResponse({ status: 201, description: 'Order created successfully' })\n    async createOrder(@Body() dto: CreateOrderDto): Promise<{ orderId: string }> {\n        const command = new CreateOrderCommand(dto.customerId, dto.items);\n        const orderId = await this.commandBus.execute(command);\n        return { orderId };\n    }\n\n    @Get(':id')\n    @ApiOperation({ summary: 'Get order by ID' })\n    @ApiResponse({ status: 200, type: OrderResponseDto })\n    async getOrder(@Param('id') id: string): Promise<OrderResponseDto> {\n        const query = new GetOrderQuery(id);\n        return this.queryBus.execute(query);\n    }\n\n    @Get()\n    @ApiOperation({ summary: 'List orders' })\n    @ApiResponse({ status: 200, type: OrderListResponseDto })\n    async listOrders(@Query('customerId') customerId?: string): Promise<OrderListResponseDto> {\n        const query = new ListOrdersQuery(customerId);\n        return this.queryBus.execute(query);\n    }\n\n    @Post(':id/confirm')\n    @HttpCode(HttpStatus.NO_CONTENT)\n    @ApiOperation({ summary: 'Confirm an order' })\n    @ApiResponse({ status: 204, description: 'Order confirmed successfully' })\n    async confirmOrder(@Param('id') id: string): Promise<void> {\n        const command = new ConfirmOrderCommand(id);\n        await this.commandBus.execute(command);\n    }\n\n    @Post(':id/cancel')\n    @HttpCode(HttpStatus.NO_CONTENT)\n    @ApiOperation({ summary: 'Cancel an order' })\n    @ApiResponse({ status: 204, description: 'Order cancelled successfully' })\n    async cancelOrder(@Param('id') id: string): Promise<void> {\n        const command = new CancelOrderCommand(id);\n        await this.commandBus.execute(command);\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/api-response/api-response.dto.ts",
    "content": "// ============================================================================\n// API Response DTO - Standardized API response wrapper\n// ============================================================================\n\nimport { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';\n\nexport class ApiResponseDto<T> {\n    @ApiProperty({ description: 'Response code', example: 200 })\n    code: number;\n\n    @ApiProperty({ description: 'Response message', example: 'Success' })\n    message: string;\n\n    @ApiPropertyOptional({ description: 'Response data' })\n    data?: T;\n\n    @ApiPropertyOptional({ description: 'Request ID for tracing' })\n    requestId?: string;\n\n    @ApiProperty({ description: 'Timestamp' })\n    timestamp: string;\n\n    constructor(partial: Partial<ApiResponseDto<T>>) {\n        Object.assign(this, partial);\n        this.timestamp = this.timestamp || new Date().toISOString();\n    }\n}\n\nexport class PaginatedResponseDto<T> {\n    @ApiProperty({ description: 'Items array', type: () => Array })\n    items: T[];\n\n    @ApiProperty({ description: 'Total count' })\n    total: number;\n\n    @ApiProperty({ description: 'Current page' })\n    page: number;\n\n    @ApiProperty({ description: 'Page size' })\n    pageSize: number;\n\n    @ApiProperty({ description: 'Total pages' })\n    totalPages: number;\n\n    @ApiProperty({ description: 'Has next page' })\n    hasNext: boolean;\n\n    @ApiProperty({ description: 'Has previous page' })\n    hasPrevious: boolean;\n\n    constructor(partial: Partial<PaginatedResponseDto<T>>) {\n        Object.assign(this, partial);\n    }\n}\n\nexport class ApiErrorResponseDto {\n    @ApiProperty({ description: 'Error code', example: 'NOT_FOUND' })\n    code: string;\n\n    @ApiProperty({ description: 'Error message', example: 'Resource not found' })\n    message: string;\n\n    @ApiPropertyOptional({ description: 'Detailed error info' })\n    details?: Record<string, unknown>;\n\n    @ApiProperty({ description: 'Timestamp' })\n    timestamp: string;\n\n    @ApiPropertyOptional({ description: 'Request ID for tracing' })\n    requestId?: string;\n\n    constructor(partial: Partial<ApiErrorResponseDto>) {\n        Object.assign(this, partial);\n        this.timestamp = this.timestamp || new Date().toISOString();\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/api-response/api-response.interceptor.ts",
    "content": "// ============================================================================\n// API Response Interceptor - Wrap all responses in ApiResponseDto\n// ============================================================================\n\nimport {\n    Injectable,\n    NestInterceptor,\n    ExecutionContext,\n    CallHandler,\n} from '@nestjs/common';\nimport { Observable } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport { Request } from 'express';\nimport { ApiResponseDto } from './api-response.dto';\n\n@Injectable()\nexport class ApiResponseInterceptor implements NestInterceptor {\n    intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {\n        const request = context.switchToHttp().getRequest<Request>();\n        const requestId =\n            (request.headers['x-request-id'] as string) ||\n            (request.headers['x-correlation-id'] as string);\n\n        return next.handle().pipe(\n            map((data) => {\n                // If already wrapped in ApiResponseDto, return as is\n                if (data instanceof ApiResponseDto) {\n                    return data;\n                }\n\n                // Return wrapped response\n                return new ApiResponseDto({\n                    code: 200,\n                    message: 'Success',\n                    data,\n                    requestId,\n                    timestamp: new Date().toISOString(),\n                });\n            }),\n        );\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/api-response/api-response.service.ts",
    "content": "// ============================================================================\n// API Response Service - Factory for creating standardized responses\n// ============================================================================\n\nimport { Injectable, Logger } from '@nestjs/common';\nimport { Request } from 'express';\nimport {\n    ApiResponseDto,\n    PaginatedResponseDto,\n    ApiErrorResponseDto,\n} from './api-response.dto';\n\n@Injectable()\nexport class ApiResponseService {\n    private readonly logger = new Logger(ApiResponseService.name);\n\n    /**\n     * Create a success response\n     */\n    success<T>(data?: T, message = 'Success', requestId?: string): ApiResponseDto<T> {\n        return new ApiResponseDto<T>({\n            code: 200,\n            message,\n            data,\n            requestId,\n        });\n    }\n\n    /**\n     * Create a created response (201)\n     */\n    created<T>(data?: T, message = 'Created', requestId?: string): ApiResponseDto<T> {\n        return new ApiResponseDto<T>({\n            code: 201,\n            message,\n            data,\n            requestId,\n        });\n    }\n\n    /**\n     * Create an accepted response (202)\n     */\n    accepted<T>(data?: T, message = 'Accepted', requestId?: string): ApiResponseDto<T> {\n        return new ApiResponseDto<T>({\n            code: 202,\n            message,\n            data,\n            requestId,\n        });\n    }\n\n    /**\n     * Create a no content response (204)\n     */\n    noContent(requestId?: string): ApiResponseDto<null> {\n        return new ApiResponseDto<null>({\n            code: 204,\n            message: 'No Content',\n            requestId,\n        });\n    }\n\n    /**\n     * Create a paginated response\n     */\n    paginated<T>(\n        items: T[],\n        total: number,\n        page: number,\n        pageSize: number,\n        message = 'Success',\n        requestId?: string,\n    ): PaginatedResponseDto<T> {\n        const totalPages = Math.ceil(total / pageSize);\n\n        return new PaginatedResponseDto<T>({\n            items,\n            total,\n            page,\n            pageSize,\n            totalPages,\n            hasNext: page < totalPages,\n            hasPrevious: page > 1,\n        });\n    }\n\n    /**\n     * Create an error response\n     */\n    error(\n        code: string,\n        message: string,\n        details?: Record<string, unknown>,\n        requestId?: string,\n    ): ApiErrorResponseDto {\n        return new ApiErrorResponseDto({\n            code,\n            message,\n            details,\n            requestId,\n        });\n    }\n\n    /**\n     * Get request ID from request object\n     */\n    getRequestId(request: Request): string | undefined {\n        return (\n            (request.headers['x-request-id'] as string) ||\n            (request.headers['x-correlation-id'] as string) ||\n            undefined\n        );\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/api-response/index.ts",
    "content": "export * from './api-response.dto';\nexport * from './api-response.service';\nexport * from './api-response.interceptor';\n"
  },
  {
    "path": "apps/api/src/shared/api-versioning/api-versioning.decorator.ts",
    "content": "// ============================================================================\n// API Versioning Decorators\n// ============================================================================\n\nimport { SetMetadata } from '@nestjs/common';\n\nexport const API_VERSION_KEY = 'api_version';\n\n/**\n * Set API version for a controller or route\n */\nexport const ApiVersion = (version: string) => SetMetadata(API_VERSION_KEY, version);\n\n/**\n * Mark endpoint as deprecated\n */\nexport const Deprecated = () => SetMetadata('isDeprecated', true);\n\n/**\n * Set sunset date for endpoint\n */\nexport const Sunset = (date: Date) => SetMetadata('sunsetDate', date);\n"
  },
  {
    "path": "apps/api/src/shared/api-versioning/api-versioning.interceptor.ts",
    "content": "// ============================================================================\n// API Versioning - URL and Header based versioning\n// ============================================================================\n\nimport { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';\nimport { Observable } from 'rxjs';\nimport { Request } from 'express';\n\nexport interface ApiVersionOptions {\n    /** Current API version */\n    version: string;\n    /** Deprecation info */\n    deprecated?: boolean;\n    /** Sunset date */\n    sunsetDate?: Date;\n}\n\n/**\n * Default API version\n */\nexport const DEFAULT_API_VERSION = '1';\n\n/**\n * Extract API version from URL path\n * Supports /api/v1, /api/v2 patterns\n */\nexport function extractVersionFromUrl(url: string): string | null {\n    const match = url.match(/\\/api\\/v(\\d+)/);\n    return match ? match[1] : null;\n}\n\n/**\n * Extract API version from Accept-Header\n * Supports: application/vnd.api.v1+json\n */\nexport function extractVersionFromHeader(header: string | string[] | undefined): string | null {\n    if (!header) return null;\n\n    const headerValue = Array.isArray(header) ? header[0] : header;\n    const match = headerValue.match(/v(\\d+)/);\n    return match ? match[1] : null;\n}\n\n/**\n * Extract API version from custom header\n * Supports: X-API-Version: 1\n */\nexport function extractVersionFromCustomHeader(\n    header: string | string[] | undefined,\n): string | null {\n    if (!header) return null;\n    const value = Array.isArray(header) ? header[0] : header;\n    return value || null;\n}\n\n@Injectable()\nexport class ApiVersioningInterceptor implements NestInterceptor {\n    intercept(context: ExecutionContext, next: CallHandler): Observable<any> {\n        const request = context.switchToHttp().getRequest<Request>();\n\n        // Try URL first (e.g., /api/v1/users)\n        let version = extractVersionFromUrl(request.url);\n\n        // Fallback to Accept header\n        if (!version) {\n            version = extractVersionFromHeader(request.headers.accept);\n        }\n\n        // Fallback to custom header\n        if (!version) {\n            version = extractVersionFromCustomHeader(\n                request.headers['x-api-version'],\n            );\n        }\n\n        // Default to v1 if no version specified\n        if (!version) {\n            version = DEFAULT_API_VERSION;\n        }\n\n        // Attach version to request\n        (request as any).apiVersion = version;\n\n        return next.handle();\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/api-versioning/index.ts",
    "content": "// ============================================================================\n// API Versioning Module\n// ============================================================================\n\nexport { ApiVersioningInterceptor } from './api-versioning.interceptor';\nexport { ApiVersion, Deprecated, Sunset } from './api-versioning.decorator';\n"
  },
  {
    "path": "apps/api/src/shared/application/dto.base.ts",
    "content": "export abstract class BaseDto {\n    constructor(partial?: Partial<any>) {\n        if (partial) {\n            Object.assign(this, partial);\n        }\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/application/query.interface.ts",
    "content": "export interface IQuery<IResponse> {\n    execute(): Promise<IResponse>;\n}\n"
  },
  {
    "path": "apps/api/src/shared/application/use-case.interface.ts",
    "content": "export interface IUseCase<IRequest, IResponse> {\n    execute(request: IRequest): Promise<IResponse>;\n}\n"
  },
  {
    "path": "apps/api/src/shared/audit/audit.decorator.ts",
    "content": "// ============================================================================\n// Audit Decorators\n// ============================================================================\n\nimport { SetMetadata } from '@nestjs/common';\n\nexport const AUDIT_KEY = 'audit';\n\n/**\n * Mark endpoint to be audited\n */\nexport const Audited = (resource?: string) => SetMetadata(AUDIT_KEY, { resource });\n"
  },
  {
    "path": "apps/api/src/shared/audit/audit.interceptor.ts",
    "content": "// ============================================================================\n// Audit Interceptor - Automatically logs operations\n// ============================================================================\n\nimport {\n    Injectable,\n    NestInterceptor,\n    ExecutionContext,\n    CallHandler,\n    Logger,\n} from '@nestjs/common';\nimport { Observable } from 'rxjs';\nimport { tap, catchError } from 'rxjs/operators';\nimport { AuditService } from './audit.service';\nimport { Request } from 'express';\n\nexport const AUDIT_ACTION_KEY = 'audit_action';\nexport const AUDIT_RESOURCE_KEY = 'audit_resource';\n\n/**\n * Decorator to mark endpoint for audit logging\n */\nexport function AuditedAction(action: string) {\n    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {\n        Reflect.defineMetadata(AUDIT_ACTION_KEY, action, descriptor.value);\n        return descriptor;\n    };\n}\n\n/**\n * Decorator to specify audit resource\n */\nexport function AuditedResource(resource: string) {\n    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {\n        Reflect.defineMetadata(AUDIT_RESOURCE_KEY, resource, descriptor.value);\n        return descriptor;\n    };\n}\n\n@Injectable()\nexport class AuditInterceptor implements NestInterceptor {\n    private readonly logger = new Logger(AuditInterceptor.name);\n\n    constructor(private readonly auditService: AuditService) {}\n\n    intercept(context: ExecutionContext, next: CallHandler): Observable<any> {\n        const request = context.switchToHttp().getRequest<Request>();\n        const user = (request as any).user;\n        const action = this.getAction(context);\n        const resource = this.getResource(context);\n\n        if (!user?.sub || !action || !resource) {\n            return next.handle();\n        }\n\n        const startTime = Date.now();\n        const ipAddress = this.getClientIp(request);\n        const userAgent = request.headers['user-agent'];\n\n        return next.handle().pipe(\n            tap(async (response) => {\n                const duration = Date.now() - startTime;\n                await this.auditService.log({\n                    userId: user.sub,\n                    organizationId: user.organizationId,\n                    action,\n                    resource,\n                    resourceId: response?.id ?? request.params?.id,\n                    metadata: {\n                        method: request.method,\n                        path: request.path,\n                        duration,\n                    },\n                    ipAddress,\n                    userAgent,\n                    status: 'success',\n                });\n            }),\n            catchError(async (error) => {\n                const duration = Date.now() - startTime;\n                await this.auditService.log({\n                    userId: user.sub,\n                    organizationId: user.organizationId,\n                    action,\n                    resource,\n                    resourceId: request.params?.id,\n                    metadata: {\n                        method: request.method,\n                        path: request.path,\n                        duration,\n                    },\n                    ipAddress,\n                    userAgent,\n                    status: 'error',\n                    errorMessage: error.message,\n                });\n\n                throw error;\n            }),\n        );\n    }\n\n    private getAction(context: ExecutionContext): string | undefined {\n        return (\n            Reflect.getMetadata(AUDIT_ACTION_KEY, context.getHandler()) ||\n            this.inferAction(context)\n        );\n    }\n\n    private getResource(context: ExecutionContext): string | undefined {\n        return (\n            Reflect.getMetadata(AUDIT_RESOURCE_KEY, context.getHandler()) ||\n            this.inferResource(context)\n        );\n    }\n\n    private inferAction(context: ExecutionContext): string {\n        const method = context.getHandler().name;\n        const httpMethod = context.switchToHttp().getRequest().method;\n\n        // Map HTTP methods to CRUD actions\n        const actionMap: Record<string, string> = {\n            GET: 'read',\n            POST: 'create',\n            PUT: 'update',\n            PATCH: 'update',\n            DELETE: 'delete',\n        };\n\n        return actionMap[httpMethod] ?? method;\n    }\n\n    private inferResource(context: ExecutionContext): string {\n        const controller = context.getClass();\n        return controller.name.replace('Controller', '').toLowerCase();\n    }\n\n    private getClientIp(request: Request): string {\n        const forwarded = request.headers['x-forwarded-for'];\n        if (forwarded) {\n            const ips = Array.isArray(forwarded) ? forwarded[0] : forwarded.split(',')[0];\n            return ips.trim();\n        }\n        return request.ip ?? request.socket.remoteAddress ?? 'unknown';\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/audit/audit.service.ts",
    "content": "// ============================================================================\n// Audit Service - Operation logging for compliance\n// ============================================================================\n\nimport { Injectable, Logger } from '@nestjs/common';\nimport { KyselyService } from '@a3s-lab/kysely';\nimport { v4 as uuidv4 } from 'uuid';\n\nexport interface AuditLogEntry {\n    id?: string;\n    timestamp?: Date;\n    userId: string;\n    organizationId: string;\n    action: string;\n    resource: string;\n    resourceId?: string;\n    changes?: Record<string, { before: unknown; after: unknown }>;\n    metadata?: Record<string, unknown>;\n    ipAddress?: string;\n    userAgent?: string;\n    status: 'success' | 'failure' | 'error';\n    errorMessage?: string;\n}\n\nexport interface AuditQueryOptions {\n    userId?: string;\n    organizationId: string;\n    resource?: string;\n    action?: string;\n    startDate?: Date;\n    endDate?: Date;\n    page?: number;\n    pageSize?: number;\n}\n\nexport interface PaginatedAuditResult {\n    items: AuditLogEntry[];\n    total: number;\n    page: number;\n    pageSize: number;\n}\n\n/**\n * Audit Service - logs all operations for compliance\n */\n@Injectable()\nexport class AuditService {\n    private readonly logger = new Logger(AuditService.name);\n    private readonly tableName = 'audit_logs';\n\n    constructor(private readonly kysely: KyselyService<any>) {}\n\n    /**\n     * Log an audit entry\n     */\n    async log(entry: AuditLogEntry): Promise<void> {\n        const auditEntry: Record<string, unknown> = {\n            id: entry.id ?? uuidv4(),\n            timestamp: entry.timestamp ?? new Date(),\n            user_id: entry.userId,\n            organization_id: entry.organizationId,\n            action: entry.action,\n            resource: entry.resource,\n            resource_id: entry.resourceId,\n            changes: entry.changes ? JSON.stringify(entry.changes) : null,\n            metadata: entry.metadata ? JSON.stringify(entry.metadata) : null,\n            ip_address: entry.ipAddress,\n            user_agent: entry.userAgent,\n            status: entry.status,\n            error_message: entry.errorMessage,\n        };\n\n        try {\n            await this.kysely.insertInto(this.tableName).values(auditEntry).executeTakeFirst();\n            this.logger.debug(`Audit log created: ${entry.action} on ${entry.resource}`);\n        } catch (error) {\n            // Don't fail the operation if audit logging fails\n            this.logger.error(`Failed to write audit log: ${error}`);\n        }\n    }\n\n    /**\n     * Log entity creation\n     */\n    async logCreate(\n        userId: string,\n        organizationId: string,\n        resource: string,\n        resourceId: string,\n        data: Record<string, unknown>,\n        metadata?: Record<string, unknown>,\n    ): Promise<void> {\n        await this.log({\n            userId,\n            organizationId,\n            action: 'create',\n            resource,\n            resourceId,\n            changes: Object.fromEntries(\n                Object.entries(data).map(([key, value]) => [key, { before: null, after: value }]),\n            ),\n            metadata,\n            status: 'success',\n        });\n    }\n\n    /**\n     * Log entity update\n     */\n    async logUpdate(\n        userId: string,\n        organizationId: string,\n        resource: string,\n        resourceId: string,\n        before: Record<string, unknown>,\n        after: Record<string, unknown>,\n        metadata?: Record<string, unknown>,\n    ): Promise<void> {\n        const changes: Record<string, { before: unknown; after: unknown }> = {};\n\n        for (const key of Object.keys(after)) {\n            if (JSON.stringify(before[key]) !== JSON.stringify(after[key])) {\n                changes[key] = { before: before[key], after: after[key] };\n            }\n        }\n\n        if (Object.keys(changes).length > 0) {\n            await this.log({\n                userId,\n                organizationId,\n                action: 'update',\n                resource,\n                resourceId,\n                changes,\n                metadata,\n                status: 'success',\n            });\n        }\n    }\n\n    /**\n     * Log entity deletion\n     */\n    async logDelete(\n        userId: string,\n        organizationId: string,\n        resource: string,\n        resourceId: string,\n        data: Record<string, unknown>,\n        metadata?: Record<string, unknown>,\n    ): Promise<void> {\n        await this.log({\n            userId,\n            organizationId,\n            action: 'delete',\n            resource,\n            resourceId,\n            changes: Object.fromEntries(\n                Object.entries(data).map(([key, value]) => [key, { before: value, after: null }]),\n            ),\n            metadata,\n            status: 'success',\n        });\n    }\n\n    /**\n     * Log failed operation\n     */\n    async logFailure(\n        userId: string,\n        organizationId: string,\n        action: string,\n        resource: string,\n        errorMessage: string,\n        metadata?: Record<string, unknown>,\n    ): Promise<void> {\n        await this.log({\n            userId,\n            organizationId,\n            action,\n            resource,\n            metadata,\n            status: 'failure',\n            errorMessage,\n        });\n    }\n\n    /**\n     * Query audit logs\n     */\n    async query(options: AuditQueryOptions): Promise<PaginatedAuditResult> {\n        const page = options.page ?? 1;\n        const pageSize = options.pageSize ?? 20;\n        const offset = (page - 1) * pageSize;\n\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n        let query = (this.kysely as any).selectFrom(this.tableName).where(\n            'organization_id',\n            '=',\n            options.organizationId,\n        );\n\n        if (options.userId) {\n            query = query.where('user_id', '=', options.userId);\n        }\n\n        if (options.resource) {\n            query = query.where('resource', '=', options.resource);\n        }\n\n        if (options.action) {\n            query = query.where('action', '=', options.action);\n        }\n\n        if (options.startDate) {\n            query = query.where('timestamp', '>=', options.startDate);\n        }\n\n        if (options.endDate) {\n            query = query.where('timestamp', '<=', options.endDate);\n        }\n\n        const countResult = await query\n            .select((eb: any) => eb.fn.countAll().as('count'))\n            .executeTakeFirst();\n\n        const total = Number((countResult as { count?: number })?.count ?? 0);\n\n        const items = await query\n            .orderBy('timestamp', 'desc')\n            .limit(pageSize)\n            .offset(offset)\n            .execute();\n\n        return {\n            items: items as AuditLogEntry[],\n            total,\n            page,\n            pageSize,\n        };\n    }\n\n    /**\n     * Get audit log by ID\n     */\n    async getById(id: string, organizationId: string): Promise<AuditLogEntry | null> {\n        const row = await this.kysely\n            .selectFrom(this.tableName)\n            .where('id', '=', id)\n            .where('organization_id', '=', organizationId)\n            .executeTakeFirst();\n\n        return (row as AuditLogEntry) || null;\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/audit/index.ts",
    "content": "// ============================================================================\n// Audit Module\n// ============================================================================\n\nexport * from './audit.service';\nexport * from './audit.interceptor';\nexport * from './audit.decorator';\n"
  },
  {
    "path": "apps/api/src/shared/auth/auth.module.ts",
    "content": "// ============================================================================\n// Auth Module - Authentication and Authorization\n// ============================================================================\n\nimport { Module, Global } from '@nestjs/common';\nimport { JwtService } from './jwt/jwt.service';\nimport { RbacService } from './rbac/rbac.service';\nimport { JwtAuthGuard } from './guards/jwt-auth.guard';\nimport { RolesGuard } from './guards/roles.guard';\nimport { PermissionsGuard } from './guards/permissions.guard';\n\n@Global()\n@Module({\n    providers: [\n        JwtService,\n        RbacService,\n        JwtAuthGuard,\n        RolesGuard,\n        PermissionsGuard,\n    ],\n    exports: [\n        JwtService,\n        RbacService,\n        JwtAuthGuard,\n        RolesGuard,\n        PermissionsGuard,\n    ],\n})\nexport class AuthModule {}\n"
  },
  {
    "path": "apps/api/src/shared/auth/decorators/current-user.decorator.ts",
    "content": "// ============================================================================\n// Auth Decorators - Convenience decorators for extracting user info\n// ============================================================================\n\nimport { createParamDecorator, ExecutionContext } from '@nestjs/common';\nimport { JwtPayload } from '../jwt/jwt.types';\n\n/**\n * Get current user from request\n */\nexport const CurrentUser = createParamDecorator(\n    (data: keyof JwtPayload | undefined, ctx: ExecutionContext) => {\n        const request = ctx.switchToHttp().getRequest();\n        const user = request.user as JwtPayload;\n\n        if (!user) {\n            return null;\n        }\n\n        return data ? user[data] : user;\n    },\n);\n\n/**\n * Get current user ID\n */\nexport const CurrentUserId = createParamDecorator(\n    (_data: unknown, ctx: ExecutionContext) => {\n        const request = ctx.switchToHttp().getRequest();\n        const user = request.user as JwtPayload;\n        return user?.sub;\n    },\n);\n\n/**\n * Get current organization ID\n */\nexport const CurrentOrganization = createParamDecorator(\n    (_data: unknown, ctx: ExecutionContext) => {\n        const request = ctx.switchToHttp().getRequest();\n        const user = request.user as JwtPayload;\n        return user?.organizationId;\n    },\n);\n\n/**\n * Get current user roles\n */\nexport const CurrentRoles = createParamDecorator(\n    (_data: unknown, ctx: ExecutionContext) => {\n        const request = ctx.switchToHttp().getRequest();\n        const user = request.user as JwtPayload;\n        return user?.roles ?? [];\n    },\n);\n"
  },
  {
    "path": "apps/api/src/shared/auth/decorators/index.ts",
    "content": "// ============================================================================\n// Auth Decorators\n// ============================================================================\n\nexport * from './current-user.decorator';\n"
  },
  {
    "path": "apps/api/src/shared/auth/dto/auth.dto.ts",
    "content": "// ============================================================================\n// Auth DTOs - Data Transfer Objects for authentication\n// ============================================================================\n\nimport { IsEmail, IsString, MinLength, MaxLength, IsOptional } from 'class-validator';\nimport { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';\n\n/**\n * Login DTO\n */\nexport class LoginDto {\n    @IsEmail()\n    @ApiProperty({ description: 'User email', example: 'user@example.com' })\n    email: string;\n\n    @IsString()\n    @MinLength(8)\n    @ApiProperty({ description: 'User password', minLength: 8 })\n    password: string;\n}\n\n/**\n * Register DTO\n */\nexport class RegisterDto {\n    @IsEmail()\n    @ApiProperty({ description: 'User email', example: 'user@example.com' })\n    email: string;\n\n    @IsString()\n    @MinLength(3)\n    @MaxLength(30)\n    @ApiProperty({ description: 'Username', minLength: 3, maxLength: 30 })\n    username: string;\n\n    @IsString()\n    @MinLength(8)\n    @ApiProperty({ description: 'Password', minLength: 8 })\n    password: string;\n\n    @IsOptional()\n    @IsString()\n    @ApiPropertyOptional({ description: 'Display name' })\n    displayName?: string;\n\n    @IsOptional()\n    @IsString()\n    @ApiPropertyOptional({ description: 'Organization name (for first user)' })\n    organizationName?: string;\n}\n\n/**\n * Refresh Token DTO\n */\nexport class RefreshTokenDto {\n    @IsString()\n    @ApiProperty({ description: 'Refresh token' })\n    refreshToken: string;\n}\n\n/**\n * Change Password DTO\n */\nexport class ChangePasswordDto {\n    @IsString()\n    @ApiProperty({ description: 'Current password' })\n    currentPassword: string;\n\n    @IsString()\n    @MinLength(8)\n    @ApiProperty({ description: 'New password', minLength: 8 })\n    newPassword: string;\n}\n\n/**\n * Forgot Password DTO\n */\nexport class ForgotPasswordDto {\n    @IsEmail()\n    @ApiProperty({ description: 'User email' })\n    email: string;\n}\n\n/**\n * Reset Password DTO\n */\nexport class ResetPasswordDto {\n    @IsString()\n    @ApiProperty({ description: 'Reset token' })\n    resetToken: string;\n\n    @IsString()\n    @MinLength(8)\n    @ApiProperty({ description: 'New password', minLength: 8 })\n    newPassword: string;\n}\n\n/**\n * Verify Email DTO\n */\nexport class VerifyEmailDto {\n    @IsString()\n    @ApiProperty({ description: 'Verification token' })\n    verifyToken: string;\n}\n\n/**\n * Token Response DTO\n */\nexport class TokenResponseDto {\n    @ApiProperty({ description: 'Access token' })\n    accessToken: string;\n\n    @ApiProperty({ description: 'Refresh token' })\n    refreshToken: string;\n\n    @ApiProperty({ description: 'Token type', default: 'Bearer' })\n    tokenType: string;\n\n    @ApiProperty({ description: 'Expires in seconds' })\n    expiresIn: number;\n}\n\n/**\n * User Response DTO (public info)\n */\nexport class UserResponseDto {\n    @ApiProperty({ description: 'User ID' })\n    id: string;\n\n    @ApiProperty({ description: 'Email' })\n    email: string;\n\n    @ApiProperty({ description: 'Username' })\n    username: string;\n\n    @ApiPropertyOptional({ description: 'Display name' })\n    displayName?: string;\n\n    @ApiProperty({ description: 'Roles' })\n    roles: string[];\n}\n"
  },
  {
    "path": "apps/api/src/shared/auth/dto/index.ts",
    "content": "// ============================================================================\n// Auth DTOs\n// ============================================================================\n\nexport * from './auth.dto';\n"
  },
  {
    "path": "apps/api/src/shared/auth/guards/index.ts",
    "content": "// ============================================================================\n// Auth Guards\n// ============================================================================\n\nexport * from './jwt-auth.guard';\nexport * from './roles.guard';\nexport * from './permissions.guard';\n"
  },
  {
    "path": "apps/api/src/shared/auth/guards/jwt-auth.guard.ts",
    "content": "// ============================================================================\n// JWT Auth Guard - Validates JWT tokens\n// ============================================================================\n\nimport {\n    Injectable,\n    CanActivate,\n    ExecutionContext,\n    UnauthorizedException,\n    SetMetadata,\n} from '@nestjs/common';\nimport { JwtService } from '../jwt/jwt.service';\nimport { Request } from 'express';\nimport { JwtPayload } from '../jwt/jwt.types';\n\n/**\n * Metadata key for public routes (skip auth)\n */\nexport const IS_PUBLIC_KEY = 'isPublic';\n\n/**\n * Mark a route as public (skip JWT validation)\n */\nexport const Public = () => SetMetadata(IS_PUBLIC_KEY, true);\n\n@Injectable()\nexport class JwtAuthGuard implements CanActivate {\n    constructor(private readonly jwtService: JwtService) {}\n\n    async canActivate(context: ExecutionContext): Promise<boolean> {\n        const request = context.switchToHttp().getRequest<Request>();\n        const token = this.extractTokenFromHeader(request);\n\n        if (!token) {\n            throw new UnauthorizedException('No token provided');\n        }\n\n        try {\n            const payload = this.jwtService.verifyAccessToken(token);\n            // Attach user to request\n            (request as any).user = payload;\n            (request as any).userId = payload.sub;\n            (request as any).organizationId = payload.organizationId;\n        } catch (error) {\n            throw new UnauthorizedException('Invalid or expired token');\n        }\n\n        return true;\n    }\n\n    private extractTokenFromHeader(request: Request): string | undefined {\n        const authHeader = request.headers.authorization;\n        if (!authHeader) {\n            return undefined;\n        }\n\n        const [type, token] = authHeader.split(' ');\n        return type === 'Bearer' ? token : undefined;\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/auth/guards/permissions.guard.ts",
    "content": "// ============================================================================\n// Permissions Guard - Checks user permissions (RBAC)\n// ============================================================================\n\nimport {\n    Injectable,\n    CanActivate,\n    ExecutionContext,\n    ForbiddenException,\n    SetMetadata,\n} from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport { RbacService } from '../rbac/rbac.service';\nimport { JwtPayload } from '../jwt/jwt.types';\n\n/**\n * Metadata key for required permissions\n */\nexport const PERMISSIONS_KEY = 'permissions';\n\n/**\n * Require specific permissions to access route\n * Format: 'resource:action' e.g., 'users:read', 'workflows:delete'\n */\nexport const Permissions = (...permissions: string[]) =>\n    SetMetadata(PERMISSIONS_KEY, permissions);\n\n/**\n * Permissions Guard - checks if user has required permissions\n */\n@Injectable()\nexport class PermissionsGuard implements CanActivate {\n    constructor(\n        private readonly reflector: Reflector,\n        private readonly rbacService: RbacService,\n    ) {}\n\n    canActivate(context: ExecutionContext): boolean {\n        const requiredPermissions = this.reflector.getAllAndOverride<string[]>(\n            PERMISSIONS_KEY,\n            [context.getHandler(), context.getClass()],\n        );\n\n        if (!requiredPermissions || requiredPermissions.length === 0) {\n            return true;\n        }\n\n        const request = context.switchToHttp().getRequest();\n        const user = request.user as JwtPayload;\n\n        if (!user || !user.roles) {\n            throw new ForbiddenException('Access denied: No permissions assigned');\n        }\n\n        for (const permission of requiredPermissions) {\n            const [resource, action] = permission.split(':');\n            const hasPermission = this.rbacService.hasAnyPermission(\n                user.roles,\n                resource,\n                action,\n            );\n\n            if (!hasPermission) {\n                throw new ForbiddenException(\n                    `Access denied: Missing permission '${permission}'`,\n                );\n            }\n        }\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/auth/guards/roles.guard.ts",
    "content": "// ============================================================================\n// Roles Guard - Checks user roles\n// ============================================================================\n\nimport {\n    Injectable,\n    CanActivate,\n    ExecutionContext,\n    ForbiddenException,\n    SetMetadata,\n} from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport { RbacService } from '../rbac/rbac.service';\nimport { JwtPayload } from '../jwt/jwt.types';\n\n/**\n * Metadata key for required roles\n */\nexport const ROLES_KEY = 'roles';\n\n/**\n * Require specific roles to access route\n */\nexport const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);\n\n/**\n * Roles Guard - checks if user has required roles\n */\n@Injectable()\nexport class RolesGuard implements CanActivate {\n    constructor(\n        private readonly reflector: Reflector,\n        private readonly rbacService: RbacService,\n    ) {}\n\n    canActivate(context: ExecutionContext): boolean {\n        const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [\n            context.getHandler(),\n            context.getClass(),\n        ]);\n\n        if (!requiredRoles || requiredRoles.length === 0) {\n            return true;\n        }\n\n        const request = context.switchToHttp().getRequest();\n        const user = request.user as JwtPayload;\n\n        if (!user || !user.roles) {\n            throw new ForbiddenException('Access denied: No roles assigned');\n        }\n\n        const hasRole = this.rbacService.hasAnyRole(user.roles, requiredRoles);\n        if (!hasRole) {\n            throw new ForbiddenException(\n                `Access denied: Required role(s): ${requiredRoles.join(', ')}`,\n            );\n        }\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/auth/index.ts",
    "content": "// ============================================================================\n// Auth Module - Authentication and Authorization\n// ============================================================================\n\nexport * from './jwt';\nexport * from './rbac';\nexport * from './guards';\nexport * from './decorators';\nexport * from './dto';\nexport * from './auth.module';\n"
  },
  {
    "path": "apps/api/src/shared/auth/jwt/index.ts",
    "content": "// ============================================================================\n// JWT Module\n// ============================================================================\n\nexport * from './jwt.service';\nexport * from './jwt.types';\n"
  },
  {
    "path": "apps/api/src/shared/auth/jwt/jwt.service.ts",
    "content": "// ============================================================================\n// JWT Service - Token generation and verification\n// ============================================================================\n\nimport { Injectable, UnauthorizedException } from '@nestjs/common';\nimport { ConfigService } from '@nestjs/config';\nimport * as jwt from 'jsonwebtoken';\nimport { JwtPayload, TokenPair } from './jwt.types';\n\n@Injectable()\nexport class JwtService {\n    private readonly accessTokenSecret: string;\n    private readonly refreshTokenSecret: string;\n    private readonly accessTokenExpiry: string;\n    private readonly refreshTokenExpiry: string;\n\n    constructor(private readonly configService: ConfigService) {\n        this.accessTokenSecret = this.configService.get<string>('JWT_ACCESS_SECRET') || 'default-access-secret';\n        this.refreshTokenSecret = this.configService.get<string>('JWT_REFRESH_SECRET') || 'default-refresh-secret';\n        this.accessTokenExpiry = this.configService.get<string>('JWT_ACCESS_EXPIRY') || '15m';\n        this.refreshTokenExpiry = this.configService.get<string>('JWT_REFRESH_EXPIRY') || '7d';\n    }\n\n    /**\n     * Generate access token\n     */\n    generateAccessToken(payload: JwtPayload): string {\n        return jwt.sign(payload as object, this.accessTokenSecret, {\n            expiresIn: this.accessTokenExpiry as jwt.SignOptions['expiresIn'],\n        });\n    }\n\n    /**\n     * Generate refresh token\n     */\n    generateRefreshToken(payload: JwtPayload): string {\n        return jwt.sign(payload as object, this.refreshTokenSecret, {\n            expiresIn: this.refreshTokenExpiry as jwt.SignOptions['expiresIn'],\n        });\n    }\n\n    /**\n     * Generate both access and refresh tokens\n     */\n    generateTokenPair(payload: JwtPayload): TokenPair {\n        return {\n            accessToken: this.generateAccessToken(payload),\n            refreshToken: this.generateRefreshToken(payload),\n        };\n    }\n\n    /**\n     * Verify access token\n     */\n    verifyAccessToken(token: string): JwtPayload {\n        try {\n            return jwt.verify(token, this.accessTokenSecret) as JwtPayload;\n        } catch (error) {\n            throw new UnauthorizedException('Invalid or expired access token');\n        }\n    }\n\n    /**\n     * Verify refresh token\n     */\n    verifyRefreshToken(token: string): JwtPayload {\n        try {\n            return jwt.verify(token, this.refreshTokenSecret) as JwtPayload;\n        } catch (error) {\n            throw new UnauthorizedException('Invalid or expired refresh token');\n        }\n    }\n\n    /**\n     * Decode token without verification (for debugging)\n     */\n    decodeToken(token: string): JwtPayload | null {\n        try {\n            return jwt.decode(token) as JwtPayload;\n        } catch {\n            return null;\n        }\n    }\n\n    /**\n     * Check if token is about to expire (within 5 minutes)\n     */\n    isTokenExpiringSoon(token: string): boolean {\n        const decoded = this.decodeToken(token);\n        if (!decoded || !decoded.exp) {\n            return true;\n        }\n\n        const fiveMinutesFromNow = Math.floor(Date.now() / 1000) + 300;\n        return decoded.exp < fiveMinutesFromNow;\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/auth/jwt/jwt.types.ts",
    "content": "// ============================================================================\n// JWT Types\n// ============================================================================\n\n/**\n * JWT Payload - contains user information stored in the token\n */\nexport interface JwtPayload {\n    /** User ID */\n    sub: string;\n    /** User email */\n    email: string;\n    /** Organization/Tenant ID */\n    organizationId: string;\n    /** User roles */\n    roles: string[];\n    /** Permissions (optional, can be computed from roles) */\n    permissions?: string[];\n    /** Token type: access or refresh */\n    type: 'access' | 'refresh';\n    /** Issued at timestamp */\n    iat?: number;\n    /** Expiration timestamp */\n    exp?: number;\n}\n\n/**\n * Access Token\n */\nexport interface AccessToken {\n    token: string;\n    expiresIn: string;\n}\n\n/**\n * Refresh Token\n */\nexport interface RefreshToken {\n    token: string;\n    expiresIn: string;\n}\n\n/**\n * Token Pair - both access and refresh tokens\n */\nexport interface TokenPair {\n    accessToken: string;\n    refreshToken: string;\n}\n\n/**\n * Token Response - returned to client after login\n */\nexport interface TokenResponse {\n    accessToken: string;\n    refreshToken: string;\n    tokenType: 'Bearer';\n    expiresIn: number;\n}\n"
  },
  {
    "path": "apps/api/src/shared/auth/rbac/index.ts",
    "content": "// ============================================================================\n// RBAC Module\n// ============================================================================\n\nexport * from './rbac.service';\n"
  },
  {
    "path": "apps/api/src/shared/auth/rbac/rbac.service.ts",
    "content": "// ============================================================================\n// RBAC - Role-Based Access Control\n// ============================================================================\n\nimport { Injectable } from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\n\n/**\n * Permission definition\n */\nexport interface Permission {\n    resource: string;\n    actions: string[]; // e.g., ['create', 'read', 'update', 'delete']\n}\n\n/**\n * Role definition\n */\nexport interface Role {\n    name: string;\n    permissions: Permission[];\n}\n\n/**\n * Default roles and permissions\n */\nexport const DEFAULT_PERMISSIONS: Permission[] = [\n    { resource: 'users', actions: ['create', 'read', 'update', 'delete'] },\n    { resource: 'organizations', actions: ['create', 'read', 'update', 'delete'] },\n    { resource: 'agents', actions: ['create', 'read', 'update', 'delete'] },\n    { resource: 'workflows', actions: ['create', 'read', 'update', 'delete'] },\n    { resource: 'knowledge', actions: ['create', 'read', 'update', 'delete'] },\n    { resource: 'repositories', actions: ['create', 'read', 'update', 'delete'] },\n    { resource: 'pipeline', actions: ['create', 'read', 'update', 'delete'] },\n    { resource: 'audit', actions: ['read'] },\n];\n\nexport const DEFAULT_ROLES: Role[] = [\n    {\n        name: 'owner',\n        permissions: DEFAULT_PERMISSIONS,\n    },\n    {\n        name: 'admin',\n        permissions: DEFAULT_PERMISSIONS.filter(p => p.resource !== 'organizations'),\n    },\n    {\n        name: 'member',\n        permissions: [\n            { resource: 'users', actions: ['read', 'update'] },\n            { resource: 'agents', actions: ['create', 'read', 'update'] },\n            { resource: 'workflows', actions: ['create', 'read', 'update'] },\n            { resource: 'knowledge', actions: ['create', 'read', 'update'] },\n            { resource: 'repositories', actions: ['create', 'read', 'update'] },\n            { resource: 'pipeline', actions: ['read'] },\n        ],\n    },\n    {\n        name: 'viewer',\n        permissions: [\n            { resource: 'users', actions: ['read'] },\n            { resource: 'agents', actions: ['read'] },\n            { resource: 'workflows', actions: ['read'] },\n            { resource: 'knowledge', actions: ['read'] },\n            { resource: 'repositories', actions: ['read'] },\n            { resource: 'pipeline', actions: ['read'] },\n        ],\n    },\n];\n\n/**\n * RBAC Service - handles permission checking\n */\n@Injectable()\nexport class RbacService {\n    private readonly roles: Map<string, Role> = new Map();\n\n    constructor() {\n        // Initialize default roles\n        for (const role of DEFAULT_ROLES) {\n            this.roles.set(role.name, role);\n        }\n    }\n\n    /**\n     * Get role by name\n     */\n    getRole(roleName: string): Role | undefined {\n        return this.roles.get(roleName);\n    }\n\n    /**\n     * Check if a role has a specific permission\n     */\n    hasPermission(roleName: string, resource: string, action: string): boolean {\n        const role = this.getRole(roleName);\n        if (!role) {\n            return false;\n        }\n\n        const permission = role.permissions.find(p => p.resource === resource);\n        if (!permission) {\n            return false;\n        }\n\n        return permission.actions.includes(action) || permission.actions.includes('*');\n    }\n\n    /**\n     * Check if any of the roles has a specific permission\n     */\n    hasAnyPermission(roleNames: string[], resource: string, action: string): boolean {\n        return roleNames.some(roleName => this.hasPermission(roleName, resource, action));\n    }\n\n    /**\n     * Check if all roles have a specific permission\n     */\n    hasAllPermissions(roleNames: string[], resource: string, action: string): boolean {\n        return roleNames.every(roleName => this.hasPermission(roleName, resource, action));\n    }\n\n    /**\n     * Get all permissions for a role\n     */\n    getPermissions(roleName: string): Permission[] {\n        const role = this.getRole(roleName);\n        return role?.permissions ?? [];\n    }\n\n    /**\n     * Register a custom role\n     */\n    registerRole(role: Role): void {\n        this.roles.set(role.name, role);\n    }\n\n    /**\n     * Check if user has role\n     */\n    hasRole(userRoles: string[], requiredRole: string): boolean {\n        return userRoles.includes(requiredRole);\n    }\n\n    /**\n     * Check if user has any of the roles\n     */\n    hasAnyRole(userRoles: string[], requiredRoles: string[]): boolean {\n        return userRoles.some(role => requiredRoles.includes(role));\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/base/base.entity.ts",
    "content": "// ============================================================================\n// Base Entity Interface - Common properties for all entities\n// ============================================================================\n\nexport interface BaseEntity {\n    id: string;\n    createdAt: Date;\n    updatedAt: Date;\n}\n\nexport interface TenantEntity extends BaseEntity {\n    organizationId: string;\n}\n\nexport interface SoftDeleteEntity extends BaseEntity {\n    deletedAt?: Date;\n    deletedBy?: string;\n}\n\nexport interface VersionedEntity extends BaseEntity {\n    version: number;\n}\n"
  },
  {
    "path": "apps/api/src/shared/base/base.service.ts",
    "content": "// ============================================================================\n// Base Service - Generic CRUD operations with Kysely\n// ============================================================================\n\nimport { KyselyService } from '@a3s-lab/kysely';\nimport { NotFoundException } from '@nestjs/common';\nimport { parsePaginationOptions, PaginationOptions, PaginationQueryDto } from './pagination.dto';\n\nexport interface FindOptions<FilterDto, SortDto> {\n    filter?: FilterDto;\n    sort?: SortDto;\n    pagination?: PaginationQueryDto;\n}\n\nexport interface PaginatedResult<T> {\n    items: T[];\n    total: number;\n    page: number;\n    pageSize: number;\n    totalPages: number;\n}\n\nexport abstract class BaseService<\n    Entity extends { id: string },\n    CreateDto,\n    UpdateDto,\n    FilterDto = never,\n    SortDto = never,\n> {\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    constructor(\n        protected readonly kysely: KyselyService<any>,\n        protected readonly tableName: string,\n    ) {}\n\n    /**\n     * Create a new entity\n     */\n    async create(dto: CreateDto, additionalData?: Partial<Entity>): Promise<Entity> {\n        const now = new Date();\n        const entity: Partial<Entity> = {\n            ...dto,\n            ...additionalData,\n            id: crypto.randomUUID(),\n            createdAt: now,\n            updatedAt: now,\n        } as Partial<Entity>;\n\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n        await (this.kysely as any)\n            .insertInto(this.tableName)\n            .values(entity as Record<string, unknown>)\n            .executeTakeFirstOrThrow();\n\n        return entity as Entity;\n    }\n\n    /**\n     * Find entity by ID\n     */\n    async findById(id: string): Promise<Entity | null> {\n        const row = await (this.kysely as any)\n            .selectFrom(this.tableName)\n            .where('id', '=', id)\n            .executeTakeFirst();\n\n        return (row as Entity) || null;\n    }\n\n    /**\n     * Find entity by ID or throw NotFoundException\n     */\n    async findByIdOrThrow(id: string): Promise<Entity> {\n        const entity = await this.findById(id);\n        if (!entity) {\n            throw new NotFoundException(`${this.tableName} with id '${id}' not found`);\n        }\n        return entity;\n    }\n\n    /**\n     * Find all entities with pagination (optimized - uses SQL COUNT)\n     */\n    async findAll(options?: FindOptions<FilterDto, SortDto>): Promise<PaginatedResult<Entity>> {\n        const paginationOptions = options?.pagination\n            ? parsePaginationOptions(options.pagination)\n            : { page: 1, pageSize: 20, limit: 20, offset: 0 };\n\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n        let baseQuery = (this.kysely as any).selectFrom(this.tableName);\n\n        // Apply filters if FilterDto is provided\n        if (options?.filter) {\n            baseQuery = this.applyFilters(baseQuery, options.filter);\n        }\n\n        // Get total count using efficient SQL COUNT\n        const countResult = await baseQuery\n            .select((eb: any) => eb.fn.countAll().as('count'))\n            .executeTakeFirst();\n\n        const total = Number(countResult?.count ?? 0);\n\n        // Apply pagination and sorting to a separate query\n        let dataQuery = baseQuery.limit(paginationOptions.limit).offset(paginationOptions.offset);\n\n        if (options?.sort) {\n            dataQuery = this.applySort(dataQuery, options.sort);\n        }\n\n        const items = await dataQuery.execute();\n\n        return {\n            items: items as Entity[],\n            total,\n            page: paginationOptions.page,\n            pageSize: paginationOptions.pageSize,\n            totalPages: Math.ceil(total / paginationOptions.pageSize),\n        };\n    }\n\n    /**\n     * Find entities with pagination using cursor-based approach (for large datasets)\n     */\n    async findAllCursor(\n        cursor: string | null,\n        limit: number,\n        options?: FindOptions<FilterDto, SortDto>,\n    ): Promise<{ items: Entity[]; nextCursor: string | null }> {\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n        let query = (this.kysely as any).selectFrom(this.tableName);\n\n        if (options?.filter) {\n            query = this.applyFilters(query, options.filter);\n        }\n\n        // Cursor-based pagination using id > cursor\n        if (cursor) {\n            query = query.where('id', '>', cursor);\n        }\n\n        query = query.orderBy('id', 'asc').limit(limit + 1); // Fetch one extra to check if there's more\n\n        const items = await query.execute();\n        const hasMore = items.length > limit;\n        const result = hasMore ? items.slice(0, -1) : items;\n\n        return {\n            items: result as Entity[],\n            nextCursor: hasMore ? (result[result.length - 1] as Entity).id : null,\n        };\n    }\n\n    /**\n     * Update entity by ID\n     */\n    async update(id: string, dto: UpdateDto): Promise<Entity> {\n        const existing = await this.findByIdOrThrow(id);\n\n        const updated: Partial<Entity> = {\n            ...existing,\n            ...dto,\n            id: existing.id,\n            createdAt: (existing as any).createdAt,\n            updatedAt: new Date(),\n        } as Partial<Entity>;\n\n        await (this.kysely as any)\n            .updateTable(this.tableName)\n            .set(updated as Record<string, unknown>)\n            .where('id', '=', id)\n            .executeTakeFirst();\n\n        return updated as Entity;\n    }\n\n    /**\n     * Update entities by filter (batch update)\n     */\n    async updateMany(filter: FilterDto, dto: Partial<UpdateDto>): Promise<number> {\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n        let query = (this.kysely as any).updateTable(this.tableName).set({\n            ...dto,\n            updatedAt: new Date(),\n        } as Record<string, unknown>);\n\n        query = this.applyFilters(query, filter);\n\n        const result = await query.execute();\n        return result.numUpdatedRows ?? 0;\n    }\n\n    /**\n     * Delete entity by ID (hard delete)\n     */\n    async delete(id: string): Promise<void> {\n        const existing = await this.findByIdOrThrow(id);\n\n        await (this.kysely as any)\n            .deleteFrom(this.tableName)\n            .where('id', '=', id)\n            .executeTakeFirst();\n    }\n\n    /**\n     * Soft delete entity by ID\n     */\n    async softDelete(id: string, deletedBy?: string): Promise<void> {\n        await this.findByIdOrThrow(id);\n\n        await (this.kysely as any)\n            .updateTable(this.tableName)\n            .set({\n                deletedAt: new Date(),\n                deletedBy,\n                updatedAt: new Date(),\n            } as Record<string, unknown>)\n            .where('id', '=', id)\n            .executeTakeFirst();\n    }\n\n    /**\n     * Soft delete entities by filter (batch)\n     */\n    async softDeleteMany(filter: FilterDto, deletedBy?: string): Promise<number> {\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n        let query = (this.kysely as any).updateTable(this.tableName).set({\n            deletedAt: new Date(),\n            deletedBy,\n            updatedAt: new Date(),\n        } as Record<string, unknown>);\n\n        query = this.applyFilters(query, filter);\n\n        const result = await query.execute();\n        return result.numUpdatedRows ?? 0;\n    }\n\n    /**\n     * Check if entity exists\n     */\n    async exists(id: string): Promise<boolean> {\n        const entity = await (this.kysely as any)\n            .selectFrom(this.tableName)\n            .select('id')\n            .where('id', '=', id)\n            .executeTakeFirst();\n\n        return !!entity;\n    }\n\n    /**\n     * Count entities with optional filters (efficient SQL COUNT)\n     */\n    async count(filter?: FilterDto): Promise<number> {\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n        let query = (this.kysely as any)\n            .selectFrom(this.tableName)\n            .select((eb: any) => eb.fn.countAll().as('count'));\n\n        if (filter) {\n            query = this.applyFilters(query, filter);\n        }\n\n        const result = await query.executeTakeFirst();\n        return Number(result?.count ?? 0);\n    }\n\n    /**\n     * Insert many entities (batch insert)\n     */\n    async createMany(dtos: CreateDto[]): Promise<Entity[]> {\n        if (dtos.length === 0) return [];\n\n        const now = new Date();\n        const entities = dtos.map((dto) => ({\n            ...dto,\n            id: crypto.randomUUID(),\n            createdAt: now,\n            updatedAt: now,\n        })) as Array<Record<string, unknown>>;\n\n        await (this.kysely as any)\n            .insertInto(this.tableName)\n            .values(entities)\n            .executeTakeFirst();\n\n        return entities as unknown as Entity[];\n    }\n\n    /**\n     * Apply filters to query (override in subclasses)\n     */\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    protected applyFilters(qb: any, filter: FilterDto): any {\n        return qb;\n    }\n\n    /**\n     * Apply sorting to query (override in subclasses)\n     */\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    protected applySort(qb: any, sort: SortDto): any {\n        return qb;\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/base/index.ts",
    "content": "export * from './base.entity';\nexport * from './base.service';\nexport * from './pagination.dto';\n"
  },
  {
    "path": "apps/api/src/shared/base/pagination.dto.ts",
    "content": "// ============================================================================\n// Pagination DTO - Standardized pagination parameters\n// ============================================================================\n\nimport { Type } from 'class-transformer';\nimport { IsOptional, IsInt, Min, Max, IsString, IsIn } from 'class-validator';\nimport { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';\n\nexport class PaginationQueryDto {\n    @IsOptional()\n    @Type(() => Number)\n    @IsInt()\n    @Min(1)\n    @ApiPropertyOptional({ description: 'Page number', default: 1, minimum: 1 })\n    page?: number = 1;\n\n    @IsOptional()\n    @Type(() => Number)\n    @IsInt()\n    @Min(1)\n    @Max(100)\n    @ApiPropertyOptional({ description: 'Page size', default: 20, minimum: 1, maximum: 100 })\n    pageSize?: number = 20;\n}\n\nexport class CursorPaginationQueryDto {\n    @IsOptional()\n    @IsString()\n    @ApiPropertyOptional({ description: 'Cursor (last item ID)', required: false })\n    cursor?: string;\n\n    @IsOptional()\n    @Type(() => Number)\n    @IsInt()\n    @Min(1)\n    @Max(100)\n    @ApiPropertyOptional({ description: 'Page size', default: 20, minimum: 1, maximum: 100 })\n    limit?: number = 20;\n\n    @IsOptional()\n    @IsString()\n    @IsIn(['asc', 'desc'])\n    @ApiPropertyOptional({ description: 'Sort order', default: 'asc' })\n    order?: 'asc' | 'desc' = 'asc';\n}\n\nexport interface PaginationOptions {\n    page: number;\n    pageSize: number;\n    limit: number;\n    offset: number;\n}\n\nexport interface CursorPaginationOptions {\n    cursor: string | null;\n    limit: number;\n    order: 'asc' | 'desc';\n}\n\nexport function parsePaginationOptions(query: PaginationQueryDto): PaginationOptions {\n    const page = query.page ?? 1;\n    const pageSize = query.pageSize ?? 20;\n    const limit = pageSize;\n    const offset = (page - 1) * pageSize;\n\n    return { page, pageSize, limit, offset };\n}\n\nexport function parseCursorPaginationOptions(query: CursorPaginationQueryDto): CursorPaginationOptions {\n    return {\n        cursor: query.cursor ?? null,\n        limit: query.limit ?? 20,\n        order: query.order ?? 'asc',\n    };\n}\n\nexport class PaginatedResponseDto<T> {\n    @ApiProperty({ description: 'Items in current page' })\n    items: T[];\n\n    @ApiProperty({ description: 'Total number of items' })\n    total: number;\n\n    @ApiProperty({ description: 'Current page number' })\n    page: number;\n\n    @ApiProperty({ description: 'Number of items per page' })\n    pageSize: number;\n\n    @ApiProperty({ description: 'Total number of pages' })\n    totalPages: number;\n\n    @ApiProperty({ description: 'Whether there is a next page' })\n    hasNext: boolean;\n\n    @ApiProperty({ description: 'Whether there is a previous page' })\n    hasPrevious: boolean;\n\n    constructor(partial: Partial<PaginatedResponseDto<T>>) {\n        Object.assign(this, partial);\n    }\n}\n\nexport class CursorPaginatedResponseDto<T> {\n    @ApiProperty({ description: 'Items in current page' })\n    items: T[];\n\n    @ApiProperty({ description: 'Cursor for next page', nullable: true })\n    nextCursor: string | null;\n\n    @ApiProperty({ description: 'Whether there is a next page' })\n    hasMore: boolean;\n\n    constructor(partial: Partial<CursorPaginatedResponseDto<T>>) {\n        Object.assign(this, partial);\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/cache/cache.decorator.ts",
    "content": "// ============================================================================\n// Cache Decorator - Method-level caching\n// ============================================================================\n\nimport { SetMetadata } from '@nestjs/common';\nimport { CacheDecoratorOptions } from './cache.service';\n\nexport const CACHE_KEY = 'cache_options';\n\n/**\n * Cache the result of a method\n */\nexport function Cache(options: CacheDecoratorOptions) {\n    return SetMetadata(CACHE_KEY, options);\n}\n\n/**\n * Cache key prefix decorator\n */\nexport const CachePrefix = (prefix: string) => SetMetadata(CACHE_KEY, { prefix });\n"
  },
  {
    "path": "apps/api/src/shared/cache/cache.interceptor.ts",
    "content": "// ============================================================================\n// Cache Interceptor - Automatically caches method results\n// ============================================================================\n\nimport {\n    Injectable,\n    NestInterceptor,\n    ExecutionContext,\n    CallHandler,\n    Logger,\n} from '@nestjs/common';\nimport { Observable, of } from 'rxjs';\nimport { tap } from 'rxjs/operators';\nimport { Reflector } from '@nestjs/core';\nimport { CacheService, CacheDecoratorOptions } from './cache.service';\nimport { CACHE_KEY } from './cache.decorator';\n\n@Injectable()\nexport class CacheInterceptor implements NestInterceptor {\n    private readonly logger = new Logger(CacheInterceptor.name);\n\n    constructor(\n        private readonly cacheService: CacheService,\n        private readonly reflector: Reflector,\n    ) {}\n\n    async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {\n        const cacheOptions = this.reflector.get<CacheDecoratorOptions>(\n            CACHE_KEY,\n            context.getHandler(),\n        );\n\n        // Skip if no cache options\n        if (!cacheOptions) {\n            return next.handle();\n        }\n\n        const cacheKey = this.buildCacheKey(context, cacheOptions);\n\n        // Try to get from cache\n        const cached = await this.cacheService.get(cacheKey, cacheOptions);\n        if (cached !== null) {\n            this.logger.debug(`Cache hit: ${cacheKey}`);\n            return of(cached);\n        }\n\n        this.logger.debug(`Cache miss: ${cacheKey}`);\n\n        // Execute handler and cache result\n        return next.handle().pipe(\n            tap(async (result) => {\n                if (result !== undefined && result !== null) {\n                    await this.cacheService.set(cacheKey, result, cacheOptions);\n                    this.logger.debug(`Cached: ${cacheKey}`);\n                }\n            }),\n        );\n    }\n\n    private buildCacheKey(context: ExecutionContext, options: CacheDecoratorOptions): string {\n        const className = context.getClass().name;\n        const methodName = context.getHandler().name;\n        const prefix = options.keyPrefix ?? `${className}:${methodName}`;\n\n        // Include route params in key if present\n        const request = context.switchToHttp().getRequest();\n        const params = request.params ? JSON.stringify(request.params) : '';\n\n        return `${prefix}:${params}`;\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/cache/cache.service.ts",
    "content": "// ============================================================================\n// Cache Service - Redis-based caching abstraction\n// ============================================================================\n\nimport { Injectable, OnModuleDestroy, Logger } from '@nestjs/common';\nimport { RedissonService } from '@a3s-lab/redisson';\n\nexport interface CacheOptions {\n    /** Time to live in seconds */\n    ttl?: number;\n    /** Namespace prefix */\n    prefix?: string;\n    /** Whether to refresh TTL on access */\n    touch?: boolean;\n}\n\nexport interface CacheStats {\n    hits: number;\n    misses: number;\n    sets: number;\n    deletes: number;\n    hitRate: number;\n}\n\n/**\n * Decorator options for caching (extends CacheOptions)\n */\nexport interface CacheDecoratorOptions extends CacheOptions {\n    /** Key prefix for the cached method */\n    keyPrefix?: string;\n    /** Whether to skip cache on error */\n    skipOnError?: boolean;\n}\n\n/**\n * Default cache options\n */\nconst DEFAULT_CACHE_OPTIONS: Required<CacheOptions> = {\n    ttl: 300, // 5 minutes\n    prefix: 'cache',\n    touch: false,\n};\n\n/**\n * Cache Service - provides Redis-based caching with stats tracking\n */\n@Injectable()\nexport class CacheService implements OnModuleDestroy {\n    private readonly logger = new Logger(CacheService.name);\n    private readonly keyPrefix = 'cache:';\n    private stats = { hits: 0, misses: 0, sets: 0, deletes: 0 };\n\n    constructor(private readonly redis: RedissonService) {}\n\n    /**\n     * Get a value from cache\n     */\n    async get<T>(key: string, options?: CacheOptions): Promise<T | null> {\n        const fullKey = this.buildKey(key, options);\n        const data = await this.redis.get(fullKey);\n\n        if (data) {\n            this.stats.hits++;\n            // Optionally refresh TTL on access\n            if (options?.touch) {\n                const ttl = options?.ttl ?? DEFAULT_CACHE_OPTIONS.ttl;\n                await this.redis.expire(fullKey, ttl);\n            }\n            try {\n                return JSON.parse(data) as T;\n            } catch {\n                return data as unknown as T;\n            }\n        }\n\n        this.stats.misses++;\n        return null;\n    }\n\n    /**\n     * Set a value in cache\n     */\n    async set<T>(key: string, value: T, options?: CacheOptions): Promise<void> {\n        const fullKey = this.buildKey(key, options);\n        const ttl = options?.ttl ?? DEFAULT_CACHE_OPTIONS.ttl;\n        const serialized = typeof value === 'string' ? value : JSON.stringify(value);\n\n        await this.redis.set(fullKey, serialized, ttl);\n        this.stats.sets++;\n    }\n\n    /**\n     * Delete a key from cache\n     */\n    async delete(key: string, options?: CacheOptions): Promise<void> {\n        const fullKey = this.buildKey(key, options);\n        await this.redis.delete(fullKey);\n        this.stats.deletes++;\n    }\n\n    /**\n     * Delete all keys matching a pattern\n     */\n    async deleteByPattern(pattern: string): Promise<number> {\n        const fullPattern = `${this.keyPrefix}${pattern}`;\n        const count = await this.redis.deleteByPattern(fullPattern);\n        this.stats.deletes += count;\n        return count;\n    }\n\n    /**\n     * Check if a key exists\n     */\n    async has(key: string, options?: CacheOptions): Promise<boolean> {\n        const fullKey = this.buildKey(key, options);\n        return this.redis.exists(fullKey);\n    }\n\n    /**\n     * Get or set - fetch from cache or execute factory and cache the result\n     */\n    async getOrSet<T>(\n        key: string,\n        factory: () => Promise<T>,\n        options?: CacheOptions,\n    ): Promise<T> {\n        const cached = await this.get<T>(key, options);\n        if (cached !== null) {\n            return cached;\n        }\n\n        const value = await factory();\n        await this.set(key, value, options);\n        return value;\n    }\n\n    /**\n     * Get multiple values from cache\n     */\n    async getMany<T>(keys: string[], options?: CacheOptions): Promise<Array<T | null>> {\n        const promises = keys.map(key => this.get<T>(key, options));\n        return Promise.all(promises);\n    }\n\n    /**\n     * Set multiple values in cache\n     */\n    async setMany<T>(entries: Array<{ key: string; value: T }>, options?: CacheOptions): Promise<void> {\n        const promises = entries.map(({ key, value }) => this.set(key, value, options));\n        await Promise.all(promises);\n    }\n\n    /**\n     * Increment a counter in cache\n     */\n    async increment(key: string, amount = 1, options?: CacheOptions): Promise<number> {\n        const fullKey = this.buildKey(key, options);\n        const value = await this.redis.increment(fullKey, amount);\n        // Set TTL if not exists\n        const ttl = options?.ttl ?? DEFAULT_CACHE_OPTIONS.ttl;\n        await this.redis.expire(fullKey, ttl);\n        return value;\n    }\n\n    /**\n     * Decrement a counter in cache\n     */\n    async decrement(key: string, amount = 1, options?: CacheOptions): Promise<number> {\n        const fullKey = this.buildKey(key, options);\n        const value = await this.redis.decrement(fullKey, amount);\n        return value;\n    }\n\n    /**\n     * Get cache statistics\n     */\n    getStats(): CacheStats {\n        const total = this.stats.hits + this.stats.misses;\n        return {\n            ...this.stats,\n            hitRate: total > 0 ? this.stats.hits / total : 0,\n        };\n    }\n\n    /**\n     * Reset cache statistics\n     */\n    resetStats(): void {\n        this.stats = { hits: 0, misses: 0, sets: 0, deletes: 0 };\n    }\n\n    /**\n     * Build full cache key\n     */\n    private buildKey(key: string, options?: CacheOptions): string {\n        const prefix = options?.prefix ?? 'cache';\n        return `${this.keyPrefix}${prefix}:${key}`;\n    }\n\n    onModuleDestroy(): void {\n        this.logger.log('Cache service destroyed');\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/cache/index.ts",
    "content": "// ============================================================================\n// Cache Module\n// ============================================================================\n\nexport { CacheService } from './cache.service';\nexport { CacheInterceptor } from './cache.interceptor';\nexport { Cache, CachePrefix } from './cache.decorator';\nexport type { CacheOptions, CacheStats, CacheDecoratorOptions } from './cache.service';\n"
  },
  {
    "path": "apps/api/src/shared/circuit-breaker/circuit-breaker.decorator.ts",
    "content": "// ============================================================================\n// Circuit Breaker Decorator\n// ============================================================================\n\nimport { SetMetadata } from '@nestjs/common';\nimport { CircuitBreakerOptions } from './circuit-breaker.service';\n\nexport const CIRCUIT_BREAKER_KEY = 'circuit_breaker';\nexport const CIRCUIT_BREAKER_OPTIONS = 'circuit_breaker_options';\n\n/**\n * Decorator to mark a method for circuit breaker protection\n */\nexport function CircuitBreaker(options: CircuitBreakerOptions) {\n    return SetMetadata(CIRCUIT_BREAKER_OPTIONS, options);\n}\n"
  },
  {
    "path": "apps/api/src/shared/circuit-breaker/circuit-breaker.module.ts",
    "content": "// ============================================================================\n// Circuit Breaker Module - Fault tolerance pattern\n// ============================================================================\n\nimport { Module, Global } from '@nestjs/common';\nimport { CircuitBreakerService } from './circuit-breaker.service';\n\n@Global()\n@Module({\n    providers: [CircuitBreakerService],\n    exports: [CircuitBreakerService],\n})\nexport class CircuitBreakerModule {}\n"
  },
  {
    "path": "apps/api/src/shared/circuit-breaker/circuit-breaker.service.ts",
    "content": "// ============================================================================\n// Circuit Breaker - Protect external calls from cascading failures\n// ============================================================================\n\nimport { Injectable, Logger, OnModuleDestroy } from '@nestjs/common';\n\nexport enum CircuitState {\n    CLOSED = 'CLOSED',       // Normal operation\n    OPEN = 'OPEN',           // Failing, reject calls\n    HALF_OPEN = 'HALF_OPEN', // Testing if service recovered\n}\n\nexport interface CircuitBreakerOptions {\n    /** Failure threshold to open circuit */\n    failureThreshold?: number;\n    /** Success threshold to close circuit (from half-open) */\n    successThreshold?: number;\n    /** Time in ms before attempting recovery */\n    resetTimeout?: number;\n    /** Name for this circuit breaker */\n    name?: string;\n}\n\nexport interface CircuitBreakerStats {\n    name: string;\n    state: CircuitState;\n    failures: number;\n    successes: number;\n    lastFailure?: Date;\n    lastSuccess?: Date;\n    nextAttempt?: Date;\n}\n\n/**\n * Default circuit breaker options\n */\nconst DEFAULT_OPTIONS: Required<CircuitBreakerOptions> = {\n    failureThreshold: 5,\n    successThreshold: 2,\n    resetTimeout: 30000, // 30 seconds\n    name: 'default',\n};\n\n/**\n * Circuit Breaker states:\n * - CLOSED: Normal operation, requests pass through\n * - OPEN: Circuit is tripped, requests are rejected immediately\n * - HALF_OPEN: Testing recovery, limited requests pass through\n */\n@Injectable()\nexport class CircuitBreakerService implements OnModuleDestroy {\n    private readonly logger = new Logger(CircuitBreakerService.name);\n    private readonly circuits: Map<string, CircuitBreaker> = new Map();\n\n    constructor() {}\n\n    /**\n     * Get or create a circuit breaker\n     */\n    getCircuitBreaker(name: string, options?: CircuitBreakerOptions): CircuitBreaker {\n        if (this.circuits.has(name)) {\n            return this.circuits.get(name)!;\n        }\n\n        const circuit = new CircuitBreaker(name, {\n            ...DEFAULT_OPTIONS,\n            ...options,\n        });\n        this.circuits.set(name, circuit);\n        return circuit;\n    }\n\n    /**\n     * Execute a function with circuit breaker protection\n     */\n    async execute<T>(\n        name: string,\n        fn: () => Promise<T>,\n        options?: CircuitBreakerOptions,\n    ): Promise<T> {\n        const circuit = this.getCircuitBreaker(name, options);\n\n        if (!circuit.canExecute()) {\n            throw new CircuitBreakerOpenError(name, circuit.getNextAttempt());\n        }\n\n        try {\n            const result = await fn();\n            circuit.recordSuccess();\n            return result;\n        } catch (error) {\n            circuit.recordFailure();\n            throw error;\n        }\n    }\n\n    /**\n     * Get all circuit breaker stats\n     */\n    getAllStats(): CircuitBreakerStats[] {\n        return Array.from(this.circuits.values()).map(c => c.getStats());\n    }\n\n    /**\n     * Get stats for a specific circuit\n     */\n    getStats(name: string): CircuitBreakerStats | undefined {\n        return this.circuits.get(name)?.getStats();\n    }\n\n    /**\n     * Reset a specific circuit\n     */\n    reset(name: string): void {\n        this.circuits.get(name)?.reset();\n    }\n\n    /**\n     * Reset all circuits\n     */\n    resetAll(): void {\n        for (const circuit of this.circuits.values()) {\n            circuit.reset();\n        }\n    }\n\n    onModuleDestroy(): void {\n        this.logger.log('Circuit breaker service destroyed');\n    }\n}\n\n/**\n * Individual circuit breaker instance\n */\nexport class CircuitBreaker {\n    private state: CircuitState = CircuitState.CLOSED;\n    private failures = 0;\n    private successes = 0;\n    private lastFailure?: Date;\n    private lastSuccess?: Date;\n    private nextAttempt?: Date;\n    private readonly name: string;\n    private readonly options: Required<CircuitBreakerOptions>;\n\n    constructor(name: string, options: Required<CircuitBreakerOptions>) {\n        this.name = options.name ?? name;\n        this.options = options;\n    }\n\n    /**\n     * Check if a request can be executed\n     */\n    canExecute(): boolean {\n        switch (this.state) {\n            case CircuitState.CLOSED:\n                return true;\n\n            case CircuitState.OPEN:\n                // Check if reset timeout has passed\n                if (this.nextAttempt && new Date() >= this.nextAttempt) {\n                    this.transitionToHalfOpen();\n                    return true;\n                }\n                return false;\n\n            case CircuitState.HALF_OPEN:\n                // In half-open, allow limited requests through\n                return true;\n        }\n    }\n\n    /**\n     * Record a successful call\n     */\n    recordSuccess(): void {\n        this.lastSuccess = new Date();\n        this.successes++;\n\n        if (this.state === CircuitState.HALF_OPEN) {\n            if (this.successes >= this.options.successThreshold) {\n                this.transitionToClosed();\n            }\n        }\n    }\n\n    /**\n     * Record a failed call\n     */\n    recordFailure(): void {\n        this.lastFailure = new Date();\n        this.failures++;\n\n        if (this.state === CircuitState.CLOSED) {\n            if (this.failures >= this.options.failureThreshold) {\n                this.transitionToOpen();\n            }\n        } else if (this.state === CircuitState.HALF_OPEN) {\n            // Any failure in half-open immediately opens the circuit\n            this.transitionToOpen();\n        }\n    }\n\n    /**\n     * Get current stats\n     */\n    getStats(): CircuitBreakerStats {\n        return {\n            name: this.name,\n            state: this.state,\n            failures: this.failures,\n            successes: this.successes,\n            lastFailure: this.lastFailure,\n            lastSuccess: this.lastSuccess,\n            nextAttempt: this.nextAttempt,\n        };\n    }\n\n    /**\n     * Get next attempt time\n     */\n    getNextAttempt(): Date | undefined {\n        return this.nextAttempt;\n    }\n\n    /**\n     * Reset the circuit breaker\n     */\n    reset(): void {\n        this.state = CircuitState.CLOSED;\n        this.failures = 0;\n        this.successes = 0;\n        this.lastFailure = undefined;\n        this.lastSuccess = undefined;\n        this.nextAttempt = undefined;\n    }\n\n    /**\n     * Transition to OPEN state\n     */\n    private transitionToOpen(): void {\n        this.state = CircuitState.OPEN;\n        this.nextAttempt = new Date(Date.now() + this.options.resetTimeout);\n        this.successes = 0; // Reset success count\n    }\n\n    /**\n     * Transition to HALF_OPEN state\n     */\n    private transitionToHalfOpen(): void {\n        this.state = CircuitState.HALF_OPEN;\n        this.successes = 0;\n    }\n\n    /**\n     * Transition to CLOSED state\n     */\n    private transitionToClosed(): void {\n        this.state = CircuitState.CLOSED;\n        this.failures = 0;\n        this.successes = 0;\n        this.nextAttempt = undefined;\n    }\n}\n\n/**\n * Error thrown when circuit breaker is open\n */\nexport class CircuitBreakerOpenError extends Error {\n    constructor(\n        public readonly circuitName: string,\n        public readonly nextAttempt?: Date,\n    ) {\n        super(`Circuit breaker '${circuitName}' is open. Next attempt: ${nextAttempt?.toISOString() ?? 'unknown'}`);\n        this.name = 'CircuitBreakerOpenError';\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/circuit-breaker/index.ts",
    "content": "// ============================================================================\n// Circuit Breaker Module\n// ============================================================================\n\nexport {\n    CircuitBreakerService,\n    CircuitBreaker,\n    CircuitBreakerOpenError,\n    CircuitState,\n} from './circuit-breaker.service';\nexport type { CircuitBreakerOptions, CircuitBreakerStats } from './circuit-breaker.service';\nexport { CircuitBreakerModule } from './circuit-breaker.module';\n"
  },
  {
    "path": "apps/api/src/shared/database/database.module.ts",
    "content": "import { Module, Global } from '@nestjs/common';\nimport { ConfigModule, ConfigService } from '@nestjs/config';\nimport { KyselyModule, createKyselyLogger } from '@a3s-lab/kysely';\nimport { PostgresDialect } from 'kysely';\nimport { Pool } from 'pg';\n\n@Global()\n@Module({\n    imports: [\n        KyselyModule.registerAsync({\n            imports: [ConfigModule],\n            useFactory: (configService: ConfigService) => ({\n                config: {\n                    dialect: new PostgresDialect({\n                        pool: new Pool({\n                            host: configService.get('DB_HOST', 'localhost'),\n                            port: configService.get('DB_PORT', 5432),\n                            user: configService.get('DB_USERNAME', 'postgres'),\n                            password: configService.get('DB_PASSWORD', 'postgres'),\n                            database: configService.get('DB_DATABASE', 'nestify'),\n                            max: 10,\n                        }),\n                    }),\n                    log: configService.get('NODE_ENV') === 'development'\n                        ? createKyselyLogger()\n                        : undefined,\n                },\n            }),\n            inject: [ConfigService],\n        }),\n    ],\n    exports: [KyselyModule],\n})\nexport class DatabaseModule { }\n"
  },
  {
    "path": "apps/api/src/shared/database/database.types.ts",
    "content": "import type { ColumnType, Generated, Insertable, Selectable, Updateable } from 'kysely';\n\n/**\n * Database schema types for Kysely\n */\nexport interface Database {\n    orders: OrderTable;\n    order_items: OrderItemTable;\n}\n\n/**\n * Order table schema\n */\nexport interface OrderTable {\n    id: Generated<string>;\n    customer_id: string;\n    status: 'pending' | 'confirmed' | 'cancelled';\n    total_amount: number;\n    created_at: ColumnType<Date, string | undefined, never>;\n    updated_at: ColumnType<Date, string | undefined, string>;\n}\n\n/**\n * Order item table schema\n */\nexport interface OrderItemTable {\n    id: Generated<string>;\n    order_id: string;\n    product_id: string;\n    quantity: number;\n    unit_price: number;\n    subtotal: number;\n    created_at: ColumnType<Date, string | undefined, never>;\n}\n\n/**\n * Type helpers for database operations\n */\nexport type Order = Selectable<OrderTable>;\nexport type NewOrder = Insertable<OrderTable>;\nexport type OrderUpdate = Updateable<OrderTable>;\n\nexport type OrderItem = Selectable<OrderItemTable>;\nexport type NewOrderItem = Insertable<OrderItemTable>;\nexport type OrderItemUpdate = Updateable<OrderItemTable>;\n"
  },
  {
    "path": "apps/api/src/shared/database/index.ts",
    "content": "export * from './database.module';\nexport * from './database.types';\n"
  },
  {
    "path": "apps/api/src/shared/domain/aggregate-root.ts",
    "content": "import { Entity } from './entity';\nimport { DomainEvent } from './domain-event';\n\nexport abstract class AggregateRoot<T> extends Entity<T> {\n    private _domainEvents: DomainEvent[] = [];\n\n    get domainEvents(): DomainEvent[] {\n        return this._domainEvents;\n    }\n\n    protected addDomainEvent(domainEvent: DomainEvent): void {\n        this._domainEvents.push(domainEvent);\n    }\n\n    public clearEvents(): void {\n        this._domainEvents = [];\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/domain/domain-event-publisher.ts",
    "content": "import { DomainEvent } from './domain-event';\n\nexport interface IDomainEventPublisher {\n    publish(event: DomainEvent): Promise<void>;\n    publishAll(events: DomainEvent[]): Promise<void>;\n}\n\nexport const DOMAIN_EVENT_PUBLISHER = Symbol('DOMAIN_EVENT_PUBLISHER');\n"
  },
  {
    "path": "apps/api/src/shared/domain/domain-event.ts",
    "content": "export interface IDomainEvent {\n    occurredOn: Date;\n    getAggregateId(): string;\n}\n\nexport abstract class DomainEvent implements IDomainEvent {\n    public readonly occurredOn: Date;\n\n    constructor() {\n        this.occurredOn = new Date();\n    }\n\n    abstract getAggregateId(): string;\n}\n"
  },
  {
    "path": "apps/api/src/shared/domain/entity.ts",
    "content": "// ============================================================================\n// Entity - Base class for domain entities with identity\n// ============================================================================\n\n/**\n * Base class for all domain entities.\n * Entities have identity - two entities with the same ID are considered equal.\n */\nexport abstract class Entity<T = string> {\n    protected readonly _id: T;\n\n    constructor(id: T) {\n        this._id = id;\n    }\n\n    get id(): T {\n        return this._id;\n    }\n\n    /**\n     * Check equality based on identity\n     */\n    equals(entity?: Entity<T>): boolean {\n        if (entity === null || entity === undefined) {\n            return false;\n        }\n\n        if (this === entity) {\n            return true;\n        }\n\n        if (!(entity instanceof Entity)) {\n            return false;\n        }\n\n        return this._id === entity._id;\n    }\n\n    /**\n     * Check equality by ID directly (for primitive IDs)\n     */\n    equalsById(id: T): boolean {\n        return this._id === id;\n    }\n\n    /**\n     * Get the entity's identity as a string (for logging, etc.)\n     */\n    toString(): string {\n        return `${this.constructor.name}:${String(this._id)}`;\n    }\n\n    /**\n     * Get identity for persistence mapping\n     */\n    toObject(): { id: T } {\n        return { id: this._id };\n    }\n}\n\n/**\n * Interface for entities with audit fields\n */\nexport interface IAuditableEntity {\n    createdAt: Date;\n    updatedAt: Date;\n    createdBy?: string;\n    updatedBy?: string;\n}\n\n/**\n * Interface for soft-deletable entities\n */\nexport interface ISoftDeletable {\n    deletedAt?: Date;\n    deletedBy?: string;\n}\n\n/**\n * Base entity with audit fields\n */\nexport abstract class AuditableEntity<T = string> extends Entity<T> {\n    public readonly createdAt: Date;\n    public readonly updatedAt: Date;\n    public readonly createdBy?: string;\n    public readonly updatedBy?: string;\n\n    constructor(\n        id: T,\n        createdAt: Date,\n        updatedAt: Date,\n        createdBy?: string,\n        updatedBy?: string,\n    ) {\n        super(id);\n        this.createdAt = createdAt;\n        this.updatedAt = updatedAt;\n        this.createdBy = createdBy;\n        this.updatedBy = updatedBy;\n    }\n\n    /**\n     * Check if entity was created after a given date\n     */\n    isCreatedAfter(date: Date): boolean {\n        return this.createdAt > date;\n    }\n\n    /**\n     * Check if entity was updated after a given date\n     */\n    isUpdatedAfter(date: Date): boolean {\n        return this.updatedAt > date;\n    }\n\n    /**\n     * Check if entity was updated by a specific user\n     */\n    isUpdatedBy(userId: string): boolean {\n        return this.updatedBy === userId;\n    }\n}\n\n/**\n * Base entity with soft delete support\n */\nexport abstract class SoftDeletableEntity<T = string> extends AuditableEntity<T> {\n    public readonly deletedAt?: Date;\n    public readonly deletedBy?: string;\n\n    constructor(\n        id: T,\n        createdAt: Date,\n        updatedAt: Date,\n        deletedAt?: Date,\n        deletedBy?: string,\n        createdBy?: string,\n        updatedBy?: string,\n    ) {\n        super(id, createdAt, updatedAt, createdBy, updatedBy);\n        this.deletedAt = deletedAt;\n        this.deletedBy = deletedBy;\n    }\n\n    /**\n     * Check if entity is deleted\n     */\n    isDeleted(): boolean {\n        return this.deletedAt !== undefined && this.deletedAt !== null;\n    }\n\n    /**\n     * Check if deleted by a specific user\n     */\n    isDeletedBy(userId: string): boolean {\n        return this.deletedBy === userId;\n    }\n\n    /**\n     * Get days since deletion\n     */\n    daysSinceDeletion(): number | null {\n        if (!this.deletedAt) return null;\n        const now = new Date();\n        const diff = now.getTime() - this.deletedAt.getTime();\n        return Math.floor(diff / (1000 * 60 * 60 * 24));\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/domain/index.ts",
    "content": "// ============================================================================\n// Domain - DDD core building blocks\n// ============================================================================\n\nexport * from './entity';\nexport * from './value-object';\nexport * from './domain-event';\nexport * from './aggregate-root';\nexport * from './domain-event-publisher';\n"
  },
  {
    "path": "apps/api/src/shared/domain/value-object.ts",
    "content": "export interface ValueObjectProps {\n    [index: string]: any;\n}\n\nexport abstract class ValueObject<T extends ValueObjectProps> {\n    protected readonly props: T;\n\n    constructor(props: T) {\n        this.props = Object.freeze(props);\n    }\n\n    public equals(vo?: ValueObject<T>): boolean {\n        if (vo === null || vo === undefined) {\n            return false;\n        }\n        if (vo.props === undefined) {\n            return false;\n        }\n        return JSON.stringify(this.props) === JSON.stringify(vo.props);\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/errors/business.exception.ts",
    "content": "// ============================================================================\n// Business Exception - Base exception for business logic errors\n// ============================================================================\n\nimport { HttpException, HttpStatus } from '@nestjs/common';\nimport { ErrorCode, ErrorCodeHttpStatus } from './error-codes';\n\nexport interface BusinessExceptionOptions {\n    code: ErrorCode;\n    message: string;\n    details?: Record<string, unknown>;\n    httpStatus?: HttpStatus;\n}\n\nexport class BusinessException extends HttpException {\n    public readonly code: ErrorCode;\n    public readonly details?: Record<string, unknown>;\n\n    constructor(options: BusinessExceptionOptions) {\n        const httpStatus = options.httpStatus || ErrorCodeHttpStatus[options.code] || 400;\n\n        super(\n            {\n                code: options.code,\n                message: options.message,\n                details: options.details,\n            },\n            httpStatus,\n        );\n\n        this.code = options.code;\n        this.details = options.details;\n    }\n\n    getResponse(): Record<string, unknown> {\n        return {\n            code: this.code,\n            message: this.message,\n            details: this.details,\n        };\n    }\n}\n\n// ============================================================================\n// Common Business Exceptions\n// ============================================================================\n\nexport class ValidationException extends BusinessException {\n    constructor(message: string, details?: Record<string, unknown>) {\n        super({\n            code: ErrorCode.VALIDATION_ERROR,\n            message,\n            details,\n        });\n    }\n}\n\nexport class NotFoundException extends BusinessException {\n    constructor(resource: string, identifier?: string | number) {\n        const message = identifier\n            ? `${resource} with identifier '${identifier}' not found`\n            : `${resource} not found`;\n\n        super({\n            code: ErrorCode.RESOURCE_NOT_FOUND,\n            message,\n            httpStatus: HttpStatus.NOT_FOUND,\n        });\n    }\n}\n\nexport class DuplicateEntryException extends BusinessException {\n    constructor(resource: string, field: string, value: string) {\n        super({\n            code: ErrorCode.DUPLICATE_ENTRY,\n            message: `${resource} with ${field} '${value}' already exists`,\n            httpStatus: HttpStatus.CONFLICT,\n        });\n    }\n}\n\nexport class ForbiddenException extends BusinessException {\n    constructor(message = 'You do not have permission to perform this action') {\n        super({\n            code: ErrorCode.PERMISSION_DENIED,\n            message,\n            httpStatus: HttpStatus.FORBIDDEN,\n        });\n    }\n}\n\nexport class UnauthorizedException extends BusinessException {\n    constructor(message = 'Authentication required') {\n        super({\n            code: ErrorCode.UNAUTHORIZED,\n            message,\n            httpStatus: HttpStatus.UNAUTHORIZED,\n        });\n    }\n}\n\nexport class OperationFailedException extends BusinessException {\n    constructor(operation: string, reason?: string) {\n        super({\n            code: ErrorCode.OPERATION_FAILED,\n            message: reason ? `${operation} failed: ${reason}` : `${operation} failed`,\n        });\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/errors/error-codes.ts",
    "content": "// ============================================================================\n// Error Codes - Standardized error codes for the application\n// ============================================================================\n\nexport enum ErrorCode {\n    // 4xx Client Errors\n    BAD_REQUEST = 'BAD_REQUEST',\n    UNAUTHORIZED = 'UNAUTHORIZED',\n    FORBIDDEN = 'FORBIDDEN',\n    NOT_FOUND = 'NOT_FOUND',\n    CONFLICT = 'CONFLICT',\n    GONE = 'GONE',\n    UNPROCESSABLE_ENTITY = 'UNPROCESSABLE_ENTITY',\n    TOO_MANY_REQUESTS = 'TOO_MANY_REQUESTS',\n\n    // 5xx Server Errors\n    INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR',\n    NOT_IMPLEMENTED = 'NOT_IMPLEMENTED',\n    SERVICE_UNAVAILABLE = 'SERVICE_UNAVAILABLE',\n    GATEWAY_TIMEOUT = 'GATEWAY_TIMEOUT',\n\n    // Business Errors (10xxx)\n    VALIDATION_ERROR = '10001',\n    DUPLICATE_ENTRY = '10002',\n    RESOURCE_NOT_FOUND = '10003',\n    INVALID_OPERATION = '10004',\n    OPERATION_FAILED = '10005',\n    BUSINESS_RULE_VIOLATION = '10006',\n\n    // Auth Errors (20xxx)\n    TOKEN_EXPIRED = '20001',\n    TOKEN_INVALID = '20002',\n    TOKEN_MISSING = '20003',\n    PERMISSION_DENIED = '20004',\n    ACCOUNT_DISABLED = '20005',\n\n    // Domain Errors (30xxx)\n    ENTITY_NOT_FOUND = '30001',\n    ENTITY_ALREADY_EXISTS = '30002',\n    ENTITY_CONFLICT = '30003',\n\n    // External Service Errors (40xxx)\n    EXTERNAL_SERVICE_ERROR = '40001',\n    EXTERNAL_SERVICE_TIMEOUT = '40002',\n    EXTERNAL_SERVICE_UNAVAILABLE = '40003',\n}\n\nexport const ErrorCodeHttpStatus: Record<ErrorCode, number> = {\n    [ErrorCode.BAD_REQUEST]: 400,\n    [ErrorCode.UNAUTHORIZED]: 401,\n    [ErrorCode.FORBIDDEN]: 403,\n    [ErrorCode.NOT_FOUND]: 404,\n    [ErrorCode.CONFLICT]: 409,\n    [ErrorCode.GONE]: 410,\n    [ErrorCode.UNPROCESSABLE_ENTITY]: 422,\n    [ErrorCode.TOO_MANY_REQUESTS]: 429,\n    [ErrorCode.INTERNAL_SERVER_ERROR]: 500,\n    [ErrorCode.NOT_IMPLEMENTED]: 501,\n    [ErrorCode.SERVICE_UNAVAILABLE]: 503,\n    [ErrorCode.GATEWAY_TIMEOUT]: 504,\n    // Business errors map to 400 by default\n    [ErrorCode.VALIDATION_ERROR]: 400,\n    [ErrorCode.DUPLICATE_ENTRY]: 409,\n    [ErrorCode.RESOURCE_NOT_FOUND]: 404,\n    [ErrorCode.INVALID_OPERATION]: 400,\n    [ErrorCode.OPERATION_FAILED]: 400,\n    [ErrorCode.BUSINESS_RULE_VIOLATION]: 400,\n    // Auth errors\n    [ErrorCode.TOKEN_EXPIRED]: 401,\n    [ErrorCode.TOKEN_INVALID]: 401,\n    [ErrorCode.TOKEN_MISSING]: 401,\n    [ErrorCode.PERMISSION_DENIED]: 403,\n    [ErrorCode.ACCOUNT_DISABLED]: 403,\n    // Domain errors\n    [ErrorCode.ENTITY_NOT_FOUND]: 404,\n    [ErrorCode.ENTITY_ALREADY_EXISTS]: 409,\n    [ErrorCode.ENTITY_CONFLICT]: 409,\n    // External errors\n    [ErrorCode.EXTERNAL_SERVICE_ERROR]: 502,\n    [ErrorCode.EXTERNAL_SERVICE_TIMEOUT]: 504,\n    [ErrorCode.EXTERNAL_SERVICE_UNAVAILABLE]: 503,\n};\n"
  },
  {
    "path": "apps/api/src/shared/errors/error.filter.ts",
    "content": "// ============================================================================\n// Global Error Filter - Handle all exceptions and format error responses\n// ============================================================================\n\nimport {\n    ExceptionFilter,\n    Catch,\n    ArgumentsHost,\n    HttpException,\n    HttpStatus,\n    Logger,\n} from '@nestjs/common';\nimport { Request, Response } from 'express';\nimport { BusinessException } from './business.exception';\nimport { ErrorCode } from './error-codes';\n\ninterface ErrorResponse {\n    code: string;\n    message: string;\n    details?: Record<string, unknown>;\n    requestId?: string;\n    timestamp: string;\n    path?: string;\n    method?: string;\n}\n\n@Catch()\nexport class GlobalErrorFilter implements ExceptionFilter {\n    private readonly logger = new Logger(GlobalErrorFilter.name);\n\n    catch(exception: unknown, host: ArgumentsHost): void {\n        const ctx = host.switchToHttp();\n        const response = ctx.getResponse<Response>();\n        const request = ctx.getRequest<Request>();\n\n        const requestId =\n            (request.headers['x-request-id'] as string) ||\n            (request.headers['x-correlation-id'] as string);\n\n        let errorResponse: ErrorResponse;\n\n        if (exception instanceof BusinessException) {\n            errorResponse = {\n                code: exception.code,\n                message: exception.message,\n                details: exception.details,\n                requestId,\n                timestamp: new Date().toISOString(),\n                path: request.url,\n                method: request.method,\n            };\n\n            this.logger.warn(\n                `[${errorResponse.code}] ${errorResponse.message}`,\n                {\n                    requestId,\n                    path: request.url,\n                    method: request.method,\n                },\n            );\n        } else if (exception instanceof HttpException) {\n            const status = exception.getStatus();\n            const exceptionResponse = exception.getResponse();\n\n            if (typeof exceptionResponse === 'object' && exceptionResponse !== null) {\n                const resp = exceptionResponse as Record<string, unknown>;\n                errorResponse = {\n                    code: this.getErrorCode(status),\n                    message: (resp.message as string) || exception.message,\n                    details: resp.errors as Record<string, unknown>,\n                    requestId,\n                    timestamp: new Date().toISOString(),\n                    path: request.url,\n                    method: request.method,\n                };\n            } else {\n                errorResponse = {\n                    code: this.getErrorCode(status),\n                    message: exception.message,\n                    requestId,\n                    timestamp: new Date().toISOString(),\n                    path: request.url,\n                    method: request.method,\n                };\n            }\n\n            if (status >= 500) {\n                this.logger.error(\n                    `[${errorResponse.code}] ${errorResponse.message}`,\n                    exception instanceof Error ? exception.stack : undefined,\n                    { requestId, path: request.url, method: request.method },\n                );\n            } else {\n                this.logger.warn(\n                    `[${errorResponse.code}] ${errorResponse.message}`,\n                    { requestId, path: request.url, method: request.method },\n                );\n            }\n        } else if (exception instanceof Error) {\n            errorResponse = {\n                code: ErrorCode.INTERNAL_SERVER_ERROR,\n                message:\n                    process.env.NODE_ENV === 'production'\n                        ? 'Internal server error'\n                        : exception.message,\n                requestId,\n                timestamp: new Date().toISOString(),\n                path: request.url,\n                method: request.method,\n            };\n\n            this.logger.error(\n                `[${errorResponse.code}] ${exception.message}`,\n                exception.stack,\n                { requestId, path: request.url, method: request.method },\n            );\n        } else {\n            errorResponse = {\n                code: ErrorCode.INTERNAL_SERVER_ERROR,\n                message: 'An unexpected error occurred',\n                requestId,\n                timestamp: new Date().toISOString(),\n                path: request.url,\n                method: request.method,\n            };\n\n            this.logger.error(\n                `[${errorResponse.code}] Unknown exception`,\n                exception as Error,\n                { requestId, path: request.url, method: request.method },\n            );\n        }\n\n        response.status(errorResponse.code.startsWith('2') ? 200 : (exception instanceof HttpException ? exception.getStatus() : 500)).json({\n            code: errorResponse.code,\n            message: errorResponse.message,\n            details: errorResponse.details,\n            requestId: errorResponse.requestId,\n            timestamp: errorResponse.timestamp,\n        });\n    }\n\n    private getErrorCode(status: number): string {\n        const statusToCode: Record<number, ErrorCode> = {\n            400: ErrorCode.BAD_REQUEST,\n            401: ErrorCode.UNAUTHORIZED,\n            403: ErrorCode.FORBIDDEN,\n            404: ErrorCode.NOT_FOUND,\n            409: ErrorCode.CONFLICT,\n            422: ErrorCode.UNPROCESSABLE_ENTITY,\n            429: ErrorCode.TOO_MANY_REQUESTS,\n            500: ErrorCode.INTERNAL_SERVER_ERROR,\n            502: ErrorCode.EXTERNAL_SERVICE_ERROR,\n            503: ErrorCode.SERVICE_UNAVAILABLE,\n            504: ErrorCode.GATEWAY_TIMEOUT,\n        };\n\n        return statusToCode[status] || ErrorCode.INTERNAL_SERVER_ERROR;\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/errors/index.ts",
    "content": "export * from './error-codes';\nexport * from './business.exception';\nexport * from './error.filter';\n"
  },
  {
    "path": "apps/api/src/shared/feature-flags/feature-flags.decorator.ts",
    "content": "// ============================================================================\n// Feature Flags Decorators\n// ============================================================================\n\nimport { createParamDecorator, ExecutionContext } from '@nestjs/common';\nimport { FeatureFlagsService } from './feature-flags.service';\n\n/**\n * Check if a feature flag is enabled\n */\nexport const FeatureFlag = createParamDecorator(\n    async (flagName: string, ctx: ExecutionContext) => {\n        const request = ctx.switchToHttp().getRequest();\n        const user = request.user;\n\n        const flagsService = request.featureFlagsService;\n        if (!flagsService) {\n            return false;\n        }\n\n        return flagsService.isEnabled(flagName);\n    },\n);\n\n/**\n * Get feature flag evaluation result\n */\nexport const FeatureFlagEvaluation = createParamDecorator(\n    async (flagName: string, ctx: ExecutionContext) => {\n        const request = ctx.switchToHttp().getRequest();\n        const user = request.user;\n\n        const flagsService = request.featureFlagsService;\n        if (!flagsService) {\n            return { enabled: false, reason: 'Service not available' };\n        }\n\n        return flagsService.evaluate(flagName, {\n            userId: user?.sub,\n            organizationId: user?.organizationId,\n            groups: user?.groups,\n        });\n    },\n);\n"
  },
  {
    "path": "apps/api/src/shared/feature-flags/feature-flags.guard.ts",
    "content": "// ============================================================================\n// Feature Flags Guard - Protects routes based on feature flags\n// ============================================================================\n\nimport {\n    Injectable,\n    CanActivate,\n    ExecutionContext,\n    ForbiddenException,\n    SetMetadata,\n} from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport { FeatureFlagsService } from './feature-flags.service';\n\nexport const FEATURE_FLAG_KEY = 'feature_flag';\n\n/**\n * Require a feature flag to be enabled\n */\nexport const RequiresFeature = (flagName: string) =>\n    SetMetadata(FEATURE_FLAG_KEY, flagName);\n\n@Injectable()\nexport class FeatureFlagsGuard implements CanActivate {\n    constructor(\n        private readonly featureFlagsService: FeatureFlagsService,\n        private readonly reflector: Reflector,\n    ) {}\n\n    async canActivate(context: ExecutionContext): Promise<boolean> {\n        const flagName = this.reflector.get<string>(\n            FEATURE_FLAG_KEY,\n            context.getHandler(),\n        );\n\n        if (!flagName) {\n            return true;\n        }\n\n        const request = context.switchToHttp().getRequest();\n        const user = request.user;\n\n        const evaluation = await this.featureFlagsService.evaluate(flagName, {\n            userId: user?.sub,\n            organizationId: user?.organizationId,\n            groups: user?.groups,\n        });\n\n        if (!evaluation.enabled) {\n            throw new ForbiddenException(\n                `This feature is currently unavailable: ${flagName}`,\n            );\n        }\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/feature-flags/feature-flags.service.ts",
    "content": "// ============================================================================\n// Feature Flags Service - Feature toggles and gradual rollouts\n// ============================================================================\n\nimport { Injectable, OnModuleDestroy, Logger } from '@nestjs/common';\nimport { RedissonService } from '@a3s-lab/redisson';\n\nexport interface FeatureFlag {\n    name: string;\n    enabled: boolean;\n    description?: string;\n    /** Rollout percentage (0-100) */\n    rolloutPercentage?: number;\n    /** User groups that always have access */\n    allowedGroups?: string[];\n    /** User groups that never have access */\n    excludedGroups?: string[];\n    /** Metadata for the feature */\n    metadata?: Record<string, unknown>;\n    /** When the feature was enabled */\n    enabledAt?: Date;\n    /** When the feature will be disabled (optional) */\n    expiresAt?: Date;\n}\n\nexport interface FeatureFlagEvaluation {\n    enabled: boolean;\n    reason: string;\n    metadata?: Record<string, unknown>;\n}\n\n/**\n * Default feature flags\n */\nexport const DEFAULT_FEATURE_FLAGS: Record<string, Omit<FeatureFlag, 'name' | 'enabledAt'>> = {\n    'new-dashboard': { enabled: true, rolloutPercentage: 100 },\n    'ai-assistant': { enabled: true, rolloutPercentage: 50 },\n    'advanced-analytics': { enabled: false },\n    'dark-mode': { enabled: true, rolloutPercentage: 100 },\n};\n\n/**\n * Feature Flags Service\n */\n@Injectable()\nexport class FeatureFlagsService implements OnModuleDestroy {\n    private readonly logger = new Logger(FeatureFlagsService.name);\n    private readonly keyPrefix = 'feature:';\n    private readonly localCache: Map<string, FeatureFlag> = new Map();\n\n    constructor(private readonly redis: RedissonService) {}\n\n    /**\n     * Check if a feature flag is enabled\n     */\n    async isEnabled(flagName: string): Promise<boolean> {\n        const evaluation = await this.evaluate(flagName);\n        return evaluation.enabled;\n    }\n\n    /**\n     * Evaluate a feature flag for a specific user/context\n     */\n    async evaluate(\n        flagName: string,\n        context?: {\n            userId?: string;\n            organizationId?: string;\n            groups?: string[];\n            attributes?: Record<string, unknown>;\n        },\n    ): Promise<FeatureFlagEvaluation> {\n        const flag = await this.getFlag(flagName);\n\n        if (!flag) {\n            return { enabled: false, reason: 'Flag not found' };\n        }\n\n        if (!flag.enabled) {\n            return { enabled: false, reason: 'Flag disabled', metadata: flag.metadata };\n        }\n\n        // Check expiration\n        if (flag.expiresAt && new Date() > flag.expiresAt) {\n            return { enabled: false, reason: 'Flag expired', metadata: flag.metadata };\n        }\n\n        // Check excluded groups\n        if (context?.groups && flag.excludedGroups) {\n            const hasExcludedGroup = context.groups.some(g => flag.excludedGroups!.includes(g));\n            if (hasExcludedGroup) {\n                return { enabled: false, reason: 'User in excluded group', metadata: flag.metadata };\n            }\n        }\n\n        // Check allowed groups\n        if (context?.groups && flag.allowedGroups) {\n            const hasAllowedGroup = context.groups.some(g => flag.allowedGroups!.includes(g));\n            if (hasAllowedGroup) {\n                return { enabled: true, reason: 'User in allowed group', metadata: flag.metadata };\n            }\n        }\n\n        // Check rollout percentage\n        if (flag.rolloutPercentage !== undefined && flag.rolloutPercentage < 100) {\n            const userId = context?.userId ?? context?.organizationId ?? 'anonymous';\n            const hash = this.hashUserId(userId, flagName);\n            const bucket = hash % 100;\n\n            if (bucket >= flag.rolloutPercentage) {\n                return {\n                    enabled: false,\n                    reason: `User not in rollout percentage (${flag.rolloutPercentage}%)`,\n                    metadata: flag.metadata,\n                };\n            }\n        }\n\n        return { enabled: true, reason: 'Flag enabled', metadata: flag.metadata };\n    }\n\n    /**\n     * Get feature flag definition\n     */\n    async getFlag(flagName: string): Promise<FeatureFlag | null> {\n        // Check local cache first\n        const cached = this.localCache.get(flagName);\n        if (cached) {\n            return cached;\n        }\n\n        try {\n            const key = `${this.keyPrefix}${flagName}`;\n            const data = await this.redis.get(key);\n\n            if (data) {\n                const flag = JSON.parse(data) as FeatureFlag;\n                this.localCache.set(flagName, flag);\n                return flag;\n            }\n\n            // Fallback to defaults\n            const defaultFlag = DEFAULT_FEATURE_FLAGS[flagName];\n            if (defaultFlag) {\n                const flag: FeatureFlag = {\n                    name: flagName,\n                    enabled: defaultFlag.enabled ?? false,\n                    rolloutPercentage: defaultFlag.rolloutPercentage,\n                    allowedGroups: defaultFlag.allowedGroups,\n                    excludedGroups: defaultFlag.excludedGroups,\n                    metadata: defaultFlag.metadata,\n                };\n                this.localCache.set(flagName, flag);\n                return flag;\n            }\n\n            return null;\n        } catch (error) {\n            this.logger.error(`Error getting feature flag ${flagName}: ${error}`);\n            return null;\n        }\n    }\n\n    /**\n     * Enable a feature flag\n     */\n    async enable(flagName: string, metadata?: Record<string, unknown>): Promise<void> {\n        const existing = await this.getFlag(flagName);\n        const flag: FeatureFlag = {\n            name: flagName,\n            enabled: true,\n            enabledAt: new Date(),\n            description: existing?.description,\n            rolloutPercentage: existing?.rolloutPercentage,\n            allowedGroups: existing?.allowedGroups,\n            excludedGroups: existing?.excludedGroups,\n            metadata,\n        };\n\n        await this.saveFlag(flag);\n        this.logger.log(`Feature flag enabled: ${flagName}`);\n    }\n\n    /**\n     * Disable a feature flag\n     */\n    async disable(flagName: string): Promise<void> {\n        const existing = await this.getFlag(flagName);\n        const flag: FeatureFlag = {\n            name: flagName,\n            enabled: false,\n            description: existing?.description,\n            rolloutPercentage: existing?.rolloutPercentage,\n            allowedGroups: existing?.allowedGroups,\n            excludedGroups: existing?.excludedGroups,\n            metadata: existing?.metadata,\n            enabledAt: existing?.enabledAt,\n        };\n\n        await this.saveFlag(flag);\n        this.logger.log(`Feature flag disabled: ${flagName}`);\n    }\n\n    /**\n     * Set rollout percentage\n     */\n    async setRollout(flagName: string, percentage: number): Promise<void> {\n        const existing = await this.getFlag(flagName);\n        const flag: FeatureFlag = {\n            name: flagName,\n            enabled: true,\n            rolloutPercentage: Math.max(0, Math.min(100, percentage)),\n            description: existing?.description,\n            allowedGroups: existing?.allowedGroups,\n            excludedGroups: existing?.excludedGroups,\n            metadata: existing?.metadata,\n            enabledAt: existing?.enabledAt,\n        };\n\n        await this.saveFlag(flag);\n        this.logger.log(`Feature flag rollout set: ${flagName} = ${percentage}%`);\n    }\n\n    /**\n     * Set expiration date\n     */\n    async setExpiration(flagName: string, expiresAt: Date): Promise<void> {\n        const existing = await this.getFlag(flagName);\n        const flag: FeatureFlag = {\n            name: flagName,\n            enabled: existing?.enabled ?? false,\n            description: existing?.description,\n            rolloutPercentage: existing?.rolloutPercentage,\n            allowedGroups: existing?.allowedGroups,\n            excludedGroups: existing?.excludedGroups,\n            metadata: existing?.metadata,\n            enabledAt: existing?.enabledAt,\n            expiresAt,\n        };\n\n        await this.saveFlag(flag);\n        this.logger.log(`Feature flag expiration set: ${flagName} = ${expiresAt.toISOString()}`);\n    }\n\n    /**\n     * Get all feature flags\n     */\n    async getAllFlags(): Promise<FeatureFlag[]> {\n        try {\n            const redisClient = (this.redis as any).redis;\n            const keys = await redisClient.keys(`${this.keyPrefix}*`);\n            const flags: FeatureFlag[] = [];\n\n            for (const key of keys) {\n                const data = await this.redis.get(key);\n                if (data) {\n                    flags.push(JSON.parse(data));\n                }\n            }\n\n            // Add default flags that don't exist in Redis\n            for (const [name, defaultFlag] of Object.entries(DEFAULT_FEATURE_FLAGS)) {\n                if (!flags.find(f => f.name === name)) {\n                    flags.push({\n                        name,\n                        enabled: defaultFlag.enabled ?? false,\n                        rolloutPercentage: defaultFlag.rolloutPercentage,\n                        allowedGroups: defaultFlag.allowedGroups,\n                        excludedGroups: defaultFlag.excludedGroups,\n                        metadata: defaultFlag.metadata,\n                    });\n                }\n            }\n\n            return flags;\n        } catch (error) {\n            this.logger.error(`Error getting all feature flags: ${error}`);\n            return [];\n        }\n    }\n\n    /**\n     * Save flag to Redis\n     */\n    private async saveFlag(flag: FeatureFlag): Promise<void> {\n        const key = `${this.keyPrefix}${flag.name}`;\n        await this.redis.set(key, JSON.stringify(flag));\n        this.localCache.set(flag.name, flag);\n    }\n\n    /**\n     * Hash user ID for consistent rollout bucket\n     */\n    private hashUserId(userId: string, flagName: string): number {\n        const input = `${userId}:${flagName}`;\n        let hash = 0;\n        for (let i = 0; i < input.length; i++) {\n            const char = input.charCodeAt(i);\n            hash = ((hash << 5) - hash) + char;\n            hash = hash & hash; // Convert to 32bit integer\n        }\n        return Math.abs(hash);\n    }\n\n    onModuleDestroy(): void {\n        this.localCache.clear();\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/feature-flags/index.ts",
    "content": "// ============================================================================\n// Feature Flags Module\n// ============================================================================\n\nexport { FeatureFlagsService } from './feature-flags.service';\nexport { FeatureFlagsGuard, RequiresFeature } from './feature-flags.guard';\nexport { FeatureFlag, FeatureFlagEvaluation } from './feature-flags.decorator';\n"
  },
  {
    "path": "apps/api/src/shared/file-upload/file-upload.decorator.ts",
    "content": "// ============================================================================\n// File Upload Decorators\n// ============================================================================\n\nimport { SetMetadata } from '@nestjs/common';\n\nexport const FILE_UPLOAD_KEY = 'file_upload';\n\n/**\n * Decorator to mark endpoint for file upload handling\n */\nexport const FileUpload = (options?: {\n    fieldName?: string;\n    maxSize?: number;\n    allowedTypes?: string[];\n    allowedExtensions?: string[];\n    maxCount?: number;\n}) => SetMetadata(FILE_UPLOAD_KEY, options ?? {});\n\n/**\n * Upload single file\n */\nexport const UploadedFile = () => SetMetadata('isSingleFile', true);\n\n/**\n * Upload multiple files\n */\nexport const UploadedFiles = () => SetMetadata('isMultipleFiles', true);\n"
  },
  {
    "path": "apps/api/src/shared/file-upload/file-upload.interceptor.ts",
    "content": "// ============================================================================\n// File Upload Interceptor - Handles multipart file uploads\n// ============================================================================\n\nimport {\n    Injectable,\n    NestInterceptor,\n    ExecutionContext,\n    CallHandler,\n    BadRequestException,\n} from '@nestjs/common';\nimport { Observable } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport { FileUploadService } from './file-upload.service';\n\nexport interface UploadedFile {\n    fieldname: string;\n    originalname: string;\n    encoding: string;\n    mimetype: string;\n    size: number;\n    buffer: Buffer;\n}\n\nexport interface FileUploadMetadata {\n    /** Field name in the form */\n    fieldname: string;\n    /** Maximum file size in bytes */\n    maxSize?: number;\n    /** Allowed MIME types */\n    allowedTypes?: string[];\n    /** Allowed extensions */\n    allowedExtensions?: string[];\n    /** Whether file is required */\n    required?: boolean;\n}\n\n/**\n * Interceptor that processes multipart file uploads\n */\n@Injectable()\nexport class FileUploadInterceptor implements NestInterceptor {\n    constructor(private readonly fileUploadService: FileUploadService) {}\n\n    async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {\n        const request = context.switchToHttp().getRequest();\n\n        if (!request.body || !request.files) {\n            throw new BadRequestException('No file uploaded');\n        }\n\n        // Handle both single and multiple files\n        const files = this.extractFiles(request);\n\n        // Process and upload files\n        const uploadedFiles = await Promise.all(\n            files.map(async (file) => {\n                return this.fileUploadService.uploadFile(\n                    file.buffer,\n                    file.originalname,\n                    file.mimetype,\n                );\n            }),\n        );\n\n        // Attach uploaded files to request\n        request.uploadedFiles = uploadedFiles;\n\n        return next.handle().pipe(\n            map((response) => {\n                // If response already contains data, merge uploaded files\n                if (response && typeof response === 'object') {\n                    return {\n                        ...response,\n                        files: uploadedFiles,\n                    };\n                }\n                return { files: uploadedFiles };\n            }),\n        );\n    }\n\n    /**\n     * Extract files from request\n     */\n    private extractFiles(request: any): UploadedFile[] {\n        const files: UploadedFile[] = [];\n\n        if (Array.isArray(request.files)) {\n            // Multiple files as array\n            files.push(...request.files);\n        } else if (request.files && typeof request.files === 'object') {\n            // Files as object (fieldname -> file or array)\n            for (const fieldName of Object.keys(request.files)) {\n                const fieldFiles = request.files[fieldName];\n                if (Array.isArray(fieldFiles)) {\n                    files.push(...fieldFiles);\n                } else {\n                    files.push(fieldFiles);\n                }\n            }\n        }\n\n        return files;\n    }\n}\n\n/**\n * Single file upload interceptor\n */\n@Injectable()\nexport class SingleFileUploadInterceptor implements NestInterceptor {\n    constructor(private readonly fileUploadService: FileUploadService) {}\n\n    async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {\n        const request = context.switchToHttp().getRequest();\n\n        if (!request.file && !request.files?.file) {\n            throw new BadRequestException('No file uploaded');\n        }\n\n        const file = request.file || request.files?.file;\n\n        const uploadedFile = await this.fileUploadService.uploadFile(\n            file.buffer,\n            file.originalname,\n            file.mimetype,\n        );\n\n        request.uploadedFile = uploadedFile;\n\n        return next.handle().pipe(\n            map((response) => {\n                if (response && typeof response === 'object') {\n                    return {\n                        ...response,\n                        file: uploadedFile,\n                    };\n                }\n                return { file: uploadedFile };\n            }),\n        );\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/file-upload/file-upload.service.ts",
    "content": "// ============================================================================\n// File Upload Service - Multipart handling and storage\n// ============================================================================\n\nimport { Injectable, BadRequestException, Logger } from '@nestjs/common';\nimport { RustFSService } from '../rustfs/rustfs.service';\n\nexport interface UploadedFile {\n    /** Original filename */\n    originalName: string;\n    /** Stored filename (unique) */\n    storedName: string;\n    /** File MIME type */\n    mimeType: string;\n    /** File size in bytes */\n    size: number;\n    /** Full URL to access the file */\n    url: string;\n    /** SHA256 hash of file content */\n    hash?: string;\n    /** Upload timestamp */\n    uploadedAt: Date;\n}\n\nexport interface FileValidationOptions {\n    /** Maximum file size in bytes */\n    maxSize?: number;\n    /** Allowed MIME types (e.g., ['image/png', 'image/jpeg']) */\n    allowedTypes?: string[];\n    /** Allowed extensions (e.g., ['.png', '.jpg']) */\n    allowedExtensions?: string[];\n}\n\n/**\n * Default validation options\n */\nexport const DEFAULT_FILE_VALIDATION: FileValidationOptions = {\n    maxSize: 10 * 1024 * 1024, // 10MB\n    allowedTypes: [\n        'image/png',\n        'image/jpeg',\n        'image/gif',\n        'image/webp',\n        'application/pdf',\n        'text/plain',\n        'application/json',\n    ],\n};\n\n/**\n * File Upload Service - handles file uploads with RustFS storage\n */\n@Injectable()\nexport class FileUploadService {\n    private readonly logger = new Logger(FileUploadService.name);\n\n    constructor(private readonly rustFsService: RustFSService) {}\n\n    /**\n     * Upload a single file\n     */\n    async uploadFile(\n        buffer: Buffer,\n        filename: string,\n        mimeType: string,\n        options?: FileValidationOptions,\n    ): Promise<UploadedFile> {\n        const validation = { ...DEFAULT_FILE_VALIDATION, ...options };\n\n        // Validate file\n        this.validateFile(buffer, filename, mimeType, validation);\n\n        // Generate unique filename\n        const storedName = this.generateStoredName(filename);\n\n        // Upload to storage\n        const key = `uploads/${storedName}`;\n        await this.rustFsService.upload(key, buffer, { contentType: mimeType });\n\n        this.logger.log(`File uploaded: ${filename} -> ${storedName}`);\n\n        return {\n            originalName: filename,\n            storedName,\n            mimeType,\n            size: buffer.length,\n            url: await this.rustFsService.getSignedUrl(key),\n            uploadedAt: new Date(),\n        };\n    }\n\n    /**\n     * Upload multiple files\n     */\n    async uploadFiles(\n        files: Array<{ buffer: Buffer; filename: string; mimeType: string }>,\n        options?: FileValidationOptions,\n    ): Promise<UploadedFile[]> {\n        const results: UploadedFile[] = [];\n\n        for (const file of files) {\n            const result = await this.uploadFile(file.buffer, file.filename, file.mimeType, options);\n            results.push(result);\n        }\n\n        return results;\n    }\n\n    /**\n     * Delete a file\n     */\n    async deleteFile(storedName: string): Promise<void> {\n        const key = `uploads/${storedName}`;\n        await this.rustFsService.delete(key);\n        this.logger.log(`File deleted: ${storedName}`);\n    }\n\n    /**\n     * Get file URL\n     */\n    async getFileUrl(storedName: string, expiresInSeconds = 3600): Promise<string> {\n        const key = `uploads/${storedName}`;\n        return this.rustFsService.getSignedUrl(key, expiresInSeconds);\n    }\n\n    /**\n     * Check if file exists\n     */\n    async fileExists(storedName: string): Promise<boolean> {\n        const key = `uploads/${storedName}`;\n        return this.rustFsService.exists(key);\n    }\n\n    /**\n     * Validate file\n     */\n    private validateFile(\n        buffer: Buffer,\n        filename: string,\n        mimeType: string,\n        options: FileValidationOptions,\n    ): void {\n        // Check size\n        if (options.maxSize && buffer.length > options.maxSize) {\n            const maxMB = options.maxSize / (1024 * 1024);\n            throw new BadRequestException(`File size exceeds maximum allowed (${maxMB}MB)`);\n        }\n\n        // Check MIME type\n        if (options.allowedTypes && !options.allowedTypes.includes(mimeType)) {\n            throw new BadRequestException(\n                `File type '${mimeType}' is not allowed. Allowed types: ${options.allowedTypes.join(', ')}`,\n            );\n        }\n\n        // Check extension\n        if (options.allowedExtensions) {\n            const ext = this.getExtension(filename).toLowerCase();\n            const allowedExts = options.allowedExtensions.map(e => e.toLowerCase());\n            if (!allowedExts.includes(ext)) {\n                throw new BadRequestException(\n                    `File extension '${ext}' is not allowed. Allowed extensions: ${allowedExts.join(', ')}`,\n                );\n            }\n        }\n    }\n\n    /**\n     * Generate unique filename\n     */\n    private generateStoredName(originalName: string): string {\n        const ext = this.getExtension(originalName);\n        const timestamp = Date.now();\n        const random = Math.random().toString(36).substring(2, 15);\n        return `${timestamp}-${random}${ext}`;\n    }\n\n    /**\n     * Get file extension\n     */\n    private getExtension(filename: string): string {\n        const lastDot = filename.lastIndexOf('.');\n        return lastDot !== -1 ? filename.substring(lastDot) : '';\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/file-upload/index.ts",
    "content": "// ============================================================================\n// File Upload Module\n// ============================================================================\n\nexport { FileUploadService } from './file-upload.service';\nexport type { UploadedFile, FileValidationOptions } from './file-upload.service';\nexport { FileUploadInterceptor, SingleFileUploadInterceptor } from './file-upload.interceptor';\n"
  },
  {
    "path": "apps/api/src/shared/health/health.controller.ts",
    "content": "// ============================================================================\n// Health Controller - Health check endpoints\n// ============================================================================\n\nimport { Controller, Get } from '@nestjs/common';\nimport {\n    HealthCheck,\n    HealthCheckService,\n    HealthCheckResult,\n} from '@nestjs/terminus';\nimport { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';\nimport { DatabaseHealthIndicator } from './indicators/database.indicator';\nimport { RedisHealthIndicator } from './indicators/redis.indicator';\n\n@ApiTags('Health')\n@Controller()\nexport class HealthController {\n    constructor(\n        private readonly health: HealthCheckService,\n        private readonly databaseIndicator: DatabaseHealthIndicator,\n        private readonly redisIndicator: RedisHealthIndicator,\n    ) {}\n\n    @Get('health')\n    @HealthCheck()\n    @ApiOperation({ summary: 'Health check endpoint' })\n    @ApiResponse({ status: 200, description: 'Service is healthy' })\n    @ApiResponse({ status: 503, description: 'Service is unhealthy' })\n    async check(): Promise<HealthCheckResult> {\n        return this.health.check([\n            () => this.databaseIndicator.isHealthy('database'),\n            () => this.redisIndicator.isHealthy('redis'),\n        ]);\n    }\n\n    @Get('health/live')\n    @ApiOperation({ summary: 'Liveness probe - is the service running?' })\n    @ApiResponse({ status: 200, description: 'Service is alive' })\n    live(): { status: string } {\n        return { status: 'ok' };\n    }\n\n    @Get('health/ready')\n    @HealthCheck()\n    @ApiOperation({ summary: 'Readiness probe - is the service ready to accept traffic?' })\n    @ApiResponse({ status: 200, description: 'Service is ready' })\n    @ApiResponse({ status: 503, description: 'Service is not ready' })\n    async ready(): Promise<HealthCheckResult> {\n        return this.health.check([\n            () => this.databaseIndicator.isHealthy('database'),\n            () => this.redisIndicator.isHealthy('redis'),\n        ]);\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/health/health.module.ts",
    "content": "// ============================================================================\n// Health Module\n// ============================================================================\n\nimport { Module } from '@nestjs/common';\nimport { TerminusModule } from '@nestjs/terminus';\nimport { HealthController } from './health.controller';\nimport { DatabaseHealthIndicator } from './indicators/database.indicator';\nimport { RedisHealthIndicator } from './indicators/redis.indicator';\nimport { NatsHealthIndicator } from './indicators/nats.indicator';\nimport { RustFSHealthIndicator } from './indicators/rustfs.indicator';\n\n@Module({\n    imports: [TerminusModule],\n    controllers: [HealthController],\n    providers: [\n        DatabaseHealthIndicator,\n        RedisHealthIndicator,\n        NatsHealthIndicator,\n        RustFSHealthIndicator,\n    ],\n})\nexport class HealthModule {}\n"
  },
  {
    "path": "apps/api/src/shared/health/index.ts",
    "content": "export * from './health.controller';\nexport * from './health.module';\nexport * from './indicators/database.indicator';\nexport * from './indicators/redis.indicator';\n"
  },
  {
    "path": "apps/api/src/shared/health/indicators/database.indicator.ts",
    "content": "// ============================================================================\n// Health Indicator - Database\n// ============================================================================\n\nimport { Injectable } from '@nestjs/common';\nimport { HealthIndicator, HealthIndicatorResult, HealthCheckError } from '@nestjs/terminus';\nimport { KyselyService } from '@a3s-lab/kysely';\n\n@Injectable()\nexport class DatabaseHealthIndicator extends HealthIndicator {\n    constructor(private readonly kysely: KyselyService) {\n        super();\n    }\n\n    async isHealthy(key: string): Promise<HealthIndicatorResult> {\n        try {\n            await this.kysely.execute('SELECT 1');\n            return this.getStatus(key, true);\n        } catch (error) {\n            throw new HealthCheckError(\n                'Database check failed',\n                this.getStatus(key, false, { message: (error as Error).message }),\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/health/indicators/nats.indicator.ts",
    "content": "// ============================================================================\n// Health Indicator - NATS\n// ============================================================================\n\nimport { Injectable } from '@nestjs/common';\nimport { HealthIndicator, HealthIndicatorResult, HealthCheckError } from '@nestjs/terminus';\nimport { NatsServiceImpl } from '@a3s-lab/nats';\nimport { IMessagingService } from '../../infrastructure/messaging/messaging.interface';\n\n@Injectable()\nexport class NatsHealthIndicator extends HealthIndicator {\n    constructor(private readonly nats: IMessagingService) {\n        super();\n    }\n\n    async isHealthy(key: string): Promise<HealthIndicatorResult> {\n        try {\n            const isHealthy = await this.nats.isHealthy();\n            if (isHealthy) {\n                return this.getStatus(key, true);\n            }\n            throw new Error('NATS connection not healthy');\n        } catch (error) {\n            throw new HealthCheckError(\n                'NATS check failed',\n                this.getStatus(key, false, { message: (error as Error).message }),\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/health/indicators/redis.indicator.ts",
    "content": "// ============================================================================\n// Health Indicator - Redis\n// ============================================================================\n\nimport { Injectable } from '@nestjs/common';\nimport { HealthIndicator, HealthIndicatorResult, HealthCheckError } from '@nestjs/terminus';\nimport { RedissonService } from '@a3s-lab/redisson';\n\n@Injectable()\nexport class RedisHealthIndicator extends HealthIndicator {\n    constructor(private readonly redis: RedissonService) {\n        super();\n    }\n\n    async isHealthy(key: string): Promise<HealthIndicatorResult> {\n        try {\n            await this.redis.ping();\n            return this.getStatus(key, true);\n        } catch (error) {\n            throw new HealthCheckError(\n                'Redis check failed',\n                this.getStatus(key, false, { message: (error as Error).message }),\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/health/indicators/rustfs.indicator.ts",
    "content": "// ============================================================================\n// Health Indicator - RustFS\n// ============================================================================\n\nimport { Injectable } from '@nestjs/common';\nimport { HealthIndicator, HealthIndicatorResult, HealthCheckError } from '@nestjs/terminus';\nimport { RustFSServiceImpl } from '@a3s-lab/rustfs';\nimport { IStorageService } from '../../infrastructure/storage/storage.interface';\n\n@Injectable()\nexport class RustFSHealthIndicator extends HealthIndicator {\n    constructor(private readonly rustfs: IStorageService) {\n        super();\n    }\n\n    async isHealthy(key: string): Promise<HealthIndicatorResult> {\n        try {\n            const isHealthy = await this.rustfs.isHealthy();\n            if (isHealthy) {\n                return this.getStatus(key, true);\n            }\n            throw new Error('RustFS not healthy');\n        } catch (error) {\n            throw new HealthCheckError(\n                'RustFS check failed',\n                this.getStatus(key, false, { message: (error as Error).message }),\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/infrastructure/messaging/event-bus.interface.ts",
    "content": "import { DomainEvent } from '@/shared/domain/domain-event';\n\nexport interface IEventBus {\n    publish(event: DomainEvent): Promise<void>;\n    publishAll(events: DomainEvent[]): Promise<void>;\n}\n\nexport const EVENT_BUS = Symbol('EVENT_BUS');\n"
  },
  {
    "path": "apps/api/src/shared/infrastructure/messaging/event-bus.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { EventBus as NestEventBus } from '@nestjs/cqrs';\nimport { IEventBus } from './event-bus.interface';\nimport { DomainEvent } from '@/shared/domain/domain-event';\n\n@Injectable()\nexport class EventBusService implements IEventBus {\n    constructor(private readonly eventBus: NestEventBus) {}\n\n    async publish(event: DomainEvent): Promise<void> {\n        await this.eventBus.publish(event);\n    }\n\n    async publishAll(events: DomainEvent[]): Promise<void> {\n        await Promise.all(events.map(event => this.eventBus.publish(event)));\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/infrastructure/messaging/messaging.interface.ts",
    "content": "// ============================================================================\n// Messaging Infrastructure Interface\n// ============================================================================\n\nimport type { JetStreamClient, NatsConnection } from 'nats';\nimport type { PublishOptions, RequestOptions, SubscribeOptions, NatsMessage, SubscriptionHandler } from '@a3s-lab/nats';\n\nexport interface ISubscription {\n    sid: number;\n    subject: string;\n    queue?: string;\n    cancel(): void;\n    isCancelled(): boolean;\n}\n\nexport interface IMessagingService {\n    getConnection(): Promise<NatsConnection>;\n    getJetStream(): Promise<JetStreamClient>;\n\n    publish(options: PublishOptions): Promise<void>;\n    pubsub(subject: string, data: object): Promise<void>;\n    request(options: RequestOptions): Promise<NatsMessage>;\n    request$<T>(subject: string, data?: object): Promise<T>;\n\n    subscribe(options: SubscribeOptions, handler: SubscriptionHandler): Promise<ISubscription>;\n    subscribe$(subject: string, handler: (data: unknown) => Promise<void>): Promise<ISubscription>;\n    unsubscribe(subscription: ISubscription): void;\n\n    // Health check\n    isHealthy(): Promise<boolean>;\n}\n"
  },
  {
    "path": "apps/api/src/shared/infrastructure/persistence/repository.interface.ts",
    "content": "export interface IRepository<T> {\n    findById(id: string): Promise<T | null>;\n    save(entity: T): Promise<T>;\n    delete(id: string): Promise<void>;\n}\n"
  },
  {
    "path": "apps/api/src/shared/infrastructure/persistence/unit-of-work.interface.ts",
    "content": "export interface IUnitOfWork {\n    start(): Promise<void>;\n    commit(): Promise<void>;\n    rollback(): Promise<void>;\n}\n"
  },
  {
    "path": "apps/api/src/shared/infrastructure/storage/storage.interface.ts",
    "content": "// ============================================================================\n// Storage Infrastructure Interface\n// ============================================================================\n\nimport type { Bucket, StorageObject, CreateBucketOptions, BucketAcl, ListObjectsResult } from '@a3s-lab/rustfs';\n\nexport interface IStorageService {\n    // Bucket operations\n    createBucket(options: CreateBucketOptions): Promise<Bucket>;\n    listBuckets(): Promise<Bucket[]>;\n    getBucketAcl(bucketName: string): Promise<BucketAcl>;\n    setBucketAcl(bucketName: string, acl: BucketAcl): Promise<void>;\n    deleteBucket(bucketName: string): Promise<void>;\n    bucketExists(bucketName: string): Promise<boolean>;\n\n    // Object operations\n    putObject(bucketName: string, options: {\n        key: string;\n        body?: Buffer | Uint8Array | string;\n        contentType?: string;\n        contentEncoding?: string;\n        contentDisposition?: string;\n        contentLanguage?: string;\n        metadata?: Record<string, string>;\n        acl?: string;\n        storageClass?: string;\n        expires?: Date;\n        cacheControl?: string;\n    }): Promise<StorageObject>;\n    getObject(bucketName: string, options: {\n        key: string;\n        range?: { start: number; end: number };\n        ifMatch?: string;\n        ifNoneMatch?: string;\n        ifModifiedSince?: Date;\n        ifUnmodifiedSince?: Date;\n    }): Promise<Buffer>;\n    getObjectMetadata(bucketName: string, key: string): Promise<StorageObject>;\n    copyObject(bucketName: string, options: {\n        sourceKey: string;\n        destinationKey: string;\n        sourceBucket?: string;\n        destinationBucket?: string;\n        acl?: string;\n        metadata?: Record<string, string>;\n        storageClass?: string;\n    }): Promise<StorageObject>;\n    deleteObject(bucketName: string, key: string): Promise<void>;\n    deleteObjects(bucketName: string, keys: string[]): Promise<void>;\n    listObjects(bucketName: string, options?: {\n        prefix?: string;\n        delimiter?: string;\n        maxKeys?: number;\n        continuationToken?: string;\n        startAfter?: string;\n        includeOwn?: boolean;\n    }): Promise<ListObjectsResult>;\n\n    // Presigned URLs\n    getPresignedUrl(bucketName: string, options: {\n        key: string;\n        expiresIn?: number;\n        method?: 'GET' | 'PUT' | 'DELETE' | 'POST';\n        contentType?: string;\n        queryParams?: Record<string, string>;\n    }): Promise<string>;\n    getPresignedPostUrl(bucketName: string, options: {\n        key: string;\n        expiresIn?: number;\n        conditions?: {\n            contentLengthRange?: { min: number; max: number };\n            contentType?: string;\n            acl?: string;\n        };\n    }): Promise<{ url: string; fields: Record<string, string> }>;\n\n    // Health check\n    isHealthy(): Promise<boolean>;\n}\n\nexport const STORAGE_SERVICE = Symbol('STORAGE_SERVICE');\n"
  },
  {
    "path": "apps/api/src/shared/metrics/index.ts",
    "content": "// ============================================================================\n// Metrics Module\n// ============================================================================\n\nexport { MetricsService } from './metrics.service';\nexport { MetricsInterceptor } from './metrics.interceptor';\nexport { MetricsController } from './metrics.controller';\n"
  },
  {
    "path": "apps/api/src/shared/metrics/metrics.controller.ts",
    "content": "// ============================================================================\n// Metrics Controller - Exposes /metrics endpoint for Prometheus\n// ============================================================================\n\nimport { Controller, Get, Header } from '@nestjs/common';\nimport { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';\nimport { MetricsService } from './metrics.service';\n\n@ApiTags('Metrics')\n@Controller('metrics')\nexport class MetricsController {\n    constructor(private readonly metricsService: MetricsService) {}\n\n    @Get()\n    @Header('Content-Type', 'text/plain')\n    @ApiOperation({ summary: 'Prometheus metrics endpoint' })\n    @ApiResponse({ status: 200, description: 'Metrics in Prometheus format' })\n    getMetrics(): string {\n        return this.metricsService.toPrometheusFormat();\n    }\n\n    @Get('json')\n    @ApiOperation({ summary: 'Metrics in JSON format' })\n    @ApiResponse({ status: 200, description: 'Metrics in JSON format' })\n    getMetricsJson(): Record<string, unknown> {\n        return this.metricsService.toJSON();\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/metrics/metrics.interceptor.ts",
    "content": "// ============================================================================\n// Metrics Interceptor - Automatically records HTTP metrics\n// ============================================================================\n\nimport {\n    Injectable,\n    NestInterceptor,\n    ExecutionContext,\n    CallHandler,\n    Logger,\n} from '@nestjs/common';\nimport { Observable } from 'rxjs';\nimport { tap } from 'rxjs/operators';\nimport { Request, Response } from 'express';\nimport { MetricsService } from './metrics.service';\n\n@Injectable()\nexport class MetricsInterceptor implements NestInterceptor {\n    private readonly logger = new Logger(MetricsInterceptor.name);\n\n    constructor(private readonly metricsService: MetricsService) {}\n\n    intercept(context: ExecutionContext, next: CallHandler): Observable<any> {\n        const startTime = process.hrtime.bigint();\n        const request = context.switchToHttp().getRequest<Request>();\n        const response = context.switchToHttp().getResponse<Response>();\n\n        // Track active requests\n        this.metricsService.incGauge('http_active_requests');\n\n        return next.handle().pipe(\n            tap({\n                next: () => {\n                    const duration = this.getDuration(startTime);\n                    this.recordMetrics(request, response, duration);\n                    this.metricsService.decGauge('http_active_requests');\n                },\n                error: (error) => {\n                    const duration = this.getDuration(startTime);\n                    // On error, status might not be set, default to 500\n                    const status = error.status || 500;\n                    this.recordMetrics(request, response, duration, status);\n                    this.metricsService.decGauge('http_active_requests');\n                },\n            }),\n        );\n    }\n\n    private recordMetrics(request: Request, response: Response, duration: number, status?: number): void {\n        const method = request.method;\n        const path = this.normalizePath(request.route?.path || request.path);\n        const statusCode = status ?? response.statusCode;\n\n        // Convert duration to seconds\n        const durationSeconds = duration / 1e9;\n\n        this.metricsService.recordHttpRequest(method, path, statusCode, durationSeconds);\n\n        // Record request/response size if available\n        const requestSize = parseInt(request.headers['content-length'] as string) || 0;\n        const responseSize = parseInt(response.get('content-length') as string) || 0;\n\n        if (requestSize > 0) {\n            this.metricsService.observeHistogram('http_request_size_bytes', requestSize, { method, path });\n        }\n        if (responseSize > 0) {\n            this.metricsService.observeHistogram('http_response_size_bytes', responseSize, { method, path });\n        }\n    }\n\n    /**\n     * Normalize path to avoid high cardinality\n     * Replaces dynamic segments like IDs with placeholders\n     */\n    private normalizePath(path: string): string {\n        // Replace UUIDs\n        path = path.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, ':id');\n        // Replace numeric IDs\n        path = path.replace(/\\/\\d+/g, '/:id');\n        return path;\n    }\n\n    /**\n     * Get duration in nanoseconds and convert to number\n     */\n    private getDuration(startTime: bigint): number {\n        return Number(process.hrtime.bigint() - startTime);\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/metrics/metrics.service.ts",
    "content": "// ============================================================================\n// Metrics Service - Prometheus + OpenTelemetry metrics\n// ============================================================================\n\nimport { Injectable, OnModuleDestroy, Logger } from '@nestjs/common';\n\n/**\n * Counter metric - for counting events (e.g., requests, errors)\n */\nexport interface CounterMetric {\n    name: string;\n    help: string;\n    labelNames?: string[];\n}\n\n/**\n * Gauge metric - for current values (e.g., queue size, memory usage)\n */\nexport interface GaugeMetric {\n    name: string;\n    help: string;\n    labelNames?: string[];\n}\n\n/**\n * Histogram metric - for distributions (e.g., request duration, response size)\n */\nexport interface HistogramMetric {\n    name: string;\n    help: string;\n    labelNames?: string[];\n    buckets?: number[];\n}\n\n/**\n * Default buckets for HTTP request duration\n */\nexport const DEFAULT_HISTOGRAM_BUCKETS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];\n\n/**\n * Default buckets for HTTP request size in bytes\n */\nexport const DEFAULT_SIZE_BUCKETS = [100, 1000, 10000, 100000, 1000000, 10000000];\n\n/**\n * Metrics Service - provides Prometheus-compatible metrics\n */\n@Injectable()\nexport class MetricsService implements OnModuleDestroy {\n    private readonly logger = new Logger(MetricsService.name);\n    private readonly counters: Map<string, number> = new Map();\n    private readonly gauges: Map<string, number> = new Map();\n    private readonly histograms: Map<string, number[]> = new Map();\n    private readonly labelValues: Map<string, Record<string, string>> = new Map();\n\n    // Default metrics\n    private readonly defaultCounters: CounterMetric[] = [\n        { name: 'http_requests_total', help: 'Total HTTP requests', labelNames: ['method', 'path', 'status'] },\n        { name: 'http_errors_total', help: 'Total HTTP errors', labelNames: ['method', 'path', 'status'] },\n        { name: 'db_queries_total', help: 'Total database queries', labelNames: ['operation', 'table'] },\n        { name: 'cache_hits_total', help: 'Total cache hits', labelNames: ['cache'] },\n        { name: 'cache_misses_total', help: 'Total cache misses', labelNames: ['cache'] },\n    ];\n\n    private readonly defaultGauges: GaugeMetric[] = [\n        { name: 'http_active_requests', help: 'Active HTTP requests' },\n        { name: 'db_connection_pool_size', help: 'Database connection pool size' },\n        { name: 'db_connection_pool_used', help: 'Database connection pool used' },\n        { name: 'redis_connection_status', help: 'Redis connection status (1=up, 0=down)' },\n    ];\n\n    private readonly defaultHistograms: HistogramMetric[] = [\n        { name: 'http_request_duration_seconds', help: 'HTTP request duration', labelNames: ['method', 'path'], buckets: DEFAULT_HISTOGRAM_BUCKETS },\n        { name: 'http_request_size_bytes', help: 'HTTP request size', labelNames: ['method', 'path'], buckets: DEFAULT_SIZE_BUCKETS },\n        { name: 'http_response_size_bytes', help: 'HTTP response size', labelNames: ['method', 'path'], buckets: DEFAULT_SIZE_BUCKETS },\n        { name: 'db_query_duration_seconds', help: 'Database query duration', labelNames: ['operation', 'table'], buckets: DEFAULT_HISTOGRAM_BUCKETS },\n    ];\n\n    constructor() {\n        this.initializeMetrics();\n    }\n\n    private initializeMetrics(): void {\n        // Initialize counters\n        for (const counter of this.defaultCounters) {\n            const key = this.getKey(counter.name, counter.labelNames);\n            this.counters.set(key, 0);\n        }\n\n        // Initialize gauges\n        for (const gauge of this.defaultGauges) {\n            const key = this.getKey(gauge.name, gauge.labelNames);\n            this.gauges.set(key, 0);\n        }\n\n        // Initialize histograms\n        for (const histogram of this.defaultHistograms) {\n            const key = this.getKey(histogram.name, histogram.labelNames);\n            this.histograms.set(key, []);\n        }\n\n        this.logger.log('Metrics initialized');\n    }\n\n    private getKey(name: string, labelNames?: string[]): string {\n        return labelNames ? `${name}:${labelNames.join(',')}` : name;\n    }\n\n    private getLabelValues(labelNames: string[], labels: Record<string, string>): string[] {\n        return labelNames.map(name => labels[name] ?? 'unknown');\n    }\n\n    // =========================================================================\n    // Counter Operations\n    // =========================================================================\n\n    /**\n     * Increment a counter\n     */\n    incCounter(name: string, labels?: Record<string, string>, amount = 1): void {\n        const key = this.getKey(name, Object.keys(labels ?? {}));\n        const current = this.counters.get(key) ?? 0;\n        this.counters.set(key, current + amount);\n    }\n\n    /**\n     * Get counter value\n     */\n    getCounter(name: string, labels?: Record<string, string>): number {\n        const key = this.getKey(name, Object.keys(labels ?? {}));\n        return this.counters.get(key) ?? 0;\n    }\n\n    // =========================================================================\n    // Gauge Operations\n    // =========================================================================\n\n    /**\n     * Set a gauge value\n     */\n    setGauge(name: string, value: number, labels?: Record<string, string>): void {\n        const key = this.getKey(name, Object.keys(labels ?? {}));\n        this.gauges.set(key, value);\n    }\n\n    /**\n     * Increment a gauge\n     */\n    incGauge(name: string, labels?: Record<string, string>, amount = 1): void {\n        const key = this.getKey(name, Object.keys(labels ?? {}));\n        const current = this.gauges.get(key) ?? 0;\n        this.gauges.set(key, current + amount);\n    }\n\n    /**\n     * Decrement a gauge\n     */\n    decGauge(name: string, labels?: Record<string, string>, amount = 1): void {\n        const key = this.getKey(name, Object.keys(labels ?? {}));\n        const current = this.gauges.get(key) ?? 0;\n        this.gauges.set(key, current - amount);\n    }\n\n    /**\n     * Get gauge value\n     */\n    getGauge(name: string, labels?: Record<string, string>): number {\n        const key = this.getKey(name, Object.keys(labels ?? {}));\n        return this.gauges.get(key) ?? 0;\n    }\n\n    // =========================================================================\n    // Histogram Operations\n    // =========================================================================\n\n    /**\n     * Observe a value in histogram\n     */\n    observeHistogram(name: string, value: number, labels?: Record<string, string>): void {\n        const key = this.getKey(name, Object.keys(labels ?? {}));\n        const values = this.histograms.get(key) ?? [];\n        values.push(value);\n        this.histograms.set(key, values);\n    }\n\n    /**\n     * Get histogram values\n     */\n    getHistogram(name: string, labels?: Record<string, string>): number[] {\n        const key = this.getKey(name, Object.keys(labels ?? {}));\n        return this.histograms.get(key) ?? [];\n    }\n\n    /**\n     * Calculate histogram statistics\n     */\n    getHistogramStats(name: string, labels?: Record<string, string>): { count: number; sum: number; min: number; max: number; avg: number; p50: number; p95: number; p99: number } {\n        const values = this.getHistogram(name, labels);\n        if (values.length === 0) {\n            return { count: 0, sum: 0, min: 0, max: 0, avg: 0, p50: 0, p95: 0, p99: 0 };\n        }\n\n        const sorted = [...values].sort((a, b) => a - b);\n        const sum = sorted.reduce((a, b) => a + b, 0);\n        const count = sorted.length;\n\n        return {\n            count,\n            sum,\n            min: sorted[0],\n            max: sorted[count - 1],\n            avg: sum / count,\n            p50: sorted[Math.floor(count * 0.5)],\n            p95: sorted[Math.floor(count * 0.95)],\n            p99: sorted[Math.floor(count * 0.99)],\n        };\n    }\n\n    // =========================================================================\n    // Convenience Methods\n    // =========================================================================\n\n    /**\n     * Record HTTP request\n     */\n    recordHttpRequest(method: string, path: string, status: number, duration: number): void {\n        const labels = { method, path, status: status.toString() };\n        this.incCounter('http_requests_total', labels);\n        if (status >= 400) {\n            this.incCounter('http_errors_total', labels);\n        }\n        this.observeHistogram('http_request_duration_seconds', duration, { method, path });\n    }\n\n    /**\n     * Record database query\n     */\n    recordDbQuery(operation: string, table: string, duration: number): void {\n        this.incCounter('db_queries_total', { operation, table });\n        this.observeHistogram('db_query_duration_seconds', duration, { operation, table });\n    }\n\n    /**\n     * Record cache hit\n     */\n    recordCacheHit(cache: string): void {\n        this.incCounter('cache_hits_total', { cache });\n    }\n\n    /**\n     * Record cache miss\n     */\n    recordCacheMiss(cache: string): void {\n        this.incCounter('cache_misses_total', { cache });\n    }\n\n    // =========================================================================\n    // Export\n    // =========================================================================\n\n    /**\n     * Get all metrics in Prometheus format\n     */\n    toPrometheusFormat(): string {\n        const lines: string[] = [];\n\n        // Export counters\n        for (const [key, value] of this.counters.entries()) {\n            lines.push(`# HELP ${key} counter`);\n            lines.push(`# TYPE ${key} counter`);\n            lines.push(`${key} ${value}`);\n        }\n\n        // Export gauges\n        for (const [key, value] of this.gauges.entries()) {\n            lines.push(`# HELP ${key} gauge`);\n            lines.push(`# TYPE ${key} gauge`);\n            lines.push(`${key} ${value}`);\n        }\n\n        // Export histograms (as summary for simplicity)\n        for (const [key, values] of this.histograms.entries()) {\n            if (values.length === 0) continue;\n            const stats = this.getHistogramStats(key.split(':')[0]);\n            lines.push(`# HELP ${key} histogram`);\n            lines.push(`# TYPE ${key} histogram`);\n            lines.push(`${key}_count ${stats.count}`);\n            lines.push(`${key}_sum ${stats.sum}`);\n            lines.push(`${key}_avg ${stats.avg}`);\n        }\n\n        return lines.join('\\n');\n    }\n\n    /**\n     * Get all metrics as JSON\n     */\n    toJSON(): Record<string, unknown> {\n        return {\n            counters: Object.fromEntries(this.counters),\n            gauges: Object.fromEntries(this.gauges),\n            histograms: Object.fromEntries(\n                [...this.histograms.entries()].map(([k, v]) => [k, this.getHistogramStats(k.split(':')[0])])\n            ),\n        };\n    }\n\n    onModuleDestroy(): void {\n        this.logger.log('Metrics service destroyed');\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/openapi/index.ts",
    "content": "export * from './openapi-decorators';\nexport * from './openapi-common.dto';\n"
  },
  {
    "path": "apps/api/src/shared/openapi/openapi-common.dto.ts",
    "content": "// ============================================================================\n// OpenAPI Common DTOs - Reusable documentation objects\n// ============================================================================\n\nimport { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';\n\nexport class PaginationParamsDto {\n    @ApiPropertyOptional({ description: 'Page number', default: 1, minimum: 1 })\n    page?: number;\n\n    @ApiPropertyOptional({ description: 'Page size', default: 20, minimum: 1, maximum: 100 })\n    pageSize?: number;\n}\n\nexport class IdParamDto {\n    @ApiProperty({ description: 'Unique identifier' })\n    id: string;\n}\n\nexport class SlugParamDto {\n    @ApiProperty({ description: 'URL-friendly identifier' })\n    slug: string;\n}\n\nexport class CreatedAtFilterDto {\n    @ApiPropertyOptional({ description: 'Filter by creation date (from)', example: '2024-01-01T00:00:00Z' })\n    createdFrom?: string;\n\n    @ApiPropertyOptional({ description: 'Filter by creation date (to)', example: '2024-12-31T23:59:59Z' })\n    createdTo?: string;\n}\n\nexport class StatusFilterDto {\n    @ApiPropertyOptional({ description: 'Filter by status', enum: ['active', 'inactive', 'pending'] })\n    status?: string;\n}\n\nexport class SearchQueryDto {\n    @ApiPropertyOptional({ description: 'Search query', example: 'keyword' })\n    q?: string;\n\n    @ApiPropertyOptional({ description: 'Page number', default: 1, minimum: 1 })\n    page?: number;\n\n    @ApiPropertyOptional({ description: 'Page size', default: 20, minimum: 1, maximum: 100 })\n    pageSize?: number;\n}\n"
  },
  {
    "path": "apps/api/src/shared/openapi/openapi-decorators.ts",
    "content": "// ============================================================================\n// OpenAPI Common Decorators - Reusable API documentation decorators\n// ============================================================================\n\nimport { applyDecorators, HttpStatus } from '@nestjs/common';\nimport {\n    ApiBearerAuth,\n    ApiUnauthorizedResponse,\n    ApiForbiddenResponse,\n    ApiInternalServerErrorResponse,\n    ApiOperation,\n    ApiResponse,\n    ApiExtraModels,\n    getSchemaPath,\n} from '@nestjs/swagger';\nimport { ApiResponseDto, PaginatedResponseDto } from '../api-response';\n\n// ============================================================================\n// Auth Decorators\n// ============================================================================\n\nexport function ApiAuth(summary?: string) {\n    return applyDecorators(\n        ApiBearerAuth(),\n        ApiOperation({ summary }),\n        ApiUnauthorizedResponse({\n            description: 'Unauthorized - Invalid or missing authentication token',\n            schema: {\n                type: 'object',\n                properties: {\n                    code: { type: 'string', example: 'UNAUTHORIZED' },\n                    message: { type: 'string', example: 'Authentication required' },\n                },\n            },\n        }),\n    );\n}\n\nexport function ApiPermission(resource: string, action: string, summary?: string) {\n    return applyDecorators(\n        ApiAuth(summary),\n        ApiForbiddenResponse({\n            description: 'Forbidden - Insufficient permissions',\n            schema: {\n                type: 'object',\n                properties: {\n                    code: { type: 'string', example: 'PERMISSION_DENIED' },\n                    message: { type: 'string', example: `Permission denied: ${resource}:${action}` },\n                },\n            },\n        }),\n    );\n}\n\n// ============================================================================\n// Standard Response Decorators\n// ============================================================================\n\nexport function ApiStandardResponse<T>(options: {\n    status?: HttpStatus;\n    summary?: string;\n    description?: string;\n    type?: T;\n    isArray?: boolean;\n    schema?: Record<string, unknown>;\n}) {\n    const { status = 200, summary, description, type, isArray, schema } = options;\n    const code = status;\n\n    const responseDecorators = [\n        ApiOperation({ summary, description }),\n        ApiResponse({\n            status: code,\n            description: description || (code === 200 ? 'Success' : 'Response'),\n            schema: schema || {\n                type: 'object',\n                properties: {\n                    code: { type: 'number', example: code },\n                    message: { type: 'string', example: 'Success' },\n                    data: schema\n                        ? schema\n                        : isArray\n                            ? { type: 'array', items: type ? { $ref: getSchemaPath(type as any) } : {} }\n                            : type\n                                ? { $ref: getSchemaPath(type as any) }\n                                : {},\n                    requestId: { type: 'string' },\n                    timestamp: { type: 'string' },\n                },\n            },\n        }),\n    ];\n\n    return applyDecorators(...responseDecorators);\n}\n\nexport function ApiCreatedResponse<T>(options: {\n    summary?: string;\n    type?: T;\n    description?: string;\n}) {\n    return ApiStandardResponse<T>({\n        status: HttpStatus.CREATED,\n        ...options,\n    });\n}\n\nexport function ApiNoContentResponse(summary?: string) {\n    return applyDecorators(\n        ApiOperation({ summary }),\n        ApiResponse({\n            status: 204,\n            description: 'No Content',\n        }),\n    );\n}\n\n// ============================================================================\n// Paginated Response Decorators\n// ============================================================================\n\nexport function ApiPaginatedResponse<T>(options: {\n    summary?: string;\n    type?: T;\n    description?: string;\n}) {\n    const { summary, type, description } = options;\n\n    return applyDecorators(\n        ApiExtraModels(PaginatedResponseDto),\n        ApiOperation({ summary, description }),\n        ApiResponse({\n            status: 200,\n            description: description || 'Paginated response',\n            schema: {\n                type: 'object',\n                properties: {\n                    code: { type: 'number', example: 200 },\n                    message: { type: 'string', example: 'Success' },\n                    data: {\n                        type: 'object',\n                        properties: {\n                            items: {\n                                type: 'array',\n                                items: type ? { $ref: getSchemaPath(type as any) } : {},\n                            },\n                            total: { type: 'number', example: 100 },\n                            page: { type: 'number', example: 1 },\n                            pageSize: { type: 'number', example: 20 },\n                            totalPages: { type: 'number', example: 5 },\n                            hasNext: { type: 'boolean', example: true },\n                            hasPrevious: { type: 'boolean', example: false },\n                        },\n                    },\n                    requestId: { type: 'string' },\n                    timestamp: { type: 'string' },\n                },\n            },\n        }),\n    );\n}\n\n// ============================================================================\n// Error Response Decorators\n// ============================================================================\n\nexport function ApiBadRequestResponse(description = 'Bad Request - Invalid input') {\n    return ApiResponse({\n        status: 400,\n        description,\n        schema: {\n            type: 'object',\n            properties: {\n                code: { type: 'string', example: 'BAD_REQUEST' },\n                message: { type: 'string', example: 'Validation failed' },\n                details: { type: 'object' },\n                requestId: { type: 'string' },\n                timestamp: { type: 'string' },\n            },\n        },\n    });\n}\n\nexport function ApiNotFoundResponse(resource = 'Resource') {\n    return ApiResponse({\n        status: 404,\n        description: `${resource} not found`,\n        schema: {\n            type: 'object',\n            properties: {\n                code: { type: 'string', example: 'NOT_FOUND' },\n                message: { type: 'string', example: `${resource} not found` },\n                requestId: { type: 'string' },\n                timestamp: { type: 'string' },\n            },\n        },\n    });\n}\n\nexport function ApiConflictResponse(description = 'Conflict - Resource already exists') {\n    return ApiResponse({\n        status: 409,\n        description,\n        schema: {\n            type: 'object',\n            properties: {\n                code: { type: 'string', example: 'CONFLICT' },\n                message: { type: 'string', example: description },\n                requestId: { type: 'string' },\n                timestamp: { type: 'string' },\n            },\n        },\n    });\n}\n\nexport function ApiServerErrorResponse() {\n    return applyDecorators(\n        ApiResponse({\n            status: 500,\n            description: 'Internal Server Error',\n            schema: {\n                type: 'object',\n                properties: {\n                    code: { type: 'string', example: 'INTERNAL_SERVER_ERROR' },\n                    message: { type: 'string', example: 'An unexpected error occurred' },\n                    requestId: { type: 'string' },\n                    timestamp: { type: 'string' },\n                },\n            },\n        }),\n        ApiInternalServerErrorResponse({\n            description: 'Internal Server Error',\n        }),\n    );\n}\n"
  },
  {
    "path": "apps/api/src/shared/presentation/filters/domain-exception.filter.ts",
    "content": "import { ExceptionFilter, Catch, ArgumentsHost, HttpStatus, Logger } from '@nestjs/common';\nimport { Response } from 'express';\n\nexport class DomainException extends Error {\n    constructor(message: string) {\n        super(message);\n        this.name = this.constructor.name;\n        Error.captureStackTrace(this, this.constructor);\n    }\n}\n\n@Catch(DomainException)\nexport class DomainExceptionFilter implements ExceptionFilter {\n    private readonly logger = new Logger(DomainExceptionFilter.name);\n\n    catch(exception: DomainException, host: ArgumentsHost) {\n        const ctx = host.switchToHttp();\n        const response = ctx.getResponse<Response>();\n        const request = ctx.getRequest();\n\n        const errorResponse = {\n            statusCode: HttpStatus.BAD_REQUEST,\n            timestamp: new Date().toISOString(),\n            path: request.url,\n            method: request.method,\n            message: exception.message,\n            type: exception.name,\n        };\n\n        this.logger.error(`Domain Exception: ${exception.name} - ${exception.message}`);\n\n        response.status(HttpStatus.BAD_REQUEST).json(errorResponse);\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/presentation/filters/http-exception.filter.ts",
    "content": "import { ExceptionFilter, Catch, ArgumentsHost, HttpException, Logger } from '@nestjs/common';\nimport { Request, Response } from 'express';\n\n@Catch(HttpException)\nexport class HttpExceptionFilter implements ExceptionFilter {\n    private readonly logger = new Logger(HttpExceptionFilter.name);\n\n    catch(exception: HttpException, host: ArgumentsHost) {\n        const ctx = host.switchToHttp();\n        const response = ctx.getResponse<Response>();\n        const request = ctx.getRequest<Request>();\n        const status = exception.getStatus();\n        const exceptionResponse = exception.getResponse();\n\n        const errorResponse = {\n            statusCode: status,\n            timestamp: new Date().toISOString(),\n            path: request.url,\n            method: request.method,\n            message:\n                typeof exceptionResponse === 'string'\n                    ? exceptionResponse\n                    : (exceptionResponse as any).message || exception.message,\n        };\n\n        this.logger.error(`${request.method} ${request.url} ${status} - ${JSON.stringify(errorResponse.message)}`);\n\n        response.status(status).json(errorResponse);\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/presentation/interceptors/logging.interceptor.ts",
    "content": "import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger } from '@nestjs/common';\nimport { Observable } from 'rxjs';\nimport { tap } from 'rxjs/operators';\n\n@Injectable()\nexport class LoggingInterceptor implements NestInterceptor {\n    private readonly logger = new Logger(LoggingInterceptor.name);\n\n    intercept(context: ExecutionContext, next: CallHandler): Observable<any> {\n        const request = context.switchToHttp().getRequest();\n        const { method, url } = request;\n        const now = Date.now();\n\n        this.logger.log(`Incoming Request: ${method} ${url}`);\n\n        return next.handle().pipe(\n            tap(() => {\n                const response = context.switchToHttp().getResponse();\n                const { statusCode } = response;\n                const delay = Date.now() - now;\n                this.logger.log(`Outgoing Response: ${method} ${url} ${statusCode} - ${delay}ms`);\n            }),\n        );\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/rate-limiting/index.ts",
    "content": "// ============================================================================\n// Rate Limiting Module\n// ============================================================================\n\nexport { RateLimitingService, RateLimitExceededException } from './rate-limiting.service';\nexport type { RateLimitConfig, RateLimitResult } from './rate-limiting.service';\nexport { RateLimitingGuard } from './rate-limiting.guard';\nexport { RateLimit, RateLimitAuth, RateLimitApi, RateLimitUpload } from './rate-limiting.decorator';\n"
  },
  {
    "path": "apps/api/src/shared/rate-limiting/rate-limiting.decorator.ts",
    "content": "// ============================================================================\n// Rate Limiting Decorators\n// ============================================================================\n\nimport { SetMetadata } from '@nestjs/common';\nimport { RateLimitConfig, DEFAULT_RATE_LIMITS } from './rate-limiting.service';\n\n/**\n * Rate limit decorator options\n */\nexport interface RateLimitOptions {\n    /** Maximum requests allowed */\n    limit: number;\n    /** Window size in seconds */\n    windowSeconds: number;\n}\n\nexport const RATE_LIMIT_KEY = 'rate_limit_config';\n\n/**\n * Apply rate limiting to a route\n */\nexport const RateLimit = (options: RateLimitOptions) =>\n    SetMetadata(RATE_LIMIT_KEY, options);\n\n/**\n * Apply strict rate limiting (auth endpoints)\n */\nexport const RateLimitAuth = () =>\n    SetMetadata(RATE_LIMIT_KEY, DEFAULT_RATE_LIMITS.auth);\n\n/**\n * Apply API rate limiting\n */\nexport const RateLimitApi = () =>\n    SetMetadata(RATE_LIMIT_KEY, DEFAULT_RATE_LIMITS.api);\n\n/**\n * Apply upload rate limiting\n */\nexport const RateLimitUpload = () =>\n    SetMetadata(RATE_LIMIT_KEY, DEFAULT_RATE_LIMITS.upload);\n"
  },
  {
    "path": "apps/api/src/shared/rate-limiting/rate-limiting.guard.ts",
    "content": "// ============================================================================\n// Rate Limiting Guard - Guard that enforces rate limits\n// ============================================================================\n\nimport {\n    Injectable,\n    CanActivate,\n    ExecutionContext,\n    SetMetadata,\n    HttpException,\n    HttpStatus,\n} from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport { Request } from 'express';\nimport { RateLimitingService, RateLimitConfig, DEFAULT_RATE_LIMITS } from './rate-limiting.service';\n\nexport const RATE_LIMIT_KEY = 'rate_limit';\nexport const RATE_LIMIT_CONFIG_KEY = 'rate_limit_config';\n\nexport interface RateLimitMetadata {\n    name?: string;\n    config?: RateLimitConfig;\n}\n\n/**\n * Set rate limit for a route\n */\nexport const RateLimit = (config?: RateLimitConfig) =>\n    SetMetadata(RATE_LIMIT_CONFIG_KEY, config ?? DEFAULT_RATE_LIMITS.default);\n\nexport const RateLimitByName = (name: keyof typeof DEFAULT_RATE_LIMITS) =>\n    SetMetadata(RATE_LIMIT_CONFIG_KEY, DEFAULT_RATE_LIMITS[name]);\n\n@Injectable()\nexport class RateLimitingGuard implements CanActivate {\n    constructor(\n        private readonly rateLimitingService: RateLimitingService,\n        private readonly reflector: Reflector,\n    ) {}\n\n    async canActivate(context: ExecutionContext): Promise<boolean> {\n        const config = this.reflector.get<RateLimitConfig>(\n            RATE_LIMIT_CONFIG_KEY,\n            context.getHandler(),\n        );\n\n        // If no rate limit config, skip\n        if (!config) {\n            return true;\n        }\n\n        const request = context.switchToHttp().getRequest<Request>();\n        const identifier = this.getIdentifier(request);\n\n        const result = await this.rateLimitingService.checkLimit(identifier, config);\n\n        // Add rate limit headers to response\n        const response = context.switchToHttp().getResponse();\n        response.set({\n            'X-RateLimit-Limit': config.limit,\n            'X-RateLimit-Remaining': result.remaining,\n            'X-RateLimit-Reset': result.resetAt.toISOString(),\n        });\n\n        if (!result.allowed) {\n            response.set('Retry-After', result.retryAfter?.toString() ?? '60');\n            throw new HttpException(\n                {\n                    statusCode: HttpStatus.TOO_MANY_REQUESTS,\n                    message: 'Too many requests',\n                    error: 'Rate limit exceeded',\n                    retryAfter: result.retryAfter,\n                },\n                HttpStatus.TOO_MANY_REQUESTS,\n            );\n        }\n\n        return true;\n    }\n\n    /**\n     * Get identifier for rate limiting\n     * Uses user ID if authenticated, otherwise uses IP\n     */\n    private getIdentifier(request: Request): string {\n        const user = (request as any).user;\n        if (user?.sub) {\n            return `user:${user.sub}`;\n        }\n\n        // Fallback to IP address\n        const ip = this.getClientIp(request);\n        return `ip:${ip}`;\n    }\n\n    /**\n     * Extract client IP from request\n     */\n    private getClientIp(request: Request): string {\n        const forwarded = request.headers['x-forwarded-for'];\n        if (forwarded) {\n            const ips = Array.isArray(forwarded) ? forwarded[0] : forwarded.split(',')[0];\n            return ips.trim();\n        }\n        return request.ip ?? request.socket.remoteAddress ?? 'unknown';\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/rate-limiting/rate-limiting.service.ts",
    "content": "// ============================================================================\n// Rate Limiting Service - API throttling and abuse protection\n// ============================================================================\n\nimport { Injectable, OnModuleDestroy, HttpException, HttpStatus } from '@nestjs/common';\nimport { RedissonService } from '@a3s-lab/redisson';\n\nexport interface RateLimitConfig {\n    /** Maximum requests allowed in window */\n    limit: number;\n    /** Window size in seconds */\n    windowSeconds: number;\n    /** Key prefix for Redis */\n    keyPrefix?: string;\n}\n\nexport interface RateLimitResult {\n    allowed: boolean;\n    remaining: number;\n    resetAt: Date;\n    retryAfter?: number;\n}\n\n/**\n * Default rate limit configurations\n */\nexport const DEFAULT_RATE_LIMITS: Record<string, RateLimitConfig> = {\n    // 100 requests per minute per user\n    default: { limit: 100, windowSeconds: 60 },\n    // 10 requests per minute for auth endpoints\n    auth: { limit: 10, windowSeconds: 60 },\n    // 5 requests per minute for password reset\n    passwordReset: { limit: 5, windowSeconds: 60 },\n    // 1000 requests per hour for API\n    api: { limit: 1000, windowSeconds: 3600 },\n    // 100 requests per minute for file uploads\n    upload: { limit: 100, windowSeconds: 60 },\n};\n\n@Injectable()\nexport class RateLimitingService implements OnModuleDestroy {\n    private readonly keyPrefix = 'ratelimit:';\n    private readonly localCache: Map<string, { count: number; resetAt: number }> = new Map();\n\n    constructor(private readonly redis: RedissonService) {}\n\n    /**\n     * Check rate limit using sliding window algorithm\n     */\n    async checkLimit(\n        identifier: string,\n        config: RateLimitConfig = DEFAULT_RATE_LIMITS.default,\n    ): Promise<RateLimitResult> {\n        const key = `${config.keyPrefix ?? this.keyPrefix}${identifier}`;\n        const now = Date.now();\n        const windowMs = config.windowSeconds * 1000;\n        const windowStart = now - windowMs;\n\n        try {\n            // Use Redis sorted set for sliding window\n            const redisClient = (this.redis as any).redis;\n            const pipeline = redisClient.pipeline();\n\n            // Remove old entries outside window\n            pipeline.zremrangebyscore(key, 0, windowStart);\n\n            // Add current request\n            pipeline.zadd(key, now.toString(), `${now}-${Math.random()}`);\n\n            // Count requests in window\n            pipeline.zcard(key);\n\n            // Set expiry on key\n            pipeline.expire(key, config.windowSeconds + 1);\n\n            const results = await pipeline.exec();\n            const count = results[2][1] as number;\n\n            const allowed = count <= config.limit;\n            const remaining = Math.max(0, config.limit - count);\n            const resetAt = new Date(now + windowMs);\n\n            if (!allowed) {\n                // Calculate when the oldest request will expire\n                const oldest = await redisClient.zrange(key, 0, 0, 'WITHSCORES');\n                const oldestTime = oldest.length >= 2 ? parseInt(oldest[1]) : now;\n                const retryAfter = Math.ceil((oldestTime + windowMs - now) / 1000);\n\n                return {\n                    allowed: false,\n                    remaining: 0,\n                    resetAt,\n                    retryAfter,\n                };\n            }\n\n            return { allowed: true, remaining, resetAt };\n        } catch (error) {\n            // Fallback to local cache if Redis fails\n            return this.checkLimitLocal(identifier, config);\n        }\n    }\n\n    /**\n     * Fallback local rate limiting\n     */\n    private checkLimitLocal(\n        identifier: string,\n        config: RateLimitConfig,\n    ): RateLimitResult {\n        const key = identifier;\n        const now = Date.now();\n        const windowMs = config.windowSeconds * 1000;\n\n        const cached = this.localCache.get(key);\n\n        if (!cached || cached.resetAt < now) {\n            // Start new window\n            this.localCache.set(key, { count: 1, resetAt: now + windowMs });\n            return {\n                allowed: true,\n                remaining: config.limit - 1,\n                resetAt: new Date(now + windowMs),\n            };\n        }\n\n        cached.count++;\n        const allowed = cached.count <= config.limit;\n        const remaining = Math.max(0, config.limit - cached.count);\n\n        if (!allowed) {\n            return {\n                allowed: false,\n                remaining: 0,\n                resetAt: new Date(cached.resetAt),\n                retryAfter: Math.ceil((cached.resetAt - now) / 1000),\n            };\n        }\n\n        return {\n            allowed: true,\n            remaining,\n            resetAt: new Date(cached.resetAt),\n        };\n    }\n\n    /**\n     * Reset rate limit for an identifier\n     */\n    async resetLimit(identifier: string): Promise<void> {\n        const key = `${this.keyPrefix}${identifier}`;\n        await this.redis.delete(key);\n        this.localCache.delete(identifier);\n    }\n\n    /**\n     * Get current usage for an identifier\n     */\n    async getUsage(identifier: string): Promise<{ count: number; resetAt: Date }> {\n        const key = `${this.keyPrefix}${identifier}`;\n        const now = Date.now();\n\n        try {\n            const redisClient = (this.redis as any).redis;\n            const count = await redisClient.zcard(key);\n            const ttl = await redisClient.ttl(key);\n            return {\n                count,\n                resetAt: new Date(now + ttl * 1000),\n            };\n        } catch {\n            const cached = this.localCache.get(identifier);\n            if (cached) {\n                return { count: cached.count, resetAt: new Date(cached.resetAt) };\n            }\n            return { count: 0, resetAt: new Date(now) };\n        }\n    }\n\n    onModuleDestroy(): void {\n        this.localCache.clear();\n    }\n}\n\n/**\n * Rate limit exceeded exception\n */\nexport class RateLimitExceededException extends HttpException {\n    constructor(retryAfter: number) {\n        super(\n            {\n                statusCode: HttpStatus.TOO_MANY_REQUESTS,\n                message: 'Too many requests',\n                error: 'Rate limit exceeded',\n                retryAfter,\n            },\n            HttpStatus.TOO_MANY_REQUESTS,\n        );\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/redis/index.ts",
    "content": "export * from './redis.module';\n"
  },
  {
    "path": "apps/api/src/shared/redis/redis.module.ts",
    "content": "import { Module, Global } from '@nestjs/common';\nimport { ConfigModule, ConfigService } from '@nestjs/config';\nimport { RedissonModule } from '@a3s-lab/redisson';\n\n@Global()\n@Module({\n    imports: [\n        RedissonModule.registerAsync({\n            imports: [ConfigModule],\n            useFactory: (configService: ConfigService) => ({\n                redis: {\n                    options: {\n                        host: configService.get('REDIS_HOST', 'localhost'),\n                        port: configService.get('REDIS_PORT', 6379),\n                        password: configService.get('REDIS_PASSWORD'),\n                        db: configService.get('REDIS_DB', 0),\n                    },\n                },\n            }),\n            inject: [ConfigService],\n        }),\n    ],\n    exports: [RedissonModule],\n})\nexport class RedisModule {}\n"
  },
  {
    "path": "apps/api/src/shared/retry/index.ts",
    "content": "// ============================================================================\n// Retry Module\n// ============================================================================\n\nexport { RetryService, RetryExhaustedError, Retry, DEFAULT_RETRYABLE_HTTP_CODES } from './retry.service';\nexport type { RetryOptions, RetryResult, RetryDecoratorOptions } from './retry.service';\n"
  },
  {
    "path": "apps/api/src/shared/retry/retry.module.ts",
    "content": "// ============================================================================\n// Retry Module - Automatic retry with exponential backoff\n// ============================================================================\n\nimport { Module, Global } from '@nestjs/common';\nimport { RetryService } from './retry.service';\n\n@Global()\n@Module({\n    providers: [RetryService],\n    exports: [RetryService],\n})\nexport class RetryModule {}\n"
  },
  {
    "path": "apps/api/src/shared/retry/retry.service.ts",
    "content": "// ============================================================================\n// Retry Service - Automatic retry with exponential backoff\n// ============================================================================\n\nimport { Injectable, Logger } from '@nestjs/common';\n\nexport interface RetryOptions {\n    /** Maximum number of attempts */\n    maxAttempts?: number;\n    /** Initial delay in ms */\n    initialDelay?: number;\n    /** Maximum delay in ms */\n    maxDelay?: number;\n    /** Backoff multiplier */\n    backoffMultiplier?: number;\n    /** List of errors that should trigger retry */\n    retryableErrors?: Array<new (...args: any[]) => Error>;\n    /** Function to determine if error is retryable */\n    isRetryable?: (error: Error) => boolean;\n    /** Callback on retry */\n    onRetry?: (attempt: number, error: Error, delay: number) => void;\n}\n\nexport interface RetryResult<T> {\n    success: boolean;\n    result?: T;\n    error?: Error;\n    attempts: number;\n    totalDuration: number;\n}\n\n/**\n * Default retry options\n */\nconst DEFAULT_OPTIONS: Required<Omit<RetryOptions, 'onRetry' | 'retryableErrors' | 'isRetryable'>> & { retryableErrors: any[]; isRetryable: (error: Error) => boolean } = {\n    maxAttempts: 3,\n    initialDelay: 100,\n    maxDelay: 30000,\n    backoffMultiplier: 2,\n    retryableErrors: [],\n    isRetryable: () => true,\n};\n\n/**\n * Common retryable HTTP errors\n */\nexport const DEFAULT_RETRYABLE_HTTP_CODES = [408, 429, 500, 502, 503, 504];\n\n/**\n * Retry Service - provides automatic retry with exponential backoff\n */\n@Injectable()\nexport class RetryService {\n    private readonly logger = new Logger(RetryService.name);\n\n    constructor() {}\n\n    /**\n     * Execute a function with retry logic\n     */\n    async execute<T>(\n        fn: () => Promise<T>,\n        options?: RetryOptions,\n    ): Promise<RetryResult<T>> {\n        const opts = {\n            ...DEFAULT_OPTIONS,\n            ...options,\n            retryableErrors: options?.retryableErrors ?? DEFAULT_OPTIONS.retryableErrors,\n            isRetryable: options?.isRetryable ?? DEFAULT_OPTIONS.isRetryable,\n        };\n        const startTime = Date.now();\n        let lastError: Error | undefined;\n        let attempt = 0;\n\n        while (attempt < opts.maxAttempts) {\n            attempt++;\n\n            try {\n                const result = await fn();\n                return {\n                    success: true,\n                    result,\n                    attempts: attempt,\n                    totalDuration: Date.now() - startTime,\n                };\n            } catch (error) {\n                lastError = error instanceof Error ? error : new Error(String(error));\n\n                // Check if we should retry\n                if (attempt >= opts.maxAttempts) {\n                    break;\n                }\n\n                if (!this.isRetryable(lastError, opts)) {\n                    break;\n                }\n\n                // Calculate delay with exponential backoff\n                const delay = this.calculateDelay(attempt, opts);\n                const jitter = this.calculateJitter(delay);\n\n                if (opts.onRetry) {\n                    opts.onRetry(attempt, lastError, delay + jitter);\n                }\n\n                this.logger.warn(\n                    `Retry attempt ${attempt}/${opts.maxAttempts} after ${delay + jitter}ms due to: ${lastError.message}`,\n                );\n\n                // Wait before next attempt\n                await this.sleep(delay + jitter);\n            }\n        }\n\n        return {\n            success: false,\n            error: lastError,\n            attempts: attempt,\n            totalDuration: Date.now() - startTime,\n        };\n    }\n\n    /**\n     * Execute with retry - throws on failure\n     */\n    async executeOrThrow<T>(\n        fn: () => Promise<T>,\n        options?: RetryOptions,\n    ): Promise<T> {\n        const result = await this.execute(fn, options);\n\n        if (!result.success) {\n            throw new RetryExhaustedError(\n                result.attempts,\n                result.totalDuration,\n                result.error,\n            );\n        }\n\n        return result.result!;\n    }\n\n    /**\n     * Check if error is retryable\n     */\n    private isRetryable(error: Error, options: { retryableErrors: any[]; isRetryable: (error: Error) => boolean }): boolean {\n        // Check custom retryable errors\n        if (options.retryableErrors.length > 0) {\n            for (const ErrorClass of options.retryableErrors) {\n                if (error instanceof ErrorClass) {\n                    return true;\n                }\n            }\n        }\n\n        // Check custom function\n        return options.isRetryable(error);\n    }\n\n    /**\n     * Calculate delay with exponential backoff\n     */\n    private calculateDelay(attempt: number, options: { initialDelay: number; backoffMultiplier: number; maxDelay: number }): number {\n        const delay = options.initialDelay * Math.pow(options.backoffMultiplier, attempt - 1);\n        return Math.min(delay, options.maxDelay);\n    }\n\n    /**\n     * Add jitter to prevent thundering herd\n     */\n    private calculateJitter(delay: number): number {\n        // 0-25% of delay\n        return Math.random() * delay * 0.25;\n    }\n\n    /**\n     * Sleep for specified milliseconds\n     */\n    private sleep(ms: number): Promise<void> {\n        return new Promise(resolve => setTimeout(resolve, ms));\n    }\n}\n\n/**\n * Error thrown when all retries are exhausted\n */\nexport class RetryExhaustedError extends Error {\n    constructor(\n        public readonly attempts: number,\n        public readonly totalDuration: number,\n        public readonly lastError?: Error,\n    ) {\n        super(`Retry exhausted after ${attempts} attempts (${totalDuration}ms): ${lastError?.message ?? 'Unknown error'}`);\n        this.name = 'RetryExhaustedError';\n    }\n}\n\n/**\n * Decorator options\n */\nexport interface RetryDecoratorOptions extends RetryOptions {\n    /** Name for logging */\n    name?: string;\n}\n\n/**\n * Decorator to automatically retry a method\n */\nexport function Retry(options: RetryDecoratorOptions = {}) {\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {\n        const originalMethod = descriptor.value;\n        const retryService = new RetryService();\n\n        descriptor.value = async function (...args: any[]) {\n            return retryService.executeOrThrow(\n                () => originalMethod.apply(this, args),\n                options,\n            );\n        };\n\n        return descriptor;\n    };\n}\n"
  },
  {
    "path": "apps/api/src/shared/serialization/example.ts",
    "content": "// ============================================================================\n// Serializer Examples\n// ============================================================================\n\nimport { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';\n\n/**\n * Example: User Entity\n */\ninterface UserEntity {\n    id: string;\n    email: string;\n    username: string;\n    displayName?: string;\n    avatar?: string;\n    status: 'active' | 'inactive' | 'suspended';\n    passwordHash?: string;\n    organizationId: string;\n    createdAt: Date;\n    updatedAt: Date;\n}\n\n/**\n * Example: User DTO for response\n */\nclass UserDto {\n    @ApiProperty({ description: 'User ID' })\n    id: string;\n\n    @ApiProperty({ description: 'Email address' })\n    email: string;\n\n    @ApiProperty({ description: 'Username' })\n    username: string;\n\n    @ApiPropertyOptional({ description: 'Display name' })\n    displayName?: string;\n\n    @ApiPropertyOptional({ description: 'Avatar URL' })\n    avatar?: string;\n\n    @ApiProperty({ description: 'Account status' })\n    status: string;\n\n    @ApiProperty({ description: 'Organization ID' })\n    organizationId: string;\n\n    @ApiProperty({ description: 'Creation timestamp' })\n    createdAt: Date;\n\n    @ApiProperty({ description: 'Last update timestamp' })\n    updatedAt: Date;\n}\n\n/**\n * Example: Create User DTO\n */\nclass CreateUserDto {\n    @ApiProperty({ description: 'Email address' })\n    email: string;\n\n    @ApiProperty({ description: 'Username' })\n    username: string;\n\n    @ApiPropertyOptional({ description: 'Display name' })\n    displayName?: string;\n\n    @ApiProperty({ description: 'Initial password' })\n    password: string;\n}\n\n/**\n * Example: Update User DTO\n */\nclass UpdateUserDto {\n    @ApiPropertyOptional({ description: 'Display name' })\n    displayName?: string;\n\n    @ApiPropertyOptional({ description: 'Avatar URL' })\n    avatar?: string;\n}\n\n// ============================================================================\n// User Serializer Implementation - Functional Approach (Recommended)\n// ============================================================================\n\n/**\n * Convert User entity to UserDto\n */\nfunction userToDto(user: UserEntity): UserDto {\n    return {\n        id: user.id,\n        email: user.email,\n        username: user.username,\n        displayName: user.displayName,\n        avatar: user.avatar,\n        status: user.status,\n        organizationId: user.organizationId,\n        createdAt: user.createdAt,\n        updatedAt: user.updatedAt,\n    };\n}\n\n/**\n * Convert User entity to CreateUserDto (excludes sensitive fields)\n */\nfunction userToCreateDto(user: UserEntity): CreateUserDto {\n    return {\n        email: user.email,\n        username: user.username,\n        displayName: user.displayName,\n        password: '', // Never expose password hash\n    };\n}\n\n/**\n * Convert list of User entities to UserDto[]\n */\nfunction userListToDto(users: UserEntity[]): UserDto[] {\n    return users.map(userToDto);\n}\n\n// ============================================================================\n// User Serializer Implementation - Class-based Approach\n// ============================================================================\n\nimport { Serializer } from './serializer';\n\nclass UserSerializer extends Serializer<UserEntity, UserDto> {\n    protected static _instance: UserSerializer;\n\n    static get instance(): UserSerializer {\n        return this._instance || (this._instance = new UserSerializer());\n    }\n\n    toDto(user: UserEntity): UserDto {\n        return {\n            id: user.id,\n            email: user.email,\n            username: user.username,\n            displayName: user.displayName,\n            avatar: user.avatar,\n            status: user.status,\n            organizationId: user.organizationId,\n            createdAt: user.createdAt,\n            updatedAt: user.updatedAt,\n        };\n    }\n}\n\nclass CreateUserSerializer extends Serializer<UserEntity, CreateUserDto> {\n    protected static _instance: CreateUserSerializer;\n\n    static get instance(): CreateUserSerializer {\n        return this._instance || (this._instance = new CreateUserSerializer());\n    }\n\n    toDto(user: UserEntity): CreateUserDto {\n        return {\n            email: user.email,\n            username: user.username,\n            displayName: user.displayName,\n            password: '', // Never expose password hash\n        };\n    }\n}\n\n// Usage in Service\n// class UserService {\n//   findAll(): UserDto[] {\n//     const users = await this.userRepository.findAll();\n//     return users.map(userToDto);\n//     // Or: return UserSerializer.instance.toDtoList(users);\n//   }\n//\n//   findOne(id: string): UserDto {\n//     const user = await this.userRepository.findById(id);\n//     return userToDto(user);\n//     // Or: return UserSerializer.instance.toDto(user);\n//   }\n//\n//   create(dto: CreateUserDto): UserDto {\n//     const user = this.userRepository.create(dto);\n//     return userToCreateDto(user);\n//     // Or: return CreateUserSerializer.instance.toDto(user);\n//   }\n// }\n\nexport { UserDto, CreateUserDto, UpdateUserDto, userToDto, userToCreateDto, userListToDto, UserSerializer, CreateUserSerializer };\n"
  },
  {
    "path": "apps/api/src/shared/serialization/index.ts",
    "content": "export * from './serializer';\nexport * from './example';\n"
  },
  {
    "path": "apps/api/src/shared/serialization/serializer.ts",
    "content": "// ============================================================================\n// Serializer - Entity ↔ DTO mapping\n// ============================================================================\n\nimport { instanceToPlain, plainToInstance } from 'class-transformer';\n\n/**\n * Base serializer class for Entity ↔ DTO mapping\n *\n * Usage:\n * ```typescript\n * class UserSerializer extends Serializer<User, UserDto> {\n *   protected static _instance: UserSerializer;\n *\n *   static get instance(): UserSerializer {\n *     return this._instance || (this._instance = new UserSerializer());\n *   }\n *\n *   toDto(entity: User): UserDto {\n *     return {\n *       id: entity.id,\n *       email: entity.email,\n *       ...\n *     };\n *   }\n * }\n * ```\n *\n * Or use the simpler functional approach:\n * ```typescript\n * const userToDto = (user: User): UserDto => ({\n *   id: user.id,\n *   email: user.email,\n *   ...\n * });\n *\n * const userToDtoList = (users: User[]): UserDto[] => users.map(userToDto);\n * ```\n */\nexport abstract class Serializer<Entity, Dto> {\n    /**\n     * Convert Entity to DTO\n     */\n    abstract toDto(entity: Entity): Dto;\n\n    /**\n     * Convert list of Entities to DTOs\n     */\n    toDtoList(entities: Entity[]): Dto[] {\n        return entities.map((entity) => this.toDto(entity));\n    }\n}\n\n/**\n * Type for class constructors\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type ClassType<T = any> = new (...args: any[]) => T;\n\n/**\n * Transform a plain object to a class instance\n */\nexport function transformToInstance<T>(\n    plain: Record<string, unknown>,\n    cls: ClassType<T>,\n    options?: { excludeExtraneousValues?: boolean },\n): T {\n    return plainToInstance(cls, plain, {\n        excludeExtraneousValues: options?.excludeExtraneousValues ?? true,\n        enableImplicitConversion: true,\n    });\n}\n\n/**\n * Transform an object to a plain JavaScript object\n */\nexport function transformToPlain<T>(entity: T, options?: { excludeExtraneousValues?: boolean }): Record<string, unknown> {\n    return instanceToPlain(entity, {\n        excludeExtraneousValues: options?.excludeExtraneousValues ?? true,\n    }) as Record<string, unknown>;\n}\n\n/**\n * Transform list of plain objects to class instances\n */\nexport function transformListToInstance<T>(\n    plainList: Record<string, unknown>[],\n    cls: ClassType<T>,\n    options?: { excludeExtraneousValues?: boolean },\n): T[] {\n    return plainToInstance(cls, plainList, {\n        excludeExtraneousValues: options?.excludeExtraneousValues ?? true,\n        enableImplicitConversion: true,\n    });\n}\n\n/**\n * Transform list of objects to plain objects\n */\nexport function transformListToPlain<T>(entities: T[]): Record<string, unknown>[] {\n    return entities.map((entity) => transformToPlain(entity));\n}\n\n/**\n * Simple mapper function type\n */\nexport type Mapper<Entity, Dto> = (entity: Entity) => Dto;\n\n/**\n * Create a mapper that converts entity to DTO\n */\nexport function toDto<Entity, Dto>(mapper: Mapper<Entity, Dto>): Mapper<Entity, Dto> {\n    return mapper;\n}\n\n/**\n * Create a mapper that converts list of entities to DTOs\n */\nexport function toDtoList<Entity, Dto>(mapper: Mapper<Entity, Dto>): (entities: Entity[]) => Dto[] {\n    return (entities: Entity[]) => entities.map(mapper);\n}\n"
  },
  {
    "path": "apps/api/src/shared/tenant/index.ts",
    "content": "// ============================================================================\n// Tenant Module - Multi-tenancy support\n// ============================================================================\n\nexport * from './tenant.service';\nexport * from './tenant.guard';\nexport * from './tenant.interceptor';\nexport * from './tenant.decorator';\n"
  },
  {
    "path": "apps/api/src/shared/tenant/tenant.decorator.ts",
    "content": "// ============================================================================\n// Tenant Decorators\n// ============================================================================\n\nimport { createParamDecorator, ExecutionContext } from '@nestjs/common';\nimport { TenantService } from './tenant.service';\n\n/**\n * Get current organization ID\n */\nexport const OrganizationId = createParamDecorator(\n    (_data: unknown, ctx: ExecutionContext) => {\n        const tenantService = ctx.switchToHttp().getRequest().tenantService;\n        if (tenantService) {\n            return tenantService.getOrganizationId();\n        }\n        const request = ctx.switchToHttp().getRequest();\n        return request.user?.organizationId;\n    },\n);\n\n/**\n * Get current tenant context\n */\nexport const Tenant = createParamDecorator(\n    (_data: unknown, ctx: ExecutionContext) => {\n        const tenantService = ctx.switchToHttp().getRequest().tenantService;\n        if (tenantService) {\n            return tenantService.getContext();\n        }\n        const request = ctx.switchToHttp().getRequest();\n        return {\n            organizationId: request.user?.organizationId,\n            userId: request.user?.sub,\n            roles: request.user?.roles,\n        };\n    },\n);\n"
  },
  {
    "path": "apps/api/src/shared/tenant/tenant.guard.ts",
    "content": "// ============================================================================\n// Tenant Guard - Ensures tenant context is present\n// ============================================================================\n\nimport {\n    Injectable,\n    CanActivate,\n    ExecutionContext,\n    ForbiddenException,\n    UnauthorizedException,\n} from '@nestjs/common';\nimport { TenantService } from './tenant.service';\n\n/**\n * Tenant Guard - validates that tenant context exists\n */\n@Injectable()\nexport class TenantGuard implements CanActivate {\n    constructor(private readonly tenantService: TenantService) {}\n\n    canActivate(context: ExecutionContext): boolean {\n        const request = context.switchToHttp().getRequest();\n        const user = request.user;\n\n        if (!user?.organizationId) {\n            throw new UnauthorizedException('Tenant context not available');\n        }\n\n        // Set tenant context\n        this.tenantService.setContext({\n            organizationId: user.organizationId,\n            userId: user.sub,\n            roles: user.roles,\n        });\n\n        return true;\n    }\n}\n\n/**\n * Tenant Guard with optional context (doesn't throw if no tenant)\n */\n@Injectable()\nexport class OptionalTenantGuard implements CanActivate {\n    constructor(private readonly tenantService: TenantService) {}\n\n    canActivate(context: ExecutionContext): boolean {\n        const request = context.switchToHttp().getRequest();\n        const user = request.user;\n\n        if (user?.organizationId) {\n            this.tenantService.setContext({\n                organizationId: user.organizationId,\n                userId: user.sub,\n                roles: user.roles,\n            });\n        }\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/tenant/tenant.interceptor.ts",
    "content": "// ============================================================================\n// Tenant Interceptor - Automatically extracts tenant from request\n// ============================================================================\n\nimport {\n    Injectable,\n    NestInterceptor,\n    ExecutionContext,\n    CallHandler,\n} from '@nestjs/common';\nimport { Observable } from 'rxjs';\nimport { TenantService } from './tenant.service';\n\n@Injectable()\nexport class TenantInterceptor implements NestInterceptor {\n    constructor(private readonly tenantService: TenantService) {}\n\n    intercept(context: ExecutionContext, next: CallHandler): Observable<any> {\n        const request = context.switchToHttp().getRequest();\n        const user = request.user;\n\n        if (user?.organizationId) {\n            this.tenantService.setContext({\n                organizationId: user.organizationId,\n                userId: user.sub,\n                roles: user.roles,\n            });\n        }\n\n        return next.handle();\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/tenant/tenant.service.ts",
    "content": "// ============================================================================\n// Tenant Service - Multi-tenancy context management\n// ============================================================================\n\nimport { Injectable, Scope, Inject, Optional } from '@nestjs/common';\nimport { REQUEST } from '@nestjs/core';\nimport { Request } from 'express';\n\n/**\n * Tenant context - contains current tenant information\n */\nexport interface TenantContext {\n    organizationId: string;\n    userId?: string;\n    roles?: string[];\n    metadata?: Record<string, unknown>;\n}\n\n/**\n * Tenant Service - provides access to current tenant context\n */\n@Injectable({ scope: Scope.REQUEST })\nexport class TenantService {\n    private context: TenantContext | null = null;\n\n    constructor(@Optional() @Inject(REQUEST) private readonly request: Request) {}\n\n    /**\n     * Set tenant context\n     */\n    setContext(context: TenantContext): void {\n        this.context = context;\n    }\n\n    /**\n     * Get current organization ID\n     */\n    getOrganizationId(): string {\n        if (!this.context?.organizationId) {\n            throw new Error('Tenant context not available');\n        }\n        return this.context.organizationId;\n    }\n\n    /**\n     * Get current user ID\n     */\n    getUserId(): string | undefined {\n        return this.context?.userId;\n    }\n\n    /**\n     * Get tenant context\n     */\n    getContext(): TenantContext | null {\n        return this.context;\n    }\n\n    /**\n     * Check if tenant context is available\n     */\n    hasContext(): boolean {\n        return this.context !== null && !!this.context.organizationId;\n    }\n\n    /**\n     * Get metadata value\n     */\n    getMetadata<T>(key: string): T | undefined {\n        return this.context?.metadata?.[key] as T;\n    }\n}\n\n/**\n * Request-scoped storage for tenant context\n */\nexport class TenantStorage {\n    private static instance: TenantContext | null = null;\n\n    static set(context: TenantContext): void {\n        TenantStorage.instance = context;\n    }\n\n    static get(): TenantContext | null {\n        return TenantStorage.instance;\n    }\n\n    static clear(): void {\n        TenantStorage.instance = null;\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/testing/index.ts",
    "content": "// ============================================================================\n// Testing Module\n// ============================================================================\n\nexport {\n    createTestingModule,\n    createMock,\n    createMockInstance,\n    FixtureBuilder,\n    fixture,\n    createPaginatedFixture,\n    mockJwtPayload,\n    createMockRequest,\n    TimeMock,\n    createMockResponse,\n    createMockQueryBuilder,\n    createMockRedis,\n} from './testing.utils';\n"
  },
  {
    "path": "apps/api/src/shared/testing/testing.utils.ts",
    "content": "// ============================================================================\n// Testing Utils - Mock factories, fixtures, and test helpers\n// ============================================================================\n\nimport { Test, TestingModule } from '@nestjs/testing';\nimport { Request } from 'express';\n\n/**\n * Create a testing module with all required providers\n */\nexport async function createTestingModule(options: {\n    imports?: any[];\n    controllers?: any[];\n    providers?: any[];\n    mocks?: Map<any, any>;\n    globalPipes?: any[];\n}): Promise<TestingModule> {\n    const { imports = [], controllers = [], providers = [], mocks = new Map(), globalPipes = [] } = options;\n\n    // Create mock providers from mocks map\n    const mockProviders = Array.from(mocks.entries()).map(([token, mock]) => ({\n        provide: token,\n        useValue: mock,\n    }));\n\n    return Test.createTestingModule({\n        imports,\n        controllers,\n        providers: [...providers, ...mockProviders],\n    })\n        .compile();\n}\n\n/**\n * Create a mock for a class or token\n */\nexport function createMock<T>(overrides?: Partial<T>): jest.Mocked<T> {\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const mock = jest.fn() as any;\n\n    if (overrides) {\n        Object.keys(overrides).forEach(key => {\n            (mock as any)[key] = overrides[key as keyof T];\n        });\n    }\n\n    return mock;\n}\n\n/**\n * Create a mock instance with spy methods\n */\nexport function createMockInstance<T>(classType: new (...args: any[]) => T): jest.Mocked<T> {\n    const instance = Object.create(classType.prototype);\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const mock = jest.fn() as any;\n\n    // Copy all methods from prototype\n    const proto = classType.prototype;\n    Object.getOwnPropertyNames(proto).forEach(key => {\n        if (key !== 'constructor' && typeof (proto as any)[key] === 'function') {\n            (mock as any)[key] = jest.fn();\n            (instance as any)[key] = (mock as any)[key];\n        }\n    });\n\n    return mock;\n}\n\n/**\n * Builder for creating test fixtures\n */\nexport class FixtureBuilder<T> {\n    private data: Partial<T> = {};\n\n    constructor(private defaultData: T) {\n        this.data = { ...defaultData };\n    }\n\n    with<K extends keyof T>(key: K, value: T[K]): this {\n        this.data[key] = value;\n        return this;\n    }\n\n    withPartial(partial: Partial<T>): this {\n        this.data = { ...this.data, ...partial };\n        return this;\n    }\n\n    build(): T {\n        return { ...this.defaultData, ...this.data } as T;\n    }\n\n    buildMany(count: number): T[] {\n        return Array.from({ length: count }, () => this.build());\n    }\n}\n\n/**\n * Create a fixture builder\n */\nexport function fixture<T>(defaultData: T): FixtureBuilder<T> {\n    return new FixtureBuilder(defaultData);\n}\n\n/**\n * Create pagination test fixtures\n */\nexport function createPaginatedFixture<T>(items: T[], total: number, page = 1, pageSize = 20) {\n    return {\n        items,\n        total,\n        page,\n        pageSize,\n        totalPages: Math.ceil(total / pageSize),\n        hasNext: page < Math.ceil(total / pageSize),\n        hasPrevious: page > 1,\n    };\n}\n\n/**\n * Mock JWT payload for tests\n */\nexport const mockJwtPayload = {\n    sub: 'user-123',\n    email: 'test@example.com',\n    organizationId: 'org-123',\n    roles: ['member'],\n    permissions: ['read', 'write'],\n    type: 'access' as const,\n};\n\n/**\n * Mock request with user\n */\nexport function createMockRequest(overrides?: Partial<Request>): Request {\n    return {\n        user: mockJwtPayload,\n        headers: {},\n        params: {},\n        query: {},\n        body: {},\n        ...overrides,\n    } as unknown as Request;\n}\n\n/**\n * Time mocking utilities\n */\nexport const TimeMock = {\n    /** Freeze time to a specific date */\n    freeze(date: Date = new Date()): void {\n        jest.useFakeTimers();\n        jest.setSystemTime(date);\n    },\n\n    /** Use real timers */\n    useReal(): void {\n        jest.useRealTimers();\n    },\n\n    /** Advance time by ms */\n    advance(ms: number): void {\n        jest.advanceTimersByTime(ms);\n    },\n\n    /** Set a date in the future */\n    setFuture(days = 1): Date {\n        const date = new Date();\n        date.setDate(date.getDate() + days);\n        jest.setSystemTime(date);\n        return date;\n    },\n};\n\n/**\n * Create a mock response\n */\nexport function createMockResponse() {\n    const res: any = {\n        status: jest.fn().mockReturnThis(),\n        json: jest.fn().mockReturnThis(),\n        set: jest.fn().mockReturnThis(),\n        send: jest.fn().mockReturnThis(),\n    };\n    return res;\n}\n\n/**\n * Create a mock query builder for Kysely\n */\nexport function createMockQueryBuilder() {\n    const queryBuilder: any = {\n        selectFrom: jest.fn().mockReturnThis(),\n        select: jest.fn().mockReturnThis(),\n        where: jest.fn().mockReturnThis(),\n        orderBy: jest.fn().mockReturnThis(),\n        limit: jest.fn().mockReturnThis(),\n        offset: jest.fn().mockReturnThis(),\n        execute: jest.fn().mockResolvedValue([]),\n        executeTakeFirst: jest.fn().mockResolvedValue(null),\n        executeTakeFirstOrThrow: jest.fn().mockResolvedValue(null),\n        insertInto: jest.fn().mockReturnThis(),\n        values: jest.fn().mockReturnThis(),\n        updateTable: jest.fn().mockReturnThis(),\n        set: jest.fn().mockReturnThis(),\n        deleteFrom: jest.fn().mockReturnThis(),\n    };\n    return queryBuilder;\n}\n\n/**\n * Create a mock Redis client\n */\nexport function createMockRedis() {\n    return {\n        get: jest.fn().mockResolvedValue(null),\n        set: jest.fn().mockResolvedValue('OK'),\n        setex: jest.fn().mockResolvedValue('OK'),\n        del: jest.fn().mockResolvedValue(1),\n        exists: jest.fn().mockResolvedValue(0),\n        keys: jest.fn().mockResolvedValue([]),\n        expire: jest.fn().mockResolvedValue(1),\n        zcard: jest.fn().mockResolvedValue(0),\n        zrange: jest.fn().mockResolvedValue([]),\n        zadd: jest.fn().mockResolvedValue(1),\n        zremrangebyscore: jest.fn().mockResolvedValue(0),\n        incrby: jest.fn().mockResolvedValue(1),\n        decrby: jest.fn().mockResolvedValue(0),\n        hset: jest.fn().mockResolvedValue(1),\n        hget: jest.fn().mockResolvedValue(null),\n        hgetall: jest.fn().mockResolvedValue({}),\n        hdel: jest.fn().mockResolvedValue(1),\n        pipeline: jest.fn().mockReturnValue({\n            zremrangebyscore: jest.fn().mockReturnThis(),\n            zadd: jest.fn().mockReturnThis(),\n            zcard: jest.fn().mockReturnThis(),\n            expire: jest.fn().mockReturnThis(),\n            exec: jest.fn().mockResolvedValue([]),\n        }),\n    };\n}\n"
  },
  {
    "path": "apps/api/src/shared/tracking/index.ts",
    "content": "export * from './tracking.interceptor';\n"
  },
  {
    "path": "apps/api/src/shared/tracking/tracking.interceptor.ts",
    "content": "// ============================================================================\n// Tracking Interceptor - Request ID and Correlation ID injection\n// ============================================================================\n\nimport {\n    Injectable,\n    NestInterceptor,\n    ExecutionContext,\n    CallHandler,\n} from '@nestjs/common';\nimport { Observable } from 'rxjs';\nimport { v4 as uuidv4 } from 'uuid';\nimport { AsyncLocalStorage } from 'async_hooks';\n\n// Async local storage for request context\nexport const trackingStorage = new AsyncLocalStorage<TrackingContext>();\n\nexport interface TrackingContext {\n    requestId: string;\n    correlationId?: string;\n    userId?: string;\n    organizationId?: string;\n    startTime: number;\n}\n\n@Injectable()\nexport class TrackingInterceptor implements NestInterceptor {\n    intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {\n        const request = context.switchToHttp().getRequest();\n        const response = context.switchToHttp().getResponse();\n\n        // Get or generate request ID\n        const requestId =\n            (request.headers['x-request-id'] as string) ||\n            (request.headers['x-correlation-id'] as string) ||\n            uuidv4();\n\n        // Get correlation ID (for distributed tracing)\n        const correlationId = (request.headers['x-correlation-id'] as string) || requestId;\n\n        // Set headers for downstream services\n        response.setHeader('x-request-id', requestId);\n        response.setHeader('x-correlation-id', correlationId);\n\n        // Extract user context if available\n        const userId = request.user?.id;\n        const organizationId = request.user?.organizationId;\n\n        const trackingContext: TrackingContext = {\n            requestId,\n            correlationId,\n            userId,\n            organizationId,\n            startTime: Date.now(),\n        };\n\n        // Store in async local storage\n        trackingStorage.run(trackingContext, () => {\n            return next.handle();\n        });\n\n        return next.handle();\n    }\n}\n\n// Helper to get current tracking context\nexport function getTrackingContext(): TrackingContext | undefined {\n    return trackingStorage.getStore();\n}\n\n// Helper to get request ID\nexport function getRequestId(): string | undefined {\n    return trackingStorage.getStore()?.requestId;\n}\n\n// Helper to get correlation ID\nexport function getCorrelationId(): string | undefined {\n    return trackingStorage.getStore()?.correlationId;\n}\n"
  },
  {
    "path": "apps/api/src/shared/transform/index.ts",
    "content": "// ============================================================================\n// Transform Module\n// ============================================================================\n\nexport {\n    TransformInterceptor,\n    KeyTransformInterceptor,\n    transformKeysToCamelCase,\n    transformKeysToSnakeCase,\n} from './transform.interceptor';\nexport type { TransformOptions, ResponseMetadata } from './transform.interceptor';\n"
  },
  {
    "path": "apps/api/src/shared/transform/transform.interceptor.ts",
    "content": "// ============================================================================\n// Transform Interceptor - Global request/response transformation\n// ============================================================================\n\nimport {\n    Injectable,\n    NestInterceptor,\n    ExecutionContext,\n    CallHandler,\n    Logger,\n} from '@nestjs/common';\nimport { Observable } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport { Request, Response } from 'express';\n\nexport interface TransformOptions {\n    /** Enable request body transformation */\n    transformRequest?: boolean;\n    /** Enable response transformation */\n    transformResponse?: boolean;\n    /** Custom response wrapper key */\n    wrapperKey?: string;\n    /** Metadata to include in response */\n    includeMetadata?: boolean;\n}\n\n/**\n * Metadata included in transformed responses\n */\nexport interface ResponseMetadata {\n    timestamp: string;\n    path: string;\n    method: string;\n    duration?: number;\n    requestId?: string;\n}\n\n/**\n * Transform Interceptor - Wraps responses and optionally transforms requests\n */\n@Injectable()\nexport class TransformInterceptor implements NestInterceptor {\n    private readonly logger = new Logger(TransformInterceptor.name);\n    private readonly defaultOptions: Required<TransformOptions>;\n\n    constructor(options: TransformOptions = {}) {\n        this.defaultOptions = {\n            transformRequest: options.transformRequest ?? true,\n            transformResponse: options.transformResponse ?? true,\n            wrapperKey: options.wrapperKey ?? 'data',\n            includeMetadata: options.includeMetadata ?? true,\n        };\n    }\n\n    intercept(context: ExecutionContext, next: CallHandler): Observable<any> {\n        const startTime = process.hrtime.bigint();\n        const request = context.switchToHttp().getRequest<Request>();\n        const response = context.switchToHttp().getResponse<Response>();\n\n        if (!this.defaultOptions.transformResponse) {\n            return next.handle();\n        }\n\n        return next.handle().pipe(\n            map((data) => {\n                const duration = this.getDuration(startTime);\n                const metadata = this.buildMetadata(request, response, duration);\n\n                // If data is already wrapped or is a primitive, return as-is or wrap\n                if (this.isPrimitive(data)) {\n                    return this.wrapResponse(data, metadata);\n                }\n\n                // If data has its own structure (e.g., PaginatedResponse), merge metadata\n                if (this.isWrappedResponse(data)) {\n                    return {\n                        ...data,\n                        _meta: metadata,\n                    };\n                }\n\n                // Default: wrap in data object\n                return this.wrapResponse(data, metadata);\n            }),\n        );\n    }\n\n    /**\n     * Build response metadata\n     */\n    private buildMetadata(request: Request, response: Response, duration: bigint): ResponseMetadata {\n        return {\n            timestamp: new Date().toISOString(),\n            path: request.path,\n            method: request.method,\n            duration: Number(duration) / 1e6, // Convert to ms\n            requestId: (request as any).id || (request.headers['x-request-id'] as string),\n        };\n    }\n\n    /**\n     * Wrap response data\n     */\n    private wrapResponse(data: any, metadata: ResponseMetadata): any {\n        const response: any = {\n            [this.defaultOptions.wrapperKey]: data,\n        };\n\n        if (this.defaultOptions.includeMetadata) {\n            response._meta = metadata;\n        }\n\n        return response;\n    }\n\n    /**\n     * Check if value is primitive\n     */\n    private isPrimitive(value: any): boolean {\n        return value === null || value === undefined ||\n            typeof value === 'string' ||\n            typeof value === 'number' ||\n            typeof value === 'boolean';\n    }\n\n    /**\n     * Check if response is already wrapped\n     */\n    private isWrappedResponse(value: any): boolean {\n        if (!value || typeof value !== 'object') return false;\n        return value.items !== undefined && value.total !== undefined ||\n            value.data !== undefined ||\n            value._meta !== undefined;\n    }\n\n    /**\n     * Get duration in nanoseconds\n     */\n    private getDuration(startTime: bigint): bigint {\n        return process.hrtime.bigint() - startTime;\n    }\n}\n\n/**\n * Snake case to camel case converter for keys\n */\nexport function transformKeysToCamelCase<T>(obj: any): T {\n    if (obj === null || obj === undefined) return obj;\n\n    if (Array.isArray(obj)) {\n        return obj.map(item => transformKeysToCamelCase(item)) as T;\n    }\n\n    if (typeof obj === 'object') {\n        return Object.keys(obj).reduce((acc, key) => {\n            const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());\n            acc[camelKey] = transformKeysToCamelCase(obj[key]);\n            return acc;\n        }, {} as any) as T;\n    }\n\n    return obj;\n}\n\n/**\n * Camel case to snake case converter for keys\n */\nexport function transformKeysToSnakeCase<T>(obj: any): T {\n    if (obj === null || obj === undefined) return obj;\n\n    if (Array.isArray(obj)) {\n        return obj.map(item => transformKeysToSnakeCase(item)) as T;\n    }\n\n    if (typeof obj === 'object') {\n        return Object.keys(obj).reduce((acc, key) => {\n            const snakeKey = key.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);\n            acc[snakeKey] = transformKeysToSnakeCase(obj[key]);\n            return acc;\n        }, {} as any) as T;\n    }\n\n    return obj;\n}\n\n/**\n * Request key transformer interceptor\n */\n@Injectable()\nexport class KeyTransformInterceptor implements NestInterceptor {\n    constructor(\n        private readonly toCamelCase: boolean = true,\n    ) {}\n\n    intercept(context: ExecutionContext, next: CallHandler): Observable<any> {\n        const request = context.switchToHttp().getRequest();\n\n        // Transform query params\n        if (request.query) {\n            request.query = this.toCamelCase\n                ? transformKeysToCamelCase(request.query)\n                : transformKeysToSnakeCase(request.query);\n        }\n\n        // Transform body\n        if (request.body && typeof request.body === 'object') {\n            request.body = this.toCamelCase\n                ? transformKeysToCamelCase(request.body)\n                : transformKeysToSnakeCase(request.body);\n        }\n\n        return next.handle();\n    }\n}\n"
  },
  {
    "path": "apps/api/src/shared/utils/guard.ts",
    "content": "export class Guard {\n    public static againstNullOrUndefined(argument: any, argumentName: string): Result {\n        if (argument === null || argument === undefined) {\n            return { succeeded: false, message: `${argumentName} is null or undefined` };\n        }\n        return { succeeded: true };\n    }\n\n    public static againstNullOrUndefinedBulk(args: GuardArgument[]): Result {\n        for (const arg of args) {\n            const result = this.againstNullOrUndefined(arg.argument, arg.argumentName);\n            if (!result.succeeded) return result;\n        }\n        return { succeeded: true };\n    }\n\n    public static isOneOf(value: any, validValues: any[], argumentName: string): Result {\n        let isValid = false;\n        for (const validValue of validValues) {\n            if (value === validValue) {\n                isValid = true;\n            }\n        }\n\n        if (isValid) {\n            return { succeeded: true };\n        } else {\n            return {\n                succeeded: false,\n                message: `${argumentName} isn't oneOf the correct types in ${JSON.stringify(\n                    validValues,\n                )}. Got \"${value}\".`,\n            };\n        }\n    }\n\n    public static inRange(num: number, min: number, max: number, argumentName: string): Result {\n        const isInRange = num >= min && num <= max;\n        if (!isInRange) {\n            return {\n                succeeded: false,\n                message: `${argumentName} is not within range ${min} to ${max}.`,\n            };\n        }\n        return { succeeded: true };\n    }\n\n    public static allInRange(numbers: number[], min: number, max: number, argumentName: string): Result {\n        let failingResult: Result | null = null;\n        for (const num of numbers) {\n            const numIsInRangeResult = this.inRange(num, min, max, argumentName);\n            if (!numIsInRangeResult.succeeded) failingResult = numIsInRangeResult;\n        }\n\n        if (failingResult) {\n            return { succeeded: false, message: `${argumentName} is not within the range.` };\n        }\n        return { succeeded: true };\n    }\n}\n\nexport interface GuardArgument {\n    argument: any;\n    argumentName: string;\n}\n\nexport interface Result {\n    succeeded: boolean;\n    message?: string;\n}\n"
  },
  {
    "path": "apps/api/src/shared/utils/result.ts",
    "content": "// ============================================================================\n// Result Type - Functional error handling without exceptions\n// ============================================================================\n\n/**\n * A result type that represents either a success value or a failure with error.\n * Inspired by Rust's Result type and fp-ts Either.\n */\nexport class Result<T> {\n    public readonly isSuccess: boolean;\n    public readonly isFailure: boolean;\n    public readonly error: string | null;\n    private readonly _value: T | null;\n\n    private constructor(isSuccess: boolean, error: string | null, value: T | null) {\n        this.isSuccess = isSuccess;\n        this.isFailure = !isSuccess;\n        this.error = error;\n        this._value = value;\n\n        Object.freeze(this);\n    }\n\n    /**\n     * Get the value or throw if error\n     */\n    getValue(): T {\n        if (this.isFailure) {\n            throw new Error(`Result is in failure state: ${this.error}`);\n        }\n        return this._value as T;\n    }\n\n    /**\n     * Get the value or a default if error\n     */\n    getValueOrElse(defaultValue: T): T {\n        return this.isSuccess ? (this._value as T) : defaultValue;\n    }\n\n    /**\n     * Get the value or undefined\n     */\n    getValueOrUndefined(): T | undefined {\n        return this.isSuccess ? (this._value as T) : undefined;\n    }\n\n    /**\n     * Map success value to a new Result\n     */\n    map<U>(fn: (value: T) => U): Result<U> {\n        if (this.isSuccess) {\n            return Result.ok(fn(this._value as T));\n        }\n        return Result.fail(this.error!);\n    }\n\n    /**\n     * Map success value to a new Result (async)\n     */\n    async mapAsync<U>(fn: (value: T) => Promise<U>): Promise<Result<U>> {\n        if (this.isSuccess) {\n            return Result.ok(await fn(this._value as T));\n        }\n        return Result.fail(this.error!);\n    }\n\n    /**\n     * FlatMap - chain operations that return Results\n     */\n    flatMap<U>(fn: (value: T) => Result<U>): Result<U> {\n        if (this.isSuccess) {\n            return fn(this._value as T);\n        }\n        return Result.fail(this.error!);\n    }\n\n    /**\n     * FlatMap - chain async operations that return Results\n     */\n    async flatMapAsync<U>(fn: (value: T) => Promise<Result<U>>): Promise<Result<U>> {\n        if (this.isSuccess) {\n            return await fn(this._value as T);\n        }\n        return Result.fail(this.error!);\n    }\n\n    /**\n     * Fold - handle both success and failure cases\n     */\n    fold<U>(onSuccess: (value: T) => U, onFailure: (error: string) => U): U {\n        if (this.isSuccess) {\n            return onSuccess(this._value as T);\n        }\n        return onFailure(this.error!);\n    }\n\n    /**\n     * Fold async - handle both success and failure cases (async)\n     */\n    async foldAsync<U>(\n        onSuccess: (value: T) => Promise<U>,\n        onFailure: (error: string) => Promise<U>,\n    ): Promise<U> {\n        if (this.isSuccess) {\n            return await onSuccess(this._value as T);\n        }\n        return await onFailure(this.error!);\n    }\n\n    /**\n     * Tap - execute side effects without changing the result\n     */\n    tap(fn: (value: T) => void): Result<T> {\n        if (this.isSuccess) {\n            fn(this._value as T);\n        }\n        return this;\n    }\n\n    /**\n     * Tap async - execute async side effects without changing the result\n     */\n    async tapAsync(fn: (value: T) => Promise<void>): Promise<Result<T>> {\n        if (this.isSuccess) {\n            await fn(this._value as T);\n        }\n        return this;\n    }\n\n    /**\n     * Check if result contains a specific value\n     */\n    contains(value: T): boolean {\n        return this.isSuccess && this._value === value;\n    }\n\n    /**\n     * Check if result's error matches a predicate\n     */\n    existsError(predicate: (error: string) => boolean): boolean {\n        return this.isFailure && predicate(this.error!);\n    }\n\n    // =========================================================================\n    // Static Constructors\n    // =========================================================================\n\n    static ok<U>(value?: U): Result<U> {\n        return new Result<U>(true, null, value ?? null);\n    }\n\n    static fail<U>(error: string): Result<U> {\n        return new Result<U>(false, error, null);\n    }\n\n    /**\n     * Create Result from a try/catch\n     */\n    static fromTry<U>(fn: () => U): Result<U> {\n        try {\n            return Result.ok(fn());\n        } catch (error) {\n            return Result.fail(error instanceof Error ? error.message : String(error));\n        }\n    }\n\n    /**\n     * Create Result from an async try/catch\n     */\n    static async fromTryAsync<U>(fn: () => Promise<U>): Promise<Result<U>> {\n        try {\n            return Result.ok(await fn());\n        } catch (error) {\n            return Result.fail(error instanceof Error ? error.message : String(error));\n        }\n    }\n\n    /**\n     * Combine multiple Results - fail fast on first failure\n     */\n    static combine<T extends Result<any>[]>(...results: T): Result<{ [K in keyof T]: UnwrapResult<T[K]> }> {\n        const failures: string[] = [];\n\n        for (const result of results) {\n            if (result.isFailure) {\n                failures.push(result.error!);\n            }\n        }\n\n        if (failures.length > 0) {\n            return Result.fail(failures.join('; ')) as any;\n        }\n\n        return Result.ok(results.map(r => r.getValue())) as any;\n    }\n\n    /**\n     * Combine multiple Results - collect all failures\n     */\n    static combineAll<T>(...results: Array<Result<T>>): Result<T[]> {\n        const failures: string[] = [];\n        const values: T[] = [];\n\n        for (const result of results) {\n            if (result.isFailure) {\n                failures.push(result.error!);\n            } else {\n                values.push(result.getValue());\n            }\n        }\n\n        if (failures.length > 0) {\n            return Result.fail(`Multiple failures (${failures.length}): ${failures.join('; ')}`);\n        }\n\n        return Result.ok(values);\n    }\n}\n\n/**\n * Type helper to unwrap Result<T>\n */\nexport type UnwrapResult<T> = T extends Result<infer U> ? U : T;\n\n/**\n * Shorthand for Result<null>\n */\nexport type VoidResult = Result<null>;\n\n/**\n * Create a successful void result\n */\nexport const voidOk = (): VoidResult => Result.ok(null);\n"
  },
  {
    "path": "apps/api/src/shared/validation/index.ts",
    "content": "// ============================================================================\n// Validation - Common validation decorators and utilities\n// ============================================================================\n\nexport * from './validation.pipe';\nexport * from './validation-options';\n"
  },
  {
    "path": "apps/api/src/shared/validation/validation-options.ts",
    "content": "// ============================================================================\n// Validation Options & Decorators\n// ============================================================================\n\nimport {\n    IsString,\n    IsEmail,\n    IsUUID,\n    IsOptional,\n    IsEnum,\n    IsInt,\n    IsPositive,\n    MinLength,\n    MaxLength,\n    IsNotEmpty,\n    IsIn,\n    Matches,\n    IsUrl,\n    IsPhoneNumber,\n    IsDateString,\n    IsBoolean,\n    ValidationOptions,\n    ValidationArguments,\n    registerDecorator,\n} from 'class-validator';\n\n/**\n * Common validation messages\n */\nexport const ValidationMessage = {\n    REQUIRED: 'This field is required',\n    INVALID_EMAIL: 'Invalid email address',\n    INVALID_UUID: 'Invalid UUID format',\n    INVALID_URL: 'Invalid URL format',\n    MIN_LENGTH: (min: number) => `Minimum length is ${min} characters`,\n    MAX_LENGTH: (max: number) => `Maximum length is ${max} characters`,\n    MIN_VALUE: (min: number) => `Minimum value is ${min}`,\n    MAX_VALUE: (max: number) => `Maximum value is ${max}`,\n    INVALID_ENUM: (enumValues: string[]) =>\n        `Must be one of: ${enumValues.join(', ')}`,\n    INVALID_PHONE: 'Invalid phone number format',\n    INVALID_DATE: 'Invalid date format (ISO 8601 expected)',\n};\n\n// ============================================================================\n// String Validators\n// ============================================================================\n\n/**\n * Password field with strength requirements\n * Minimum 8 characters, at least one uppercase, one lowercase, one number\n */\nexport function IsPassword(options?: { minLength?: number; ValidationOptions?: ValidationOptions }) {\n    const minLen = options?.minLength ?? 8;\n    return function (object: object, propertyName: string) {\n        registerDecorator({\n            target: object.constructor,\n            propertyName,\n            options: options?.ValidationOptions,\n            validator: {\n                validate(value: string) {\n                    if (!value || typeof value !== 'string') return false;\n                    if (value.length < minLen) return false;\n                    if (!/[A-Z]/.test(value)) return false;\n                    if (!/[a-z]/.test(value)) return false;\n                    if (!/[0-9]/.test(value)) return false;\n                    return true;\n                },\n                defaultMessage() {\n                    return `Password must be at least ${minLen} characters with uppercase, lowercase and number`;\n                },\n            },\n        });\n    };\n}\n\n/**\n * Strong password field - requires special character\n */\nexport function IsStrongPassword(options?: { minLength?: number; ValidationOptions?: ValidationOptions }) {\n    const minLen = options?.minLength ?? 8;\n    return function (object: object, propertyName: string) {\n        registerDecorator({\n            target: object.constructor,\n            propertyName,\n            options: options?.ValidationOptions,\n            validator: {\n                validate(value: string) {\n                    if (!value || typeof value !== 'string') return false;\n                    if (value.length < minLen) return false;\n                    if (!/[A-Z]/.test(value)) return false;\n                    if (!/[a-z]/.test(value)) return false;\n                    if (!/[0-9]/.test(value)) return false;\n                    if (!/[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>\\/?]/.test(value)) return false;\n                    return true;\n                },\n                defaultMessage() {\n                    return `Password must be at least ${minLen} characters with uppercase, lowercase, number and special character`;\n                },\n            },\n        });\n    };\n}\n\n/**\n * Username field - alphanumeric with underscores\n */\nexport function IsUsername(options?: { minLength?: number; maxLength?: number; ValidationOptions?: ValidationOptions }) {\n    const minLen = options?.minLength ?? 3;\n    const maxLen = options?.maxLength ?? 30;\n    return function (object: object, propertyName: string) {\n        registerDecorator({\n            target: object.constructor,\n            propertyName,\n            options: options?.ValidationOptions,\n            validator: {\n                validate(value: string) {\n                    if (!value || typeof value !== 'string') return false;\n                    if (value.length < minLen || value.length > maxLen) return false;\n                    return /^[a-zA-Z0-9_]+$/.test(value);\n                },\n                defaultMessage() {\n                    return `Username must be ${minLen}-${maxLen} alphanumeric characters or underscores`;\n                },\n            },\n        });\n    };\n}\n\n/**\n * Slug field - lowercase alphanumeric with hyphens\n */\nexport function IsSlug(options?: { maxLength?: number; ValidationOptions?: ValidationOptions }) {\n    const maxLen = options?.maxLength ?? 64;\n    return function (object: object, propertyName: string) {\n        registerDecorator({\n            target: object.constructor,\n            propertyName,\n            options: options?.ValidationOptions,\n            validator: {\n                validate(value: string) {\n                    if (!value || typeof value !== 'string') return false;\n                    if (value.length > maxLen) return false;\n                    return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value);\n                },\n                defaultMessage() {\n                    return 'Slug must be lowercase alphanumeric with hyphens (e.g., my-slug)';\n                },\n            },\n        });\n    };\n}\n\n/**\n * JSON string field\n */\nexport function IsJsonString(ValidationOptions?: ValidationOptions) {\n    return function (object: object, propertyName: string) {\n        registerDecorator({\n            target: object.constructor,\n            propertyName,\n            options: ValidationOptions,\n            validator: {\n                validate(value: string) {\n                    if (!value || typeof value !== 'string') return false;\n                    try {\n                        JSON.parse(value);\n                        return true;\n                    } catch {\n                        return false;\n                    }\n                },\n                defaultMessage() {\n                    return 'Invalid JSON string';\n                },\n            },\n        });\n    };\n}\n\n// ============================================================================\n// ID Validators\n// ============================================================================\n\n/**\n * MongoDB ObjectId field\n */\nexport function IsObjectId(ValidationOptions?: ValidationOptions) {\n    return function (object: object, propertyName: string) {\n        registerDecorator({\n            target: object.constructor,\n            propertyName,\n            options: ValidationOptions,\n            validator: {\n                validate(value: string) {\n                    if (!value || typeof value !== 'string') return false;\n                    return /^[a-fA-F0-9]{24}$/.test(value);\n                },\n                defaultMessage() {\n                    return 'Invalid MongoDB ObjectId format';\n                },\n            },\n        });\n    };\n}\n\n/**\n * Custom ID field with prefix (e.g., user_xxx, org_xxx)\n */\nexport function IsPrefixedId(prefix: string, ValidationOptions?: ValidationOptions) {\n    return function (object: object, propertyName: string) {\n        registerDecorator({\n            target: object.constructor,\n            propertyName,\n            options: ValidationOptions,\n            validator: {\n                validate(value: string) {\n                    if (!value || typeof value !== 'string') return false;\n                    const pattern = new RegExp(`^${prefix}_[a-zA-Z0-9]+$`);\n                    return pattern.test(value);\n                },\n                defaultMessage() {\n                    return `ID must start with '${prefix}_' followed by alphanumeric characters`;\n                },\n            },\n        });\n    };\n}\n\n// ============================================================================\n// Array Validators\n// ============================================================================\n\n/**\n * Non-empty array\n */\nexport function IsNonEmptyArray(ValidationOptions?: ValidationOptions) {\n    return function (object: object, propertyName: string) {\n        registerDecorator({\n            target: object.constructor,\n            propertyName,\n            options: ValidationOptions,\n            validator: {\n                validate(value: unknown[]) {\n                    return Array.isArray(value) && value.length > 0;\n                },\n                defaultMessage() {\n                    return 'Array must not be empty';\n                },\n            },\n        });\n    };\n}\n\n/**\n * Array with unique items\n */\nexport function IsUniqueArray(ValidationOptions?: ValidationOptions) {\n    return function (object: object, propertyName: string) {\n        registerDecorator({\n            target: object.constructor,\n            propertyName,\n            options: ValidationOptions,\n            validator: {\n                validate(value: unknown[]) {\n                    if (!Array.isArray(value)) return false;\n                    return new Set(value).size === value.length;\n                },\n                defaultMessage() {\n                    return 'Array must contain only unique items';\n                },\n            },\n        });\n    };\n}\n\n// ============================================================================\n// Date Validators\n// ============================================================================\n\n/**\n * ISO 8601 date string\n */\nexport function IsIso8601Date(ValidationOptions?: ValidationOptions) {\n    return IsDateString(undefined, ValidationOptions);\n}\n\n/**\n * Future date\n */\nexport function IsFutureDate(ValidationOptions?: ValidationOptions) {\n    return function (object: object, propertyName: string) {\n        registerDecorator({\n            target: object.constructor,\n            propertyName,\n            options: ValidationOptions,\n            validator: {\n                validate(value: string) {\n                    if (!value) return false;\n                    const date = new Date(value);\n                    return date > new Date();\n                },\n                defaultMessage() {\n                    return 'Date must be in the future';\n                },\n            },\n        });\n    };\n}\n\n/**\n * Past date\n */\nexport function IsPastDate(ValidationOptions?: ValidationOptions) {\n    return function (object: object, propertyName: string) {\n        registerDecorator({\n            target: object.constructor,\n            propertyName,\n            options: ValidationOptions,\n            validator: {\n                validate(value: string) {\n                    if (!value) return false;\n                    const date = new Date(value);\n                    return date < new Date();\n                },\n                defaultMessage() {\n                    return 'Date must be in the past';\n                },\n            },\n        });\n    };\n}\n\n// ============================================================================\n// Range Validators\n// ============================================================================\n\n/**\n * Number in range (inclusive)\n */\nexport function IsInRange(min: number, max: number, ValidationOptions?: ValidationOptions) {\n    return function (object: object, propertyName: string) {\n        registerDecorator({\n            target: object.constructor,\n            propertyName,\n            options: ValidationOptions,\n            validator: {\n                validate(value: number) {\n                    return typeof value === 'number' && value >= min && value <= max;\n                },\n                defaultMessage() {\n                    return `Value must be between ${min} and ${max}`;\n                },\n            },\n        });\n    };\n}\n\n/**\n * String length in range\n */\nexport function IsLengthInRange(min: number, max: number, ValidationOptions?: ValidationOptions) {\n    return function (object: object, propertyName: string) {\n        registerDecorator({\n            target: object.constructor,\n            propertyName,\n            options: ValidationOptions,\n            validator: {\n                validate(value: string) {\n                    if (typeof value !== 'string') return false;\n                    return value.length >= min && value.length <= max;\n                },\n                defaultMessage() {\n                    return `Length must be between ${min} and ${max} characters`;\n                },\n            },\n        });\n    };\n}\n\n// ============================================================================\n// Conditional Validators\n// ============================================================================\n\n/**\n * Match another field exactly\n */\nexport function MatchesField(\n    field: string,\n    message?: string,\n    ValidationOptions?: ValidationOptions,\n) {\n    return function (object: object, propertyName: string) {\n        registerDecorator({\n            target: object.constructor,\n            propertyName,\n            options: ValidationOptions,\n            validator: {\n                validate(value: unknown, args: ValidationArguments) {\n                    const objectToCompare = args.object as Record<string, unknown>;\n                    return objectToCompare[field] === value;\n                },\n                defaultMessage() {\n                    return message ?? `Must match '${field}'`;\n                },\n            },\n        });\n    };\n}\n\n// ============================================================================\n// Type Validators\n// ============================================================================\n\n/**\n * Instance of specific class\n */\nexport function IsInstanceOf<T extends new (...args: unknown[]) => unknown>(\n    classType: T,\n    ValidationOptions?: ValidationOptions,\n) {\n    return function (object: object, propertyName: string) {\n        registerDecorator({\n            target: object.constructor,\n            propertyName,\n            options: ValidationOptions,\n            validator: {\n                validate(value: unknown) {\n                    return value instanceof classType;\n                },\n                defaultMessage() {\n                    return `Must be an instance of ${classType.name}`;\n                },\n            },\n        });\n    };\n}\n\n/**\n * Array of specific type/items\n */\nexport function IsArrayOf<T>(\n    itemValidator: (value: unknown) => boolean,\n    ValidationOptions?: ValidationOptions,\n) {\n    return function (object: object, propertyName: string) {\n        registerDecorator({\n            target: object.constructor,\n            propertyName,\n            options: ValidationOptions,\n            validator: {\n                validate(value: unknown[]) {\n                    if (!Array.isArray(value)) return false;\n                    return value.every(itemValidator);\n                },\n                defaultMessage() {\n                    return 'All items must be valid';\n                },\n            },\n        });\n    };\n}\n"
  },
  {
    "path": "apps/api/src/shared/validation/validation.pipe.ts",
    "content": "// ============================================================================\n// Validation Pipe - Global validation configuration\n// ============================================================================\n\nimport {\n    ValidationPipe,\n    ValidationPipeOptions,\n    BadRequestException,\n} from '@nestjs/common';\nimport { ValidatorOptions, ValidationError } from 'class-validator';\n\n/**\n * Default validator options for class-validator\n */\nexport const DEFAULT_VALIDATOR_OPTIONS: ValidatorOptions = {\n    whitelist: true,\n    forbidNonWhitelisted: true,\n    forbidUnknownValues: true,\n};\n\n/**\n * Default transform options\n */\nexport const DEFAULT_TRANSFORM_OPTIONS = {\n    enableImplicitConversion: true,\n};\n\n/**\n * Transform NestJS ValidationError to readable format\n */\nexport function formatValidationErrors(\n    errors: ValidationError[],\n    parentProperty = '',\n): Array<{ field: string; constraints: string[] }> {\n    const formatted: Array<{ field: string; constraints: string[] }> = [];\n\n    for (const error of errors) {\n        const field = parentProperty\n            ? `${parentProperty}.${error.property}`\n            : error.property;\n\n        if (error.constraints) {\n            formatted.push({\n                field,\n                constraints: Object.values(error.constraints),\n            });\n        }\n\n        if (error.children && error.children.length > 0) {\n            formatted.push(\n                ...formatValidationErrors(error.children, field),\n            );\n        }\n    }\n\n    return formatted;\n}\n\n/**\n * Create a ValidationPipe with standardized configuration\n */\nexport function createValidationPipe(\n    options: ValidationPipeOptions = {},\n): ValidationPipe {\n    return new ValidationPipe({\n        ...options,\n        transform: true,\n        transformOptions: DEFAULT_TRANSFORM_OPTIONS,\n        exceptionFactory: (errors: ValidationError[]) => {\n            const formatted = formatValidationErrors(errors);\n            return new BadRequestException({\n                code: 'VALIDATION_ERROR',\n                message: 'Validation failed',\n                errors: formatted,\n            });\n        },\n    });\n}\n\n/**\n * Default global validation pipe instance\n */\nexport const globalValidationPipe = createValidationPipe({\n    whitelist: true,\n    forbidNonWhitelisted: true,\n});\n\n/**\n * Strict validation pipe (for DTOs that must be exact)\n */\nexport const strictValidationPipe = createValidationPipe({\n    whitelist: true,\n    forbidNonWhitelisted: true,\n    skipMissingProperties: false,\n});\n\n/**\n * Partial validation pipe (for optional/update DTOs)\n */\nexport const partialValidationPipe = createValidationPipe({\n    whitelist: true,\n    forbidNonWhitelisted: false,\n    skipMissingProperties: true,\n});\n"
  },
  {
    "path": "apps/api/test/jest-e2e.json",
    "content": "{\n    \"moduleFileExtensions\": [\"js\", \"json\", \"ts\"],\n    \"rootDir\": \".\",\n    \"testEnvironment\": \"node\",\n    \"testRegex\": \".e2e-spec.ts$\",\n    \"transform\": {\n        \"^.+\\\\.(t|j)s$\": \"ts-jest\"\n    },\n    \"moduleNameMapper\": {\n        \"^@/(.*)$\": \"<rootDir>/../src/$1\"\n    }\n}\n"
  },
  {
    "path": "apps/api/tsconfig.build.json",
    "content": "{\n    \"extends\": \"./tsconfig.json\",\n    \"exclude\": [\"node_modules\", \"test\", \"dist\", \"**/*spec.ts\"]\n}\n"
  },
  {
    "path": "apps/api/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"declaration\": true,\n        \"removeComments\": true,\n        \"emitDecoratorMetadata\": true,\n        \"experimentalDecorators\": true,\n        \"allowSyntheticDefaultImports\": true,\n        \"target\": \"ES2024\",\n        \"sourceMap\": true,\n        \"outDir\": \"./dist\",\n        \"baseUrl\": \"./\",\n        \"incremental\": true,\n        \"skipLibCheck\": true,\n        \"strictNullChecks\": true,\n        \"noImplicitAny\": true,\n        \"strictBindCallApply\": true,\n        \"forceConsistentCasingInFileNames\": true,\n        \"noFallthroughCasesInSwitch\": true,\n        \"paths\": {\n            \"@/*\": [\"src/*\"]\n        }\n    }\n}\n"
  },
  {
    "path": "biome.json",
    "content": "{\n    \"$schema\": \"https://biomejs.dev/schemas/2.3.11/schema.json\",\n    \"vcs\": {\n        \"enabled\": true,\n        \"defaultBranch\": \"main\",\n        \"clientKind\": \"git\",\n        \"useIgnoreFile\": true\n    },\n    \"files\": {\n        \"maxSize\": 10240000,\n        \"ignoreUnknown\": false,\n        \"experimentalScannerIgnores\": [\n            \"**/dist/**\",\n            \".vscode/**/*\",\n            \"node_modules/**\",\n            \"dist/**\",\n            \"build/**\",\n            \"coverage/**\",\n            \"tsconfig.*\"\n        ]\n    },\n    \"formatter\": {\n        \"enabled\": true,\n        \"indentStyle\": \"space\",\n        \"indentWidth\": 4\n    },\n    \"javascript\": {\n        \"parser\": {\n            \"unsafeParameterDecoratorsEnabled\": true\n        },\n        \"formatter\": {\n            \"quoteStyle\": \"single\",\n            \"arrowParentheses\": \"asNeeded\",\n            \"jsxQuoteStyle\": \"double\",\n            \"lineWidth\": 120\n        }\n    },\n    \"linter\": {\n        \"enabled\": true,\n        \"rules\": {\n            \"recommended\": true,\n            \"security\": {\n                \"noDangerouslySetInnerHtml\": \"off\"\n            },\n            \"suspicious\": {\n                \"noExplicitAny\": \"off\",\n                \"noArrayIndexKey\": \"off\",\n                \"noAsyncPromiseExecutor\": \"off\",\n                \"noAssignInExpressions\": \"off\",\n                \"noConfusingVoidType\": \"off\",\n                \"noControlCharactersInRegex\": \"off\"\n            },\n            \"style\": {\n                \"useConst\": \"off\",\n                \"noUselessElse\": \"off\",\n                \"useImportType\": \"off\",\n                \"noParameterAssign\": \"off\",\n                \"noInferrableTypes\": \"off\",\n                \"noNonNullAssertion\": \"off\",\n                \"useExportType\": \"off\",\n                \"useNodejsImportProtocol\": \"off\"\n            },\n            \"performance\": {\n                \"noDelete\": \"off\",\n                \"noAccumulatingSpread\": \"off\"\n            },\n            \"complexity\": {\n                \"noUselessTypeConstraint\": \"off\",\n                \"noStaticOnlyClass\": \"off\",\n                \"noUselessFragments\": \"off\",\n                \"useOptionalChain\": \"off\",\n                \"noExtraBooleanCast\": \"off\",\n                \"useLiteralKeys\": \"off\",\n                \"noThisInStatic\": \"off\",\n                \"noBannedTypes\": \"off\",\n                \"noForEach\": \"off\"\n            },\n            \"correctness\": {\n                \"noEmptyCharacterClassInRegex\": \"off\",\n                \"useExhaustiveDependencies\": \"off\",\n                \"useJsxKeyInIterable\": \"off\",\n                \"noConstructorReturn\": \"off\"\n            },\n            \"a11y\": {\n                \"useKeyWithClickEvents\": \"off\",\n                \"noSvgWithoutTitle\": \"off\",\n                \"useAltText\": \"off\",\n                \"useButtonType\": \"off\",\n                \"useValidAnchor\": \"off\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "docker/Dockerfile",
    "content": "# =============================================================================\n# Stage 1: Dependencies\n# =============================================================================\nFROM node:20-alpine AS deps\n\n# Install pnpm\nRUN corepack enable && corepack prepare pnpm@latest --activate\n\nWORKDIR /app\n\n# Copy package files for dependency installation\nCOPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./\nCOPY apps/api/package.json ./apps/api/\nCOPY packages/kysely/package.json ./packages/kysely/\nCOPY packages/redisson/package.json ./packages/redisson/\n\n# Install dependencies (production only for smaller image)\nRUN pnpm install --frozen-lockfile --prod\n\n# =============================================================================\n# Stage 2: Builder\n# =============================================================================\nFROM node:20-alpine AS builder\n\n# Install pnpm\nRUN corepack enable && corepack prepare pnpm@latest --activate\n\nWORKDIR /app\n\n# Copy package files\nCOPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./\nCOPY apps/api/package.json ./apps/api/\nCOPY packages/kysely/package.json ./packages/kysely/\nCOPY packages/redisson/package.json ./packages/redisson/\n\n# Install all dependencies (including devDependencies for build)\nRUN pnpm install --frozen-lockfile\n\n# Copy source code\nCOPY tsconfig.json tsconfig.build.json ./\nCOPY apps/api/ ./apps/api/\nCOPY packages/kysely/ ./packages/kysely/\nCOPY packages/redisson/ ./packages/redisson/\n\n# Build packages first, then the app\nRUN pnpm --filter @a3s-lab/kysely build && \\\n    pnpm --filter @a3s-lab/redisson build && \\\n    pnpm --filter api build\n\n# =============================================================================\n# Stage 3: Runner (Production)\n# =============================================================================\nFROM node:20-alpine AS runner\n\n# Install pnpm for runtime\nRUN corepack enable && corepack prepare pnpm@latest --activate\n\n# Add non-root user for security\nRUN addgroup --system --gid 1001 nodejs && \\\n    adduser --system --uid 1001 nestjs\n\nWORKDIR /app\n\n# Copy production dependencies from deps stage\nCOPY --from=deps /app/node_modules ./node_modules\nCOPY --from=deps /app/apps/api/node_modules ./apps/api/node_modules\nCOPY --from=deps /app/packages/kysely/node_modules ./packages/kysely/node_modules\nCOPY --from=deps /app/packages/redisson/node_modules ./packages/redisson/node_modules\n\n# Copy built artifacts from builder stage\nCOPY --from=builder /app/apps/api/dist ./apps/api/dist\nCOPY --from=builder /app/packages/kysely/dist ./packages/kysely/dist\nCOPY --from=builder /app/packages/redisson/dist ./packages/redisson/dist\n\n# Copy package.json files for runtime\nCOPY --from=builder /app/package.json ./\nCOPY --from=builder /app/apps/api/package.json ./apps/api/\nCOPY --from=builder /app/packages/kysely/package.json ./packages/kysely/\nCOPY --from=builder /app/packages/redisson/package.json ./packages/redisson/\n\n# Set ownership\nRUN chown -R nestjs:nodejs /app\n\n# Switch to non-root user\nUSER nestjs\n\n# Environment variables\nENV NODE_ENV=production\nENV APP_PORT=3000\n\nEXPOSE 3000\n\n# Health check\nHEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \\\n    CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1\n\n# Start the application\nCMD [\"node\", \"apps/api/dist/main.js\"]\n"
  },
  {
    "path": "docker/Dockerfile.dev",
    "content": "# =============================================================================\n# Development Dockerfile\n# =============================================================================\nFROM node:20-alpine\n\n# Install pnpm\nRUN corepack enable && corepack prepare pnpm@latest --activate\n\nWORKDIR /app\n\n# Copy package files for dependency installation\nCOPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./\nCOPY apps/api/package.json ./apps/api/\nCOPY packages/kysely/package.json ./packages/kysely/\nCOPY packages/redisson/package.json ./packages/redisson/\n\n# Install all dependencies\nRUN pnpm install --frozen-lockfile\n\n# Copy source code\nCOPY . .\n\n# Build packages (required for workspace dependencies)\nRUN pnpm --filter @a3s-lab/kysely build && \\\n    pnpm --filter @a3s-lab/redisson build\n\n# Environment variables\nENV NODE_ENV=development\nENV APP_PORT=3000\n\nEXPOSE 3000\n\n# Start in development mode with hot reload\nCMD [\"pnpm\", \"--filter\", \"api\", \"start:dev\"]\n"
  },
  {
    "path": "docker/docker-compose.prod.yml",
    "content": "services:\n  # =============================================================================\n  # PostgreSQL Database (Production)\n  # =============================================================================\n  postgres:\n    image: postgres:16-alpine\n    container_name: nestify-postgres\n    environment:\n      POSTGRES_USER: ${DB_USERNAME}\n      POSTGRES_PASSWORD: ${DB_PASSWORD}\n      POSTGRES_DB: ${DB_DATABASE}\n    volumes:\n      - postgres_data:/var/lib/postgresql/data\n    healthcheck:\n      test: [\"CMD-SHELL\", \"pg_isready -U ${DB_USERNAME} -d ${DB_DATABASE}\"]\n      interval: 10s\n      timeout: 5s\n      retries: 5\n      start_period: 10s\n    networks:\n      - nestify-network\n    restart: always\n    deploy:\n      resources:\n        limits:\n          memory: 512M\n\n  # =============================================================================\n  # Redis Cache (Production)\n  # =============================================================================\n  redis:\n    image: redis:7-alpine\n    container_name: nestify-redis\n    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD} --maxmemory 256mb --maxmemory-policy allkeys-lru\n    volumes:\n      - redis_data:/data\n    healthcheck:\n      test: [\"CMD\", \"redis-cli\", \"-a\", \"${REDIS_PASSWORD}\", \"ping\"]\n      interval: 10s\n      timeout: 5s\n      retries: 5\n      start_period: 5s\n    networks:\n      - nestify-network\n    restart: always\n    deploy:\n      resources:\n        limits:\n          memory: 256M\n\n  # =============================================================================\n  # NestJS Application (Production)\n  # =============================================================================\n  app:\n    build:\n      context: ..\n      dockerfile: docker/Dockerfile\n    container_name: nestify-app\n    ports:\n      - \"${APP_PORT:-3000}:3000\"\n    environment:\n      NODE_ENV: production\n      APP_PORT: 3000\n      # Database\n      DB_HOST: postgres\n      DB_PORT: 5432\n      DB_USERNAME: ${DB_USERNAME}\n      DB_PASSWORD: ${DB_PASSWORD}\n      DB_DATABASE: ${DB_DATABASE}\n      # Redis\n      REDIS_HOST: redis\n      REDIS_PORT: 6379\n      REDIS_PASSWORD: ${REDIS_PASSWORD}\n      REDIS_DB: ${REDIS_DB:-0}\n    depends_on:\n      postgres:\n        condition: service_healthy\n      redis:\n        condition: service_healthy\n    networks:\n      - nestify-network\n    restart: always\n    deploy:\n      resources:\n        limits:\n          memory: 512M\n      replicas: 1\n\n# =============================================================================\n# Networks\n# =============================================================================\nnetworks:\n  nestify-network:\n    driver: bridge\n\n# =============================================================================\n# Volumes\n# =============================================================================\nvolumes:\n  postgres_data:\n    driver: local\n  redis_data:\n    driver: local\n"
  },
  {
    "path": "docker/docker-compose.yml",
    "content": "services:\n  # =============================================================================\n  # PostgreSQL Database\n  # =============================================================================\n  postgres:\n    image: postgres:16-alpine\n    container_name: nestify-postgres\n    environment:\n      POSTGRES_USER: ${DB_USERNAME:-postgres}\n      POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres}\n      POSTGRES_DB: ${DB_DATABASE:-nestify}\n    ports:\n      - \"${DB_PORT:-5432}:5432\"\n    volumes:\n      - postgres_data:/var/lib/postgresql/data\n      - ../apps/api/migrations:/docker-entrypoint-initdb.d:ro\n    healthcheck:\n      test: [\"CMD-SHELL\", \"pg_isready -U ${DB_USERNAME:-postgres} -d ${DB_DATABASE:-nestify}\"]\n      interval: 10s\n      timeout: 5s\n      retries: 5\n      start_period: 10s\n    networks:\n      - nestify-network\n\n  # =============================================================================\n  # Redis Cache\n  # =============================================================================\n  redis:\n    image: redis:7-alpine\n    container_name: nestify-redis\n    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-redis123}\n    ports:\n      - \"${REDIS_PORT:-6379}:6379\"\n    volumes:\n      - redis_data:/data\n    healthcheck:\n      test: [\"CMD\", \"redis-cli\", \"-a\", \"${REDIS_PASSWORD:-redis123}\", \"ping\"]\n      interval: 10s\n      timeout: 5s\n      retries: 5\n      start_period: 5s\n    networks:\n      - nestify-network\n\n  # =============================================================================\n  # NestJS Application (Development)\n  # =============================================================================\n  app:\n    build:\n      context: ..\n      dockerfile: docker/Dockerfile.dev\n    container_name: nestify-app\n    ports:\n      - \"${APP_PORT:-3000}:3000\"\n    environment:\n      NODE_ENV: development\n      APP_PORT: 3000\n      # Database\n      DB_HOST: postgres\n      DB_PORT: 5432\n      DB_USERNAME: ${DB_USERNAME:-postgres}\n      DB_PASSWORD: ${DB_PASSWORD:-postgres}\n      DB_DATABASE: ${DB_DATABASE:-nestify}\n      # Redis\n      REDIS_HOST: redis\n      REDIS_PORT: 6379\n      REDIS_PASSWORD: ${REDIS_PASSWORD:-redis123}\n      REDIS_DB: ${REDIS_DB:-0}\n    depends_on:\n      postgres:\n        condition: service_healthy\n      redis:\n        condition: service_healthy\n    volumes:\n      # Mount source code for hot reload\n      - ../apps/api/src:/app/apps/api/src:ro\n      - ../packages/kysely/src:/app/packages/kysely/src:ro\n      - ../packages/redisson/src:/app/packages/redisson/src:ro\n    networks:\n      - nestify-network\n    restart: unless-stopped\n\n# =============================================================================\n# Networks\n# =============================================================================\nnetworks:\n  nestify-network:\n    driver: bridge\n\n# =============================================================================\n# Volumes\n# =============================================================================\nvolumes:\n  postgres_data:\n    driver: local\n  redis_data:\n    driver: local\n"
  },
  {
    "path": "docs/architecture.md",
    "content": "# Architecture Guide\n\n## Overview\n\nThis template implements Clean Architecture and Domain-Driven Design (DDD) principles to create a maintainable, testable, and scalable application structure.\n\n## The Dependency Rule\n\nThe fundamental rule of Clean Architecture:\n\n> Source code dependencies must point only inward, toward higher-level policies.\n\n```\n┌─────────────────────────────────────────┐\n│         Presentation Layer              │\n│         (Controllers, DTOs)             │\n└──────────────┬──────────────────────────┘\n               │ depends on\n┌──────────────▼──────────────────────────┐\n│         Application Layer               │\n│    (Commands, Queries, Handlers)        │\n└──────────────┬──────────────────────────┘\n               │ depends on\n┌──────────────▼──────────────────────────┐\n│           Domain Layer                  │\n│  (Entities, Value Objects, Events)      │\n└─────────────────────────────────────────┘\n               ▲\n               │ implements\n┌──────────────┴──────────────────────────┐\n│       Infrastructure Layer              │\n│  (Repositories, Database, External)     │\n└─────────────────────────────────────────┘\n```\n\n## Layer Responsibilities\n\n### 1. Domain Layer (Core)\n\n**Purpose**: Contains business logic and rules. This is the heart of the application.\n\n**Components**:\n- **Entities**: Objects with identity that persist over time\n  - Example: `Order`, `OrderItem`\n  - Have unique identifiers\n  - Contain business logic\n  - Can change state through methods\n\n- **Value Objects**: Immutable objects defined by their attributes\n  - Example: `Money`, `Quantity`, `OrderStatus`\n  - No identity\n  - Immutable\n  - Compared by value, not reference\n\n- **Aggregates**: Clusters of entities and value objects\n  - Example: `Order` (aggregate root) contains `OrderItem` entities\n  - Enforce consistency boundaries\n  - Only aggregate roots can be accessed from outside\n\n- **Domain Events**: Represent business occurrences\n  - Example: `OrderCreatedEvent`, `OrderConfirmedEvent`\n  - Immutable\n  - Past tense naming\n  - Contain relevant data\n\n- **Domain Services**: Business logic that doesn't belong to a single entity\n  - Example: `OrderPricingService`\n  - Stateless\n  - Operate on multiple entities\n\n- **Repository Interfaces**: Contracts for data access\n  - Example: `IOrderRepository`\n  - Defined in domain, implemented in infrastructure\n  - Abstract persistence details\n\n**Rules**:\n- No dependencies on outer layers\n- No framework dependencies\n- Pure business logic\n- Framework-agnostic\n\n### 2. Application Layer (Use Cases)\n\n**Purpose**: Orchestrates domain objects to fulfill use cases.\n\n**Components**:\n- **Commands**: Represent write operations\n  - Example: `CreateOrderCommand`, `ConfirmOrderCommand`\n  - Contain data needed for the operation\n  - Handled by command handlers\n\n- **Command Handlers**: Execute commands\n  - Example: `CreateOrderHandler`\n  - Orchestrate domain objects\n  - Persist changes\n  - Publish events\n\n- **Queries**: Represent read operations\n  - Example: `GetOrderQuery`, `ListOrdersQuery`\n  - Return DTOs, not domain entities\n  - Optimized for reading\n\n- **Query Handlers**: Execute queries\n  - Example: `GetOrderHandler`\n  - Fetch data\n  - Transform to DTOs\n\n- **DTOs**: Data transfer objects\n  - Example: `CreateOrderDto`, `OrderResponseDto`\n  - Define API contracts\n  - Validation rules\n  - No business logic\n\n- **Event Handlers**: React to domain events\n  - Example: `OrderCreatedHandler`\n  - Side effects\n  - Asynchronous processing\n\n**Rules**:\n- Depends only on domain layer\n- No direct database access\n- Uses repository interfaces\n- Coordinates domain objects\n\n### 3. Infrastructure Layer (Technical Details)\n\n**Purpose**: Implements technical concerns and external dependencies.\n\n**Components**:\n- **Repository Implementations**: Concrete data access\n  - Example: `OrderRepository`\n  - Implements domain repository interfaces\n  - Uses TypeORM\n  - Maps between domain and persistence models\n\n- **Database Schemas**: ORM entities\n  - Example: `OrderSchema`, `OrderItemSchema`\n  - TypeORM entities\n  - Database-specific\n\n- **Mappers**: Convert between layers\n  - Example: `OrderMapper`\n  - Domain ↔ Persistence\n  - Isolate domain from infrastructure\n\n- **Event Bus**: Publish domain events\n  - Example: `EventBusService`\n  - Uses @nestjs/cqrs\n  - Decouples event producers and consumers\n\n- **External Services**: Third-party integrations\n  - Example: Payment gateways, email services\n  - Implement domain interfaces\n  - Isolate external dependencies\n\n**Rules**:\n- Implements domain interfaces\n- Contains framework-specific code\n- Handles technical concerns\n- No business logic\n\n### 4. Presentation Layer (API)\n\n**Purpose**: Exposes application functionality through APIs.\n\n**Components**:\n- **Controllers**: HTTP endpoints\n  - Example: `OrderController`\n  - Route requests\n  - Validate input\n  - Return responses\n\n- **Filters**: Exception handling\n  - Example: `HttpExceptionFilter`, `DomainExceptionFilter`\n  - Transform exceptions to HTTP responses\n  - Logging\n\n- **Interceptors**: Cross-cutting concerns\n  - Example: `LoggingInterceptor`\n  - Logging\n  - Transformation\n  - Caching\n\n- **Validation**: Input validation\n  - Uses class-validator\n  - DTOs with decorators\n  - Automatic validation\n\n**Rules**:\n- Depends on application layer\n- No direct domain access\n- Uses DTOs for data transfer\n- Framework-specific\n\n## Data Flow\n\n### Command Flow (Write Operation)\n\n```\n1. Controller receives HTTP request\n   ↓\n2. Validates DTO\n   ↓\n3. Creates Command\n   ↓\n4. CommandBus executes CommandHandler\n   ↓\n5. Handler loads domain entities (via repository)\n   ↓\n6. Handler calls domain methods\n   ↓\n7. Domain entity changes state, raises events\n   ↓\n8. Handler persists entity (via repository)\n   ↓\n9. Handler publishes domain events\n   ↓\n10. EventHandlers react to events\n   ↓\n11. Controller returns response\n```\n\n### Query Flow (Read Operation)\n\n```\n1. Controller receives HTTP request\n   ↓\n2. Creates Query\n   ↓\n3. QueryBus executes QueryHandler\n   ↓\n4. Handler fetches data (via repository)\n   ↓\n5. Handler transforms to DTO\n   ↓\n6. Controller returns DTO\n```\n\n## CQRS Pattern\n\n### Why CQRS?\n\n- **Separation of Concerns**: Different models for reads and writes\n- **Scalability**: Scale reads and writes independently\n- **Optimization**: Optimize queries without affecting commands\n- **Clarity**: Clear distinction between state changes and queries\n\n### Implementation\n\n**Commands** (Write):\n- Change state\n- Validate business rules\n- Raise domain events\n- Return minimal data (usually just ID)\n\n**Queries** (Read):\n- Don't change state\n- Optimized for reading\n- Return DTOs\n- Can bypass domain layer for performance\n\n## Event-Driven Architecture\n\n### Domain Events\n\nDomain events represent something that happened in the domain:\n\n```typescript\nexport class OrderCreatedEvent extends DomainEvent {\n  constructor(\n    public readonly orderId: string,\n    public readonly customerId: string,\n    public readonly totalAmount: Money,\n  ) {\n    super();\n  }\n}\n```\n\n### Event Flow\n\n```\n1. Domain entity raises event\n   ↓\n2. Event stored in aggregate\n   ↓\n3. Handler persists aggregate\n   ↓\n4. Handler publishes events\n   ↓\n5. EventHandlers react\n   ↓\n6. Side effects executed\n```\n\n### Benefits\n\n- **Decoupling**: Producers don't know consumers\n- **Extensibility**: Add new handlers without changing existing code\n- **Audit Trail**: Events provide history\n- **Integration**: Easy to integrate with external systems\n\n## Dependency Injection\n\n### Inversion of Control\n\nThe domain layer defines interfaces, infrastructure implements them:\n\n```typescript\n// Domain layer (interface)\nexport interface IOrderRepository {\n  findById(id: string): Promise<Order | null>;\n  save(order: Order): Promise<Order>;\n}\n\n// Infrastructure layer (implementation)\n@Injectable()\nexport class OrderRepository implements IOrderRepository {\n  // Implementation using TypeORM\n}\n\n// Application layer (usage)\n@CommandHandler(CreateOrderCommand)\nexport class CreateOrderHandler {\n  constructor(\n    @Inject(ORDER_REPOSITORY)\n    private readonly orderRepository: IOrderRepository,\n  ) {}\n}\n```\n\n### Benefits\n\n- Domain doesn't depend on infrastructure\n- Easy to test (mock interfaces)\n- Easy to swap implementations\n\n## Testing Strategy\n\n### Unit Tests\n\nTest domain logic in isolation:\n\n```typescript\ndescribe('Order', () => {\n  it('should calculate total amount', () => {\n    const order = Order.create('customer-1', items);\n    expect(order.getTotalAmount().amount).toBe(100);\n  });\n\n  it('should not allow confirming cancelled order', () => {\n    order.cancel();\n    expect(() => order.confirm()).toThrow(InvalidOrderStateException);\n  });\n});\n```\n\n### Integration Tests\n\nTest infrastructure components:\n\n```typescript\ndescribe('OrderRepository', () => {\n  it('should save and retrieve order', async () => {\n    const order = Order.create('customer-1', items);\n    await repository.save(order);\n\n    const retrieved = await repository.findById(order.id);\n    expect(retrieved).toBeDefined();\n  });\n});\n```\n\n### E2E Tests\n\nTest complete flows:\n\n```typescript\ndescribe('Order API', () => {\n  it('should create order', async () => {\n    const response = await request(app.getHttpServer())\n      .post('/api/orders')\n      .send(createOrderDto)\n      .expect(201);\n\n    expect(response.body.orderId).toBeDefined();\n  });\n});\n```\n\n## Best Practices\n\n### 1. Keep Domain Pure\n\n```typescript\n// ✅ Good: Pure domain logic\nexport class Order extends AggregateRoot<string> {\n  public confirm(): void {\n    if (!this._status.isPending()) {\n      throw new InvalidOrderStateException('Cannot confirm non-pending order');\n    }\n    this._status = OrderStatus.confirmed();\n  }\n}\n\n// ❌ Bad: Infrastructure concerns in domain\nexport class Order extends AggregateRoot<string> {\n  public async confirm(): Promise<void> {\n    await this.repository.save(this); // NO!\n  }\n}\n```\n\n### 2. Use Value Objects\n\n```typescript\n// ✅ Good: Value object with validation\nexport class Money extends ValueObject<MoneyProps> {\n  private constructor(props: MoneyProps) {\n    super(props);\n  }\n\n  public static create(amount: number): Money {\n    if (amount < 0) {\n      throw new Error('Money cannot be negative');\n    }\n    return new Money({ amount });\n  }\n}\n\n// ❌ Bad: Primitive obsession\nexport class Order {\n  private amount: number; // No validation, no behavior\n}\n```\n\n### 3. Raise Domain Events\n\n```typescript\n// ✅ Good: Raise events for important occurrences\nexport class Order extends AggregateRoot<string> {\n  public confirm(): void {\n    this._status = OrderStatus.confirmed();\n    this.addDomainEvent(new OrderConfirmedEvent(this.id));\n  }\n}\n\n// ❌ Bad: Side effects in domain\nexport class Order extends AggregateRoot<string> {\n  public confirm(): void {\n    this._status = OrderStatus.confirmed();\n    this.sendEmail(); // NO!\n  }\n}\n```\n\n### 4. Validate at Boundaries\n\n```typescript\n// ✅ Good: Validate in DTOs and value objects\nexport class CreateOrderDto {\n  @IsString()\n  @IsNotEmpty()\n  customerId: string;\n\n  @IsArray()\n  @ValidateNested({ each: true })\n  items: CreateOrderItemDto[];\n}\n\n// ❌ Bad: No validation\nexport class CreateOrderDto {\n  customerId: string;\n  items: any[];\n}\n```\n\n## Common Pitfalls\n\n### 1. Anemic Domain Model\n\n**Problem**: Entities with only getters/setters, logic in services.\n\n**Solution**: Put behavior in entities.\n\n### 2. Leaking Infrastructure\n\n**Problem**: Domain depends on infrastructure (e.g., TypeORM entities).\n\n**Solution**: Use repository interfaces, mappers.\n\n### 3. Fat Controllers\n\n**Problem**: Business logic in controllers.\n\n**Solution**: Move logic to domain/application layers.\n\n### 4. Ignoring Events\n\n**Problem**: Direct coupling between components.\n\n**Solution**: Use domain events for side effects.\n\n## Conclusion\n\nThis architecture provides:\n- **Maintainability**: Clear separation of concerns\n- **Testability**: Easy to test each layer\n- **Flexibility**: Easy to change infrastructure\n- **Scalability**: CQRS enables independent scaling\n- **Domain Focus**: Business logic is central and protected\n\nThe key is following the dependency rule and keeping the domain pure.\n"
  },
  {
    "path": "docs/ddd-patterns.md",
    "content": "# DDD Patterns Guide\n\nThis document explains the Domain-Driven Design patterns used in this template.\n\n## Core Building Blocks\n\n### 1. Entity\n\n**Definition**: An object with a distinct identity that persists over time.\n\n**Characteristics**:\n- Has a unique identifier\n- Identity remains constant even if attributes change\n- Compared by identity, not attributes\n- Has a lifecycle\n\n**Example**:\n```typescript\nexport class OrderItem extends Entity<string> {\n  private _productId: string;\n  private _quantity: Quantity;\n  private _unitPrice: Money;\n\n  private constructor(props: OrderItemProps) {\n    super(props.id);  // Identity\n    this._productId = props.productId;\n    this._quantity = props.quantity;\n    this._unitPrice = props.unitPrice;\n  }\n\n  public static create(props: OrderItemProps): OrderItem {\n    return new OrderItem(props);\n  }\n\n  // Behavior\n  public getTotalPrice(): Money {\n    return this._unitPrice.multiply(this._quantity.value);\n  }\n\n  public updateQuantity(quantity: Quantity): void {\n    this._quantity = quantity;\n  }\n}\n```\n\n**When to Use**:\n- Object needs to be tracked over time\n- Object has a lifecycle\n- Object needs to be distinguished from similar objects\n\n### 2. Value Object\n\n**Definition**: An immutable object defined by its attributes, not identity.\n\n**Characteristics**:\n- No identity\n- Immutable\n- Compared by value\n- Self-validating\n- Side-effect free\n\n**Example**:\n```typescript\nexport class Money extends ValueObject<MoneyProps> {\n  get amount(): number {\n    return this.props.amount;\n  }\n\n  get currency(): string {\n    return this.props.currency;\n  }\n\n  private constructor(props: MoneyProps) {\n    super(props);  // Frozen/immutable\n  }\n\n  public static create(amount: number, currency: string = 'USD'): Money {\n    // Self-validation\n    if (amount < 0) {\n      throw new Error('Money amount cannot be negative');\n    }\n    return new Money({ amount, currency });\n  }\n\n  // Returns new instance (immutable)\n  public add(money: Money): Money {\n    if (this.currency !== money.currency) {\n      throw new Error('Cannot add money with different currencies');\n    }\n    return Money.create(this.amount + money.amount, this.currency);\n  }\n\n  public multiply(multiplier: number): Money {\n    return Money.create(this.amount * multiplier, this.currency);\n  }\n}\n```\n\n**When to Use**:\n- Measuring, quantifying, or describing things\n- No need to track identity\n- Immutability is desired\n- Equality is based on attributes\n\n**Common Value Objects**:\n- Money, Currency\n- Address, Email, Phone\n- DateRange, TimeSpan\n- Quantity, Percentage\n- Status, State\n\n### 3. Aggregate\n\n**Definition**: A cluster of entities and value objects with a defined boundary.\n\n**Characteristics**:\n- Has an aggregate root (entry point)\n- Enforces consistency boundaries\n- Only root is accessible from outside\n- Transactions don't cross aggregate boundaries\n- Loaded and saved as a whole\n\n**Example**:\n```typescript\nexport class Order extends AggregateRoot<string> {\n  private _customerId: string;\n  private _items: OrderItem[];  // Child entities\n  private _status: OrderStatus;  // Value object\n\n  // Factory method\n  public static create(customerId: string, items: OrderItem[]): Order {\n    const order = new Order({\n      id: OrderId.create(),\n      customerId,\n      items,\n      status: OrderStatus.pending(),\n      createdAt: new Date(),\n      updatedAt: new Date(),\n    });\n\n    // Raise domain event\n    order.addDomainEvent(new OrderCreatedEvent(order.id, customerId));\n\n    return order;\n  }\n\n  // Business logic - enforces invariants\n  public confirm(): void {\n    if (!this._status.isPending()) {\n      throw new InvalidOrderStateException('Cannot confirm non-pending order');\n    }\n    this._status = OrderStatus.confirmed();\n    this.addDomainEvent(new OrderConfirmedEvent(this.id));\n  }\n\n  // Protects child entities\n  public addItem(item: OrderItem): void {\n    if (!this._status.isPending()) {\n      throw new InvalidOrderStateException('Cannot modify non-pending order');\n    }\n    this._items.push(item);\n  }\n}\n```\n\n**Design Rules**:\n1. Reference other aggregates by ID only\n2. Keep aggregates small\n3. Update one aggregate per transaction\n4. Use eventual consistency between aggregates\n\n### 4. Domain Event\n\n**Definition**: A record of something that happened in the domain.\n\n**Characteristics**:\n- Immutable\n- Past tense naming\n- Contains relevant data\n- Timestamp of occurrence\n\n**Example**:\n```typescript\nexport class OrderCreatedEvent extends DomainEvent {\n  constructor(\n    public readonly orderId: string,\n    public readonly customerId: string,\n    public readonly totalAmount: Money,\n  ) {\n    super();  // Sets occurredOn timestamp\n  }\n\n  getAggregateId(): string {\n    return this.orderId;\n  }\n}\n```\n\n**When to Use**:\n- Something important happened in the domain\n- Other parts of the system need to react\n- Audit trail is needed\n- Integration with external systems\n\n**Event Naming**:\n- Use past tense: `OrderCreated`, `OrderConfirmed`, `PaymentReceived`\n- Be specific: `OrderShipped` not `OrderUpdated`\n- Include context: `OrderCancelledByCustomer` vs `OrderCancelledBySystem`\n\n### 5. Domain Service\n\n**Definition**: Business logic that doesn't naturally fit in an entity or value object.\n\n**Characteristics**:\n- Stateless\n- Operates on multiple entities\n- Named after domain concepts\n- Contains business logic\n\n**Example**:\n```typescript\n@Injectable()\nexport class OrderPricingService {\n  calculateTotal(order: Order): Money {\n    return order.getTotalAmount();\n  }\n\n  applyDiscount(total: Money, discountPercent: number): Money {\n    if (discountPercent < 0 || discountPercent > 100) {\n      throw new Error('Invalid discount percentage');\n    }\n    const discountMultiplier = 1 - discountPercent / 100;\n    return Money.create(total.amount * discountMultiplier, total.currency);\n  }\n\n  calculateShipping(order: Order, destination: Address): Money {\n    // Complex shipping calculation logic\n  }\n}\n```\n\n**When to Use**:\n- Logic involves multiple aggregates\n- Logic doesn't belong to any single entity\n- Stateless operations\n- Complex calculations\n\n### 6. Repository\n\n**Definition**: Abstraction for data access, providing collection-like interface.\n\n**Characteristics**:\n- Interface defined in domain\n- Implementation in infrastructure\n- Hides persistence details\n- Works with aggregates\n\n**Example**:\n```typescript\n// Domain layer - interface\nexport interface IOrderRepository {\n  findById(id: string): Promise<Order | null>;\n  findByCustomerId(customerId: string): Promise<Order[]>;\n  save(order: Order): Promise<Order>;\n  delete(id: string): Promise<void>;\n}\n\n// Infrastructure layer - implementation\n@Injectable()\nexport class OrderRepository implements IOrderRepository {\n  constructor(\n    @InjectRepository(OrderSchema)\n    private readonly orderRepo: Repository<OrderSchema>,\n  ) {}\n\n  async findById(id: string): Promise<Order | null> {\n    const schema = await this.orderRepo.findOne({ where: { id } });\n    if (!schema) return null;\n    return OrderMapper.toDomain(schema);\n  }\n\n  async save(order: Order): Promise<Order> {\n    const schema = OrderMapper.toPersistence(order);\n    await this.orderRepo.save(schema);\n    return order;\n  }\n}\n```\n\n**Repository vs DAO**:\n- Repository: Works with domain objects, collection-like\n- DAO: Works with database records, CRUD operations\n\n## Strategic Patterns\n\n### 1. Bounded Context\n\n**Definition**: A boundary within which a domain model is defined and applicable.\n\n**In This Template**:\n- `order` module is a bounded context\n- Has its own domain model\n- Clear boundaries with other contexts\n\n**Structure**:\n```\nsrc/modules/\n├── order/           # Order bounded context\n│   ├── domain/\n│   ├── application/\n│   ├── infrastructure/\n│   └── presentation/\n├── inventory/       # Inventory bounded context (future)\n└── customer/        # Customer bounded context (future)\n```\n\n### 2. Ubiquitous Language\n\n**Definition**: A shared language between developers and domain experts.\n\n**Examples in Order Context**:\n- \"Order\" not \"Purchase\" or \"Transaction\"\n- \"Confirm\" not \"Approve\" or \"Accept\"\n- \"Cancel\" not \"Delete\" or \"Remove\"\n- \"OrderItem\" not \"LineItem\" or \"OrderLine\"\n\n**Implementation**:\n```typescript\n// Use domain language in code\nclass Order {\n  confirm(): void { }      // Not approve()\n  cancel(): void { }       // Not delete()\n  addItem(): void { }      // Not addLineItem()\n}\n\n// Use domain language in events\nclass OrderConfirmed { }   // Not OrderApproved\nclass OrderCancelled { }   // Not OrderDeleted\n```\n\n### 3. Context Mapping\n\n**Definition**: Relationships between bounded contexts.\n\n**Common Patterns**:\n- **Shared Kernel**: Shared code between contexts\n- **Customer-Supplier**: One context depends on another\n- **Anti-Corruption Layer**: Translate between contexts\n\n**Example**:\n```typescript\n// Anti-corruption layer for external payment service\nexport class PaymentServiceAdapter {\n  constructor(private readonly externalPaymentService: ExternalPaymentAPI) {}\n\n  async processPayment(order: Order): Promise<PaymentResult> {\n    // Translate domain model to external API\n    const externalRequest = {\n      amount: order.getTotalAmount().amount,\n      currency: order.getTotalAmount().currency,\n      reference: order.id,\n    };\n\n    const externalResponse = await this.externalPaymentService.charge(externalRequest);\n\n    // Translate external response to domain model\n    return new PaymentResult(\n      externalResponse.success,\n      externalResponse.transactionId,\n    );\n  }\n}\n```\n\n## Application Patterns\n\n### 1. CQRS (Command Query Responsibility Segregation)\n\n**Definition**: Separate models for reading and writing data.\n\n**Commands** (Write):\n```typescript\n// Command\nexport class CreateOrderCommand {\n  constructor(\n    public readonly customerId: string,\n    public readonly items: OrderItemDto[],\n  ) {}\n}\n\n// Handler\n@CommandHandler(CreateOrderCommand)\nexport class CreateOrderHandler {\n  async execute(command: CreateOrderCommand): Promise<string> {\n    const order = Order.create(command.customerId, items);\n    await this.orderRepository.save(order);\n    return order.id;\n  }\n}\n```\n\n**Queries** (Read):\n```typescript\n// Query\nexport class GetOrderQuery {\n  constructor(public readonly orderId: string) {}\n}\n\n// Handler\n@QueryHandler(GetOrderQuery)\nexport class GetOrderHandler {\n  async execute(query: GetOrderQuery): Promise<OrderResponseDto> {\n    const order = await this.orderRepository.findById(query.orderId);\n    return this.mapToDto(order);\n  }\n}\n```\n\n### 2. Event Sourcing (Optional)\n\n**Definition**: Store state as a sequence of events.\n\n**Note**: This template uses traditional state storage, but can be extended to event sourcing.\n\n```typescript\n// Event sourced aggregate (conceptual)\nclass Order extends EventSourcedAggregate {\n  apply(event: DomainEvent): void {\n    if (event instanceof OrderCreated) {\n      this._status = OrderStatus.pending();\n    } else if (event instanceof OrderConfirmed) {\n      this._status = OrderStatus.confirmed();\n    }\n  }\n\n  // Rebuild state from events\n  static fromEvents(events: DomainEvent[]): Order {\n    const order = new Order();\n    events.forEach(event => order.apply(event));\n    return order;\n  }\n}\n```\n\n## Best Practices\n\n### 1. Rich Domain Model\n\n```typescript\n// ✅ Rich domain model - behavior in entity\nclass Order {\n  confirm(): void {\n    this.validateCanConfirm();\n    this._status = OrderStatus.confirmed();\n    this.addDomainEvent(new OrderConfirmed(this.id));\n  }\n\n  private validateCanConfirm(): void {\n    if (!this._status.isPending()) {\n      throw new InvalidOrderStateException();\n    }\n    if (this._items.length === 0) {\n      throw new EmptyOrderException();\n    }\n  }\n}\n\n// ❌ Anemic domain model - behavior in service\nclass Order {\n  status: string;\n  items: OrderItem[];\n}\n\nclass OrderService {\n  confirm(order: Order): void {\n    if (order.status !== 'PENDING') throw new Error();\n    order.status = 'CONFIRMED';\n  }\n}\n```\n\n### 2. Invariant Protection\n\n```typescript\nclass Order {\n  // Protect invariants through encapsulation\n  private _items: OrderItem[];\n\n  get items(): OrderItem[] {\n    return [...this._items];  // Return copy\n  }\n\n  addItem(item: OrderItem): void {\n    // Validate invariant\n    if (!this._status.isPending()) {\n      throw new InvalidOrderStateException();\n    }\n    this._items.push(item);\n  }\n}\n```\n\n### 3. Factory Methods\n\n```typescript\nclass Order {\n  // Use factory methods instead of constructors\n  public static create(customerId: string, items: OrderItem[]): Order {\n    // Validation\n    if (!customerId) throw new Error('Customer ID required');\n    if (items.length === 0) throw new Error('Order must have items');\n\n    // Create with proper initial state\n    const order = new Order({\n      id: OrderId.create(),\n      customerId,\n      items,\n      status: OrderStatus.pending(),\n      createdAt: new Date(),\n    });\n\n    // Raise creation event\n    order.addDomainEvent(new OrderCreated(order.id));\n\n    return order;\n  }\n\n  // For reconstituting from persistence\n  public static reconstitute(props: OrderProps): Order {\n    return new Order(props);  // No events, no validation\n  }\n}\n```\n\n### 4. Specification Pattern\n\n```typescript\n// For complex business rules\ninterface Specification<T> {\n  isSatisfiedBy(candidate: T): boolean;\n}\n\nclass OrderCanBeConfirmedSpec implements Specification<Order> {\n  isSatisfiedBy(order: Order): boolean {\n    return order.status.isPending() &&\n           order.items.length > 0 &&\n           order.getTotalAmount().amount > 0;\n  }\n}\n\n// Usage\nclass Order {\n  confirm(): void {\n    const spec = new OrderCanBeConfirmedSpec();\n    if (!spec.isSatisfiedBy(this)) {\n      throw new InvalidOrderStateException();\n    }\n    this._status = OrderStatus.confirmed();\n  }\n}\n```\n\n## Summary\n\n| Pattern | Purpose | Location |\n|---------|---------|----------|\n| Entity | Objects with identity | Domain |\n| Value Object | Immutable descriptors | Domain |\n| Aggregate | Consistency boundary | Domain |\n| Domain Event | Record of occurrence | Domain |\n| Domain Service | Cross-entity logic | Domain |\n| Repository | Data access abstraction | Domain (interface), Infrastructure (impl) |\n| CQRS | Separate read/write | Application |\n| Bounded Context | Model boundary | Module |\n\nThese patterns work together to create a maintainable, expressive domain model that captures business logic effectively.\n"
  },
  {
    "path": "nest-cli.json",
    "content": "{\n    \"$schema\": \"https://json.schemastore.org/nest-cli\",\n    \"collection\": \"@nestjs/schematics\",\n    \"sourceRoot\": \"src\",\n    \"compilerOptions\": {\n        \"builder\": \"swc\",\n        \"typeCheck\": true,\n        \"deleteOutDir\": true,\n        \"tsConfigPath\": \"tsconfig.build.json\"\n    }\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n    \"name\": \"@a3s-lab/nestify\",\n    \"version\": \"1.0.0\",\n    \"description\": \"Production-ready NestJS monorepo with pnpm workspace, Domain-Driven Design and Clean Architecture\",\n    \"author\": \"\",\n    \"private\": true,\n    \"license\": \"MIT\",\n    \"scripts\": {\n        \"build\": \"pnpm -r build\",\n        \"build:api\": \"pnpm --filter @a3s-lab/api build\",\n        \"build:packages\": \"pnpm --filter \\\"./packages/**\\\" build\",\n        \"format\": \"biome format --write .\",\n        \"format:check\": \"biome format .\",\n        \"lint\": \"biome lint --write .\",\n        \"lint:check\": \"biome lint .\",\n        \"start\": \"pnpm --filter @a3s-lab/api start\",\n        \"start:dev\": \"pnpm --filter @a3s-lab/api start:dev\",\n        \"start:debug\": \"pnpm --filter @a3s-lab/api start:debug\",\n        \"start:prod\": \"pnpm --filter @a3s-lab/api start:prod\",\n        \"test\": \"pnpm -r test\",\n        \"test:api\": \"pnpm --filter @a3s-lab/api test\",\n        \"test:packages\": \"pnpm --filter \\\"./packages/**\\\" test\",\n        \"test:kysely\": \"pnpm --filter @a3s-lab/kysely test\",\n        \"test:redisson\": \"pnpm --filter @a3s-lab/redisson test\",\n        \"test:cov\": \"pnpm -r test:cov\",\n        \"clean\": \"pnpm -r clean && rm -rf node_modules\"\n    },\n    \"devDependencies\": {\n        \"@biomejs/biome\": \"^2.3.14\",\n        \"rimraf\": \"^6.0.1\",\n        \"typescript\": \"^5.1.3\"\n    }\n}\n"
  },
  {
    "path": "packages/bullmq/package.json",
    "content": "{\n  \"name\": \"@a3s-lab/bullmq\",\n  \"version\": \"0.0.1\",\n  \"description\": \"BullMQ module for NestJS\",\n  \"main\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"scripts\": {\n    \"build\": \"tsc\",\n    \"test\": \"echo \\\"No tests specified\\\" && exit 0\"\n  },\n  \"dependencies\": {\n    \"@nestjs/common\": \"^10.0.0\",\n    \"@nestjs/config\": \"^3.0.0\",\n    \"bullmq\": \"^5.0.0\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^20.0.0\",\n    \"typescript\": \"^5.0.0\"\n  },\n  \"peerDependencies\": {\n    \"@nestjs/common\": \"^10.0.0\",\n    \"@nestjs/config\": \"^3.0.0\"\n  }\n}\n"
  },
  {
    "path": "packages/bullmq/src/bullmq.module-definition.ts",
    "content": "// ============================================================================\n// BullMQ Module Definition - Configurable module pattern\n// ============================================================================\n\nimport { ConfigurableModuleBuilder } from '@nestjs/common';\nimport { BullMQModuleOptions, BullMQOptionsFactory } from './bullmq.types';\n\nexport const MODULE_OPTIONS_TOKEN = 'BULLMQ_MODULE_OPTIONS';\n\nexport const {\n    ConfigurableModuleClass: ConfigurableBullMQModule,\n    MODULE_OPTIONS_TOKEN: BULLMQ_OPTIONS_TOKEN,\n    CONFIG_GLOBAL_MODULE_NAME: BULLMQ_GLOBAL_MODULE_NAME,\n} = new ConfigurableModuleBuilder<BullMQModuleOptions>({\n    moduleName: 'BullMQ',\n    global: true,\n})\n    .setExtras(\n        { isGlobal: true },\n        (definition, extras) => ({\n            ...definition,\n            global: extras.isGlobal ?? definition.global ?? false,\n        }),\n    )\n    .build();\n"
  },
  {
    "path": "packages/bullmq/src/bullmq.module.ts",
    "content": "// ============================================================================\n// BullMQ Module - Distributed task queue\n// ============================================================================\n\nimport { Module, Global, DynamicModule, Provider } from '@nestjs/common';\nimport { BullMQModuleOptions, BullMQOptionsFactory } from './bullmq.types';\nimport { BullMQService } from './bullmq.service';\n\n@Global()\n@Module({\n    providers: [BullMQService],\n    exports: [BullMQService],\n})\nexport class BullMQModule {\n    /**\n     * Register BullMQ module with static options\n     */\n    static register(options: BullMQModuleOptions): DynamicModule {\n        return {\n            module: BullMQModule,\n            providers: [\n                {\n                    provide: BullMQModuleOptions,\n                    useValue: options,\n                },\n            ],\n            exports: [BullMQService],\n        };\n    }\n\n    /**\n     * Register BullMQ module asynchronously (for ConfigService-based config)\n     */\n    static registerAsync(options: {\n        useFactory?: (factory: BullMQOptionsFactory) => Promise<BullMQModuleOptions> | BullMQModuleOptions;\n        inject?: any[];\n    }): DynamicModule {\n        const asyncProviders: Provider[] = [];\n\n        if (options.useFactory) {\n            asyncProviders.push({\n                provide: BullMQModuleOptions,\n                useFactory: options.useFactory,\n                inject: options.inject ?? [],\n            });\n        }\n\n        return {\n            module: BullMQModule,\n            imports: [],\n            providers: asyncProviders,\n            exports: [BullMQService],\n        };\n    }\n}\n"
  },
  {
    "path": "packages/bullmq/src/bullmq.service.ts",
    "content": "// ============================================================================\n// BullMQ Service - Queue management and job processing\n// ============================================================================\n\nimport { Injectable, OnModuleDestroy, Logger } from '@nestjs/common';\nimport { Queue, Worker, Job, QueueEvents } from 'bullmq';\nimport { BullMQModuleOptions } from './bullmq.types';\n\nexport interface JobData {\n    [key: string]: unknown;\n}\n\nexport interface JobResult {\n    success: boolean;\n    data?: unknown;\n    error?: string;\n}\n\nexport type JobProcessor<T extends JobData = JobData> = (job: Job<T>) => Promise<JobResult>;\n\n/**\n * Queue metrics for monitoring\n */\nexport interface QueueMetrics {\n    waiting: number;\n    active: number;\n    completed: number;\n    failed: number;\n    delayed: number;\n}\n\n@Injectable()\nexport class BullMQService implements OnModuleDestroy {\n    private readonly queues: Map<string, Queue> = new Map();\n    private readonly workers: Map<string, Worker> = new Map();\n    private readonly queueEvents: Map<string, QueueEvents> = new Map();\n    private readonly logger = new Logger(BullMQService.name);\n\n    constructor(private readonly options: BullMQModuleOptions) {}\n\n    /**\n     * Get or create a queue\n     */\n    getQueue(name: string): Queue {\n        if (this.queues.has(name)) {\n            return this.queues.get(name)!;\n        }\n\n        const queue = new Queue(name, {\n            connection: this.options.connection,\n            defaultJobOptions: this.options.defaultJobOptions,\n        });\n\n        this.queues.set(name, queue);\n        this.logger.log(`Queue '${name}' created`);\n\n        return queue;\n    }\n\n    /**\n     * Add a job to a queue\n     */\n    async addJob<T extends JobData>(\n        queueName: string,\n        jobName: string,\n        data: T,\n        options?: {\n            priority?: number;\n            attempts?: number;\n            backoff?: { type: 'exponential' | 'fixed'; delay: number };\n            delay?: number;\n            repeat?: { pattern: string } | { endDate: Date };\n        },\n    ): Promise<Job> {\n        const queue = this.getQueue(queueName);\n\n        const job = await queue.add(jobName, data, {\n            priority: options?.priority,\n            attempts: options?.attempts ?? 3,\n            backoff: options?.backoff ?? { type: 'exponential', delay: 1000 },\n            delay: options?.delay,\n            repeat: options?.repeat,\n        });\n\n        this.logger.debug(`Job '${jobName}' added to queue '${queueName}'`);\n        return job;\n    }\n\n    /**\n     * Add a delayed job (runs after delay)\n     */\n    async addDelayedJob<T extends JobData>(\n        queueName: string,\n        jobName: string,\n        data: T,\n        delayMs: number,\n    ): Promise<Job> {\n        return this.addJob(queueName, jobName, data, { delay: delayMs });\n    }\n\n    /**\n     * Add a repeatable job (cron-like)\n     */\n    async addRepeatableJob<T extends JobData>(\n        queueName: string,\n        jobName: string,\n        data: T,\n        pattern: string, // e.g., '*/5 * * * *'\n    ): Promise<Job> {\n        return this.addJob(queueName, jobName, data, {\n            repeat: { pattern },\n        });\n    }\n\n    /**\n     * Create a worker for a queue\n     */\n    createWorker<T extends JobData = JobData>(\n        queueName: string,\n        processor: JobProcessor<T>,\n        options?: { concurrency?: number },\n    ): Worker {\n        if (this.workers.has(queueName)) {\n            this.logger.warn(`Worker for queue '${queueName}' already exists`);\n            return this.workers.get(queueName)!;\n        }\n\n        const worker = new Worker<T>(\n            queueName,\n            async (job) => {\n                this.logger.debug(`Processing job '${job.name}' in queue '${queueName}'`);\n                try {\n                    const result = await processor(job);\n                    if (!result.success) {\n                        throw new Error(result.error);\n                    }\n                    return result;\n                } catch (error) {\n                    this.logger.error(`Job '${job.name}' failed: ${error.message}`);\n                    throw error;\n                }\n            },\n            {\n                connection: this.options.connection,\n                concurrency: options?.concurrency ?? 1,\n            },\n        );\n\n        worker.on('completed', (job) => {\n            this.logger.debug(`Job '${job.name}' completed`);\n        });\n\n        worker.on('failed', (job, error) => {\n            this.logger.error(`Job '${job?.name}' failed: ${error.message}`);\n        });\n\n        this.workers.set(queueName, worker);\n        this.logger.log(`Worker for queue '${queueName}' created`);\n\n        return worker;\n    }\n\n    /**\n     * Get queue events for monitoring\n     */\n    getQueueEvents(queueName: string): QueueEvents {\n        if (this.queueEvents.has(queueName)) {\n            return this.queueEvents.get(queueName)!;\n        }\n\n        const events = new QueueEvents(queueName, {\n            connection: this.options.connection,\n        });\n\n        this.queueEvents.set(queueName, events);\n        return events;\n    }\n\n    /**\n     * Get queue metrics\n     */\n    async getQueueMetrics(queueName: string): Promise<QueueMetrics> {\n        const queue = this.getQueue(queueName);\n\n        const [waiting, active, completed, failed, delayed] = await Promise.all([\n            queue.getWaitingCount(),\n            queue.getActiveCount(),\n            queue.getCompletedCount(),\n            queue.getFailedCount(),\n            queue.getDelayedCount(),\n        ]);\n\n        return { waiting, active, completed, failed, delayed };\n    }\n\n    /**\n     * Pause a queue\n     */\n    async pauseQueue(queueName: string): Promise<void> {\n        const queue = this.getQueue(queueName);\n        await queue.pause();\n        this.logger.log(`Queue '${queueName}' paused`);\n    }\n\n    /**\n     * Resume a queue\n     */\n    async resumeQueue(queueName: string): Promise<void> {\n        const queue = this.getQueue(queueName);\n        await queue.resume();\n        this.logger.log(`Queue '${queueName}' resumed`);\n    }\n\n    /**\n     * Drain a queue (process all waiting jobs)\n     */\n    async drainQueue(queueName: string): Promise<void> {\n        const queue = this.getQueue(queueName);\n        await queue.drain();\n        this.logger.log(`Queue '${queueName}' drained`);\n    }\n\n    /**\n     * Clean a queue (remove old jobs)\n     */\n    async cleanQueue(\n        queueName: string,\n        grace: number = 24 * 60 * 60 * 1000, // 24 hours\n        status?: 'completed' | 'failed',\n    ): Promise<string[]> {\n        const queue = this.getQueue(queueName);\n        return queue.clean(grace, 100, status ?? 'completed');\n    }\n\n    /**\n     * Remove a specific job\n     */\n    async removeJob(queueName: string, jobId: string): Promise<void> {\n        const queue = this.getQueue(queueName);\n        const job = await queue.getJob(jobId);\n        if (job) {\n            await job.remove();\n        }\n    }\n\n    /**\n     * Get a job by ID\n     */\n    async getJob(queueName: string, jobId: string): Promise<Job | undefined> {\n        const queue = this.getQueue(queueName);\n        return queue.getJob(jobId);\n    }\n\n    /**\n     * Close all queues and workers\n     */\n    async onModuleDestroy(): Promise<void> {\n        this.logger.log('Closing BullMQ connections...');\n\n        // Close all workers\n        for (const [name, worker] of this.workers) {\n            await worker.close();\n            this.logger.debug(`Worker '${name}' closed`);\n        }\n\n        // Close all queues\n        for (const [name, queue] of this.queues) {\n            await queue.close();\n            this.logger.debug(`Queue '${name}' closed`);\n        }\n\n        // Close all queue events\n        for (const [, events] of this.queueEvents) {\n            await events.close();\n        }\n\n        this.queues.clear();\n        this.workers.clear();\n        this.queueEvents.clear();\n\n        this.logger.log('BullMQ connections closed');\n    }\n}\n"
  },
  {
    "path": "packages/bullmq/src/bullmq.types.ts",
    "content": "// ============================================================================\n// BullMQ Module Options\n// ============================================================================\n\nexport interface BullMQModuleOptions {\n    /** Redis connection URL */\n    connection: {\n        host: string;\n        port: number;\n        password?: string;\n        db?: number;\n    };\n    /** Default job options */\n    defaultJobOptions?: {\n        attempts?: number;\n        backoff?: {\n            type: 'exponential' | 'fixed';\n            delay?: number;\n        };\n        removeOnComplete?: boolean | number;\n        removeOnFail?: boolean | number;\n    };\n    /** Queue prefixes */\n    prefix?: string;\n}\n\nexport interface BullMQOptionsFactory {\n    createBullMQOptions(): Promise<BullMQModuleOptions> | BullMQModuleOptions;\n}\n"
  },
  {
    "path": "packages/bullmq/src/index.ts",
    "content": "export * from './bullmq.module';\nexport * from './bullmq.service';\nexport * from './bullmq.types';\nexport * from './bullmq.module-definition';\n"
  },
  {
    "path": "packages/bullmq/tsconfig.json",
    "content": "{\n    \"extends\": \"../../tsconfig.base.json\",\n    \"compilerOptions\": {\n        \"outDir\": \"./dist\",\n        \"rootDir\": \"./src\",\n        \"composite\": true\n    },\n    \"include\": [\"src/**/*\"],\n    \"exclude\": [\"node_modules\", \"dist\", \"**/__tests__\"]\n}\n"
  },
  {
    "path": "packages/etcd/package.json",
    "content": "{\n    \"name\": \"@a3s-lab/etcd\",\n    \"version\": \"0.0.1\",\n    \"description\": \"Etcd distributed configuration center for NestJS\",\n    \"main\": \"./dist/index.js\",\n    \"types\": \"./dist/index.d.ts\",\n    \"scripts\": {\n        \"build\": \"tsc\",\n        \"test\": \"jest\",\n        \"lint\": \"biome lint src\",\n        \"format\": \"biome format --write src\"\n    },\n    \"dependencies\": {\n        \"etcd3\": \"^1.2.0\",\n        \"reflect-metadata\": \"^0.1.13\"\n    },\n    \"peerDependencies\": {\n        \"@nestjs/common\": \">=10.0.0\",\n        \"@nestjs/config\": \">=3.0.0\"\n    },\n    \"devDependencies\": {\n        \"@nestjs/common\": \"^10.0.0\",\n        \"@nestjs/config\": \"^3.0.0\",\n        \"@types/node\": \"^20.0.0\",\n        \"typescript\": \"^5.0.0\"\n    },\n    \"files\": [\n        \"dist\"\n    ]\n}\n"
  },
  {
    "path": "packages/etcd/src/config.service.ts",
    "content": "import { Injectable, Logger, OnModuleInit } from '@nestjs/common';\nimport { EtcdService } from './etcd.service';\n\nexport type ConfigSubscriber = (value: unknown) => void;\n\n/**\n * Distributed configuration service with hot-reload support\n */\n@Injectable()\nexport class EtcdConfigService implements OnModuleInit {\n    private readonly logger = new Logger(EtcdConfigService.name);\n    private subscribers: Map<string, Set<ConfigSubscriber>> = new Map();\n    private unsubscribers: Map<string, () => void> = new Map();\n    private cache: Map<string, { value: unknown; timestamp: number }> = new Map();\n    private cacheTtl = 5000;\n\n    constructor(private readonly etcd: EtcdService) {}\n\n    async onModuleInit() {\n        this.logger.log('EtcdConfigService initialized');\n    }\n\n    // ==================== Get Configuration ====================\n\n    async get<T = string>(key: string, useCache = true): Promise<T | null> {\n        if (useCache) {\n            const cached = this.cache.get(key);\n            if (cached && Date.now() - cached.timestamp < this.cacheTtl) {\n                return cached.value as T;\n            }\n        }\n\n        const value = await this.etcd.get<T>(key);\n        if (value !== null) {\n            this.cache.set(key, { value, timestamp: Date.now() });\n        }\n        return value;\n    }\n\n    async getJSON<T = unknown>(key: string, useCache = true): Promise<T | null> {\n        if (useCache) {\n            const cached = this.cache.get(key);\n            if (cached && Date.now() - cached.timestamp < this.cacheTtl) {\n                return cached.value as T;\n            }\n        }\n\n        const value = await this.etcd.getJSON<T>(key);\n        if (value !== null) {\n            this.cache.set(key, { value, timestamp: Date.now() });\n        }\n        return value;\n    }\n\n    async getByPrefix<T = unknown>(prefix: string): Promise<Map<string, T>> {\n        return await this.etcd.getEntriesAsJSON<T>(prefix);\n    }\n\n    // ==================== Set Configuration ====================\n\n    async set(key: string, value: string | number | boolean | object, options?: { ttl?: number }): Promise<void> {\n        await this.etcd.set(key, value, options);\n        this.cache.set(key, { value, timestamp: Date.now() });\n    }\n\n    async setJSON<T extends object>(key: string, value: T, options?: { ttl?: number }): Promise<void> {\n        await this.set(key, JSON.stringify(value), options);\n    }\n\n    // ==================== Delete Configuration ====================\n\n    async delete(key: string): Promise<boolean> {\n        const result = await this.etcd.delete(key);\n        this.cache.delete(key);\n        return result;\n    }\n\n    async deleteByPrefix(prefix: string): Promise<number> {\n        const count = await this.etcd.deleteByPrefix(prefix);\n        for (const key of this.cache.keys()) {\n            if (key.startsWith(prefix)) {\n                this.cache.delete(key);\n            }\n        }\n        return count;\n    }\n\n    // ==================== Hot Reload ====================\n\n    subscribe<T = string>(key: string, callback: (value: T) => void): () => void {\n        if (!this.subscribers.has(key)) {\n            this.subscribers.set(key, new Set());\n\n            const unsubscribe = this.etcd.watch<T>(key, (event) => {\n                const subs = this.subscribers.get(key);\n                if (subs) {\n                    if (event.value !== null) {\n                        this.cache.set(key, { value: event.value, timestamp: Date.now() });\n                        subs.forEach((cb) => cb(event.value as T));\n                    }\n                }\n            });\n\n            this.unsubscribers.set(key, unsubscribe);\n        }\n\n        this.subscribers.get(key)!.add(callback as ConfigSubscriber);\n\n        return () => {\n            const subs = this.subscribers.get(key);\n            if (subs) {\n                subs.delete(callback as ConfigSubscriber);\n                if (subs.size === 0) {\n                    const unsub = this.unsubscribers.get(key);\n                    if (unsub) {\n                        unsub();\n                        this.unsubscribers.delete(key);\n                    }\n                    this.subscribers.delete(key);\n                }\n            }\n        };\n    }\n\n    subscribePrefix<T = string>(prefix: string, callback: (event: { key: string; value: T | null }) => void): () => void {\n        const unsubscribe = this.etcd.watchPrefix<T>(prefix, (event) => {\n            callback({ key: event.key, value: event.value });\n        });\n\n        return unsubscribe;\n    }\n\n    // ==================== Utility ====================\n\n    async exists(key: string): Promise<boolean> {\n        return await this.etcd.exists(key);\n    }\n\n    clearCache(): void {\n        this.cache.clear();\n    }\n\n    setCacheTtl(ttl: number): void {\n        this.cacheTtl = ttl;\n    }\n}\n"
  },
  {
    "path": "packages/etcd/src/etcd.module.ts",
    "content": "import { Module, Global, DynamicModule, Provider } from '@nestjs/common';\nimport { EtcdModuleOptions } from './etcd.types';\nimport { EtcdService } from './etcd.service';\nimport { EtcdConfigService } from './config.service';\n\n@Global()\n@Module({\n    providers: [EtcdService, EtcdConfigService],\n    exports: [EtcdService, EtcdConfigService],\n})\nexport class EtcdModule {\n    static register(options: EtcdModuleOptions): DynamicModule {\n        return {\n            module: EtcdModule,\n            providers: [\n                {\n                    provide: EtcdModuleOptions,\n                    useValue: options,\n                },\n            ],\n            exports: [EtcdService, EtcdConfigService],\n        };\n    }\n\n    static registerAsync(options: {\n        useFactory?: () => Promise<EtcdModuleOptions> | EtcdModuleOptions;\n        inject?: any[];\n    }): DynamicModule {\n        const asyncProviders: Provider[] = [];\n\n        if (options.useFactory) {\n            asyncProviders.push({\n                provide: EtcdModuleOptions,\n                useFactory: options.useFactory,\n                inject: options.inject ?? [],\n            });\n        }\n\n        return {\n            module: EtcdModule,\n            imports: [],\n            providers: asyncProviders,\n            exports: [EtcdService, EtcdConfigService],\n        };\n    }\n}\n"
  },
  {
    "path": "packages/etcd/src/etcd.service.ts",
    "content": "import { Injectable, Logger, OnModuleDestroy, OnModuleInit } from '@nestjs/common';\nimport { Etcd3, EtcdOptions } from 'etcd3';\nimport type { EtcdModuleOptions, WatchEvent, WatchCallback, ConfigEntry, LeaseInfo, HealthResult } from './etcd.types';\n\n@Injectable()\nexport class EtcdService implements OnModuleInit, OnModuleDestroy {\n    private readonly logger = new Logger(EtcdService.name);\n    private client: Etcd3;\n    private watchers: Map<string, ReturnType<Etcd3['watch']>> = new Map();\n\n    constructor(private readonly options: EtcdModuleOptions) {\n        const etcdOptions: EtcdOptions = {\n            hosts: this.options.endpoints,\n            credentials: this.options.tls\n                ? {\n                      cert: this.options.tls.cert,\n                      key: this.options.tls.key,\n                      ca: this.options.tls.ca,\n                  }\n                : undefined,\n            username: this.options.auth?.username,\n            password: this.options.auth?.password,\n        };\n\n        this.client = new Etcd3(etcdOptions);\n    }\n\n    async onModuleInit() {\n        try {\n            const health = await this.healthCheck();\n            if (health.healthy) {\n                this.logger.log(`Successfully connected to etcd: ${this.options.endpoints.join(', ')}`);\n            } else {\n                throw new Error('Etcd health check failed');\n            }\n        } catch (error) {\n            this.logger.error('Failed to connect to etcd', error);\n            throw error;\n        }\n    }\n\n    async onModuleDestroy() {\n        try {\n            for (const [key, watcher] of this.watchers) {\n                this.logger.debug(`Canceling watcher: ${key}`);\n                watcher.cancel();\n            }\n            this.watchers.clear();\n            await this.client.close();\n            this.logger.log('Etcd connection closed');\n        } catch (error) {\n            this.logger.error('Error closing etcd connection', error);\n        }\n    }\n\n    // ==================== Key-Value Operations ====================\n\n    async get<T = string>(key: string): Promise<T | null> {\n        try {\n            return (await this.client.get(key).string()) as T;\n        } catch (error: unknown) {\n            if ((error as { code?: string })?.code === 'KEY_NOT_FOUND') {\n                return null;\n            }\n            throw error;\n        }\n    }\n\n    async getJSON<T = unknown>(key: string): Promise<T | null> {\n        const value = await this.get<string>(key);\n        if (!value) return null;\n        try {\n            return JSON.parse(value) as T;\n        } catch {\n            return null;\n        }\n    }\n\n    async set(key: string, value: string | number | boolean | object, options?: { ttl?: number; lease?: string }): Promise<void> {\n        if (typeof value === 'object') {\n            value = JSON.stringify(value);\n        }\n        if (options?.ttl && !options?.lease) {\n            await this.client.put(key).value(value as string).ttl(options.ttl);\n        } else if (options?.lease) {\n            await this.client.put(key).value(value as string).lease(options.lease);\n        } else {\n            await this.client.put(key).value(value as string);\n        }\n    }\n\n    async delete(key: string): Promise<boolean> {\n        const result = await this.client.delete().key(key).exec();\n        return result.deleted > 0;\n    }\n\n    async deleteByPrefix(prefix: string): Promise<number> {\n        const result = await this.client.delete().key(prefix).prefix().exec();\n        return result.deleted;\n    }\n\n    async exists(key: string): Promise<boolean> {\n        return await this.client.get(key).exists();\n    }\n\n    async getKeysByPrefix(prefix: string): Promise<string[]> {\n        return await this.client.getKeys(prefix);\n    }\n\n    // ==================== Directory Operations ====================\n\n    async getEntries<T = string>(prefix: string): Promise<ConfigEntry<T>[]> {\n        const results: ConfigEntry<T>[] = [];\n        const pairs = await this.client.getPrefix(prefix);\n\n        for (const pair of pairs) {\n            results.push({\n                key: pair.key,\n                value: pair.value as T,\n                version: pair.version,\n                revision: pair.modRevision,\n                created: pair.created,\n            });\n        }\n\n        return results;\n    }\n\n    async getEntriesAsJSON<T = unknown>(prefix: string): Promise<Map<string, T>> {\n        const entries = await this.getEntries<string>(prefix);\n        const result = new Map<string, T>();\n\n        for (const entry of entries) {\n            try {\n                result.set(entry.key, JSON.parse(entry.value) as T);\n            } catch {\n                result.set(entry.key, entry.value as unknown as T);\n            }\n        }\n\n        return result;\n    }\n\n    // ==================== Lease Operations ====================\n\n    async createLease(ttl: number): Promise<LeaseInfo> {\n        const lease = this.client.lease(ttl);\n        const id = await lease.id();\n        return { id, ttl, remainingTTL: ttl };\n    }\n\n    async grantLease(ttl: number): Promise<string> {\n        const lease = await this.client.grant(ttl);\n        return lease;\n    }\n\n    async keepAlive(leaseId: string): Promise<void> {\n        const lease = this.client.lease(0, { ID: leaseId });\n        await lease.refresh();\n    }\n\n    async revokeLease(leaseId: string): Promise<void> {\n        await this.client.revoke(leaseId);\n    }\n\n    // ==================== Watch Operations ====================\n\n    watch<T = string>(key: string, callback: WatchCallback<T>): () => void {\n        const watcher = this.client.watch().key(key).create();\n\n        watcher.on('put', (event: { kv?: { key: string; value: string; version: number; mod_revision: number } }) => {\n            if (event.kv) {\n                callback({\n                    type: 'put',\n                    key: event.kv.key,\n                    value: event.kv.value as T,\n                    version: event.kv.version,\n                    modRevision: event.kv.mod_revision,\n                });\n            }\n        });\n\n        watcher.on('delete', (event: { kv?: { key: string; version: number; mod_revision: number } }) => {\n            if (event.kv) {\n                callback({\n                    type: 'delete',\n                    key: event.kv.key,\n                    value: null,\n                    version: event.kv.version,\n                    modRevision: event.kv.mod_revision,\n                });\n            }\n        });\n\n        watcher.on('error', (error: Error) => {\n            this.logger.error(`Watch error for key ${key}:`, error);\n        });\n\n        this.watchers.set(key, watcher);\n\n        return () => {\n            watcher.cancel();\n            this.watchers.delete(key);\n        };\n    }\n\n    watchPrefix<T = string>(prefix: string, callback: WatchCallback<T>): () => void {\n        const watcher = this.client.watch().prefix(prefix).create();\n\n        watcher.on('put', (event: { kv?: { key: string; value: string; version: number; mod_revision: number } }) => {\n            if (event.kv) {\n                callback({\n                    type: 'put',\n                    key: event.kv.key,\n                    value: event.kv.value as T,\n                    version: event.kv.version,\n                    modRevision: event.kv.mod_revision,\n                });\n            }\n        });\n\n        watcher.on('delete', (event: { kv?: { key: string; version: number; mod_revision: number } }) => {\n            if (event.kv) {\n                callback({\n                    type: 'delete',\n                    key: event.kv.key,\n                    value: null,\n                    version: event.kv.version,\n                    modRevision: event.kv.mod_revision,\n                });\n            }\n        });\n\n        watcher.on('error', (error: Error) => {\n            this.logger.error(`Watch error for prefix ${prefix}:`, error);\n        });\n\n        this.watchers.set(prefix, watcher);\n\n        return () => {\n            watcher.cancel();\n            this.watchers.delete(prefix);\n        };\n    }\n\n    // ==================== Cluster Operations ====================\n\n    async healthCheck(): Promise<HealthResult> {\n        try {\n            const status = await this.client.status();\n            return {\n                healthy: true,\n                leader: status.leader,\n                etcdVersion: status.version,\n            };\n        } catch {\n            return { healthy: false };\n        }\n    }\n\n    async getMembers(): Promise<string[]> {\n        const memberList = await this.client.memberList();\n        return memberList.map((m) => m.name);\n    }\n\n    async getLeader(): Promise<string | null> {\n        try {\n            const status = await this.client.status();\n            return status.leader || null;\n        } catch {\n            return null;\n        }\n    }\n\n    // ==================== Transaction Operations ====================\n\n    async compareAndSet(\n        key: string,\n        expectedValue: string | null,\n        newValue: string,\n        options?: { ttl?: number },\n    ): Promise<boolean> {\n        const tx = this.client.transaction();\n\n        if (expectedValue === null) {\n            tx.compare.notExists(key);\n        } else {\n            tx.compare.value(key, '==', expectedValue);\n        }\n\n        tx.then(this.client.put(key).value(newValue));\n\n        if (options?.ttl) {\n            tx.then(this.client.put(key).value(newValue).ttl(options.ttl));\n        }\n\n        const result = await tx.exec();\n        return result.succeeded;\n    }\n\n    getClient(): Etcd3 {\n        return this.client;\n    }\n}\n"
  },
  {
    "path": "packages/etcd/src/etcd.types.ts",
    "content": "/**\n * Etcd configuration options\n */\nexport interface EtcdModuleOptions {\n    /** Etcd endpoints */\n    endpoints: string[];\n    /** Authentication */\n    auth?: {\n        username?: string;\n        password?: string;\n    };\n    /** TLS configuration */\n    tls?: {\n        cert?: string;\n        key?: string;\n        ca?: string;\n    };\n    /** Default request options */\n    requestOptions?: {\n        timeout?: number;\n        retry?: number;\n    };\n}\n\n/**\n * Watch event types\n */\nexport type WatchEventType = 'put' | 'delete';\n\n/**\n * Watch event from etcd\n */\nexport interface WatchEvent<T = unknown> {\n    type: WatchEventType;\n    key: string;\n    value: T | null;\n    version: number;\n    modRevision?: number;\n}\n\n/**\n * Watch callback function\n */\nexport type WatchCallback<T = unknown> = (event: WatchEvent<T>) => void;\n\n/**\n * Configuration entry\n */\nexport interface ConfigEntry<T = unknown> {\n    key: string;\n    value: T;\n    version: number;\n    revision: number;\n    created?: boolean;\n}\n\n/**\n * Lease info\n */\nexport interface LeaseInfo {\n    id: number;\n    ttl: number;\n    remainingTTL: number;\n}\n\n/**\n * Health check result\n */\nexport interface HealthResult {\n    healthy: boolean;\n    leader?: string;\n    etcdVersion?: string;\n}\n"
  },
  {
    "path": "packages/etcd/src/index.ts",
    "content": "export * from './etcd.module';\nexport * from './etcd.service';\nexport * from './config.service';\nexport * from './etcd.types';\n"
  },
  {
    "path": "packages/etcd/tsconfig.json",
    "content": "{\n    \"extends\": \"../../tsconfig.base.json\",\n    \"compilerOptions\": {\n        \"outDir\": \"./dist\",\n        \"rootDir\": \"./src\",\n        \"composite\": true\n    },\n    \"include\": [\"src/**/*\"],\n    \"exclude\": [\"node_modules\", \"dist\", \"**/__tests__\"]\n}\n"
  },
  {
    "path": "packages/kysely/package.json",
    "content": "{\n    \"name\": \"@a3s-lab/kysely\",\n    \"version\": \"1.0.0\",\n    \"description\": \"NestJS module for Kysely SQL query builder with syntax-highlighted logging\",\n    \"author\": \"ZhiXiao-Lin\",\n    \"license\": \"MIT\",\n    \"homepage\": \"https://github.com/ZhiXiao-Lin/nestify/tree/main/packages/kysely#readme\",\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"git+https://github.com/ZhiXiao-Lin/nestify.git\",\n        \"directory\": \"packages/kysely\"\n    },\n    \"bugs\": {\n        \"url\": \"https://github.com/ZhiXiao-Lin/nestify/issues\"\n    },\n    \"keywords\": [\n        \"nestjs\",\n        \"kysely\",\n        \"sql\",\n        \"query-builder\",\n        \"database\",\n        \"postgresql\",\n        \"mysql\",\n        \"sqlite\",\n        \"typescript\",\n        \"logger\"\n    ],\n    \"source\": \"./src/index.ts\",\n    \"main\": \"./dist/index.js\",\n    \"types\": \"./dist/index.d.ts\",\n    \"sideEffects\": false,\n    \"exports\": {\n        \".\": {\n            \"import\": \"./dist/index.js\",\n            \"require\": \"./dist/index.js\",\n            \"types\": \"./dist/index.d.ts\"\n        }\n    },\n    \"files\": [\n        \"dist\"\n    ],\n    \"scripts\": {\n        \"build\": \"tsc\",\n        \"test\": \"jest\",\n        \"test:cov\": \"jest --coverage\",\n        \"clean\": \"rimraf dist && rimraf node_modules\",\n        \"prepublishOnly\": \"pnpm run build\"\n    },\n    \"dependencies\": {\n        \"dayjs\": \"^1.11.13\",\n        \"kysely\": \"^0.28.9\",\n        \"picocolors\": \"^1.1.1\"\n    },\n    \"devDependencies\": {\n        \"@biomejs/biome\": \"^2.3.14\",\n        \"@nestjs/common\": \"^10.0.0\",\n        \"@nestjs/testing\": \"^10.0.0\",\n        \"@types/jest\": \"^29.5.0\",\n        \"jest\": \"^29.5.0\",\n        \"reflect-metadata\": \"^0.1.13\",\n        \"rimraf\": \"^6.0.1\",\n        \"rxjs\": \"^7.8.1\",\n        \"ts-jest\": \"^29.1.0\",\n        \"typescript\": \"^5.1.3\"\n    },\n    \"peerDependencies\": {\n        \"@nestjs/common\": \"^10.0.0\"\n    },\n    \"publishConfig\": {\n        \"access\": \"public\"\n    },\n    \"jest\": {\n        \"moduleFileExtensions\": [\"js\", \"json\", \"ts\"],\n        \"rootDir\": \"src\",\n        \"testRegex\": \".*\\\\.spec\\\\.ts$\",\n        \"transform\": {\n            \"^.+\\\\.(t|j)s$\": \"ts-jest\"\n        },\n        \"collectCoverageFrom\": [\"**/*.(t|j)s\", \"!**/__tests__/**\"],\n        \"coverageDirectory\": \"../coverage\",\n        \"testEnvironment\": \"node\"\n    }\n}\n"
  },
  {
    "path": "packages/kysely/src/__tests__/kysely.logger.spec.ts",
    "content": "import { createKyselyLogger } from '../kysely.logger';\n\ndescribe('KyselyLogger', () => {\n    let consoleSpy: jest.SpyInstance;\n    let consoleErrorSpy: jest.SpyInstance;\n\n    beforeEach(() => {\n        consoleSpy = jest.spyOn(console, 'log').mockImplementation();\n        consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();\n    });\n\n    afterEach(() => {\n        consoleSpy.mockRestore();\n        consoleErrorSpy.mockRestore();\n    });\n\n    describe('createKyselyLogger', () => {\n        it('should return a function', () => {\n            const logger = createKyselyLogger();\n            expect(typeof logger).toBe('function');\n        });\n\n        it('should log query events', () => {\n            const logger = createKyselyLogger();\n            // Use any to bypass strict type checking for test purposes\n            const event: any = {\n                level: 'query',\n                queryDurationMillis: 5.5,\n                query: {\n                    sql: 'SELECT * FROM users WHERE id = $1',\n                    parameters: ['123'],\n                },\n            };\n\n            logger(event);\n\n            expect(consoleSpy).toHaveBeenCalled();\n        });\n\n        it('should log error events', () => {\n            const logger = createKyselyLogger();\n            const event: any = {\n                level: 'error',\n                queryDurationMillis: 10.2,\n                query: {\n                    sql: 'SELECT * FROM invalid_table',\n                    parameters: [],\n                },\n                error: new Error('Table not found'),\n            };\n\n            logger(event);\n\n            expect(consoleErrorSpy).toHaveBeenCalled();\n        });\n\n        it('should handle queries without parameters', () => {\n            const logger = createKyselyLogger();\n            const event: any = {\n                level: 'query',\n                queryDurationMillis: 1.0,\n                query: {\n                    sql: 'SELECT COUNT(*) FROM users',\n                    parameters: [],\n                },\n            };\n\n            expect(() => logger(event)).not.toThrow();\n        });\n\n        it('should handle various parameter types', () => {\n            const logger = createKyselyLogger();\n            const event: any = {\n                level: 'query',\n                queryDurationMillis: 2.5,\n                query: {\n                    sql: 'INSERT INTO users (name, age, active, created_at) VALUES ($1, $2, $3, $4)',\n                    parameters: ['John', 30, true, new Date('2024-01-01')],\n                },\n            };\n\n            expect(() => logger(event)).not.toThrow();\n        });\n\n        it('should handle null and undefined parameters', () => {\n            const logger = createKyselyLogger();\n            const event: any = {\n                level: 'query',\n                queryDurationMillis: 1.5,\n                query: {\n                    sql: 'UPDATE users SET name = $1, email = $2 WHERE id = $3',\n                    parameters: [null, undefined, '123'],\n                },\n            };\n\n            expect(() => logger(event)).not.toThrow();\n        });\n    });\n});\n"
  },
  {
    "path": "packages/kysely/src/__tests__/kysely.module.spec.ts",
    "content": "import { Test, TestingModule } from '@nestjs/testing';\nimport { KyselyModule } from '../kysely.module';\nimport { KyselyService } from '../kysely.service';\nimport { MODULE_OPTIONS_TOKEN } from '../kysely.module-definition';\n\n// Mock Kysely\njest.mock('kysely', () => {\n    return {\n        Kysely: jest.fn().mockImplementation(() => ({\n            destroy: jest.fn().mockResolvedValue(undefined),\n            selectFrom: jest.fn().mockReturnThis(),\n            insertInto: jest.fn().mockReturnThis(),\n            updateTable: jest.fn().mockReturnThis(),\n            deleteFrom: jest.fn().mockReturnThis(),\n        })),\n    };\n});\n\ndescribe('KyselyModule', () => {\n    let module: TestingModule;\n\n    const mockOptions = {\n        config: {\n            dialect: {} as any,\n        },\n    };\n\n    beforeEach(async () => {\n        module = await Test.createTestingModule({\n            imports: [KyselyModule.register(mockOptions)],\n        }).compile();\n    });\n\n    afterEach(async () => {\n        if (module) {\n            await module.close();\n        }\n    });\n\n    it('should be defined', () => {\n        expect(module).toBeDefined();\n    });\n\n    it('should provide KyselyService', () => {\n        const service = module.get<KyselyService<any>>(KyselyService);\n        expect(service).toBeDefined();\n    });\n\n    it('should inject module options', () => {\n        const options = module.get(MODULE_OPTIONS_TOKEN);\n        expect(options).toEqual(mockOptions);\n    });\n});\n\ndescribe('KyselyModule.registerAsync', () => {\n    let module: TestingModule;\n\n    const mockOptions = {\n        config: {\n            dialect: {} as any,\n        },\n    };\n\n    beforeEach(async () => {\n        module = await Test.createTestingModule({\n            imports: [\n                KyselyModule.registerAsync({\n                    useFactory: () => mockOptions,\n                }),\n            ],\n        }).compile();\n    });\n\n    afterEach(async () => {\n        if (module) {\n            await module.close();\n        }\n    });\n\n    it('should be defined', () => {\n        expect(module).toBeDefined();\n    });\n\n    it('should provide KyselyService with async config', () => {\n        const service = module.get<KyselyService<any>>(KyselyService);\n        expect(service).toBeDefined();\n    });\n});\n"
  },
  {
    "path": "packages/kysely/src/index.ts",
    "content": "export * from \"./kysely.module\";\nexport * from \"./kysely.service\";\nexport * from \"./kysely.logger\";\nexport * from \"./kysely-module-options.interface\";\nexport * from \"./kysely.module-definition\";\n"
  },
  {
    "path": "packages/kysely/src/kysely-module-options.interface.ts",
    "content": "import type { ModuleMetadata, Type } from \"@nestjs/common\";\nimport type { Kysely, KyselyConfig } from \"kysely\";\n\nexport interface KyselyModuleOptions<DB = unknown> {\n    config: KyselyConfig;\n    /**\n     * Optional existing Kysely instance to use instead of creating a new one\n     */\n    instance?: Kysely<DB>;\n}\n\nexport interface KyselyModuleOptionsFactory<DB = unknown> {\n    createKyselyModuleOptions():\n        | Promise<KyselyModuleOptions<DB>>\n        | KyselyModuleOptions<DB>;\n}\n\nexport interface KyselyModuleAsyncOptions<DB = unknown>\n    extends Pick<ModuleMetadata, \"imports\"> {\n    useExisting?: Type<KyselyModuleOptionsFactory<DB>>;\n    useClass?: Type<KyselyModuleOptionsFactory<DB>>;\n    useFactory?: (\n        ...args: unknown[]\n    ) => Promise<KyselyModuleOptions<DB>> | KyselyModuleOptions<DB>;\n    inject?: unknown[];\n}\n"
  },
  {
    "path": "packages/kysely/src/kysely.logger.ts",
    "content": "import type { LogEvent } from 'kysely';\nimport * as dayjsModule from 'dayjs';\nimport * as pcModule from 'picocolors';\n\n// Handle both ESM and CJS imports\nconst dayjs = (dayjsModule as any).default || dayjsModule;\nconst pc = (pcModule as any).default || pcModule;\n\n/**\n * SQL keywords for syntax highlighting\n */\nconst SQL_KEYWORDS = [\n    'SELECT',\n    'FROM',\n    'WHERE',\n    'JOIN',\n    'LEFT JOIN',\n    'RIGHT JOIN',\n    'INNER JOIN',\n    'INSERT',\n    'INTO',\n    'VALUES',\n    'UPDATE',\n    'SET',\n    'DELETE',\n    'DROP',\n    'CREATE',\n    'ALTER',\n    'TABLE',\n    'INDEX',\n    'PRIMARY KEY',\n    'FOREIGN KEY',\n    'CONSTRAINT',\n    'GROUP BY',\n    'ORDER BY',\n    'HAVING',\n    'LIMIT',\n    'OFFSET',\n    'UNION',\n    'DISTINCT',\n    'AS',\n    'ON',\n    'IN',\n    'NOT',\n    'AND',\n    'OR',\n    'LIKE',\n    'BETWEEN',\n    'NULL',\n    'IS',\n    'COUNT',\n    'SUM',\n    'AVG',\n    'MAX',\n    'MIN',\n    'CASE',\n    'WHEN',\n    'THEN',\n    'ELSE',\n    'END',\n] as const;\n\n/**\n * Regular expressions cache for better performance\n */\nconst regexCache = new Map<string, RegExp>();\n\n/**\n * Get or create a cached regex pattern\n */\nconst getCachedRegex = (pattern: string, flags: string): RegExp => {\n    const key = `${pattern}:${flags}`;\n    let regex = regexCache.get(key);\n    if (!regex) {\n        regex = new RegExp(pattern, flags);\n        regexCache.set(key, regex);\n    }\n    return regex;\n};\n\n/**\n * Highlights SQL query with color-coded syntax\n * @param sql - The SQL query string to highlight\n * @returns Highlighted SQL string with ANSI color codes\n */\nconst highlightSql = (sql: string): string => {\n    let highlightedSql = sql;\n\n    // Highlight keywords\n    for (const keyword of SQL_KEYWORDS) {\n        const regex = getCachedRegex(`\\\\b${keyword}\\\\b`, 'gi');\n        highlightedSql = highlightedSql.replace(regex, pc.bold(pc.blue(keyword.toUpperCase())));\n    }\n\n    // Highlight string literals\n    highlightedSql = highlightedSql.replace(/'([^'\\\\]|\\\\.)*'/g, pc.green('$&'));\n\n    // Highlight numbers\n    highlightedSql = highlightedSql.replace(/\\b\\d+(?:\\.\\d+)?\\b/g, pc.yellow('$&'));\n\n    // Highlight identifiers with backticks\n    highlightedSql = highlightedSql.replace(/`([^`]+)`/g, pc.cyan('$&'));\n\n    return highlightedSql;\n};\n\n/**\n * Formats query execution duration with color coding based on performance\n * - Green: < 1ms (excellent)\n * - Yellow: 1-100ms (acceptable)\n * - Red: > 100ms (slow, needs optimization)\n * @param duration - Query duration in milliseconds\n * @returns Formatted duration string with color coding\n */\nconst formatDuration = (duration: number): string => {\n    const formatted = `${duration.toFixed(2)}ms`;\n    if (duration < 1) {\n        return pc.green(formatted);\n    }\n    if (duration < 100) {\n        return pc.yellow(formatted);\n    }\n    return pc.red(pc.bold(formatted));\n};\n\n/**\n * Formats query parameters with type-specific color coding\n * @param params - Array of query parameters\n * @returns Formatted parameters string\n */\nconst formatParameters = (params: readonly unknown[]): string => {\n    return params\n        .map((param, index) => {\n            let formattedParam: string;\n\n            if (param === null || param === undefined) {\n                formattedParam = pc.gray('NULL');\n            } else if (typeof param === 'string') {\n                formattedParam = pc.green(`'${param}'`);\n            } else if (typeof param === 'number') {\n                formattedParam = pc.yellow(String(param));\n            } else if (typeof param === 'boolean') {\n                formattedParam = pc.magenta(String(param));\n            } else if (param instanceof Date) {\n                formattedParam = pc.cyan(param.toISOString());\n            } else {\n                formattedParam = pc.white(JSON.stringify(param));\n            }\n\n            return `${pc.dim(`$${index + 1}:`)} ${formattedParam}`;\n        })\n        .join(', ');\n};\n\n/**\n * Type guard to check if error is an Error instance\n */\nconst isError = (error: unknown): error is Error => {\n    return error instanceof Error;\n};\n\n/**\n * Creates a Kysely logger function with enhanced formatting and syntax highlighting\n * @returns Logger function compatible with Kysely's log configuration\n */\nexport const createKyselyLogger = () => {\n    return (event: LogEvent) => {\n        const timestamp = dayjs().format('YYYY-MM-DD HH:mm:ss');\n        const formattedTimestamp = pc.dim(`[${timestamp}]`);\n\n        if (event.level === 'query') {\n            const duration = formatDuration(event.queryDurationMillis);\n\n            // Query log header\n            console.log(`${formattedTimestamp} ${pc.bold(pc.cyan('[KYSELY QUERY]'))} ${duration}`);\n\n            // Highlighted SQL query\n            const formattedSql = highlightSql(event.query.sql);\n            console.log(`${pc.dim('┌─')} ${formattedSql}`);\n\n            // Parameters (if any)\n            if (event.query.parameters && event.query.parameters.length > 0) {\n                console.log(\n                    `${pc.dim('├─')} ${pc.bold(pc.magenta('Parameters:'))} ${formatParameters(event.query.parameters)}`,\n                );\n            }\n\n            // Footer\n            console.log(pc.dim('└─────────────────────────────────────────────────────────────────'));\n        } else if (event.level === 'error') {\n            const duration = formatDuration(event.queryDurationMillis);\n\n            // Error log header\n            console.error(`${formattedTimestamp} ${pc.bold(pc.red('[KYSELY ERROR]'))} ${duration}`);\n\n            // SQL query that caused the error\n            const formattedSql = highlightSql(event.query.sql);\n            console.error(`${pc.dim('┌─')} ${pc.red('Query:')} ${formattedSql}`);\n\n            // Parameters (if any)\n            if (event.query.parameters && event.query.parameters.length > 0) {\n                console.error(\n                    `${pc.dim('├─')} ${pc.bold(pc.magenta('Parameters:'))} ${formatParameters(event.query.parameters)}`,\n                );\n            }\n\n            // Error details\n            const error = event.error;\n            if (isError(error)) {\n                console.error(`${pc.dim('├─')} ${pc.red('Error:')} ${pc.bold(pc.red(error.message))}`);\n\n                if (error.stack) {\n                    console.error(`${pc.dim('├─')} ${pc.red('Stack Trace:')}`);\n                    console.error(`${pc.dim('│ ')} ${pc.gray(error.stack)}`);\n                }\n            } else {\n                console.error(`${pc.dim('├─')} ${pc.red('Error:')} ${pc.bold(pc.red(String(error)))}`);\n            }\n\n            console.error(pc.dim('└─────────────────────────────────────────────────────────────────'));\n        }\n    };\n};\n"
  },
  {
    "path": "packages/kysely/src/kysely.module-definition.ts",
    "content": "import { ConfigurableModuleBuilder } from \"@nestjs/common\";\nimport { KyselyModuleOptions } from \"./kysely-module-options.interface\";\n\n/**\n * Configurable module builder for KyselyModule\n * Provides both synchronous and asynchronous registration methods\n * with optional global module configuration\n */\nexport const {\n    ConfigurableModuleClass,\n    MODULE_OPTIONS_TOKEN,\n    OPTIONS_TYPE,\n    ASYNC_OPTIONS_TYPE,\n} = new ConfigurableModuleBuilder<KyselyModuleOptions>()\n    .setExtras(\n        {\n            isGlobal: true,\n        },\n        (definition, extras) => ({\n            ...definition,\n            global: extras.isGlobal,\n        }),\n    )\n    .build();\n"
  },
  {
    "path": "packages/kysely/src/kysely.module.ts",
    "content": "import { DynamicModule, Module } from \"@nestjs/common\";\nimport {\n    ASYNC_OPTIONS_TYPE,\n    ConfigurableModuleClass,\n    OPTIONS_TYPE,\n} from \"./kysely.module-definition\";\nimport { KyselyService } from \"./kysely.service\";\n\n@Module({})\nexport class KyselyModule extends ConfigurableModuleClass {\n    static register(options: typeof OPTIONS_TYPE): DynamicModule {\n        const dynamicModule = super.register(options);\n        return {\n            ...dynamicModule,\n            providers: [...(dynamicModule.providers || []), KyselyService],\n            exports: [KyselyService],\n        };\n    }\n\n    static registerAsync(options: typeof ASYNC_OPTIONS_TYPE): DynamicModule {\n        const dynamicModule = super.registerAsync(options);\n        return {\n            ...dynamicModule,\n            providers: [...(dynamicModule.providers || []), KyselyService],\n            exports: [KyselyService],\n        };\n    }\n}\n"
  },
  {
    "path": "packages/kysely/src/kysely.service.ts",
    "content": "import { Inject, Injectable, OnModuleDestroy } from \"@nestjs/common\";\nimport { Kysely } from \"kysely\";\nimport type { KyselyModuleOptions } from \"./kysely-module-options.interface\";\nimport { MODULE_OPTIONS_TOKEN } from \"./kysely.module-definition\";\n\n@Injectable()\nexport class KyselyService<T> extends Kysely<T> implements OnModuleDestroy {\n    /**\n     * Creates a new KyselyService instance\n     * @param options - Kysely configuration options injected by NestJS\n     * @throws {Error} If options are not provided\n     */\n    constructor(\n        @Inject(MODULE_OPTIONS_TOKEN)\n        options: KyselyModuleOptions,\n    ) {\n        if (!options) {\n            throw new Error(\n                \"KyselyModuleOptions is not defined. Ensure KyselyModule is properly configured.\",\n            );\n        }\n        super(options.config);\n    }\n\n    /**\n     * Cleanup method called when the module is destroyed.\n     * Properly closes database connections to prevent leaks.\n     */\n    async onModuleDestroy(): Promise<void> {\n        await this.destroy();\n    }\n}\n"
  },
  {
    "path": "packages/kysely/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"Node16\",\n        \"moduleResolution\": \"Node16\",\n        \"declaration\": true,\n        \"removeComments\": true,\n        \"emitDecoratorMetadata\": true,\n        \"experimentalDecorators\": true,\n        \"allowSyntheticDefaultImports\": true,\n        \"sourceMap\": true,\n        \"incremental\": true,\n        \"isolatedModules\": true,\n        \"strictNullChecks\": false,\n        \"noImplicitAny\": false,\n        \"strictBindCallApply\": false,\n        \"forceConsistentCasingInFileNames\": false,\n        \"noFallthroughCasesInSwitch\": false,\n        \"jsx\": \"react-jsx\",\n        \"outDir\": \"dist\",\n        \"target\": \"ES2021\",\n        \"lib\": [\"DOM\", \"DOM.Iterable\", \"ES2021\", \"ES2022.Error\"],\n        \"noErrorTruncation\": true,\n        \"strict\": true,\n        \"skipLibCheck\": true,\n        \"baseUrl\": \".\",\n        \"paths\": {\n            \"@/*\": [\"src/*\"]\n        }\n    },\n    \"include\": [\"src/**/*\"]\n}\n"
  },
  {
    "path": "packages/logger/package.json",
    "content": "{\n    \"name\": \"@a3s-lab/logger\",\n    \"version\": \"0.0.1\",\n    \"description\": \"NestJS module for structured logging with request tracing and JSON format\",\n    \"author\": \"A3S Lab\",\n    \"license\": \"MIT\",\n    \"keywords\": [\n        \"nestjs\",\n        \"logger\",\n        \"logging\",\n        \"tracing\",\n        \"structure\"\n    ],\n    \"source\": \"./src/index.ts\",\n    \"main\": \"./dist/index.js\",\n    \"types\": \"./dist/index.d.ts\",\n    \"sideEffects\": false,\n    \"exports\": {\n        \".\": {\n            \"import\": \"./dist/index.js\",\n            \"require\": \"./dist/index.js\",\n            \"types\": \"./dist/index.d.ts\"\n        }\n    },\n    \"files\": [\n        \"dist\"\n    ],\n    \"scripts\": {\n        \"build\": \"tsc\",\n        \"test\": \"jest\",\n        \"test:cov\": \"jest --coverage\",\n        \"clean\": \"rimraf dist && rimraf node_modules\",\n        \"prepublishOnly\": \"pnpm run build\"\n    },\n    \"dependencies\": {\n        \"pino\": \"^9.0.0\",\n        \"pino-http\": \"^10.0.0\"\n    },\n    \"devDependencies\": {\n        \"@biomejs/biome\": \"^2.3.14\",\n        \"@nestjs/common\": \"^10.0.0\",\n        \"@nestjs/testing\": \"^10.0.0\",\n        \"@types/jest\": \"^29.5.0\",\n        \"jest\": \"^29.5.0\",\n        \"reflect-metadata\": \"^0.1.13\",\n        \"rimraf\": \"^6.0.1\",\n        \"rxjs\": \"^7.8.1\",\n        \"ts-jest\": \"^29.1.0\",\n        \"typescript\": \"^5.1.3\"\n    },\n    \"peerDependencies\": {\n        \"@nestjs/common\": \"^10.0.0\"\n    },\n    \"publishConfig\": {\n        \"access\": \"public\"\n    },\n    \"jest\": {\n        \"moduleFileExtensions\": [\"js\", \"json\", \"ts\"],\n        \"rootDir\": \"src\",\n        \"testRegex\": \".*\\\\.spec\\\\.ts$\",\n        \"transform\": {\n            \"^.+\\\\.(t|j)s$\": \"ts-jest\"\n        },\n        \"collectCoverageFrom\": [\"**/*.(t|j)s\", \"!**/__tests__/**\"],\n        \"coverageDirectory\": \"../coverage\",\n        \"testEnvironment\": \"node\"\n    }\n}\n"
  },
  {
    "path": "packages/logger/src/index.ts",
    "content": "export * from './logger.module';\nexport * from './logger.service';\nexport * from './logger.types';\nexport * from './logger.module-definition';\nexport * from './logging.interceptor';\n"
  },
  {
    "path": "packages/logger/src/logger.module-definition.ts",
    "content": "import { ConfigurableModuleBuilder } from '@nestjs/common';\nimport { LoggerModuleOptions } from './logger.types';\n\nexport const {\n    ConfigurableModuleClass,\n    MODULE_OPTIONS_TOKEN,\n    OPTIONS_TYPE,\n    ASYNC_OPTIONS_TYPE,\n} = new ConfigurableModuleBuilder<LoggerModuleOptions>()\n    .setExtras(\n        {\n            isGlobal: true,\n        },\n        (definition, extras) => ({\n            ...definition,\n            global: extras.isGlobal,\n        }),\n    )\n    .build();\n"
  },
  {
    "path": "packages/logger/src/logger.module.ts",
    "content": "import { Module, Global } from '@nestjs/common';\nimport {\n    ASYNC_OPTIONS_TYPE,\n    ConfigurableModuleClass,\n    OPTIONS_TYPE,\n} from './logger.module-definition';\nimport { LoggerServiceImpl, Logger } from './logger.service';\nimport { LoggingInterceptor } from './logging.interceptor';\n\n@Global()\n@Module({})\nexport class LoggerModule extends ConfigurableModuleClass {\n    static register(options: typeof OPTIONS_TYPE) {\n        const dynamicModule = super.register(options);\n        return {\n            ...dynamicModule,\n            providers: [\n                ...(dynamicModule.providers || []),\n                LoggerServiceImpl,\n                {\n                    provide: Logger,\n                    useFactory: (opts: any) => new LoggerServiceImpl(opts),\n                    inject: [OPTIONS_TYPE],\n                },\n                LoggingInterceptor,\n            ],\n            exports: [LoggerServiceImpl, Logger, LoggingInterceptor],\n        };\n    }\n\n    static registerAsync(options: typeof ASYNC_OPTIONS_TYPE) {\n        const dynamicModule = super.registerAsync(options);\n        return {\n            ...dynamicModule,\n            providers: [\n                ...(dynamicModule.providers || []),\n                LoggerServiceImpl,\n                {\n                    provide: Logger,\n                    useFactory: (opts: any) => new LoggerServiceImpl(opts),\n                    inject: [OPTIONS_TYPE],\n                },\n                LoggingInterceptor,\n            ],\n            exports: [LoggerServiceImpl, Logger, LoggingInterceptor],\n        };\n    }\n}\n"
  },
  {
    "path": "packages/logger/src/logger.service.ts",
    "content": "import { Injectable, LoggerService as NestLoggerService, Scope } from '@nestjs/common';\nimport pino, { Logger as PinoLogger, BaseLogger } from 'pino';\nimport { AsyncLocalStorage } from 'async_hooks';\nimport {\n    LoggerModuleOptions,\n    LogLevel,\n    LogContext,\n    LogEntry,\n} from './logger.types';\n\n// Async local storage for request context\nconst asyncLocalStorage = new AsyncLocalStorage<LogContext>();\n\n@Injectable({ scope: Scope.TRANSIENT })\nexport class LoggerServiceImpl implements NestLoggerService {\n    private logger: BaseLogger;\n    private name: string;\n    private baseContext: Partial<LogContext>;\n\n    constructor(options: LoggerModuleOptions = {}) {\n        this.name = options.name || 'app';\n        this.baseContext = options.base || {};\n\n        const pinoOptions: pino.LoggerOptions = {\n            level: options.level || 'info',\n            name: this.name,\n            base: {\n                service: this.name,\n                ...this.baseContext,\n            },\n            timestamp: pino.stdTimeFunctions.isoTime,\n            formatters: {\n                level: (label: string) => ({ level: label }),\n            },\n            ...(options.json !== false && {\n                // Default to JSON for K8s stdout\n                baseCrypter: options.redact ? pino.stdSerializers.noop : undefined,\n            }),\n        };\n\n        if (options.prettyPrint || process.env.NODE_ENV === 'development') {\n            pinoOptions.transport = {\n                target: 'pino-pretty',\n                options: {\n                    colorize: true,\n                    translateTime: 'SYS:standard',\n                    ignore: 'pid,hostname',\n                },\n            };\n        }\n\n        this.logger = pino(pinoOptions);\n    }\n\n    // =========================================================================\n    // Basic Logging Methods\n    // =========================================================================\n\n    log(message: string, context?: string): void;\n    log(level: LogLevel, message: string, context?: string): void;\n    log(levelOrMessage: string | LogLevel, messageOrContext?: string | LogContext, context?: string): void {\n        if (typeof levelOrMessage === 'string' && this.isLogLevel(levelOrMessage)) {\n            // overload: (level, message, context?)\n            const level = levelOrMessage;\n            const message = messageOrContext as string;\n            this.logAtLevel(level, message);\n        } else if (typeof levelOrMessage === 'string') {\n            // overload: (message, context?)\n            const message = levelOrMessage;\n            const ctx = messageOrContext as LogContext | undefined;\n            this.logAtLevel('info', message, ctx);\n        } else {\n            this.logAtLevel('info', levelOrMessage);\n        }\n    }\n\n    fatal(message: string, context?: Partial<LogContext>): void {\n        this.logAtLevel('fatal', message, context);\n    }\n\n    error(message: string, context?: Partial<LogContext>): void;\n    error(error: Error, context?: Partial<LogContext>): void;\n    error(errorOrMessage: Error | string, context?: Partial<LogContext>): void {\n        if (errorOrMessage instanceof Error) {\n            this.logError(errorOrMessage, context);\n        } else {\n            this.logAtLevel('error', errorOrMessage, context);\n        }\n    }\n\n    warn(message: string, context?: Partial<LogContext>): void {\n        this.logAtLevel('warn', message, context);\n    }\n\n    info(message: string, context?: Partial<LogContext>): void {\n        this.logAtLevel('info', message, context);\n    }\n\n    debug(message: string, context?: Partial<LogContext>): void {\n        this.logAtLevel('debug', message, context);\n    }\n\n    trace(message: string, context?: Partial<LogContext>): void {\n        this.logAtLevel('trace', message, context);\n    }\n\n    verbose(message: string, context?: Partial<LogContext>): void {\n        this.logAtLevel('trace', message, context);\n    }\n\n    // =========================================================================\n    // Child Logger\n    // =========================================================================\n\n    child(context: Partial<LogContext>): LoggerService {\n        const childLogger = new LoggerServiceImpl({\n            name: this.name,\n            base: {\n                ...this.baseContext,\n                ...this.getMergedContext(context),\n            },\n        });\n        return childLogger;\n    }\n\n    // =========================================================================\n    // Request Context\n    // =========================================================================\n\n    static getRequestContext(): LogContext | undefined {\n        return asyncLocalStorage.getStore();\n    }\n\n    static runWithContext<T>(context: LogContext, fn: () => T): T {\n        return asyncLocalStorage.run(context, fn);\n    }\n\n    static setRequestContext(context: LogContext): void {\n        asyncLocalStorage.enterWith(context);\n    }\n\n    // =========================================================================\n    // HTTP Interceptor Support\n    // =========================================================================\n\n    logRequest(options: {\n        method: string;\n        url: string;\n        headers?: Record<string, string>;\n        body?: unknown;\n        requestId?: string;\n        startTime: number;\n        statusCode?: number;\n        error?: Error;\n    }): void {\n        const { method, url, requestId, startTime, statusCode, error, body } = options;\n\n        const context: Partial<LogContext> = {\n            requestId,\n            method,\n            url,\n            statusCode,\n            responseTime: Date.now() - startTime,\n        };\n\n        if (error) {\n            this.error(error, context);\n        } else if (statusCode && statusCode >= 400) {\n            this.warn(`${method} ${url} ${statusCode}`, context);\n        } else {\n            this.info(`${method} ${url} ${statusCode}`, context);\n        }\n    }\n\n    // =========================================================================\n    // Private Methods\n    // =========================================================================\n\n    private logAtLevel(level: LogLevel, message: string, context?: Partial<LogContext>): void {\n        const mergedContext = this.getMergedContext(context);\n        const logFn = this.logger[level as keyof typeof this.logger] as (msg: string, obj?: Record<string, unknown>) => void;\n\n        if (logFn) {\n            logFn.call(this.logger, message, mergedContext);\n        } else {\n            this.logger.info({ ...mergedContext, msg: message, level });\n        }\n    }\n\n    private logError(error: Error, context?: Partial<LogContext>): void {\n        const mergedContext = this.getMergedContext(context);\n\n        const errorLog = {\n            message: error.message,\n            name: error.name,\n            stack: error.stack,\n            code: (error as any).code,\n            cause: error.cause instanceof Error ? error.cause.message : undefined,\n        };\n\n        this.logger.error(\n            { ...mergedContext, err: errorLog },\n            error.message,\n        );\n    }\n\n    private getMergedContext(context?: Partial<LogContext>): Record<string, unknown> {\n        const storeContext = asyncLocalStorage.getStore();\n        return {\n            ...this.baseContext,\n            ...storeContext,\n            ...context,\n        };\n    }\n\n    private isLogLevel(value: string): value is LogLevel {\n        return ['fatal', 'error', 'warn', 'info', 'debug', 'trace', 'silent'].includes(value);\n    }\n}\n\n// Re-export for convenience\nexport { LoggerServiceImpl as Logger };\n"
  },
  {
    "path": "packages/logger/src/logger.types.ts",
    "content": "// ============================================================================\n// Logger Types - Structured logging with request tracing\n// ============================================================================\n\nexport interface LoggerModuleOptions {\n    level?: LogLevel;\n    name?: string;\n    prettyPrint?: boolean;\n    json?: boolean;\n    redact?: string[];\n    base?: Record<string, unknown>;\n}\n\nexport type LogLevel = 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace' | 'silent';\n\nexport interface LogContext {\n    requestId?: string;\n    userId?: string;\n    organizationId?: string;\n    correlationId?: string;\n    userAgent?: string;\n    ip?: string;\n    method?: string;\n    url?: string;\n    statusCode?: number;\n    responseTime?: number;\n    [key: string]: unknown;\n}\n\nexport interface LogEntry {\n    level: LogLevel;\n    time: string;\n    name: string;\n    msg: string;\n    context?: LogContext;\n    err?: ErrorLog;\n    stack?: string;\n}\n\nexport interface ErrorLog {\n    message: string;\n    name: string;\n    stack?: string;\n    cause?: string;\n    code?: string;\n}\n\nexport interface RequestLoggingOptions {\n    excludePaths?: string[];\n    includeBody?: boolean;\n    excludeBody?: boolean;\n    headerName?: string;\n    requestIdHeader?: string;\n}\n\nexport interface LogInterceptorOptions {\n    excludePaths?: string[];\n    logRequestBody?: boolean;\n    logResponseBody?: boolean;\n    logRequestHeaders?: boolean;\n}\n"
  },
  {
    "path": "packages/logger/src/logging.interceptor.ts",
    "content": "import {\n    Injectable,\n    NestInterceptor,\n    ExecutionContext,\n    CallHandler,\n    Logger,\n} from '@nestjs/common';\nimport { Observable } from 'rxjs';\nimport { tap, catchError } from 'rxjs/operators';\nimport { Request, Response } from 'express';\nimport { LoggerServiceImpl } from './logger.service';\nimport { LogInterceptorOptions } from './logger.types';\n\n@Injectable()\nexport class LoggingInterceptor implements NestInterceptor {\n    private readonly defaultOptions: Required<LogInterceptorOptions> = {\n        excludePaths: ['/health', '/healthz', '/ready', '/metrics'],\n        logRequestBody: false,\n        logResponseBody: false,\n        logRequestHeaders: false,\n    };\n\n    constructor(\n        private readonly logger: LoggerServiceImpl,\n        private readonly options: LogInterceptorOptions = {},\n    ) {\n        this.options = { ...this.defaultOptions, ...options };\n    }\n\n    intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {\n        const ctx = context.switchToHttp();\n        const request = ctx.getRequest<Request>();\n        const response = ctx.getResponse<Response>();\n\n        const { method, url, headers, body } = request;\n        const requestId = (headers['x-request-id'] || headers['x-correlation-id'] || crypto.randomUUID()) as string;\n        const startTime = Date.now();\n\n        // Skip excluded paths\n        if (this.isExcludedPath(url)) {\n            return next.handle();\n        }\n\n        // Set request context\n        LoggerServiceImpl.setRequestContext({\n            requestId,\n            method,\n            url,\n            userAgent: headers['user-agent'] as string,\n            ip: this.getClientIp(request),\n            ...(this.options.logRequestHeaders && { headers }),\n            ...(this.options.logRequestBody && body && { requestBody: body }),\n        });\n\n        const logRequest = () => {\n            const statusCode = response.statusCode;\n            const responseTime = Date.now() - startTime;\n\n            this.logger.logRequest({\n                method,\n                url,\n                requestId,\n                startTime,\n                statusCode,\n            });\n        };\n\n        return next.handle().pipe(\n            tap(() => {\n                logRequest();\n            }),\n            catchError((error) => {\n                logRequest();\n                this.logger.error(error, {\n                    requestId,\n                    method,\n                    url,\n                    statusCode: error.status || 500,\n                });\n                throw error;\n            }),\n        );\n    }\n\n    private isExcludedPath(url: string): boolean {\n        return this.options.excludePaths.some(\n            (path) => url === path || url.startsWith(path + '/'),\n        );\n    }\n\n    private getClientIp(request: Request): string {\n        return (\n            (request.headers['x-forwarded-for'] as string)?.split(',')[0]?.trim() ||\n            (request.headers['x-real-ip'] as string) ||\n            request.socket?.remoteAddress ||\n            'unknown',\n        );\n    }\n}\n\n// Re-export\nexport { LoggingInterceptor };\n"
  },
  {
    "path": "packages/logger/tsconfig.json",
    "content": "{\n    \"extends\": \"../../tsconfig.base.json\",\n    \"compilerOptions\": {\n        \"outDir\": \"./dist\",\n        \"rootDir\": \"./src\",\n        \"composite\": true\n    },\n    \"include\": [\"src/**/*\"],\n    \"exclude\": [\"node_modules\", \"dist\", \"**/__tests__\"]\n}\n"
  },
  {
    "path": "packages/nats/package.json",
    "content": "{\n    \"name\": \"@a3s-lab/nats\",\n    \"version\": \"0.0.1\",\n    \"description\": \"NestJS module for NATS message queue - publish/subscribe and request/reply patterns\",\n    \"author\": \"A3S Lab\",\n    \"license\": \"MIT\",\n    \"keywords\": [\n        \"nestjs\",\n        \"nats\",\n        \"message-queue\",\n        \"messaging\",\n        \"pubsub\",\n        \"request-reply\"\n    ],\n    \"source\": \"./src/index.ts\",\n    \"main\": \"./dist/index.js\",\n    \"types\": \"./dist/index.d.ts\",\n    \"sideEffects\": false,\n    \"exports\": {\n        \".\": {\n            \"import\": \"./dist/index.js\",\n            \"require\": \"./dist/index.js\",\n            \"types\": \"./dist/index.d.ts\"\n        }\n    },\n    \"files\": [\n        \"dist\"\n    ],\n    \"scripts\": {\n        \"build\": \"tsc\",\n        \"test\": \"jest\",\n        \"test:cov\": \"jest --coverage\",\n        \"clean\": \"rimraf dist && rimraf node_modules\",\n        \"prepublishOnly\": \"pnpm run build\"\n    },\n    \"dependencies\": {\n        \"nats\": \"^2.28.0\"\n    },\n    \"devDependencies\": {\n        \"@biomejs/biome\": \"^2.3.14\",\n        \"@nestjs/common\": \"^10.0.0\",\n        \"@nestjs/testing\": \"^10.0.0\",\n        \"@types/jest\": \"^29.5.0\",\n        \"jest\": \"^29.5.0\",\n        \"reflect-metadata\": \"^0.1.13\",\n        \"rimraf\": \"^6.0.1\",\n        \"rxjs\": \"^7.8.1\",\n        \"ts-jest\": \"^29.1.0\",\n        \"typescript\": \"^5.1.3\"\n    },\n    \"peerDependencies\": {\n        \"@nestjs/common\": \"^10.0.0\"\n    },\n    \"publishConfig\": {\n        \"access\": \"public\"\n    },\n    \"jest\": {\n        \"moduleFileExtensions\": [\"js\", \"json\", \"ts\"],\n        \"rootDir\": \"src\",\n        \"testRegex\": \".*\\\\.spec\\\\.ts$\",\n        \"transform\": {\n            \"^.+\\\\.(t|j)s$\": \"ts-jest\"\n        },\n        \"collectCoverageFrom\": [\"**/*.(t|j)s\", \"!**/__tests__/**\"],\n        \"coverageDirectory\": \"../coverage\",\n        \"testEnvironment\": \"node\"\n    }\n}\n"
  },
  {
    "path": "packages/nats/src/index.ts",
    "content": "export * from './nats.module';\nexport * from './nats.service';\nexport * from './nats.types';\nexport * from './nats.module-definition';\n"
  },
  {
    "path": "packages/nats/src/nats.module-definition.ts",
    "content": "import { ConfigurableModuleBuilder } from '@nestjs/common';\nimport { NatsPackageOptions } from './nats.types';\n\n/**\n * Configurable module builder for NatsModule\n * Provides both synchronous and asynchronous registration methods\n * with optional global module configuration\n */\nexport const {\n    ConfigurableModuleClass,\n    MODULE_OPTIONS_TOKEN,\n    OPTIONS_TYPE,\n    ASYNC_OPTIONS_TYPE,\n} = new ConfigurableModuleBuilder<NatsPackageOptions>()\n    .setExtras(\n        {\n            isGlobal: true,\n        },\n        (definition, extras) => ({\n            ...definition,\n            global: extras.isGlobal,\n        }),\n    )\n    .build();\n"
  },
  {
    "path": "packages/nats/src/nats.module.ts",
    "content": "import { DynamicModule, Module } from '@nestjs/common';\nimport {\n    ASYNC_OPTIONS_TYPE,\n    ConfigurableModuleClass,\n    OPTIONS_TYPE,\n} from './nats.module-definition';\nimport { NatsServiceImpl } from './nats.service';\n\n@Module({})\nexport class NatsModule extends ConfigurableModuleClass {\n    static register(options: typeof OPTIONS_TYPE): DynamicModule {\n        const dynamicModule = super.register(options);\n        return {\n            ...dynamicModule,\n            providers: [...(dynamicModule.providers || []), NatsServiceImpl],\n            exports: [NatsServiceImpl],\n        };\n    }\n\n    static registerAsync(options: typeof ASYNC_OPTIONS_TYPE): DynamicModule {\n        const dynamicModule = super.registerAsync(options);\n        return {\n            ...dynamicModule,\n            providers: [...(dynamicModule.providers || []), NatsServiceImpl],\n            exports: [NatsServiceImpl],\n        };\n    }\n}\n"
  },
  {
    "path": "packages/nats/src/nats.service.ts",
    "content": "import { Injectable, OnModuleInit, OnModuleDestroy, Logger } from '@nestjs/common';\nimport { connect, NatsConnection, JetStreamClient, StringCodec, Msg, Subscription as NatsSubscription } from 'nats';\nimport {\n    NatsPackageOptions,\n    NatsConnectionState,\n    PublishOptions,\n    RequestOptions,\n    SubscribeOptions,\n    NatsMessage,\n    SubscriptionHandler,\n    JetStreamPublishOptions,\n    JetStreamSubscribeOptions,\n    NatsError,\n    NatsConnectionError,\n    NatsPublishError,\n    NatsSubscribeError,\n    NatsRequestError,\n} from './nats.types';\n\n// Local type for subscription return values (not an interface - no implementation contract)\nexport interface Subscription {\n    sid: number;\n    subject: string;\n    queue?: string;\n    cancel(): void;\n    isCancelled(): boolean;\n}\n\n@Injectable()\nexport class NatsServiceImpl implements OnModuleInit, OnModuleDestroy {\n    private connection: NatsConnection | null = null;\n    private jetStream: JetStreamClient | null = null;\n    private subscriptions: Map<number, NatsSubscription> = new Map();\n    private connectionState: NatsConnectionState;\n    private readonly logger = new Logger(NatsServiceImpl.name);\n    private readonly stringCodec = StringCodec();\n\n    constructor(private readonly options: NatsPackageOptions) {\n        this.connectionState = {\n            connected: false,\n            server: '',\n            reconnectCount: 0,\n        };\n    }\n\n    async onModuleInit() {\n        await this.connect();\n    }\n\n    async onModuleDestroy() {\n        await this.disconnect();\n    }\n\n    // =========================================================================\n    // Connection Management\n    // =========================================================================\n\n    private async connect(): Promise<void> {\n        try {\n            const servers = this.options.servers || ['nats://localhost:4222'];\n\n            this.connection = await connect({\n                servers,\n                name: this.options.name || 'nestjs-nats',\n                user: this.options.user,\n                password: this.options.pass,\n                token: this.options.token,\n                maxReconnectAttempts: this.options.maxReconnectAttempts ?? -1,\n                reconnectTimeWait: this.options.reconnectTimeWait ?? 2000,\n                timeout: this.options.timeout ?? 10000,\n                pingInterval: this.options.pingInterval ?? 60000,\n                maxPingOut: this.options.maxPingOut ?? 2,\n                ...(this.options.tls && {\n                    tls: {\n                        certFile: this.options.tls.certFile,\n                        keyFile: this.options.tls.keyFile,\n                        caFile: this.options.tls.caFile,\n                        verify: this.options.tls.verify ?? true,\n                    },\n                }),\n            });\n\n            this.connectionState = {\n                connected: true,\n                server: this.connection.getServer(),\n                reconnectCount: 0,\n            };\n\n            this.connection.closed().then((err) => {\n                if (err) {\n                    this.logger.error(`NATS connection closed with error: ${err.message}`);\n                }\n                this.connectionState.connected = false;\n            });\n\n            this.connection.on('reconnect', () => {\n                this.connectionState.connected = true;\n                this.connectionState.server = this.connection?.getServer() || '';\n                this.connectionState.reconnectCount++;\n                this.logger.log(`NATS reconnected to ${this.connectionState.server}`);\n            });\n\n            this.connection.on('error', (err) => {\n                this.logger.error(`NATS connection error: ${err.message}`);\n                this.connectionState.lastError = err.message;\n            });\n\n            if (this.options.jetstream?.enabled !== false) {\n                this.jetStream = this.connection.jetstream();\n            }\n\n            this.logger.log(`Connected to NATS at ${this.connectionState.server}`);\n        } catch (error) {\n            const err = error as Error;\n            this.connectionState.lastError = err.message;\n            this.connectionState.connected = false;\n            throw new NatsConnectionError(\n                this.options.servers?.[0] || 'localhost:4222',\n                err.message,\n            );\n        }\n    }\n\n    private async disconnect(): Promise<void> {\n        try {\n            for (const [sid, sub] of this.subscriptions) {\n                sub.unsubscribe();\n                this.subscriptions.delete(sid);\n            }\n\n            if (this.connection) {\n                await this.connection.close();\n                this.connection = null;\n                this.jetStream = null;\n                this.connectionState.connected = false;\n                this.logger.log('NATS connection closed');\n            }\n        } catch (error) {\n            this.logger.error(`Error closing NATS connection: ${(error as Error).message}`);\n        }\n    }\n\n    async getConnection(): Promise<NatsConnection> {\n        if (!this.connection || !this.connectionState.connected) {\n            await this.connect();\n        }\n        return this.connection!;\n    }\n\n    async getJetStream(): Promise<JetStreamClient> {\n        if (!this.jetStream) {\n            await this.getConnection();\n        }\n        return this.jetStream!;\n    }\n\n    getState(): NatsConnectionState {\n        return { ...this.connectionState };\n    }\n\n    async isHealthy(): Promise<boolean> {\n        try {\n            if (!this.connection || this.connectionState.connected === false) {\n                return false;\n            }\n            return true;\n        } catch {\n            return false;\n        }\n    }\n\n    // =========================================================================\n    // Publish / Subscribe\n    // =========================================================================\n\n    async publish(options: PublishOptions): Promise<void> {\n        try {\n            const conn = await this.getConnection();\n            const data = this.encodeData(options.data);\n\n            conn.publish(options.subject, data, {\n                ...(options.headers && { headers: options.headers }),\n                ...(options.reply && { reply: options.reply }),\n            });\n\n            this.logger.debug(`Published to ${options.subject}`);\n        } catch (error) {\n            throw new NatsPublishError(\n                options.subject,\n                (error as Error).message,\n            );\n        }\n    }\n\n    async pubsub(subject: string, data: object): Promise<void> {\n        await this.publish({\n            subject,\n            data,\n        });\n    }\n\n    // =========================================================================\n    // Request / Reply\n    // =========================================================================\n\n    async request(options: RequestOptions): Promise<NatsMessage> {\n        try {\n            const conn = await this.getConnection();\n            const data = this.encodeData(options.data);\n            const timeout = options.timeout ?? 5000;\n\n            const msg = await conn.request(options.subject, data, {\n                timeout,\n                ...(options.headers && { headers: options.headers }),\n            });\n\n            return this.convertMessage(msg);\n        } catch (error) {\n            throw new NatsRequestError(\n                options.subject,\n                (error as Error).message,\n            );\n        }\n    }\n\n    async request$<T>(subject: string, data?: object): Promise<T> {\n        const msg = await this.request({\n            subject,\n            data,\n        });\n\n        return this.decodeData(msg.data) as T;\n    }\n\n    // =========================================================================\n    // Subscribe\n    // =========================================================================\n\n    async subscribe(\n        options: SubscribeOptions,\n        handler: SubscriptionHandler,\n    ): Promise<Subscription> {\n        try {\n            const conn = await this.getConnection();\n\n            const sub = conn.subscribe(options.subject, {\n                queue: options.queue,\n                ...(options.config && { config: options.config }),\n                ...(options.durable && { durable: options.durable }),\n            });\n\n            this.subscriptions.set(sub.sid, sub);\n\n            this.handleSubscription(sub, handler, options);\n\n            this.logger.log(`Subscribed to ${options.subject}${options.queue ? ` (queue: ${options.queue})` : ''}`);\n\n            return {\n                sid: sub.sid,\n                subject: options.subject,\n                queue: options.queue,\n                cancel: () => {\n                    sub.unsubscribe();\n                    this.subscriptions.delete(sub.sid);\n                },\n                isCancelled: () => sub.isCancelled(),\n            };\n        } catch (error) {\n            throw new NatsSubscribeError(\n                options.subject,\n                (error as Error).message,\n            );\n        }\n    }\n\n    async subscribe$(\n        subject: string,\n        handler: (data: unknown) => Promise<void>,\n    ): Promise<Subscription> {\n        return this.subscribe(\n            { subject },\n            async (msg: NatsMessage) => {\n                const data = this.decodeData(msg.data);\n                await handler(data);\n            },\n        );\n    }\n\n    unsubscribe(subscription: Subscription): void {\n        const sub = this.subscriptions.get(subscription.sid);\n        if (sub) {\n            sub.unsubscribe();\n            this.subscriptions.delete(subscription.sid);\n        }\n    }\n\n    private async handleSubscription(\n        sub: NatsSubscription,\n        handler: SubscriptionHandler,\n        options: SubscribeOptions,\n    ): Promise<void> {\n        (async () => {\n            for await (const msg of sub) {\n                const natsMsg = this.convertMessage(msg);\n\n                try {\n                    await handler(natsMsg);\n\n                    if (!options.manualAck) {\n                        msg.ack();\n                    }\n                } catch (error) {\n                    this.logger.error(`Error handling message on ${options.subject}: ${(error as Error).message}`);\n                    if (!options.manualAck) {\n                        msg.ack();\n                    }\n                }\n            }\n        })();\n    }\n\n    // =========================================================================\n    // JetStream\n    // =========================================================================\n\n    async jsPublish(options: JetStreamPublishOptions): Promise<PubAck> {\n        try {\n            const js = await this.getJetStream();\n            const data = this.encodeData(options.data);\n\n            const jsm = js as unknown as {\n                publish(\n                    subject: string,\n                    data?: Uint8Array,\n                    options?: {\n                        timeout?: number;\n                        headers?: Record<string, string>;\n                        wait?: boolean;\n                    },\n                ): Promise<PubAck>;\n            };\n\n            const pubAck = await jsm.publish(options.subject, data, {\n                timeout: options.timeout ?? 5000,\n                headers: options.headers,\n                wait: options.wait ?? true,\n            });\n\n            this.logger.debug(`JetStream published to ${options.subject} in stream ${options.stream}`);\n\n            return pubAck;\n        } catch (error) {\n            throw new NatsPublishError(\n                `${options.stream}:${options.subject}`,\n                (error as Error).message,\n            );\n        }\n    }\n\n    async jsSubscribe(\n        options: JetStreamSubscribeOptions,\n        handler: SubscriptionHandler,\n    ): Promise<Subscription> {\n        try {\n            const js = await this.getJetStream();\n\n            const opts = {\n                stream: options.stream,\n                ...(options.deliverSubject && { deliverSubject: options.deliverSubject }),\n                ...(options.config && { config: options.config }),\n                ...(options.durable && { durable: options.durable }),\n                ...(options.queue && { queue: options.queue }),\n            };\n\n            const sub = (js as unknown as {\n                subscribe(\n                    subject: string,\n                    opts?: {\n                        stream?: string;\n                        deliverSubject?: string;\n                        durable?: string;\n                        queue?: string;\n                        config?: Record<string, unknown>;\n                    },\n                ): NatsSubscription;\n            }).subscribe(options.subject, opts);\n\n            this.subscriptions.set(sub.sid, sub);\n\n            this.handleSubscription(sub, handler, options);\n\n            this.logger.log(`JetStream subscribed to ${options.subject} in stream ${options.stream}`);\n\n            return {\n                sid: sub.sid,\n                subject: options.subject,\n                queue: options.queue,\n                cancel: () => {\n                    sub.unsubscribe();\n                    this.subscriptions.delete(sub.sid);\n                },\n                isCancelled: () => sub.isCancelled(),\n            };\n        } catch (error) {\n            throw new NatsSubscribeError(\n                `${options.stream}:${options.subject}`,\n                (error as Error).message,\n            );\n        }\n    }\n\n    // =========================================================================\n    // Helpers\n    // =========================================================================\n\n    private encodeData(data?: Uint8Array | string | object): Uint8Array {\n        if (!data) {\n            return new Uint8Array(0);\n        }\n\n        if (data instanceof Uint8Array) {\n            return data;\n        }\n\n        if (typeof data === 'string') {\n            return this.stringCodec.encode(data);\n        }\n\n        return this.stringCodec.encode(JSON.stringify(data));\n    }\n\n    private decodeData(data: Uint8Array): unknown {\n        if (!data || data.length === 0) {\n            return null;\n        }\n\n        try {\n            const str = this.stringCodec.decode(data);\n            try {\n                return JSON.parse(str);\n            } catch {\n                return str;\n            }\n        } catch {\n            return null;\n        }\n    }\n\n    private convertMessage(msg: Msg): NatsMessage {\n        const headers: Record<string, string> = {};\n        if (msg.headers) {\n            msg.headers.forEach((value, key) => {\n                headers[key] = value;\n            });\n        }\n\n        return {\n            subject: msg.subject,\n            sid: 0,\n            data: msg.data,\n            headers,\n            reply: msg.reply,\n            timestamp: Date.now(),\n        };\n    }\n}\n"
  },
  {
    "path": "packages/nats/src/nats.types.ts",
    "content": "// ============================================================================\n// NATS Types - Re-exported from module-definition for convenience\n// ============================================================================\n\nimport type { JetStreamClient, NatsConnection, PubAck } from 'nats';\n\nexport interface NatsPackageOptions {\n    servers?: string[];\n    name?: string;\n    user?: string;\n    pass?: string;\n    token?: string;\n    maxReconnectAttempts?: number;\n    reconnectTimeWait?: number;\n    timeout?: number;\n    pingInterval?: number;\n    maxPingOut?: number;\n    tls?: TlsOptions;\n    auth?: AuthOptions;\n    jetstream?: JetStreamOptions;\n}\n\n/** @deprecated Use NatsPackageOptions instead */\nexport type NatsModuleOptions = NatsPackageOptions;\n\nexport interface TlsOptions {\n    certFile?: string;\n    keyFile?: string;\n    caFile?: string;\n    verify?: boolean;\n}\n\nexport interface AuthOptions {\n    user?: string;\n    pass?: string;\n    token?: string;\n}\n\nexport interface JetStreamOptions {\n    enabled?: boolean;\n    domain?: string;\n    prefix?: string;\n}\n\nexport interface NatsConnectionState {\n    connected: boolean;\n    server: string;\n    lastError?: string;\n    reconnectCount: number;\n}\n\n// ============================================================================\n// Publisher Types\n// ============================================================================\n\nexport interface PublishOptions {\n    subject: string;\n    data?: Uint8Array | string | object;\n    headers?: Record<string, string>;\n    reply?: string;\n    timeout?: number;\n}\n\nexport interface RequestOptions extends PublishOptions {\n    expectedResponseCount?: number;\n    headers?: Record<string, string>;\n}\n\nexport interface PubAckPromise {\n    promise: Promise<PubAck>;\n    subject: string;\n    data?: Uint8Array | string | object;\n}\n\n// ============================================================================\n// Subscriber Types\n// ============================================================================\n\nexport interface SubscribeOptions {\n    subject: string;\n    queue?: string;\n    stream?: string;\n    durable?: string;\n    manualAck?: boolean;\n    config?: SubscriptionConfig;\n}\n\nexport interface SubscriptionConfig {\n    deliverPolicy?: DeliverPolicy;\n    ackPolicy?: AckPolicy;\n    ackWait?: number;\n    maxDeliver?: number;\n    maxAckPending?: number;\n    replayPolicy?: ReplayPolicy;\n    rateLimit?: number;\n    samplingRate?: number;\n    headersOnly?: boolean;\n    maxMessages?: number;\n}\n\nexport type DeliverPolicy =\n    | 'all'\n    | 'last'\n    | 'new'\n    | 'first'\n    | 'last_per_subject'\n    | 'by_start_sequence'\n    | 'by_start_time';\n\nexport type AckPolicy =\n    | 'none'\n    | 'all'\n    | 'explicit'\n    | 'allInclusive';\n\nexport type ReplayPolicy =\n    | 'instant'\n    | 'original'\n    | 'by_start_time'\n    | 'last';\n\nexport interface NatsMessage {\n    subject: string;\n    sid: number;\n    data: Uint8Array;\n    headers?: Record<string, string>;\n    reply?: string;\n    timestamp: number;\n}\n\nexport interface SubscriptionHandler {\n    (message: NatsMessage): Promise<void> | void;\n}\n\n// ============================================================================\n// JetStream Types\n// ============================================================================\n\nexport interface JetStreamPublishOptions {\n    stream: string;\n    subject: string;\n    data?: Uint8Array | string | object;\n    headers?: Record<string, string>;\n    timeout?: number;\n    wait?: boolean;\n}\n\nexport interface JetStreamSubscribeOptions extends SubscribeOptions {\n    stream: string;\n    deliverSubject?: string;\n    config?: StreamSubscriptionConfig;\n}\n\nexport interface StreamSubscriptionConfig extends SubscriptionConfig {\n    filterSubject?: string;\n    subjectTransform?: {\n        src: string;\n        dest: string;\n    };\n    idleHeartbeat?: number;\n    flowControl?: boolean;\n    active?: boolean;\n    startSeq?: number;\n    startTime?: Date;\n}\n\nexport interface StreamInfo {\n    config: StreamConfig;\n    state: StreamState;\n    created: Date;\n    cluster?: ClusterInfo;\n}\n\nexport interface StreamConfig {\n    name: string;\n    subjects?: string[];\n    retention?: RetentionPolicy;\n    maxConsumers?: number;\n    maxMsgs?: number;\n    maxBytes?: number;\n    maxAge?: number;\n    storage?: StorageType;\n    replicas?: number;\n    template?: string;\n    denyDelete?: boolean;\n    denyPurge?: boolean;\n    allowRollup?: boolean;\n}\n\nexport type RetentionPolicy =\n    | 'limits'\n    | 'interest'\n    | 'workqueue';\n\nexport type StorageType =\n    | 'file'\n    | 'memory';\n\nexport interface StreamState {\n    messages: number;\n    bytes: number;\n    firstSeq: number;\n    firstTs: Date;\n    lastSeq: number;\n    lastTs: Date;\n    consumerCount: number;\n    numSubjects?: number;\n}\n\nexport interface ClusterInfo {\n    leader: string;\n    replicas: PeerInfo[];\n}\n\nexport interface PeerInfo {\n    name: string;\n    current: boolean;\n    offline: boolean;\n    active: number;\n    lag?: number;\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class NatsError extends Error {\n    constructor(\n        message: string,\n        public code: string,\n        public statusCode: number = 500,\n    ) {\n        super(message);\n        this.name = 'NatsError';\n    }\n}\n\nexport class NatsConnectionError extends NatsError {\n    constructor(server: string, reason?: string) {\n        super(\n            `Failed to connect to NATS server ${server}: ${reason || 'Unknown error'}`,\n            'NATS_CONNECTION_ERROR',\n            503,\n        );\n    }\n}\n\nexport class NatsPublishError extends NatsError {\n    constructor(subject: string, reason?: string) {\n        super(\n            `Failed to publish to ${subject}: ${reason || 'Unknown error'}`,\n            'NATS_PUBLISH_ERROR',\n            500,\n        );\n    }\n}\n\nexport class NatsSubscribeError extends NatsError {\n    constructor(subject: string, reason?: string) {\n        super(\n            `Failed to subscribe to ${subject}: ${reason || 'Unknown error'}`,\n            'NATS_SUBSCRIBE_ERROR',\n            500,\n        );\n    }\n}\n\nexport class NatsRequestError extends NatsError {\n    constructor(subject: string, reason?: string) {\n        super(\n            `Request to ${subject} failed: ${reason || 'Unknown error'}`,\n            'NATS_REQUEST_ERROR',\n            504,\n        );\n    }\n}\n"
  },
  {
    "path": "packages/nats/tsconfig.json",
    "content": "{\n    \"extends\": \"../../tsconfig.base.json\",\n    \"compilerOptions\": {\n        \"outDir\": \"./dist\",\n        \"rootDir\": \"./src\",\n        \"composite\": true\n    },\n    \"include\": [\"src/**/*\"],\n    \"exclude\": [\"node_modules\", \"dist\", \"**/__tests__\"]\n}\n"
  },
  {
    "path": "packages/redisson/package.json",
    "content": "{\n    \"name\": \"@a3s-lab/redisson\",\n    \"version\": \"1.0.0\",\n    \"description\": \"NestJS module for Redis distributed locks and caching using Redisson\",\n    \"author\": \"ZhiXiao-Lin\",\n    \"license\": \"MIT\",\n    \"homepage\": \"https://github.com/ZhiXiao-Lin/nestify/tree/main/packages/redisson#readme\",\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"git+https://github.com/ZhiXiao-Lin/nestify.git\",\n        \"directory\": \"packages/redisson\"\n    },\n    \"bugs\": {\n        \"url\": \"https://github.com/ZhiXiao-Lin/nestify/issues\"\n    },\n    \"keywords\": [\n        \"nestjs\",\n        \"redis\",\n        \"redisson\",\n        \"distributed-lock\",\n        \"cache\",\n        \"ioredis\",\n        \"typescript\",\n        \"mutex\",\n        \"semaphore\"\n    ],\n    \"source\": \"./src/index.ts\",\n    \"main\": \"./dist/index.js\",\n    \"types\": \"./dist/index.d.ts\",\n    \"sideEffects\": false,\n    \"exports\": {\n        \".\": {\n            \"import\": \"./dist/index.js\",\n            \"require\": \"./dist/index.js\",\n            \"types\": \"./dist/index.d.ts\"\n        }\n    },\n    \"files\": [\n        \"dist\"\n    ],\n    \"scripts\": {\n        \"build\": \"tsc\",\n        \"test\": \"jest\",\n        \"test:cov\": \"jest --coverage\",\n        \"clean\": \"rimraf dist && rimraf node_modules\",\n        \"prepublishOnly\": \"pnpm run build\"\n    },\n    \"dependencies\": {\n        \"ioredis\": \"^5.9.1\",\n        \"node-redisson\": \"^1.0.3\"\n    },\n    \"devDependencies\": {\n        \"@biomejs/biome\": \"^2.3.14\",\n        \"@nestjs/common\": \"^10.0.0\",\n        \"@nestjs/testing\": \"^10.0.0\",\n        \"@types/jest\": \"^29.5.0\",\n        \"jest\": \"^29.5.0\",\n        \"reflect-metadata\": \"^0.1.13\",\n        \"rimraf\": \"^6.0.1\",\n        \"rxjs\": \"^7.8.1\",\n        \"ts-jest\": \"^29.1.0\",\n        \"typescript\": \"^5.1.3\"\n    },\n    \"peerDependencies\": {\n        \"@nestjs/common\": \"^10.0.0\"\n    },\n    \"publishConfig\": {\n        \"access\": \"public\"\n    },\n    \"jest\": {\n        \"moduleFileExtensions\": [\"js\", \"json\", \"ts\"],\n        \"rootDir\": \"src\",\n        \"testRegex\": \".*\\\\.spec\\\\.ts$\",\n        \"transform\": {\n            \"^.+\\\\.(t|j)s$\": \"ts-jest\"\n        },\n        \"collectCoverageFrom\": [\"**/*.(t|j)s\", \"!**/__tests__/**\"],\n        \"coverageDirectory\": \"../coverage\",\n        \"testEnvironment\": \"node\"\n    }\n}\n"
  },
  {
    "path": "packages/redisson/src/__tests__/redisson.module.spec.ts",
    "content": "import { Test, TestingModule } from '@nestjs/testing';\nimport { RedissonModule } from '../redisson.module';\nimport { MODULE_OPTIONS_TOKEN } from '../redisson.module-definition';\n\ndescribe('RedissonModule', () => {\n    describe('register', () => {\n        it('should create module with options', async () => {\n            const mockOptions = {\n                redis: {\n                    options: {\n                        host: 'localhost',\n                        port: 6379,\n                    },\n                },\n            };\n\n            const dynamicModule = RedissonModule.register(mockOptions);\n\n            expect(dynamicModule).toBeDefined();\n            expect(dynamicModule.module).toBe(RedissonModule);\n            expect(dynamicModule.providers).toBeDefined();\n            expect(dynamicModule.exports).toBeDefined();\n        });\n    });\n\n    describe('registerAsync', () => {\n        it('should create module with async options', async () => {\n            const mockOptions = {\n                redis: {\n                    options: {\n                        host: 'localhost',\n                        port: 6379,\n                    },\n                },\n            };\n\n            const dynamicModule = RedissonModule.registerAsync({\n                useFactory: () => mockOptions,\n            });\n\n            expect(dynamicModule).toBeDefined();\n            expect(dynamicModule.module).toBe(RedissonModule);\n            expect(dynamicModule.providers).toBeDefined();\n            expect(dynamicModule.exports).toBeDefined();\n        });\n\n        it('should support inject option', async () => {\n            const mockOptions = {\n                redis: {\n                    options: {\n                        host: 'localhost',\n                        port: 6379,\n                    },\n                },\n            };\n\n            const dynamicModule = RedissonModule.registerAsync({\n                useFactory: () => mockOptions,\n                inject: [],\n            });\n\n            expect(dynamicModule).toBeDefined();\n        });\n    });\n});\n"
  },
  {
    "path": "packages/redisson/src/__tests__/types.spec.ts",
    "content": "import { CacheOptions, LockOptions, CacheMetadata, LockMetadata, BatchResult } from '../types';\n\ndescribe('Types', () => {\n    describe('CacheOptions', () => {\n        it('should allow valid cache options', () => {\n            const options: CacheOptions = {\n                ttl: 3600,\n                prefix: 'cache:',\n            };\n            expect(options.ttl).toBe(3600);\n            expect(options.prefix).toBe('cache:');\n        });\n\n        it('should allow partial cache options', () => {\n            const options: CacheOptions = {};\n            expect(options.ttl).toBeUndefined();\n            expect(options.prefix).toBeUndefined();\n        });\n    });\n\n    describe('LockOptions', () => {\n        it('should allow valid lock options', () => {\n            const options: LockOptions = {\n                waitTime: 5000,\n                leaseTime: 10000,\n            };\n            expect(options.waitTime).toBe(5000);\n            expect(options.leaseTime).toBe(10000);\n        });\n    });\n\n    describe('CacheMetadata', () => {\n        it('should require key property', () => {\n            const metadata: CacheMetadata = {\n                key: 'test-key',\n                ttl: 3600,\n            };\n            expect(metadata.key).toBe('test-key');\n            expect(metadata.ttl).toBe(3600);\n        });\n    });\n\n    describe('LockMetadata', () => {\n        it('should require key property', () => {\n            const metadata: LockMetadata = {\n                key: 'lock-key',\n                waitTime: 5000,\n                leaseTime: 10000,\n            };\n            expect(metadata.key).toBe('lock-key');\n        });\n    });\n\n    describe('BatchResult', () => {\n        it('should track succeeded and failed items', () => {\n            const result: BatchResult<string> = {\n                succeeded: ['item1', 'item2'],\n                failed: [{ item: 'item3', error: new Error('Failed') }],\n            };\n            expect(result.succeeded).toHaveLength(2);\n            expect(result.failed).toHaveLength(1);\n            expect(result.failed[0].error.message).toBe('Failed');\n        });\n    });\n});\n"
  },
  {
    "path": "packages/redisson/src/index.ts",
    "content": "export * from 'node-redisson';\nexport * from 'ioredis';\nexport * from './redisson-module-options.interface';\nexport * from './redisson.module';\nexport * from './redisson.service';\nexport * from './types';\n"
  },
  {
    "path": "packages/redisson/src/redisson-module-options.interface.ts",
    "content": "import { IRedissonConfig } from 'node-redisson';\n\nexport interface RedissonModuleOptions extends IRedissonConfig {}\n"
  },
  {
    "path": "packages/redisson/src/redisson.module-definition.ts",
    "content": "import { ConfigurableModuleBuilder } from '@nestjs/common';\nimport { RedissonModuleOptions } from './redisson-module-options.interface';\n\nexport const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE, ASYNC_OPTIONS_TYPE } =\n    new ConfigurableModuleBuilder<RedissonModuleOptions>()\n        .setExtras(\n            {\n                isGlobal: true,\n            },\n            (definition, extras) => ({\n                ...definition,\n                global: extras.isGlobal,\n            }),\n        )\n        .build();\n"
  },
  {
    "path": "packages/redisson/src/redisson.module.ts",
    "content": "import { DynamicModule, Module } from '@nestjs/common';\nimport { ASYNC_OPTIONS_TYPE, ConfigurableModuleClass, OPTIONS_TYPE } from './redisson.module-definition';\nimport { RedissonService } from './redisson.service';\n\n@Module({})\nexport class RedissonModule extends ConfigurableModuleClass {\n    static register(options: typeof OPTIONS_TYPE): DynamicModule {\n        const dynamicModule = super.register(options);\n        return {\n            ...dynamicModule,\n            providers: [...(dynamicModule.providers || []), RedissonService],\n            exports: [RedissonService],\n        };\n    }\n\n    static registerAsync(options: typeof ASYNC_OPTIONS_TYPE): DynamicModule {\n        const dynamicModule = super.registerAsync(options);\n        return {\n            ...dynamicModule,\n            providers: [...(dynamicModule.providers || []), RedissonService],\n            exports: [RedissonService],\n        };\n    }\n}\n"
  },
  {
    "path": "packages/redisson/src/redisson.service.ts",
    "content": "import { Inject, Injectable, Logger, OnModuleDestroy, OnModuleInit } from '@nestjs/common';\nimport { Redisson } from 'node-redisson';\nimport { RedissonModuleOptions } from './redisson-module-options.interface';\nimport { MODULE_OPTIONS_TOKEN } from './redisson.module-definition';\n\n@Injectable()\nexport class RedissonService extends Redisson implements OnModuleInit, OnModuleDestroy {\n    private readonly logger = new Logger(RedissonService.name);\n\n    constructor(\n        @Inject(MODULE_OPTIONS_TOKEN)\n        private readonly options: RedissonModuleOptions,\n    ) {\n        if (!options) {\n            throw new Error('RedissonModuleOptions is not defined');\n        }\n        super(options);\n    }\n\n    async onModuleInit() {\n        try {\n            // Test connection by pinging Redis\n            await this.redis.ping();\n            this.logger.log('Successfully connected to Redis via Redisson');\n        } catch (error) {\n            this.logger.error('Failed to connect to Redis', error);\n            throw error;\n        }\n    }\n\n    async onModuleDestroy() {\n        try {\n            await this.quit();\n            this.logger.log('Redis connection closed');\n        } catch (error) {\n            this.logger.error('Error closing Redis connection', error);\n        }\n    }\n\n    /**\n     * 执行带锁的操作\n     * @param key 锁的键名\n     * @param callback 需要执行的回调函数\n     * @param waitTime 等待时间（毫秒）\n     * @param leaseTime 锁的租期（毫秒）\n     * @returns 回调函数的返回值\n     */\n    async withLock<T>(key: string, callback: () => Promise<T> | T, waitTime = 5000, leaseTime = 10000): Promise<T> {\n        const lock = this.getLock(key);\n        const acquired = await lock.tryLock(waitTime, leaseTime);\n\n        if (!acquired) {\n            throw new Error(`Failed to acquire lock: ${key}`);\n        }\n\n        try {\n            this.logger.debug(`Lock acquired: ${key}`);\n            return await callback();\n        } finally {\n            await lock.unlock();\n            this.logger.debug(`Lock released: ${key}`);\n        }\n    }\n\n    /**\n     * 缓存装饰器辅助方法 - 获取缓存或执行函数\n     * @param key 缓存键\n     * @param factory 数据工厂函数\n     * @param ttl 过期时间（秒）\n     * @returns 缓存的数据或新数据\n     */\n    async getOrSet<T>(key: string, factory: () => Promise<T> | T, ttl?: number): Promise<T> {\n        // 尝试从缓存获取\n        const cached = await this.redis.get(key);\n        if (cached) {\n            try {\n                return JSON.parse(cached) as T;\n            } catch {\n                // 如果解析失败，返回原始值\n                return cached as unknown as T;\n            }\n        }\n\n        // 执行工厂函数获取数据\n        const data = await factory();\n\n        // 存储到缓存\n        const value = typeof data === 'string' ? data : JSON.stringify(data);\n        if (ttl) {\n            await this.redis.setex(key, ttl, value);\n        } else {\n            await this.redis.set(key, value);\n        }\n\n        return data;\n    }\n\n    /**\n     * 批量删除匹配模式的键\n     * @param pattern 键的匹配模式\n     * @returns 删除的键数量\n     */\n    async deleteByPattern(pattern: string): Promise<number> {\n        const keys = await this.redis.keys(pattern);\n\n        if (keys.length === 0) {\n            return 0;\n        }\n\n        await this.redis.del(...keys);\n        this.logger.debug(`Deleted ${keys.length} keys matching pattern: ${pattern}`);\n        return keys.length;\n    }\n\n    /**\n     * 设置带过期时间的 JSON 数据\n     * @param key 键名\n     * @param value 值（会自动序列化为 JSON）\n     * @param ttl 过期时间（秒）\n     */\n    async setJSON<T>(key: string, value: T, ttl?: number): Promise<void> {\n        const serialized = JSON.stringify(value);\n\n        if (ttl) {\n            await this.redis.setex(key, ttl, serialized);\n        } else {\n            await this.redis.set(key, serialized);\n        }\n    }\n\n    /**\n     * 获取 JSON 数据\n     * @param key 键名\n     * @returns 反序列化的数据或 null\n     */\n    async getJSON<T>(key: string): Promise<T | null> {\n        const value = await this.redis.get(key);\n\n        if (!value) {\n            return null;\n        }\n\n        try {\n            return JSON.parse(value) as T;\n        } catch (error) {\n            this.logger.error(`Failed to parse JSON for key: ${key}`, error);\n            return null;\n        }\n    }\n\n    /**\n     * 检查键是否存在\n     * @param key 键名\n     * @returns 是否存在\n     */\n    async exists(key: string): Promise<boolean> {\n        const result = await this.redis.exists(key);\n        return result === 1;\n    }\n\n    /**\n     * 设置键的过期时间\n     * @param key 键名\n     * @param ttl 过期时间（秒）\n     * @returns 是否成功\n     */\n    async expire(key: string, ttl: number): Promise<boolean> {\n        const result = await this.redis.expire(key, ttl);\n        return result === 1;\n    }\n\n    /**\n     * 删除键\n     * @param keys 要删除的键\n     * @returns 删除的键数量\n     */\n    async delete(...keys: string[]): Promise<number> {\n        return await this.redis.del(...keys);\n    }\n\n    /**\n     * 增量操作\n     * @param key 键名\n     * @param increment 增量值（默认为 1）\n     * @returns 增量后的值\n     */\n    async increment(key: string, increment = 1): Promise<number> {\n        return await this.redis.incrby(key, increment);\n    }\n\n    /**\n     * 减量操作\n     * @param key 键名\n     * @param decrement 减量值（默认为 1）\n     * @returns 减量后的值\n     */\n    async decrement(key: string, decrement = 1): Promise<number> {\n        return await this.redis.decrby(key, decrement);\n    }\n\n    /**\n     * 获取键值\n     * @param key 键名\n     * @returns 键值或 null\n     */\n    async get(key: string): Promise<string | null> {\n        return await this.redis.get(key);\n    }\n\n    /**\n     * 设置键值\n     * @param key 键名\n     * @param value 值\n     * @param ttl 过期时间（秒），可选\n     */\n    async set(key: string, value: string, ttl?: number): Promise<void> {\n        if (ttl) {\n            await this.redis.setex(key, ttl, value);\n        } else {\n            await this.redis.set(key, value);\n        }\n    }\n\n    /**\n     * 设置哈希字段\n     * @param key 哈希键\n     * @param field 字段名\n     * @param value 字段值\n     */\n    async hset(key: string, field: string, value: string): Promise<void> {\n        await this.redis.hset(key, field, value);\n    }\n\n    /**\n     * 获取哈希字段\n     * @param key 哈希键\n     * @param field 字段名\n     * @returns 字段值或 null\n     */\n    async hget(key: string, field: string): Promise<string | null> {\n        return await this.redis.hget(key, field);\n    }\n\n    /**\n     * 获取整个哈希\n     * @param key 哈希键\n     * @returns 哈希对象\n     */\n    async hgetall(key: string): Promise<Record<string, string>> {\n        return await this.redis.hgetall(key);\n    }\n\n    /**\n     * 删除哈希字段\n     * @param key 哈希键\n     * @param fields 要删除的字段\n     * @returns 删除的字段数量\n     */\n    async hdel(key: string, ...fields: string[]): Promise<number> {\n        return await this.redis.hdel(key, ...fields);\n    }\n\n    /**\n     * 执行 Lua 脚本\n     * @param script Lua 脚本内容\n     * @param keys 键数组\n     * @param args 参数数组\n     * @returns 脚本执行结果\n     */\n    async eval(script: string, keys: string[], args: string[]): Promise<any> {\n        const numKeys = keys.length;\n        return await this.redis.eval(script, numKeys, ...keys, ...args);\n    }\n\n    /**\n     * 执行已缓存的 Lua 脚本（通过 SHA1）\n     * @param sha1 脚本的 SHA1 哈希\n     * @param keys 键数组\n     * @param args 参数数组\n     * @returns 脚本执行结果\n     */\n    async evalsha(sha1: string, keys: string[], args: string[]): Promise<any> {\n        const numKeys = keys.length;\n        return await this.redis.evalsha(sha1, numKeys, ...keys, ...args);\n    }\n\n    /**\n     * 加载 Lua 脚本并返回 SHA1\n     * @param script Lua 脚本内容\n     * @returns 脚本的 SHA1 哈希\n     */\n    async scriptLoad(script: string): Promise<string> {\n        return (await this.redis.call('SCRIPT', 'LOAD', script)) as string;\n    }\n\n    /**\n     * 获取底层 Redis 客户端 (IORedis)\n     * 用于需要直接访问 Redis 的高级操作\n     * @returns IORedis 客户端实例\n     */\n    getRedis(): ReturnType<Redisson['getRedis']> {\n        return this.redis;\n    }\n\n    /**\n     * 尝试获取分布式锁\n     * @param key 锁的键名\n     * @param waitTime 等待时间（毫秒）\n     * @param leaseTime 锁的租期（毫秒）\n     * @returns 是否成功获取锁\n     */\n    async tryLock(key: string, waitTime = 5000, leaseTime = 10000): Promise<boolean> {\n        const lock = this.getLock(key);\n        return await lock.tryLock(waitTime, leaseTime);\n    }\n\n    /**\n     * 释放分布式锁\n     * @param key 锁的键名\n     */\n    async unlock(key: string): Promise<void> {\n        const lock = this.getLock(key);\n        await lock.unlock();\n    }\n}\n"
  },
  {
    "path": "packages/redisson/src/types.ts",
    "content": "/**\n * Redis 键值对操作的通用类型\n */\nexport interface CacheOptions {\n    /** 过期时间（秒） */\n    ttl?: number;\n    /** 键的前缀 */\n    prefix?: string;\n}\n\n/**\n * 分布式锁配置选项\n */\nexport interface LockOptions {\n    /** 等待获取锁的时间（毫秒） */\n    waitTime?: number;\n    /** 锁的租期时间（毫秒） */\n    leaseTime?: number;\n}\n\n/**\n * 缓存装饰器元数据\n */\nexport interface CacheMetadata {\n    /** 缓存键 */\n    key: string;\n    /** 过期时间（秒） */\n    ttl?: number;\n}\n\n/**\n * 分布式锁装饰器元数据\n */\nexport interface LockMetadata {\n    /** 锁的键名 */\n    key: string;\n    /** 等待时间（毫秒） */\n    waitTime?: number;\n    /** 租期时间（毫秒） */\n    leaseTime?: number;\n}\n\n/**\n * 批量操作结果\n */\nexport interface BatchResult<T = any> {\n    /** 成功的项 */\n    succeeded: T[];\n    /** 失败的项 */\n    failed: Array<{ item: T; error: Error }>;\n}\n"
  },
  {
    "path": "packages/redisson/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"Node16\",\n        \"moduleResolution\": \"Node16\",\n        \"declaration\": true,\n        \"removeComments\": true,\n        \"emitDecoratorMetadata\": true,\n        \"experimentalDecorators\": true,\n        \"allowSyntheticDefaultImports\": true,\n        \"sourceMap\": true,\n        \"incremental\": true,\n        \"isolatedModules\": true,\n        \"strictNullChecks\": false,\n        \"noImplicitAny\": false,\n        \"strictBindCallApply\": false,\n        \"forceConsistentCasingInFileNames\": false,\n        \"noFallthroughCasesInSwitch\": false,\n        \"jsx\": \"react-jsx\",\n        \"outDir\": \"dist\",\n        \"target\": \"ES2021\",\n        \"lib\": [\"DOM\", \"DOM.Iterable\", \"ES2021\", \"ES2022.Error\"],\n        \"noErrorTruncation\": true,\n        \"strict\": true,\n        \"skipLibCheck\": true,\n        \"baseUrl\": \".\",\n        \"paths\": {\n            \"@/*\": [\"src/*\"]\n        }\n    },\n    \"include\": [\"src/**/*\"]\n}\n"
  },
  {
    "path": "packages/rustfs/package.json",
    "content": "{\n    \"name\": \"@a3s-lab/rustfs\",\n    \"version\": \"0.0.1\",\n    \"description\": \"NestJS module for RustFS S3-compatible storage - object storage with bucket and file operations\",\n    \"author\": \"A3S Lab\",\n    \"license\": \"MIT\",\n    \"keywords\": [\n        \"nestjs\",\n        \"rustfs\",\n        \"s3\",\n        \"storage\",\n        \"object-storage\",\n        \"bucket\"\n    ],\n    \"source\": \"./src/index.ts\",\n    \"main\": \"./dist/index.js\",\n    \"types\": \"./dist/index.d.ts\",\n    \"sideEffects\": false,\n    \"exports\": {\n        \".\": {\n            \"import\": \"./dist/index.js\",\n            \"require\": \"./dist/index.js\",\n            \"types\": \"./dist/index.d.ts\"\n        }\n    },\n    \"files\": [\n        \"dist\"\n    ],\n    \"scripts\": {\n        \"build\": \"tsc\",\n        \"test\": \"jest\",\n        \"test:cov\": \"jest --coverage\",\n        \"clean\": \"rimraf dist && rimraf node_modules\",\n        \"prepublishOnly\": \"pnpm run build\"\n    },\n    \"dependencies\": {\n        \"@aws-sdk/client-s3\": \"^3.600.0\",\n        \"@aws-sdk/s3-request-presigner\": \"^3.600.0\"\n    },\n    \"devDependencies\": {\n        \"@biomejs/biome\": \"^2.3.14\",\n        \"@nestjs/common\": \"^10.0.0\",\n        \"@nestjs/testing\": \"^10.0.0\",\n        \"@types/jest\": \"^29.5.0\",\n        \"jest\": \"^29.5.0\",\n        \"reflect-metadata\": \"^0.1.13\",\n        \"rimraf\": \"^6.0.1\",\n        \"rxjs\": \"^7.8.1\",\n        \"ts-jest\": \"^29.1.0\",\n        \"typescript\": \"^5.1.3\"\n    },\n    \"peerDependencies\": {\n        \"@nestjs/common\": \"^10.0.0\"\n    },\n    \"publishConfig\": {\n        \"access\": \"public\"\n    },\n    \"jest\": {\n        \"moduleFileExtensions\": [\"js\", \"json\", \"ts\"],\n        \"rootDir\": \"src\",\n        \"testRegex\": \".*\\\\.spec\\\\.ts$\",\n        \"transform\": {\n            \"^.+\\\\.(t|j)s$\": \"ts-jest\"\n        },\n        \"collectCoverageFrom\": [\"**/*.(t|j)s\", \"!**/__tests__/**\"],\n        \"coverageDirectory\": \"../coverage\",\n        \"testEnvironment\": \"node\"\n    }\n}\n"
  },
  {
    "path": "packages/rustfs/src/index.ts",
    "content": "export * from './rustfs.module';\nexport * from './rustfs.service';\nexport * from './rustfs.types';\nexport * from './rustfs.module-definition';\n"
  },
  {
    "path": "packages/rustfs/src/rustfs.module-definition.ts",
    "content": "import { ConfigurableModuleBuilder } from '@nestjs/common';\nimport { RustFSPackageOptions } from './rustfs.types';\n\n/**\n * Configurable module builder for RustFSModule\n * Provides both synchronous and asynchronous registration methods\n * with optional global module configuration\n */\nexport const {\n    ConfigurableModuleClass,\n    MODULE_OPTIONS_TOKEN,\n    OPTIONS_TYPE,\n    ASYNC_OPTIONS_TYPE,\n} = new ConfigurableModuleBuilder<RustFSPackageOptions>()\n    .setExtras(\n        {\n            isGlobal: true,\n        },\n        (definition, extras) => ({\n            ...definition,\n            global: extras.isGlobal,\n        }),\n    )\n    .build();\n"
  },
  {
    "path": "packages/rustfs/src/rustfs.module.ts",
    "content": "import { DynamicModule, Module } from '@nestjs/common';\nimport {\n    ASYNC_OPTIONS_TYPE,\n    ConfigurableModuleClass,\n    OPTIONS_TYPE,\n} from './rustfs.module-definition';\nimport { RustFSServiceImpl } from './rustfs.service';\n\n@Module({})\nexport class RustFSModule extends ConfigurableModuleClass {\n    static register(options: typeof OPTIONS_TYPE): DynamicModule {\n        const dynamicModule = super.register(options);\n        return {\n            ...dynamicModule,\n            providers: [...(dynamicModule.providers || []), RustFSServiceImpl],\n            exports: [RustFSServiceImpl],\n        };\n    }\n\n    static registerAsync(options: typeof ASYNC_OPTIONS_TYPE): DynamicModule {\n        const dynamicModule = super.registerAsync(options);\n        return {\n            ...dynamicModule,\n            providers: [...(dynamicModule.providers || []), RustFSServiceImpl],\n            exports: [RustFSServiceImpl],\n        };\n    }\n}\n"
  },
  {
    "path": "packages/rustfs/src/rustfs.service.ts",
    "content": "import { Injectable, OnModuleInit, Logger } from '@nestjs/common';\nimport {\n    S3Client,\n    CreateBucketCommand,\n    ListBucketsCommand,\n    DeleteBucketCommand,\n    HeadBucketCommand,\n    GetObjectCommand,\n    PutObjectCommand,\n    CopyObjectCommand,\n    DeleteObjectCommand,\n    DeleteObjectsCommand,\n    ListObjectsV2Command,\n    HeadObjectCommand,\n    GetBucketAclCommand,\n    PutBucketAclCommand,\n    CreateMultipartUploadCommand,\n    UploadPartCommand,\n    CompleteMultipartUploadCommand,\n    AbortMultipartUploadCommand,\n    ListPartsCommand,\n} from '@aws-sdk/client-s3';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\nimport {\n    RustFSPackageOptions,\n    Bucket,\n    CreateBucketOptions,\n    BucketAcl,\n    StorageObject,\n    PutObjectOptions,\n    GetObjectOptions,\n    CopyObjectOptions,\n    ListObjectsOptions,\n    ListObjectsResult,\n    PresignedUrlOptions,\n    PresignedPostOptions,\n    CreateMultipartUploadOptions,\n    UploadPartOptions,\n    CompleteMultipartUploadOptions,\n    ListPartsOptions,\n    ListPartsResult,\n    UploadPart,\n    RustFSError,\n    BucketNotFoundError,\n    ObjectNotFoundError,\n    BucketAlreadyExistsError,\n} from './rustfs.types';\n\n@Injectable()\nexport class RustFSServiceImpl implements OnModuleInit {\n    private client: S3Client;\n    private defaultBucket: string;\n    private readonly logger = new Logger(RustFSServiceImpl.name);\n\n    constructor(private readonly options: RustFSPackageOptions) {\n        this.defaultBucket = options.bucket || '';\n        this.client = this.createClient();\n    }\n\n    async onModuleInit() {\n        if (this.defaultBucket) {\n            const exists = await this.bucketExists(this.defaultBucket);\n            if (!exists) {\n                this.logger.warn(`Default bucket '${this.defaultBucket}' does not exist`);\n            }\n        }\n        this.logger.log(`RustFS client initialized for endpoint: ${this.options.endpoint}`);\n    }\n\n    private createClient(): S3Client {\n        const config: Record<string, unknown> = {\n            endpoint: this.options.endpoint,\n            region: this.options.region || 'us-east-1',\n            credentials: {\n                accessKeyId: this.options.accessKeyId,\n                secretAccessKey: this.options.secretAccessKey,\n            },\n            forcePathStyle: this.options.forcePathStyle ?? true,\n        };\n\n        if (this.options.sslEnabled !== false) {\n            config.tls = true;\n        }\n\n        if (this.options.timeout) {\n            config.requestTimeout = this.options.timeout;\n        }\n\n        if (this.options.maxAttempts) {\n            config.maxAttempts = this.options.maxAttempts;\n        }\n\n        return new S3Client(config as Parameters<typeof S3Client.create>[0]);\n    }\n\n    // =========================================================================\n    // Bucket Operations\n    // =========================================================================\n\n    async createBucket(options: CreateBucketOptions): Promise<Bucket> {\n        try {\n            const command = new CreateBucketCommand({\n                Bucket: options.name,\n                ACL: options.acl,\n            });\n\n            await this.client.send(command);\n\n            this.logger.log(`Bucket created: ${options.name}`);\n\n            return {\n                name: options.name,\n                creationDate: new Date(),\n            };\n        } catch (error) {\n            const err = error as { name?: string; message?: string };\n            if (err.name === 'BucketAlreadyOwnedByYou' || err.name === 'BucketAlreadyExists') {\n                throw new BucketAlreadyExistsError(options.name);\n            }\n            throw new RustFSError(\n                `Failed to create bucket: ${err.message}`,\n                'CREATE_BUCKET_ERROR',\n                500,\n            );\n        }\n    }\n\n    async listBuckets(): Promise<Bucket[]> {\n        const command = new ListBucketsCommand({});\n        const response = await this.client.send(command);\n\n        return (response.Buckets || []).map((b) => ({\n            name: b.Name || '',\n            creationDate: b.CreationDate || new Date(),\n        }));\n    }\n\n    async getBucketAcl(bucketName: string): Promise<BucketAcl> {\n        const command = new GetBucketAclCommand({ Bucket: bucketName });\n        const response = await this.client.send(command);\n\n        return {\n            owner: response.Owner?.ID || '',\n            grants: (response.Grants || []).map((g) => ({\n                grantee: {\n                    type: g.Grantee?.Type as 'canonical' | 'group' | 'email',\n                    id: g.Grantee?.ID,\n                    uri: g.Grantee?.URI,\n                    emailAddress: g.Grantee?.EmailAddress,\n                },\n                permission: g.Permission as BucketAcl['grants'][0]['permission'],\n            })),\n        };\n    }\n\n    async setBucketAcl(bucketName: string, acl: BucketAcl): Promise<void> {\n        const command = new PutBucketAclCommand({\n            Bucket: bucketName,\n            ACL: acl as unknown as string,\n        });\n\n        await this.client.send(command);\n    }\n\n    async deleteBucket(bucketName: string): Promise<void> {\n        const command = new DeleteBucketCommand({ Bucket: bucketName });\n        await this.client.send(command);\n        this.logger.log(`Bucket deleted: ${bucketName}`);\n    }\n\n    async bucketExists(bucketName: string): Promise<boolean> {\n        try {\n            const command = new HeadBucketCommand({ Bucket: bucketName });\n            await this.client.send(command);\n            return true;\n        } catch {\n            return false;\n        }\n    }\n\n    // =========================================================================\n    // Object Operations\n    // =========================================================================\n\n    async putObject(bucketName: string, options: PutObjectOptions): Promise<StorageObject> {\n        const command = new PutObjectCommand({\n            Bucket: bucketName,\n            Key: options.key,\n            Body: options.body,\n            ContentType: options.contentType,\n            ContentEncoding: options.contentEncoding,\n            ContentDisposition: options.contentDisposition,\n            ContentLanguage: options.contentLanguage,\n            Metadata: options.metadata,\n            ACL: options.acl,\n            StorageClass: options.storageClass,\n            Expires: options.expires,\n            CacheControl: options.cacheControl,\n        });\n\n        const response = await this.client.send(command);\n\n        return {\n            key: options.key,\n            bucket: bucketName,\n            etag: response.ETag || '',\n            size: response.$metadata?.httpStatusCode || 0,\n            lastModified: new Date(),\n            contentType: options.contentType,\n            metadata: options.metadata,\n            storageClass: options.storageClass as StorageObject['storageClass'],\n            versionId: response.VersionId,\n        };\n    }\n\n    async getObject(bucketName: string, options: GetObjectOptions): Promise<Buffer> {\n        const command = new GetObjectCommand({\n            Bucket: bucketName,\n            Key: options.key,\n            Range: options.range\n                ? `bytes=${options.range.start}-${options.range.end}`\n                : undefined,\n            IfMatch: options.ifMatch,\n            IfNoneMatch: options.ifNoneMatch,\n            IfModifiedSince: options.ifModifiedSince,\n            IfUnmodifiedSince: options.ifUnmodifiedSince,\n        });\n\n        try {\n            const response = await this.client.send(command);\n            const chunks: Uint8Array[] = [];\n\n            if (response.Body) {\n                for await (const chunk of response.Body as AsyncIterable<Uint8Array>) {\n                    chunks.push(chunk);\n                }\n            }\n\n            return Buffer.concat(chunks);\n        } catch (error) {\n            const err = error as { name?: string };\n            if (err.name === 'NoSuchKey' || err.name === '404') {\n                throw new ObjectNotFoundError(options.key, bucketName);\n            }\n            throw error;\n        }\n    }\n\n    async getObjectMetadata(bucketName: string, key: string): Promise<StorageObject> {\n        const command = new HeadObjectCommand({\n            Bucket: bucketName,\n            Key: key,\n        });\n\n        try {\n            const response = await this.client.send(command);\n\n            return {\n                key,\n                bucket: bucketName,\n                etag: response.ETag || '',\n                size: response.ContentLength || 0,\n                lastModified: response.LastModified || new Date(),\n                contentType: response.ContentType,\n                metadata: response.Metadata || {},\n                storageClass: response.StorageClass as StorageObject['storageClass'],\n                versionId: response.VersionId,\n            };\n        } catch (error) {\n            const err = error as { name?: string };\n            if (err.name === 'NoSuchKey' || err.name === '404') {\n                throw new ObjectNotFoundError(key, bucketName);\n            }\n            throw error;\n        }\n    }\n\n    async copyObject(bucketName: string, options: CopyObjectOptions): Promise<StorageObject> {\n        const sourceBucket = options.sourceBucket || bucketName;\n        const destinationBucket = options.destinationBucket || bucketName;\n\n        const command = new CopyObjectCommand({\n            Bucket: destinationBucket,\n            Key: options.destinationKey,\n            CopySource: `/${sourceBucket}/${options.sourceKey}`,\n            ACL: options.acl,\n            Metadata: options.metadata,\n            StorageClass: options.storageClass,\n        });\n\n        const response = await this.client.send(command);\n\n        return {\n            key: options.destinationKey,\n            bucket: destinationBucket,\n            etag: response.ETag || '',\n            size: 0,\n            lastModified: new Date(),\n            storageClass: options.storageClass as StorageObject['storageClass'],\n            versionId: response.VersionId,\n        };\n    }\n\n    async deleteObject(bucketName: string, key: string): Promise<void> {\n        const command = new DeleteObjectCommand({\n            Bucket: bucketName,\n            Key: key,\n        });\n\n        await this.client.send(command);\n        this.logger.debug(`Object deleted: ${key} from ${bucketName}`);\n    }\n\n    async deleteObjects(bucketName: string, keys: string[]): Promise<void> {\n        const command = new DeleteObjectsCommand({\n            Bucket: bucketName,\n            Delete: {\n                Objects: keys.map((key) => ({ Key: key })),\n            },\n        });\n\n        const response = await this.client.send(command);\n\n        if (response.Errors && response.Errors.length > 0) {\n            this.logger.warn(`Failed to delete some objects: ${response.Errors.length}`);\n        }\n    }\n\n    async listObjects(bucketName: string, options?: ListObjectsOptions): Promise<ListObjectsResult> {\n        const command = new ListObjectsV2Command({\n            Bucket: bucketName,\n            Prefix: options?.prefix,\n            Delimiter: options?.delimiter,\n            MaxKeys: options?.maxKeys || 1000,\n            ContinuationToken: options?.continuationToken,\n            StartAfter: options?.startAfter,\n        });\n\n        const response = await this.client.send(command);\n\n        return {\n            objects: (response.Contents || []).map((obj) => ({\n                key: obj.Key || '',\n                bucket: bucketName,\n                etag: obj.ETag || '',\n                size: obj.Size || 0,\n                lastModified: obj.LastModified || new Date(),\n                storageClass: obj.StorageClass as StorageObject['storageClass'],\n                versionId: obj.VersionId,\n            })),\n            prefixes: (response.CommonPrefixes || []).map((p) => p.Prefix || ''),\n            isTruncated: response.IsTruncated || false,\n            nextContinuationToken: response.NextContinuationToken,\n            keyCount: response.KeyCount || 0,\n            maxKeys: response.MaxKeys || 1000,\n        };\n    }\n\n    // =========================================================================\n    // Presigned URLs\n    // =========================================================================\n\n    async getPresignedUrl(\n        bucketName: string,\n        options: PresignedUrlOptions,\n    ): Promise<string> {\n        const command = new GetObjectCommand({\n            Bucket: bucketName,\n            Key: options.key,\n            ...(options.contentType && { ContentType: options.contentType }),\n        });\n\n        const url = await getSignedUrl(this.client, command, {\n            expiresIn: options.expiresIn || 3600,\n        });\n\n        return url;\n    }\n\n    async getPresignedPostUrl(\n        bucketName: string,\n        options: PresignedPostOptions,\n    ): Promise<{ url: string; fields: Record<string, string> }> {\n        // Note: Presigned POST requires additional implementation\n        // For now, return a simple presigned PUT URL as alternative\n        const url = await this.getPresignedUrl(bucketName, {\n            key: options.key,\n            expiresIn: options.expiresIn || 3600,\n            method: 'PUT',\n            contentType: options.conditions?.contentType,\n        });\n\n        return {\n            url,\n            fields: {\n                'Content-Type': options.conditions?.contentType || 'application/octet-stream',\n            },\n        };\n    }\n\n    // =========================================================================\n    // Multipart Upload\n    // =========================================================================\n\n    async createMultipartUpload(\n        bucketName: string,\n        options: CreateMultipartUploadOptions,\n    ): Promise<string> {\n        const command = new CreateMultipartUploadCommand({\n            Bucket: bucketName,\n            Key: options.key,\n            ContentType: options.contentType,\n            Metadata: options.metadata,\n            ACL: options.acl,\n            StorageClass: options.storageClass,\n        });\n\n        const response = await this.client.send(command);\n\n        if (!response.UploadId) {\n            throw new RustFSError('Failed to create multipart upload', 'MULTIPART_UPLOAD_ERROR');\n        }\n\n        return response.UploadId;\n    }\n\n    async uploadPart(bucketName: string, options: UploadPartOptions): Promise<string> {\n        const command = new UploadPartCommand({\n            Bucket: bucketName,\n            Key: options.key,\n            UploadId: options.uploadId,\n            PartNumber: options.partNumber,\n            Body: options.body,\n            ContentLength: options.contentLength,\n        });\n\n        const response = await this.client.send(command);\n\n        return response.ETag || '';\n    }\n\n    async completeMultipartUpload(\n        bucketName: string,\n        options: CompleteMultipartUploadOptions,\n    ): Promise<StorageObject> {\n        const command = new CompleteMultipartUploadCommand({\n            Bucket: bucketName,\n            Key: options.key,\n            UploadId: options.uploadId,\n            MultipartUpload: {\n                Parts: options.parts.map((p) => ({\n                    PartNumber: p.partNumber,\n                    ETag: p.etag,\n                })),\n            },\n        });\n\n        const response = await this.client.send(command);\n\n        return {\n            key: options.key,\n            bucket: bucketName,\n            etag: response.ETag || '',\n            size: 0,\n            lastModified: new Date(),\n        };\n    }\n\n    async abortMultipartUpload(bucketName: string, key: string, uploadId: string): Promise<void> {\n        const command = new AbortMultipartUploadCommand({\n            Bucket: bucketName,\n            Key: key,\n            UploadId: uploadId,\n        });\n\n        await this.client.send(command);\n    }\n\n    async listParts(bucketName: string, options: ListPartsOptions): Promise<ListPartsResult> {\n        const command = new ListPartsCommand({\n            Bucket: bucketName,\n            Key: options.key,\n            UploadId: options.uploadId,\n            MaxParts: options.maxParts,\n            PartNumberMarker: options.partNumberMarker,\n        });\n\n        const response = await this.client.send(command);\n\n        return {\n            key: options.key,\n            uploadId: options.uploadId,\n            parts: (response.Parts || []).map((p) => ({\n                partNumber: p.PartNumber || 0,\n                etag: p.ETag || '',\n                checksumSHA256: p.ChecksumSHA256,\n            })),\n            isTruncated: response.IsTruncated || false,\n            nextPartNumberMarker: response.NextPartNumberMarker,\n            maxParts: response.MaxParts || 1000,\n        };\n    }\n\n    // =========================================================================\n    // Health Check\n    // =========================================================================\n\n    async isHealthy(): Promise<boolean> {\n        try {\n            const command = new ListBucketsCommand({});\n            await this.client.send(command);\n            return true;\n        } catch {\n            return false;\n        }\n    }\n}\n\n// Re-export types for convenience\nexport type { RustFSModuleOptions } from './rustfs.types';\nexport { RustFSError } from './rustfs.types';\n"
  },
  {
    "path": "packages/rustfs/src/rustfs.types.ts",
    "content": "// ============================================================================\n// RustFS Types - S3-compatible storage\n// ============================================================================\n\nexport interface RustFSPackageOptions {\n    endpoint: string;\n    region?: string;\n    accessKeyId: string;\n    secretAccessKey: string;\n    bucket?: string;\n    forcePathStyle?: boolean;\n    sslEnabled?: boolean;\n    timeout?: number;\n    maxAttempts?: number;\n}\n\n/** @deprecated Use RustFSPackageOptions instead */\nexport type RustFSModuleOptions = RustFSPackageOptions;\n\n// ============================================================================\n// Bucket Types\n// ============================================================================\n\nexport interface Bucket {\n    name: string;\n    creationDate: Date;\n}\n\nexport interface CreateBucketOptions {\n    name: string;\n    acl?: BucketCannedAcl;\n    region?: string;\n}\n\nexport type BucketCannedAcl =\n    | 'private'\n    | 'public-read'\n    | 'public-read-write'\n    | 'authenticated-read'\n    | 'log-delivery-write';\n\nexport interface BucketAcl {\n    owner: string;\n    grants: BucketGrant[];\n}\n\nexport interface BucketGrant {\n    grantee: Grantee;\n    permission: BucketPermission;\n}\n\nexport interface Grantee {\n    type: 'canonical' | 'group' | 'email';\n    id?: string;\n    uri?: string;\n    emailAddress?: string;\n}\n\nexport type BucketPermission = 'READ' | 'WRITE' | 'READ_ACP' | 'WRITE_ACP' | 'FULL_CONTROL';\n\n// ============================================================================\n// Object Types\n// ============================================================================\n\nexport interface StorageObject {\n    key: string;\n    bucket: string;\n    etag: string;\n    size: number;\n    lastModified: Date;\n    contentType?: string;\n    metadata?: Record<string, string>;\n    storageClass?: StorageClass;\n    versionId?: string;\n}\n\nexport type StorageClass =\n    | 'STANDARD'\n    | 'REDUCED_REDUNDANCY'\n    | 'GLACIER'\n    | 'DEEP_ARCHIVE'\n    | 'INTELLIGENT_TIERING'\n    | 'ONEZONE_INFREQUENT_ACCESS';\n\nexport interface PutObjectOptions {\n    key: string;\n    body?: Buffer | Uint8Array | string;\n    contentType?: string;\n    contentEncoding?: string;\n    contentDisposition?: string;\n    contentLanguage?: string;\n    metadata?: Record<string, string>;\n    acl?: ObjectCannedAcl;\n    storageClass?: StorageClass;\n    expires?: Date;\n    cacheControl?: string;\n}\n\nexport type ObjectCannedAcl =\n    | 'private'\n    | 'public-read'\n    | 'public-read-write'\n    | 'authenticated-read'\n    | 'aws-exec-read'\n    | 'bucket-owner-read'\n    | 'bucket-owner-full-control';\n\nexport interface GetObjectOptions {\n    key: string;\n    range?: {\n        start: number;\n        end: number;\n    };\n    ifMatch?: string;\n    ifNoneMatch?: string;\n    ifModifiedSince?: Date;\n    ifUnmodifiedSince?: Date;\n}\n\nexport interface CopyObjectOptions {\n    sourceKey: string;\n    destinationKey: string;\n    sourceBucket?: string;\n    destinationBucket?: string;\n    acl?: ObjectCannedAcl;\n    metadata?: Record<string, string>;\n    storageClass?: StorageClass;\n}\n\nexport interface ListObjectsOptions {\n    prefix?: string;\n    delimiter?: string;\n    maxKeys?: number;\n    continuationToken?: string;\n    startAfter?: string;\n    includeOwn?: boolean;\n}\n\nexport interface ListObjectsResult {\n    objects: StorageObject[];\n    prefixes: string[];\n    isTruncated: boolean;\n    nextContinuationToken?: string;\n    keyCount: number;\n    maxKeys: number;\n}\n\n// ============================================================================\n// Presigned URL Types\n// ============================================================================\n\nexport interface PresignedUrlOptions {\n    key: string;\n    expiresIn?: number;\n    method?: 'GET' | 'PUT' | 'DELETE' | 'POST';\n    contentType?: string;\n    queryParams?: Record<string, string>;\n}\n\nexport interface PresignedPostOptions {\n    key: string;\n    expiresIn?: number;\n    conditions?: {\n        contentLengthRange?: {\n            min: number;\n            max: number;\n        };\n        contentType?: string;\n        acl?: ObjectCannedAcl;\n    };\n}\n\n// ============================================================================\n// Multipart Upload Types\n// ============================================================================\n\nexport interface CreateMultipartUploadOptions {\n    key: string;\n    contentType?: string;\n    metadata?: Record<string, string>;\n    acl?: ObjectCannedAcl;\n    storageClass?: StorageClass;\n}\n\nexport interface UploadPartOptions {\n    key: string;\n    uploadId: string;\n    partNumber: number;\n    body?: Buffer | Uint8Array | string;\n    contentLength?: number;\n    checksumSHA256?: string;\n}\n\nexport interface CompleteMultipartUploadOptions {\n    key: string;\n    uploadId: string;\n    parts: UploadPart[];\n}\n\nexport interface UploadPart {\n    partNumber: number;\n    etag: string;\n    checksumSHA256?: string;\n}\n\nexport interface ListPartsOptions {\n    key: string;\n    uploadId: string;\n    maxParts?: number;\n    partNumberMarker?: number;\n}\n\nexport interface ListPartsResult {\n    key: string;\n    uploadId: string;\n    parts: UploadPart[];\n    isTruncated: boolean;\n    nextPartNumberMarker?: number;\n    maxParts: number;\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class RustFSError extends Error {\n    constructor(\n        message: string,\n        public code: string,\n        public statusCode: number = 500,\n    ) {\n        super(message);\n        this.name = 'RustFSError';\n    }\n}\n\nexport class BucketNotFoundError extends RustFSError {\n    constructor(bucketName: string) {\n        super(\n            `Bucket not found: ${bucketName}`,\n            'BUCKET_NOT_FOUND',\n            404,\n        );\n    }\n}\n\nexport class ObjectNotFoundError extends RustFSError {\n    constructor(key: string, bucketName: string) {\n        super(\n            `Object not found: ${key} in ${bucketName}`,\n            'OBJECT_NOT_FOUND',\n            404,\n        );\n    }\n}\n\nexport class BucketAlreadyExistsError extends RustFSError {\n    constructor(bucketName: string) {\n        super(\n            `Bucket already exists: ${bucketName}`,\n            'BUCKET_ALREADY_EXISTS',\n            409,\n        );\n    }\n}\n\nexport class InvalidAccessKeyIdError extends RustFSError {\n    constructor() {\n        super(\n            'Invalid access key ID',\n            'INVALID_ACCESS_KEY_ID',\n            403,\n        );\n    }\n}\n\nexport class SignatureDoesNotMatchError extends RustFSError {\n    constructor() {\n        super(\n            'Signature does not match',\n            'SIGNATURE_DOES_NOT_MATCH',\n            403,\n        );\n    }\n}\n\nexport class RegionMismatchError extends RustFSError {\n    constructor(expected: string, actual: string) {\n        super(\n            `Region mismatch: expected ${expected}, got ${actual}`,\n            'REGION_MISMATCH',\n            400,\n        );\n    }\n}\n"
  },
  {
    "path": "packages/rustfs/tsconfig.json",
    "content": "{\n    \"extends\": \"../../tsconfig.base.json\",\n    \"compilerOptions\": {\n        \"outDir\": \"./dist\",\n        \"rootDir\": \"./src\",\n        \"composite\": true\n    },\n    \"include\": [\"src/**/*\"],\n    \"exclude\": [\"node_modules\", \"dist\", \"**/__tests__\"]\n}\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  - apps/*\n  - packages/*\n"
  },
  {
    "path": "tsconfig.build.json",
    "content": "{\n    \"extends\": \"./tsconfig.json\",\n    \"exclude\": [\"node_modules\", \"test\", \"dist\", \"**/*spec.ts\"]\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"declaration\": true,\n        \"removeComments\": true,\n        \"emitDecoratorMetadata\": true,\n        \"experimentalDecorators\": true,\n        \"allowSyntheticDefaultImports\": true,\n        \"target\": \"ES2024\",\n        \"sourceMap\": true,\n        \"outDir\": \"./dist\",\n        \"baseUrl\": \"./\",\n        \"incremental\": true,\n        \"skipLibCheck\": true,\n        \"strictNullChecks\": true,\n        \"noImplicitAny\": true,\n        \"strictBindCallApply\": true,\n        \"forceConsistentCasingInFileNames\": true,\n        \"noFallthroughCasesInSwitch\": true,\n        \"paths\": {\n            \"@/*\": [\"src/*\"]\n        }\n    }\n}\n"
  }
]